pub mod app_grid;
pub mod icon_cache;
pub mod search;

mod icons;
mod view;

use std::sync::Arc;
use std::time::Instant;

use crate::assets;
use crate::config::Config;
use crate::launcher::icon_cache::IconCache;
use crate::message::Message;
use crate::powermenu::PowerAction;

use icons::Icons;

pub use app_grid::AppEntry;
pub use view::column_count as column_count_for;

/// Height (logical pixels) of the forge-rendered topbar — same constant
/// as the QS module uses; keeping it duplicated here avoids a downward
/// dependency from `quicksettings` and the value is fixed by forge anyway.
pub const TOPBAR_PX: u32 = 28;

/// Linear settle speed in progress units per second, applied once the
/// finger releases. ~166 ms for the full travel, so the remaining slide
/// after release plays out over several frames and stays visible — the
/// subsurface reposition is cheap, so a slower settle costs nothing.
const OPEN_SPEED:  f32 = 6.0;

/// Linear settle speed for the CLOSE direction. Kept equal to
/// [`OPEN_SPEED`] so a cancelled drag eases back as visibly as a commit
/// settles open.
const CLOSE_SPEED: f32 = 6.0;

/// Maximum number of `app_id`s the launcher remembers as "recently
/// used". The Recents section pulls the head of this list. Capped so
/// the section never spreads beyond a couple of rows on desktop.
const RECENTS_MAX: usize = 8;

/// Stable widget id of the launcher's search field. The runtime
/// focuses this widget on launcher open so the user can type
/// immediately.
pub( crate ) const SEARCH_FIELD_ID: ltk::WidgetId = ltk::WidgetId( "launcher/search" );

pub struct AppLauncher
{
	pub apps: Vec<AppEntry>,
	pub search_query: String,
	pub search_selected: usize,
	/// Frames left to keep claiming focus for the search field after
	/// `open_animated`. A counter rather than a `bool` because the
	/// overlay surface needs a couple of frames to configure / lay
	/// out before its `widget_rects` are populated; if we cleared the
	/// claim on the first `take_focus_request` the lookup would miss
	/// and the focus would be lost. Decremented on every call until
	/// zero; reset to zero on close.
	pending_focus_frames: u8,

	/// Default fallback icon for apps without one (decoded once at startup).
	default_app_icon: ( Arc<Vec<u8>>, u32, u32 ),

	/// Lockscreen wallpaper of the active mode — paints the launcher
	/// surface opaque, no scrim, no glass on top. Owned (not Arc-shared
	/// with the homescreen) because the launcher uses the typically
	/// quieter / darker lockscreen variant; the homescreen keeps the
	/// brighter wallpaper variant.
	wallpaper: ltk::WallpaperBundle,

	/// Pre-decoded chrome glyphs for the desktop left rail (logout,
	/// settings, suspend, power, launcher toggle) plus the search-bar
	/// magnifier. Tinted on demand to `palette.text_primary` through a
	/// per-icon cache so theme flips don't re-tint every frame.
	icons: Icons,

	/// Recently launched `app_id`s, most-recent-first. Populated from
	/// inside [`Self::launch`] so opening the launcher right after using
	/// an app surfaces it at the top of the Recents section. In-memory
	/// only; resets across shell restarts on purpose — persisting it
	/// would couple this state to the on-disk config schema for marginal
	/// value.
	recents: Vec<String>,

