// Application state and logic: messages, screens, background refresh
// threads and the App trait implementation. The update loop lives in
// `update.rs`, the channel-draining poll in `poll.rs`; views live in
// `views/`, shared widgets in `ui`.

mod poll;
mod update;

use std::sync::{ Arc, mpsc };
use std::time::Duration;
use ltk::{ App, ComboState, Element, Keysym, ThemeMode, ThemePreference };

use crate::{ config, i18n, network, notif_channels, single_instance, storage, system, upower };
use crate::views::*;

#[ derive( Clone ) ]
pub( crate ) enum Msg
{
	Open( Screen ),
	Back,

	ToggleWifi,
	SelectWifi( String ),
	WifiPasswordInput( String ),
	ToggleWifiPasswordVisible,
	WifiConnect,
	WifiForget( String ),
	WifiScan,
	OpenPortal,
	ToggleBluetooth,
	SelectBt( String ),
	BtConnect( String ),
	BtDisconnect( String ),
	BtForget( String ),
	BtScan,

	SetBrightness( f32 ),
	SetVolume( f32 ),
	ToggleMute,
	SelectSink( u32 ),
	SelectSource( u32 ),
	SetMicVolume( f32 ),
	ToggleMicMute,

	SetPowerProfile( String ),
	PreviewBatteryWarn( u32 ),
	SetBatteryWarn( u32 ),
	PreviewBatterySuspend( u32 ),
	SetBatterySuspend( u32 ),

	ToggleNightLight,
	SetNightLightTemp( u32 ),
	PreviewUiScale( f32 ),
	SetUiScale( f32 ),
	PreviewInterfaceScale( f32 ),
	ToggleInterfaceAuto,
	SetInterfaceScale( f32 ),
	SetSleepTimeout( u32 ),
	RotationToggle,
	RotationSelect( usize ),
	RotationDismiss,

	SelectMonitor( String ),
	SetDisplayExtend( bool ),
	MonitorToggleScaleAuto,
	MonitorPreviewScale( f32 ),
	MonitorSetScale( f32 ),
	MonitorModeToggle,
	MonitorModeSelect( usize ),
	MonitorModeDismiss,
	MonitorPlace( system::Placement ),
	MonitorEnable( bool ),

	ToggleDnd,
	ToggleLockscreenNotifs,
	ToggleChannelBlocked( String, String ),
	ToggleLocation,
	ToggleAirplane,
	ToggleHotspot,
	LaunchApp( String ),