	// ── slide animation state ────────────────────────────────────
	/// Visible-strip height as a fraction `[0.0, 1.0]` of the strip
	/// below the topbar. `progress = 1.0` is fully open.
	progress:  f32,
	/// Settle target. `Some(1.0)` while opening, `Some(0.0)` while
	/// closing, `None` at rest. The integrator in [`Self::tick_animation`]
	/// drives `progress` toward this value, then clears it.
	target:    Option<f32>,
	/// `true` while a swipe-up is dragging `progress` directly (follow
	/// the finger). Distinguishes a live drag's `0.0` cancellation pulse
	/// from an unrelated tap so only the former snaps the panel closed.
	dragging:  bool,
	/// Wall-clock instant of the last settle step — drives `dt` for the
	/// linear integrator.
	last_tick: Option<Instant>,
	/// `true` once fully open: the host overlay shows the real interactive
	/// panel and the sliding subsurface is dropped. While sliding it is
	/// `false` and the panel rides a cheap compositor-repositioned subsurface.
	revealed:  bool,
	/// Frames to keep the covering subsurface alive after `revealed` flips,
	/// so the host's real-panel raster is up before the subsurface drops —
	/// closes a 1-frame gap when the host's frame callback is still pending.
	reveal_handoff: u8,
	/// Bumped when the rendered panel would differ, so the slide subsurface
	/// re-rasters only on a real content change, not on every reposition.
	content_version: u64,
	/// Frames to keep the interactive host painted after a close begins, so
	/// the sliding subsurface rasters flush over it before the host drops —
	/// the close-direction mirror of `reveal_handoff`. Without it a tap-dismiss
	/// blinks the panel out for a frame before sliding it down.
	close_handoff: u8,
	/// `true` while a finger-tracked close (overscroll pull-down from the open
	/// grid) is in flight: the `close_handoff` completion enters live drag mode
	/// rather than an auto-settle, and progress follows the finger until release.
	close_drag: bool,
	/// `true` when the in-flight forge bottom-swipe started with the launcher
	/// already up: the gesture dismisses (closes) the launcher on commit instead
	/// of re-driving the reveal wipe. Latched on `start_external_wipe`.
	wipe_dismiss: bool,
}

impl AppLauncher
{
	pub fn new( _config: &Config ) -> Self
	{
		let apps = app_grid::scan_applications();

		Self {
			apps,
			search_query: String::new(),
			search_selected: 0,
			pending_focus_frames: 0,
			default_app_icon: assets::decode_app_default_icon(),
			wallpaper: assets::decode_lockscreen_bundle( 0, 0 ),
			icons: Icons::new(),
			recents: Vec::new(),
			progress:  0.0,
			target:    None,
			dragging:  false,
			last_tick: None,
			revealed:  false,
			reveal_handoff: 0,
			content_version: 0,
			close_handoff: 0,
			close_drag: false,
			wipe_dismiss: false,
		}
	}

	/// Re-decode the lockscreen wallpaper for the active mode at the
	/// given surface size. Called from the shell after a Light/Dark
	/// flip so the launcher background swaps
	/// `branding/{mode}/lockscreen.svg` (or its raster variant under
	/// `branding/{mode}/lockscreen/<W>x<H>.webp` when one covers the
	/// surface). Mirrors `QuickSettings::reload_wallpaper`.
	pub fn reload_wallpaper( &mut self, sw: u32, sh: u32 )
	{
		self.wallpaper = assets::decode_lockscreen_bundle( sw, sh );
		self.content_version = self.content_version.wrapping_add( 1 );
	}

	/// Shared handle to the fallback icon used when an app has no `icon`
	/// path or its file fails to decode. Exposed so the shell can reuse
	/// the same buffer for the drag-ghost overlay without re-decoding.
	pub fn default_icon( &self ) -> &( Arc<Vec<u8>>, u32, u32 )
	{
		&self.default_app_icon
	}

	// ── search ────────────────────────────────────────────────────

	/// Replace the live search query — wired to the search box's
	/// `on_change` callback. The next view rebuild filters the grid
	/// against this string via [`search::search_apps`].
	pub fn set_search_query( &mut self, q: String )
	{
		self.search_query = q;
		self.search_selected = 0;
	}

	pub fn selected_search_app_id( &self ) -> Option<String>
	{
		if self.search_query.is_empty() { return None; }
		let results = crate::launcher::search::search_apps( &self.apps, &self.search_query );
		results.get( self.search_selected ).map( |a| a.app_id.clone() )
	}

	pub fn move_search_selection( &mut self, delta: i32 )
	{
		if self.search_query.is_empty() { return; }
		let count = crate::launcher::search::search_apps( &self.apps, &self.search_query ).len();
		if count == 0 { self.search_selected = 0; return; }
		let cur = self.search_selected as i32;
		let new = ( cur + delta ).clamp( 0, count as i32 - 1 );
		self.search_selected = new as usize;
	}

	// ── animation state ───────────────────────────────────────────

	/// `true` when the launcher surface should be on screen this frame
	/// — either fully open, animating, or held visible at any
	/// intermediate progress. The overlay in `build_overlays` is
	/// emitted under the same condition.
	pub fn is_shown( &self ) -> bool
	{
		self.revealed || self.target.is_some() || self.progress > 0.0
	}