	SetThemePref( ThemePreference ),
	SetWallpaperVariant( ThemeMode ),
	SetLanguage( &'static str ),

	ToggleNtp,
	ToggleDateEdit,
	DateChanged( ltk::Date ),
	DateView( i32, u8 ),
	TimeChanged( ltk::Time ),
	ApplyDateTime,

	SearchInput( String ),
	Refresh,
	TriggerUpdateCheck,
}

#[ derive( Clone, PartialEq ) ]
pub( crate ) enum Screen
{
	Home,
	About,
	SystemAppUpdater,
	SecurityStatus,
	Wifi,
	Bluetooth,
	Cellular,
	Interconnectivity,
	PortableHotspot,
	MoreConnectivity,
	LockScreen,
	Notifications,
	Display,
	Monitors,
	Sound,
	Wallpaper,
	Themes,
	FingerprintAndLock,
	PrivacyProtection,
	Apps,
	Battery,
	Privacy,
	Location,
	Storage,
	Language,
	DateTime,
	Accessibility,
}

// On-demand scan results (wifi network list, bt devices, notification channels)
// delivered from one-shot threads spawned when the user opens those screens.
enum ScanResult
{
	Wifi(         Vec<network::WifiNetwork> ),
	WifiConnect(  String, bool ),
	Connectivity( u32 ),
	// The bool marks the final delivery of a scan: the quick cached list
	// arrives first (false), the post-discovery list closes it (true).
	Bt(           Vec<system::BluetoothDevice>, bool ),
	BtConnect(    String, Result<(), system::BtConnectError> ),
	// Fresh audio-device snapshot, delivered after a Bluetooth audio
	// device (dis)connects so the Sound screen updates in place.
	Audio
	{
		sinks:      Vec<system::AudioDevice>,
		sources:    Vec<system::AudioDevice>,
		volume:     f32,
		muted:      bool,
		mic_volume: f32,
		mic_muted:  bool,
	},
	Notif(        Vec<notif_channels::ChannelInfo> ),
	// Fresh monitor snapshot after opening the panel or applying a change;
	// `None` when forge's socket is unreachable (nested/winit session).
	Outputs(      Option<system::Outputs> ),
}

// Fields that require spawning external processes — collected in a background
// thread so the UI thread is never blocked waiting for NetworkManager / bluetoothctl /
// gsettings / wpctl / upower to respond.
struct SlowSnapshot
{
	wifi_on:                bool,
	connected_ssid:         Option<String>,
	connectivity:           u32,
	bt_on:                  bool,
	volume:                 f32,
	muted:                  bool,
	battery:                Option<upower::BatteryInfo>,
	power_profile:          String,
	night_light_on:         bool,
	night_light_temp:       u32,
	ui_scale:               f32,
	interface_automatic:    f32,
	sleep_timeout:          u32,
	primary_ip:             Option<String>,
	dnd_enabled:            bool,
	lockscreen_show_notifs: bool,
	location_enabled:       bool,
	airplane_mode:          bool,
	hotspot_active:         bool,
	firewall_active:        bool,
	selinux_state:          Option<String>,
	sim_imei:               Option<String>,
	sim_operator:           Option<String>,
	fprintd_active:         bool,
	fprintd_enrolled:       bool,
	vpn_connections:        Vec<String>,
}

fn spawn_slow( tx: mpsc::Sender<SlowSnapshot> )
{
	std::thread::spawn( move ||
	{
		// Single wpctl call yields both volume and mute (see `audio_state`).
		let ( volume, muted ) = system::audio_state();
		let ( sim_imei, sim_operator ) = system::sim_info();
		// Only the automatic scale: the pick is Settings' own state, and a
		// snapshot in flight during a change would otherwise revert it.
		let iface_automatic = system::interface_scale().1;
		let _ = tx.send( SlowSnapshot
		{
			wifi_on:                network::wifi_enabled(),
			connected_ssid:         network::connected_wifi_ssid(),
			connectivity:           network::connectivity(),
			bt_on:                  system::bluetooth_enabled(),
			volume,
			muted,
			battery:                upower::battery(),
			power_profile:          system::power_profile(),
			night_light_on:         system::night_light_enabled(),
			night_light_temp:       system::night_light_temp(),
			ui_scale:               system::ui_scale(),
			interface_automatic:    iface_automatic,
			sleep_timeout:          system::sleep_timeout_secs(),
			primary_ip:             system::primary_ip(),
			dnd_enabled:            system::dnd_enabled(),
			lockscreen_show_notifs: system::lockscreen_show_notifications(),
			location_enabled:       system::location_enabled(),
			airplane_mode:          system::airplane_mode(),
			hotspot_active:         network::hotspot_active(),
			firewall_active:        system::firewall_active(),
			selinux_state:          system::selinux_state(),
			sim_imei,
			sim_operator,
			fprintd_active:         system::fprintd_active(),
			fprintd_enrolled:       system::fprintd_enrolled(),
			vpn_connections:        network::vpn_connections(),
		});
	});
}

/// One-shot, separate from the recurring poll: `dnf/apt check-update` is the
/// single most expensive call in the whole app (metadata refresh, disk/network
/// I/O, multiple seconds) and the result almost never changes during a session.
/// Run it once on startup in its own thread instead of every 5 s slow refresh.
fn spawn_updates_check( tx: mpsc::Sender<Option<u32>> )
{
	std::thread::spawn( move ||
	{
		let _ = tx.send( system::updates_available() );
	});
}

pub( crate ) struct State
{
	pub( crate ) screen:        Screen,

	pub( crate ) wifi_on:         bool,
	pub( crate ) connected_ssid:  Option<String>,
	pub( crate ) connectivity:    u32,
	pub( crate ) wifi_networks:   Vec<network::WifiNetwork>,
	pub( crate ) wifi_selected:   Option<String>,
	pub( crate ) wifi_password:   String,
	pub( crate ) wifi_pw_visible: bool,
	pub( crate ) wifi_connecting: Option<String>,
	pub( crate ) wifi_error:      Option<String>,
	pub( crate ) focus_request:   Option<ltk::WidgetId>,

	pub( crate ) bt_on:         bool,
	pub( crate ) bt_devices:    Vec<system::BluetoothDevice>,
	pub( crate ) bt_selected:   Option<String>,
	pub( crate ) bt_connecting: Option<String>,
	// Failed connect attempt: which device, and how it failed.
	pub( crate ) bt_error:      Option<( String, system::BtConnectError )>,

	pub( crate ) brightness:    f32,

	pub( crate ) volume:        f32,
	pub( crate ) muted:         bool,
	pub( crate ) sinks:         Vec<system::AudioDevice>,
	pub( crate ) sources:       Vec<system::AudioDevice>,
	pub( crate ) mic_volume:    f32,
	pub( crate ) mic_muted:     bool,

	pub( crate ) battery_pct:         Option<u8>,
	pub( crate ) battery_chg:         bool,
	pub( crate ) battery_present:     bool,
	pub( crate ) battery_full_design: Option<u64>,
	pub( crate ) battery_full:        Option<u64>,
	pub( crate ) battery_cycles:      Option<u32>,
	pub( crate ) battery_time_secs:   Option<i64>,
	pub( crate ) ac_online:           bool,
	pub( crate ) power_profile:       String,
	/// Read once from powermanagerd and owned here from then on, like the
	/// interface scale: a refresh in flight must not revert a fresh pick.
	pub( crate ) battery_warn_pct:     u32,
	pub( crate ) battery_suspend_pct: u32,

	pub( crate ) night_light_on:  bool,
	pub( crate ) night_light_temp: u32,
	pub( crate ) ui_scale:        f32,
	pub( crate ) interface_scale: f32,
	pub( crate ) interface_automatic: f32,
	/// Rotation picker. Index 0 is automatic; forge owns the real state and
	/// exposes no getter, so the selection is tracked here.
	pub( crate ) rotation_combo: ComboState,
	pub( crate ) sleep_timeout:   u32,

	pub( crate ) hostname:    String,
	pub( crate ) kernel:      String,
	pub( crate ) os_version:  String,
	pub( crate ) cpu_model:   String,
	pub( crate ) cpu_cores:   usize,
	pub( crate ) total_memory: u64,
	pub( crate ) used_memory:  u64,
	pub( crate ) uptime_secs:  u64,
	pub( crate ) primary_ip:   Option<String>,
	pub( crate ) username:     String,

	pub( crate ) dnd_enabled:           bool,
	pub( crate ) lockscreen_show_notifs: bool,
	pub( crate ) location_enabled:      bool,
	pub( crate ) airplane_mode:         bool,
	pub( crate ) hotspot_active:        bool,

	pub( crate ) installed_apps:    Vec<system::AppEntry>,
	pub( crate ) updates_available: Option<u32>,
	pub( crate ) firewall_active:   bool,
	pub( crate ) selinux_state:     Option<String>,
	pub( crate ) sim_imei:          Option<String>,
	pub( crate ) sim_operator:      Option<String>,
	pub( crate ) fprintd_active:    bool,
	pub( crate ) fprintd_enrolled:  bool,
	pub( crate ) vpn_connections:   Vec<String>,
	pub( crate ) nfc_available:     bool,
	pub( crate ) usb_mode:          String,

	pub( crate ) storage:      Vec<storage::MountInfo>,
	pub( crate ) search_query: String,

	pub( crate ) notif_channels: Vec<notif_channels::ChannelInfo>,

	pub( crate ) theme_pref:        ThemePreference,
	pub( crate ) wallpaper_variant: ThemeMode,
	pub( crate ) language:          String,

	// Screen-local data resolved by the `Msg::Open` arm rather than in
	// `view()` — views run on every redraw and must not touch disk/env.
	pub( crate ) sys_locale:     String,
	pub( crate ) wallpaper_file: Option<String>,
	pub( crate ) date_str:       String,
	pub( crate ) time_str:       String,
	pub( crate ) tz_str:         String,
	pub( crate ) ntp_on:         bool,
	pub( crate ) dt_date:        ltk::Date,
	pub( crate ) dt_time:        ltk::Time,
	pub( crate ) dt_view:        ( i32, u8 ),
	pub( crate ) dt_cal_open:    bool,
	pub( crate ) dt_apply_failed: bool,