	/// `true` while the slide is in flight. Drives `App::is_animating`
	/// so the run loop keeps polling at the renderer's cadence.
	pub fn is_animating( &self ) -> bool
	{
		self.target.is_some() || self.reveal_handoff > 0 || self.close_handoff > 0
	}

	/// `true` once fully open — the host overlay shows the real
	/// interactive panel and no sliding subsurface is emitted.
	pub fn revealed( &self ) -> bool
	{
		self.revealed
	}

	/// `true` while the panel rides the sliding subsurface (opening,
	/// closing or finger-tracked) rather than the interactive host. Drives
	/// `subsurface_motion_only` — only true motion, not the reveal handoff.
	pub fn is_sliding( &self ) -> bool
	{
		!self.revealed && ( self.progress > 0.0 || self.target.is_some() || self.dragging )
	}

	/// `true` while the covering subsurface should be emitted — during the
	/// slide and through the brief reveal handoff. Kept lazy (not emitted
	/// when closed) so the parked panel's Element tree isn't rebuilt every
	/// loop iteration; the always-mounted host already removes the configure
	/// round-trip that made the swipe slow to start.
	pub fn subsurface_shown( &self ) -> bool
	{
		self.is_sliding() || self.reveal_handoff > 0 || self.close_handoff > 0
	}

	/// Content revision of the sliding panel — bumped only on real
	/// content changes so the subsurface re-rasters then, not per move.
	pub fn content_version( &self ) -> u64
	{
		self.content_version
	}

	/// Top-left `y` (physical px) of the sliding subsurface within its
	/// host: parks the panel fully below the strip at `progress = 0` and
	/// flush at `progress = 1`. Off-strip overflow is clipped at the
	/// screen edge.
	pub fn subsurface_offset_y( &self, surface_h: u32 ) -> i32
	{
		( ( 1.0 - self.progress ) * surface_h as f32 ).round() as i32
	}

	/// Maximum rendered height of the launcher surface in pixels — the
	/// strip below the forge topbar. Matches QS so both panels occupy
	/// the same usable region.
	pub fn max_height( &self, screen_height: u32, display_scale: u32 ) -> u32
	{
		let base        = if screen_height == 0 { 720 } else { screen_height };
		let topbar_phys = TOPBAR_PX * display_scale.max( 1 );
		base.saturating_sub( topbar_phys ).max( 1 )
	}

	/// Open the launcher with a slide-up settle. No-op if already
	/// fully open or already settling toward open.
	///
	/// `last_tick` is left at `None` rather than seeded to the current
	/// instant: the first time the launcher opens, the layer-shell
	/// surface is created from scratch and the compositor's configure
	/// round-trip can delay the first `tick_animation` call by several
	/// hundred milliseconds. With `last_tick = Some(now_at_open)` that
	/// delay shows up as a huge `dt` on the first tick, the linear
	/// integrator's `step = OPEN_SPEED × dt` overshoots `diff`, and the
	/// branch in `tick_animation` snaps `progress` straight to `1.0`
	/// in a single step — the user sees the launcher pop in instead of
	/// sliding up. Leaving `last_tick = None` makes the first tick a
	/// no-op (dt = 0) that just records the current instant, then
	/// subsequent ticks see real ~16 ms frame deltas and the slide
	/// plays out at the intended speed.
	pub fn open_animated( &mut self )
	{
		// Re-open during the close handoff: cancel the pending slide-out and
		// stay revealed rather than tearing the still-painted host down.
		if self.close_handoff > 0 { self.close_handoff = 0; return; }
		if self.revealed || self.target == Some( 1.0 ) { return; }
		self.target    = Some( 1.0 );
		// Clear the drag flag so a later `0.0` cancellation pulse (e.g. a
		// tap that falls through the vertical-swipe branch) can't snap
		// the just-committed launcher closed.
		self.dragging  = false;
		self.last_tick = None;
	}