	slow_tx: mpsc::Sender<SlowSnapshot>,
	slow_rx: mpsc::Receiver<SlowSnapshot>,
	// True while a background slow-refresh thread is running. Prevents
	// `poll_external` (called on every event-loop iteration, not just on the
	// timer) from spawning a fresh pile of subprocesses on every wake-up.
	slow_in_flight:  bool,
	last_slow_spawn: std::time::Instant,
	// One-shot updates check delivered separately from the recurring refresh.
	updates_rx: mpsc::Receiver<Option<u32>>,
	// On-demand scan channel — wifi list, bt list, notif channels.
	scan_tx:       mpsc::SyncSender<ScanResult>,
	scan_rx:       mpsc::Receiver<ScanResult>,
	pub( crate ) wifi_scanning: bool,
	pub( crate ) bt_scanning:   bool,
	pub( crate ) notif_scanning: bool,

	pub( crate ) outputs:            Vec<system::Output>,
	pub( crate ) display_extend:     bool,
	pub( crate ) monitor_selected:   Option<String>,
	// Live label value while the per-monitor scale slider is mid-drag.
	pub( crate ) monitor_scale_drag: Option<f32>,
	pub( crate ) monitor_mode_combo: ComboState,
	pub( crate ) monitors_scanning:  bool,
	// Deep links from later launches, served by the single-instance guard.
	pub( crate ) ipc_inbox: Arc<std::sync::Mutex<single_instance::Inbox>>,
	// Wall-clock reference for spinner phases (one revolution per second).
	pub( crate ) started_at: std::time::Instant,
}

impl State
{
	pub( crate ) fn new() -> Self
	{
		let ( tx, rx ) = mpsc::channel();
		spawn_slow( tx.clone() );

		let ( utx, urx ) = mpsc::channel();
		spawn_updates_check( utx );

		let ( stx, srx ) = mpsc::sync_channel( 4 );
		let power_policy = system::power_policy();

		let ( cpu_model, cpu_cores )     = system::cpu_info();
		let ( total_memory, used_memory ) = system::memory_info();

		Self
		{
			screen:         Screen::Home,
			// Fast sysfs reads — safe to call synchronously
			wifi_on:         false,
			connected_ssid:  None,
			connectivity:    0,
			wifi_networks:   Vec::new(),
			wifi_selected:   None,
			wifi_password:   String::new(),
			wifi_pw_visible: false,
			focus_request:   None,
			wifi_connecting: None,
			wifi_error:      None,
			bt_on:          false,
			bt_devices:     Vec::new(),
			bt_selected:    None,
			bt_connecting:  None,
			bt_error:       None,
			brightness:    system::brightness(),
			volume:        0.5,
			muted:         false,
			sinks:         Vec::new(),
			sources:       Vec::new(),
			mic_volume:    0.5,
			mic_muted:     false,
			battery_pct:         None,
			battery_chg:         false,
			battery_warn_pct:     power_policy.battery_warn_percent,
			battery_suspend_pct:  power_policy.battery_suspend_percent,
			battery_present:     false,
			battery_full_design: system::battery_full_design(),
			battery_full:        system::battery_full(),
			battery_cycles:      system::battery_cycle_count(),
			battery_time_secs:   None,
			ac_online:           system::ac_online(),
			power_profile:       "balanced".into(),
			night_light_on:      false,
			night_light_temp:    4700,
			ui_scale:            1.0,
			// The pick is read once here and owned locally from then on; the
			// poll only refreshes the automatic scale, which forge owns.
			interface_scale:     system::interface_scale().0,
			interface_automatic: 1.0,
			// Index 0 is automatic, which is where an unlocked session starts.
			rotation_combo:      ComboState { selected: vec![ 0 ], ..ComboState::new() },
			sleep_timeout:       0,
			// Static info — sysfs / procfs / env, always fast
			hostname:     system::hostname(),
			kernel:       system::kernel_version(),
			os_version:   system::os_version(),
			cpu_model,
			cpu_cores,
			total_memory,
			used_memory,
			uptime_secs:  system::uptime_secs(),
			primary_ip:   None,
			username:     system::username(),
			// Slow defaults — real values arrive from the background thread
			dnd_enabled:            false,
			lockscreen_show_notifs: true,
			location_enabled:       false,
			airplane_mode:          false,
			hotspot_active:         false,
			installed_apps:    Vec::new(),
			updates_available: None,
			firewall_active:   false,
			selinux_state:     None,
			sim_imei:          None,
			sim_operator:      None,
			fprintd_active:    false,
			fprintd_enrolled:  false,
			vpn_connections:   Vec::new(),
			nfc_available:     system::nfc_available(),
			usb_mode:          system::usb_mode(),
			storage:      Vec::new(),
			search_query: String::new(),
			notif_channels: Vec::new(),
			theme_pref:        config::read_theme_pref(),
			wallpaper_variant: config::read_wallpaper_variant(),
			// The locale was already resolved and set by main().
			language:          i18n::current(),
			sys_locale:     std::env::var( "LANG" ).unwrap_or_else( |_| "en_US.UTF-8".into() ),
			ntp_on:         true,
			dt_date:        ltk::Date::new( 2000, 1, 1 ),
			dt_time:        ltk::Time::default(),
			dt_view:        ( 2000, 1 ),
			dt_cal_open:    false,
			dt_apply_failed: false,
			wallpaper_file: None,
			date_str:       String::new(),
			time_str:       String::new(),
			tz_str:         String::new(),
			slow_tx: tx,
			slow_rx: rx,
			slow_in_flight:  true, // spawn_slow above is already running
			last_slow_spawn: std::time::Instant::now(),
			updates_rx: urx,
			scan_tx:       stx,
			scan_rx:       srx,
			wifi_scanning: false,
			bt_scanning:   false,
			notif_scanning: false,
			outputs:            Vec::new(),
			display_extend:     false,
			monitor_selected:   None,
			monitor_scale_drag: None,
			monitor_mode_combo: ComboState::new(),
			monitors_scanning:  false,
			ipc_inbox: Arc::default(),
			started_at: std::time::Instant::now(),
		}
	}

	// Run a monitor-config change off the UI thread, then deliver a fresh
	// snapshot: forge applies before replying, so the follow-up list_outputs
	// already reflects the change (positions re-flowed, heads rebuilt).
	pub( super ) fn monitors_apply<F>( &mut self, apply: F )
	where
		F: FnOnce() + Send + 'static,
	{
		self.monitors_scanning = true;
		let tx = self.scan_tx.clone();
		std::thread::spawn( move ||
		{
			apply();
			let _ = tx.send( ScanResult::Outputs( system::list_outputs() ) );
		});
	}

	pub( super ) fn start_outputs_scan( &mut self )
	{
		if self.monitors_scanning { return; }
		self.monitors_apply( || {} );
	}

	fn start_wifi_scan( &mut self )
	{
		if self.wifi_scanning { return; }
		self.wifi_scanning = true;
		// Stale results clear out so the view shows the scanning state;
		// the connected network survives — it is valid regardless of
		// scan freshness and should never blink away.
		self.wifi_networks.retain( |n| n.connected );
		let tx = self.scan_tx.clone();
		std::thread::spawn( move || { let _ = tx.send( ScanResult::Wifi( network::wifi_networks() ) ); } );
	}

	// PipeWire materialises / removes the sink of a Bluetooth audio
	// device a second or two after bluetoothctl returns, so the fresh
	// snapshot is taken in the background after that settling time.
	fn refresh_audio_later( &self )
	{
		let tx = self.scan_tx.clone();
		std::thread::spawn( move ||
		{
			std::thread::sleep( Duration::from_secs( 2 ) );
			let ( sinks, sources )        = system::audio_devices();
			let ( volume, muted )         = system::audio_state();
			let ( mic_volume, mic_muted ) = system::source_state();
			let _ = tx.send( ScanResult::Audio { sinks, sources, volume, muted, mic_volume, mic_muted } );
		});
	}

	fn start_bt_scan( &mut self )
	{
		if self.bt_scanning { return; }
		self.bt_scanning = true;
		// Stale discovery results clear out so the view shows the
		// scanning state; known (paired) devices survive — they are
		// valid regardless of scan freshness.
		self.bt_devices.retain( |d| d.paired );
		let tx = self.scan_tx.clone();
		std::thread::spawn( move ||
		{
			// Known devices show immediately; discovery then runs as
			// short bursts with a list update after each, so slow
			// advertisers appear as they are seen and late name
			// resolution replaces address-only entries.
			let _ = tx.send( ScanResult::Bt( system::bluetooth_devices(), false ) );
			for i in 0..4
			{
				system::bluetooth_discover( 3 );
				let _ = tx.send( ScanResult::Bt( system::bluetooth_devices(), i == 3 ) );
			}
		});
	}