	/// Forge owns the bottom swipe-up and drives the launcher slide over
	/// IPC, mirroring the Quick Settings pull-down: `progress` is the raw
	/// screen fraction `(start_y - cur_y) / height`, so the panel tracks
	/// the finger 1:1.
	pub fn start_external_wipe( &mut self )
	{
		// A bottom swipe-up while the launcher is already up means "go home":
		// dismiss it on commit rather than restarting the reveal.
		if self.is_shown()
		{
			self.wipe_dismiss = true;
			return;
		}
		self.wipe_dismiss  = false;
		self.dragging      = true;
		self.target        = None;
		self.last_tick     = None;
		self.progress      = 0.0;
		self.close_handoff = 0;
		self.close_drag    = false;
	}

	pub fn set_external_wipe_progress( &mut self, progress: f32 )
	{
		if self.wipe_dismiss { return; }
		self.dragging  = true;
		self.target    = None;
		self.last_tick = None;
		self.progress  = progress.clamp( 0.0, 1.0 );
	}

	pub fn finish_external_wipe( &mut self, committed: bool, progress: f32 )
	{
		if self.wipe_dismiss
		{
			self.wipe_dismiss = false;
			if committed { self.close_animated(); }
			return;
		}
		self.dragging = false;
		self.progress = progress.clamp( 0.0, 1.0 );
		if committed
		{
			self.open_animated();
		} else {
			self.close_animated();
		}
	}

	/// Drive a finger-tracked close from the open grid: the overscroll
	/// pull-down past the launcher's scroll top slides the panel back down,
	/// tracking the finger 1:1. `closed_frac` is the pull distance as a
	/// fraction of the panel height (0.0 = open, grows as it pulls down).
	///
	/// From the revealed state the first pull starts the host→subsurface
	/// handoff (`close_handoff`): the interactive host stays painted a couple
	/// of frames while the sliding subsurface rasters flush over it, then the
	/// tick drops the host and the panel below tracks the finger. The panel is
	/// held flush during the handoff so the host and subsurface never disagree.
	pub fn set_close_drag_progress( &mut self, closed_frac: f32 )
	{
		if !self.is_shown() { return; }
		if self.revealed && self.close_handoff == 0 && !self.close_drag
		{
			self.close_handoff = 2;
			self.close_drag    = true;
			self.dragging      = true;
			self.target        = None;
			self.last_tick     = None;
			return;
		}
		// Once handed off, track the finger; held flush during the handoff.
		if self.close_drag && self.close_handoff == 0
		{
			self.progress = ( 1.0 - closed_frac ).clamp( 0.0, 1.0 );
		}
	}

	/// Release of a finger-tracked close: settle the rest of the way out when
	/// the pull committed, otherwise slide back open. No-op outside a close drag.
	pub fn finish_close_drag( &mut self, committed: bool )
	{
		if !self.close_drag { return; }
		self.close_drag = false;
		self.dragging   = false;
		self.last_tick  = None;
		if committed
		{
			if self.revealed
			{
				// Released before the handoff completed; the animated close runs
				// its own host→subsurface handoff from the still-painted host.
				self.close_handoff = 0;
				self.close_animated();
			}
			else
			{
				self.close_handoff = 0;
				self.target        = Some( 0.0 );
			}
		}
		else
		{
			self.close_handoff = 0;
			if self.revealed
			{
				// Never left the interactive host — the panel never moved.
				self.target = None;
			}
			else
			{
				// Slide the subsurface back up and re-reveal at the top.
				self.open_animated();
			}
		}
	}

	/// Multi-frame focus claim. Returns the search field's id while
	/// `pending_focus_frames > 0`, decrementing each call so the
	/// launcher overlay has time to configure and lay out its
	/// `widget_rects` before ltk's focus lookup runs.
	pub fn take_focus_request( &mut self ) -> Option<ltk::WidgetId>
	{
		if self.pending_focus_frames > 0
		{
			self.pending_focus_frames -= 1;
			Some( SEARCH_FIELD_ID )
		}
		else { None }
	}

	/// Drop the pending auto-focus claim — used on mobile, where the
	/// search field must not grab focus (and raise the OSK) on open.
	pub fn clear_focus_request( &mut self )
	{
		self.pending_focus_frames = 0;
	}

	/// Close the launcher with a slide-down settle. Clears the search
	/// query so the next reveal lands on the unfiltered grid.
	///
	/// From the revealed state the host→subsurface handoff is deferred via
	/// `close_handoff`: the interactive host keeps painting while the sliding
	/// subsurface rasters flush over it, so the dismiss slides down cleanly
	/// instead of blinking the panel out for a frame and back. When already
	/// sliding (a cancelled drag) it settles straight down.
	pub fn close_animated( &mut self )
	{
		self.search_query.clear();
		self.search_selected      = 0;
		self.pending_focus_frames = 0;
		self.last_tick            = None;

		if self.revealed
		{
			self.close_handoff = 2;
			self.target        = None;
		}
		else
		{
			self.target = Some( 0.0 );
		}
	}

	/// Collapse the launcher immediately — used when another overlay
	/// is about to take its place (so two panels don't fight for the
	/// same region) and on `ShowHomescreen` to ensure the desktop is
	/// the visible surface after the last app closes.
	pub fn close_immediate( &mut self )
	{
		self.search_query.clear();
		self.search_selected      = 0;
		self.progress             = 0.0;
		self.target               = None;
		self.dragging             = false;
		self.revealed             = false;
		self.reveal_handoff       = 0;
		self.close_handoff        = 0;
		self.close_drag           = false;
		self.wipe_dismiss         = false;
		self.last_tick            = None;
		self.pending_focus_frames = 0;
	}

	/// Toggle between open and closed with the animated path. Called
	/// from the topbar / hot-corner / shortcut entry points.
	pub fn toggle( &mut self )
	{
		let is_up_or_going_up =
			self.target == Some( 1.0 )
			|| ( self.target.is_none() && self.progress > 0.0 );
		if is_up_or_going_up { self.close_animated(); }
		else                 { self.open_animated();  }
	}

	/// Compatibility shim: `app.rs::ShowHomescreen` and the launcher's
	/// own drag entry point both expect a method named `hide` that
	/// dismisses the panel. Mapped to `close_immediate` so the launcher
	/// is gone the instant something else needs the surface — there's
	/// no animation to play under apps that are about to obscure it.
	pub fn hide( &mut self )
	{
		self.close_immediate();
	}

	/// Advance the slide settle by one tick. Returns `true` while the
	/// animation is in progress so the caller can pump an
	/// `AnimationFrame` message; returns `false` once the target is
	/// reached and the integrator goes idle.
	pub fn tick_animation( &mut self ) -> bool
	{
		if self.reveal_handoff > 0 { self.reveal_handoff -= 1; }

		// Hold the interactive host one tick at a time while the sliding
		// subsurface rasters flush over it, then drop the host and start the
		// down-slide so the dismiss never flashes a bare frame.
		if self.close_handoff > 0
		{
			self.close_handoff -= 1;
			if self.close_handoff == 0
			{
				self.revealed  = false;
				self.last_tick = None;
				if self.close_drag
				{
					// Live finger-drag takes over from the handoff; no auto-settle.
					self.dragging = true;
					self.target   = None;
				}
				else
				{
					self.target = Some( 0.0 );
				}
			}
			return true;
		}

		let Some( target ) = self.target else { return self.reveal_handoff > 0 };

		let now = Instant::now();
		let dt  = match self.last_tick
		{
			Some( prev ) => ( now - prev ).as_secs_f32(),
			None         => 0.0,
		};
		self.last_tick = Some( now );

		// Direction-dependent settle speed: opens snap up at
		// `OPEN_SPEED`, closes drift down at the slower `CLOSE_SPEED`.
		// `target` is always exactly 0.0 or 1.0 by construction so a
		// strict equality on `target` is a clean discriminator.
		let speed = if target == 0.0 { CLOSE_SPEED } else { OPEN_SPEED };
		let step  = speed * dt;
		let diff  = target - self.progress;

		if diff.abs() <= step
		{
			self.progress  = target;
			self.target    = None;
			self.last_tick = None;
			if target >= 1.0
			{
				// Hand off from the sliding subsurface to the interactive
				// host overlay; claim focus now that the real search field
				// exists in the host's view. Keep the covering subsurface a
				// couple of frames so the host's real-panel raster lands first.
				self.revealed             = true;
				self.reveal_handoff       = 2;
				self.pending_focus_frames = 30;
			}
		}
		else
		{
			self.progress += step * diff.signum();
		}
		true
	}