	// Only sysfs / procfs reads — never blocks. Battery charge/state comes
	// from UPower via the slow snapshot instead.
	fn refresh_fast( &mut self )
	{
		self.brightness      = system::brightness();
		self.battery_full    = system::battery_full();
		self.battery_cycles  = system::battery_cycle_count();
		self.ac_online       = system::ac_online();
		self.used_memory     = system::used_memory();
		self.uptime_secs     = system::uptime_secs();
		// Keeps the clock ticking at poll cadence while the screen shows it.
		if self.screen == Screen::DateTime
		{
			self.refresh_datetime();
		}
	}

	fn refresh_datetime( &mut self )
	{
		use chrono::Local;
		let now = Local::now();
		self.date_str = now.format( "%Y-%m-%d" ).to_string();
		self.time_str = now.format( "%H:%M:%S" ).to_string();
		self.tz_str   = std::fs::read_to_string( "/etc/timezone" )
			.map( |s| s.trim().to_string() )
			.unwrap_or_else( |_| "UTC".into() );
	}

	fn open_datetime( &mut self )
	{
		self.refresh_datetime();
		self.ntp_on = system::ntp_enabled();
		self.seed_datetime_inputs();
		self.dt_apply_failed = false;
	}

	pub( super ) fn seed_datetime_inputs( &mut self )
	{
		use chrono::{ Datelike, Timelike };
		let now = chrono::Local::now();
		self.dt_date = ltk::Date::new( now.year(), now.month() as u8, now.day() as u8 );
		self.dt_time = ltk::Time::with_seconds( now.hour() as u8, now.minute() as u8, now.second() as u8 );
		self.dt_view = ( self.dt_date.year, self.dt_date.month );
		self.dt_cal_open = false;
	}

	// Resolve the wallpaper through ltk so we display the actual file the
	// shell would load. Try a raster first (highest-quality default) and
	// fall back to the SVG variant; ltk handles the light/dark fallback
	// internally based on the *active* mode.
	fn refresh_wallpaper( &mut self )
	{
		self.wallpaper_file = ltk::theme_branding_raster( "wallpaper", 0, 0 )
			.or_else( || ltk::theme_branding_asset( "wallpaper", "svg" ) )
			.or_else( || ltk::theme_branding_asset( "wallpaper", "webp" ) )
			.map( |p| p.display().to_string() );
	}
}

const SCREEN_KEYS: &[( &str, Screen )] = &[
	( "home",                  Screen::Home ),
	( "about",                 Screen::About ),
	( "system_app_updater",    Screen::SystemAppUpdater ),
	( "security_status",       Screen::SecurityStatus ),
	( "wifi",                  Screen::Wifi ),
	( "bluetooth",             Screen::Bluetooth ),
	( "cellular",              Screen::Cellular ),
	( "interconnectivity",     Screen::Interconnectivity ),
	( "portable_hotspot",      Screen::PortableHotspot ),
	( "more_connectivity",     Screen::MoreConnectivity ),
	( "lock_screen",           Screen::LockScreen ),
	( "notifications",         Screen::Notifications ),
	( "display",               Screen::Display ),
	( "monitors",              Screen::Monitors ),
	( "sound",                 Screen::Sound ),
	( "wallpaper",             Screen::Wallpaper ),
	( "themes",                Screen::Themes ),
	( "fingerprint_and_lock",  Screen::FingerprintAndLock ),
	( "privacy_protection",    Screen::PrivacyProtection ),
	( "apps",                  Screen::Apps ),
	( "battery",               Screen::Battery ),
	( "privacy",               Screen::Privacy ),
	( "location",              Screen::Location ),
	( "storage",               Screen::Storage ),
	( "language",              Screen::Language ),
	( "date_time",             Screen::DateTime ),
	( "accessibility",         Screen::Accessibility ),
];

fn screen_key( screen: &Screen ) -> &'static str
{
	SCREEN_KEYS.iter().find( |( _, s )| s == screen ).map( |( k, _ )| *k ).unwrap_or( "home" )
}

fn screen_from_key( key: &str ) -> Option<Screen>
{
	SCREEN_KEYS.iter().find( |( k, _ )| *k == key ).map( |( _, s )| s.clone() )
}

impl App for State
{
	type Message = Msg;