	/// Launch the app whose `.desktop` id matches `app_id`, push it to
	/// the recents list, and dismiss the overlay. The launcher is hidden
	/// immediately (no slide-down) because the launched app's window is
	/// about to cover the surface — animating out underneath the new
	/// window would just burn frames the user never sees.
	pub fn launch( &mut self, app_id: &str, token: Option<&str> )
	{
		let Some( ( exec, terminal ) ) = self.apps.iter()
			.find( |a| a.app_id == app_id )
			.map( |e| ( e.exec.clone(), e.terminal ) )
		else
		{
			self.hide();
			return;
		};
		app_grid::launch_app( &exec, terminal, token );
		self.push_recent( app_id.to_string() );
		self.hide();
	}

	/// Bump `app_id` to the head of the recents list, deduping any
	/// prior occurrence so a frequently used app doesn't pin two slots.
	fn push_recent( &mut self, app_id: String )
	{
		if let Some( idx ) = self.recents.iter().position( |id| id == &app_id )
		{
			self.recents.remove( idx );
		}
		self.recents.insert( 0, app_id );
		if self.recents.len() > RECENTS_MAX
		{
			self.recents.truncate( RECENTS_MAX );
		}
		// Recents drive the parked subsurface's content; re-raster it so the
		// next reveal doesn't show a stale list.
		self.content_version = self.content_version.wrapping_add( 1 );
	}

	// ── view delegation ──────────────────────────────────────────

	/// Build the launcher overlay. Pure rendering: reads from
	/// `self.apps`, `self.recents`, `self.search_query`, the active
	/// theme palette, and the dock items inside `config`. The view
	/// branches on orientation: desktop gets a left rail of
	/// session/launcher actions and three sections (Pinned / Recently
	/// used / All Apps); mobile drops the rail and the sections and
	/// shows a single flat grid like the previous launcher did.
	///
	/// `on_launch`     fires when an icon is tapped.
	/// `on_drag_start` arms a drag with `DragOrigin::Launcher`.
	/// `on_search`     fires on every keystroke in the search field.
	pub fn view(
		&self,
		config:        &Config,
		icon_cache:    &mut IconCache,
		screen_width:  u32,
		screen_height: u32,
		display_scale: u32,
		on_launch:     impl Fn( String ) -> Message,
		on_drag_start: impl Fn( String ) -> Message,
		on_search:     impl Fn( String ) -> Message + 'static,
	) -> ltk::Element<Message>
	{
		view::build(
			self,
			config,
			icon_cache,
			screen_width,
			screen_height,
			display_scale,
			on_launch,
			on_drag_start,
			on_search,
		)
	}

	// ── view-side accessors (used by `view::*`) ──────────────────

	pub( crate ) fn icons( &self ) -> &Icons { &self.icons }
	pub( crate ) fn wallpaper( &self ) -> &ltk::WallpaperBundle { &self.wallpaper }
	pub( crate ) fn recents_ids( &self ) -> &[ String ] { &self.recents }

	// ── shell entry-points for the rail buttons ─────────────────

	/// Messages the desktop left rail dispatches (in top-to-bottom
	/// order: logout, settings, suspend, reboot, power off, launcher
	/// toggle). Centralised so the renderer doesn't need to know
	/// about `PowerAction` directly.
	///
	/// The renderer anchors the whole list to the bottom of the rail
	/// (spacer above), so reading the array top-to-bottom gives the
	/// on-screen top-to-bottom order: the launcher-toggle ends up
	/// flush against the bottom edge.
	pub( crate ) fn rail_actions() -> [ ( RailIcon, Message ); 6 ]
	{
		[
			( RailIcon::Logout,    Message::PowerAction( PowerAction::Logout   ) ),
			( RailIcon::Settings,  Message::OpenSettings ),
			( RailIcon::Suspend,   Message::PowerAction( PowerAction::Suspend  ) ),
			( RailIcon::Reboot,    Message::PowerAction( PowerAction::Reboot   ) ),
			( RailIcon::Power,     Message::PowerAction( PowerAction::PowerOff ) ),
			( RailIcon::AppToggle, Message::ToggleLauncher ),
		]
	}
}

/// Identifies which rail glyph the renderer should pull from
/// [`Icons`]. A small enum (rather than passing `&Option<RgbaIcon>`
/// directly) keeps the action ↔ icon mapping in one place — the
/// rail entry list above.
#[ derive( Clone, Copy ) ]
pub( crate ) enum RailIcon
{
	Logout,
	Settings,
	Suspend,
	Reboot,
	Power,
	AppToggle,
}