	fn app_id( &self ) -> &str { single_instance::APP_ID }

	fn save_state( &self ) -> Option<Vec<u8>>
	{
		Some( screen_key( &self.screen ).as_bytes().to_vec() )
	}

	fn restore_state( &mut self, state: Vec<u8> )
	{
		if let Some( screen ) = std::str::from_utf8( &state ).ok().and_then( screen_from_key )
		{
			self.screen = screen;
		}
	}

	fn take_focus_request( &mut self ) -> Option<ltk::WidgetId>
	{
		self.focus_request.take()
	}

	fn view( &self ) -> Element<Msg>
	{
		match self.screen
		{
			Screen::Home               => view_home( self ),
			Screen::About              => view_about( self ),
			Screen::SystemAppUpdater   => view_system_updater( self ),
			Screen::SecurityStatus     => view_security_status( self ),
			Screen::Wifi               => view_wifi( self ),
			Screen::Bluetooth          => view_bluetooth( self ),
			Screen::Cellular           => view_cellular( self ),
			Screen::Interconnectivity  => view_interconnectivity( self ),
			Screen::PortableHotspot    => view_portable_hotspot( self ),
			Screen::MoreConnectivity   => view_more_connectivity( self ),
			Screen::LockScreen         => view_lock_screen( self ),
			Screen::Notifications      => view_notifications( self ),
			Screen::Display            => view_display( self ),
			Screen::Monitors           => view_monitors( self ),
			Screen::Sound              => view_sound( self ),
			Screen::Wallpaper          => view_wallpaper( self ),
			Screen::Themes             => view_themes( self ),
			Screen::FingerprintAndLock => view_fingerprint( self ),
			Screen::PrivacyProtection  => view_privacy_protection( self ),
			Screen::Apps               => view_apps( self ),
			Screen::Battery            => view_battery( self ),
			Screen::Privacy            => view_privacy( self ),
			Screen::Location           => view_location( self ),
			Screen::Storage            => view_storage( self ),
			Screen::Language           => view_language( self ),
			Screen::DateTime           => view_datetime( self ),
			Screen::Accessibility      => view_accessibility( self ),
		}
	}

	fn update( &mut self, msg: Msg )
	{
		self.handle_msg( msg );
	}

	fn on_key( &mut self, keysym: Keysym ) -> Option<Msg>
	{
		match keysym
		{
			Keysym::Escape =>
			{
				if self.screen != Screen::Home { return Some( Msg::Back ); }
				std::process::exit( 0 );
			}
			_ => None,
		}
	}

	fn poll_interval( &self ) -> Option<Duration>
	{
		Some( Duration::from_secs( 5 ) )
	}

	// Keeps the scanning spinner turning while its screen is visible.
	fn is_animating( &self ) -> bool
	{
		( self.wifi_scanning && self.screen == Screen::Wifi )
			|| ( self.bt_scanning && self.screen == Screen::Bluetooth )
			|| ( self.monitors_scanning && self.screen == Screen::Monitors )
	}

	fn poll_external( &mut self ) -> Vec<Msg>
	{
		self.poll_msgs()
	}

	fn set_channel_sender( &mut self, sender: ltk::ChannelSender<Msg> )
	{
		self.ipc_inbox.lock().unwrap().waker = Some( sender );
	}

	fn window_config( &self ) -> Option<(&str, &str)>
	{
		Some( ( "Eydos Settings", single_instance::APP_ID ) )
	}
}

/// `--screen <name>` starts the app on a specific screen — used by the
/// shell's quick-settings deep links (WiFi / Bluetooth cards).
pub( crate ) fn screen_arg_name( args: &[String] ) -> Option<String>
{
	args.iter().position( |a| a == "--screen" ).and_then( |i| args.get( i + 1 ) ).cloned()
}

pub( crate ) fn screen_by_name( name: &str ) -> Option<Screen>
{
	match name
	{
		"wifi"      => Some( Screen::Wifi ),
		"bluetooth" => Some( Screen::Bluetooth ),
		"cellular"  => Some( Screen::Cellular ),
		"display"   => Some( Screen::Display ),
		"monitors"  => Some( Screen::Monitors ),
		"sound"     => Some( Screen::Sound ),
		"battery"   => Some( Screen::Battery ),
		_           => None,
	}
}
