// The message-handling half of the App: every user action lands here.
// Split from `mod.rs` so state layout and update logic read separately.

use std::sync::mpsc;

use crate::{ config, i18n, network, notif_channels, storage, system };
use crate::views::wifi::WIFI_PASSWORD_ID;

use super::{ spawn_updates_check, Msg, ScanResult, Screen, State };

impl State
{
	pub( super ) fn handle_msg( &mut self, msg: Msg )
	{
		match msg
		{
			Msg::Open( s ) =>
			{
				match &s
				{
					Screen::Wifi      => self.start_wifi_scan(),
					Screen::Bluetooth => self.start_bt_scan(),
					Screen::Notifications =>
					{
						if !self.notif_scanning
						{
							self.notif_scanning = true;
							let tx = self.scan_tx.clone();
							std::thread::spawn( move || { let _ = tx.send( ScanResult::Notif( notif_channels::load() ) ); } );
						}
					}
					Screen::Storage   => self.storage       = storage::scan(),
					Screen::Sound     =>
					{
						( self.sinks, self.sources ) = system::audio_devices();
						( self.volume, self.muted ) = system::audio_state();
						( self.mic_volume, self.mic_muted ) = system::source_state();
					}
					Screen::Apps      =>
					{
						if self.installed_apps.is_empty()
						{
							self.installed_apps = system::installed_apps();
						}
					}
					Screen::Wallpaper => self.refresh_wallpaper(),
					Screen::DateTime  => self.open_datetime(),
					Screen::Monitors  => self.start_outputs_scan(),
					_ => {}
				}
				self.screen = s;
			}
			Msg::Back => self.screen = Screen::Home,

			Msg::ToggleWifi =>
			{
				self.wifi_on = !self.wifi_on;
				network::set_wifi_enabled( self.wifi_on );
				if self.wifi_on
				{
					self.start_wifi_scan();
				}
				if !self.wifi_on
				{
					self.wifi_networks.clear();
					self.wifi_selected = None;
					self.connected_ssid = None;
				}
			}
			Msg::SelectWifi( ssid ) =>
			{
				self.wifi_selected = if self.wifi_selected.as_deref() == Some( ssid.as_str() ) { None } else { Some( ssid ) };
				self.wifi_password.clear();
				self.wifi_pw_visible = false;
				self.wifi_error = None;
				if let Some( sel ) = self.wifi_selected.as_deref()
				{
					let needs_password = self.wifi_networks.iter()
						.find( |n| n.ssid == sel )
						.map( |n| n.secured && !n.known && !n.connected )
						.unwrap_or( false );
					if needs_password
					{
						self.focus_request = Some( WIFI_PASSWORD_ID );
					}
				}
			}
			Msg::WifiPasswordInput( s )      => self.wifi_password = s,
			Msg::ToggleWifiPasswordVisible   => self.wifi_pw_visible = !self.wifi_pw_visible,
			Msg::WifiConnect =>
			{
				if let Some( ssid ) = self.wifi_selected.clone()
				{
					if self.wifi_connecting.is_none()
					{
						let needs_password = self.wifi_networks.iter()
							.find( |n| n.ssid == ssid )
							.map( |n| n.secured && !n.known )
							.unwrap_or( false );
						let password = if needs_password { Some( self.wifi_password.clone() ) } else { None };
						self.wifi_connecting = Some( ssid.clone() );
						self.wifi_error = None;
						let tx = self.scan_tx.clone();
						std::thread::spawn( move ||
						{
							let ok = network::connect( &ssid, password.as_deref() );
							let _ = tx.send( ScanResult::WifiConnect( ssid, ok ) );
						});
					}
				}
			}
			Msg::OpenPortal =>
			{
				let _ = std::process::Command::new( "eydos-portal" ).spawn();
			}
			Msg::WifiForget( ssid ) =>
			{
				self.wifi_selected = None;
				self.wifi_error = None;
				if self.connected_ssid.as_deref() == Some( ssid.as_str() )
				{
					self.connected_ssid = None;
				}
				for n in &mut self.wifi_networks
				{
					if n.ssid == ssid { n.known = false; n.connected = false; }
				}
				std::thread::spawn( move || network::forget( &ssid ) );
			}
			Msg::WifiScan => self.start_wifi_scan(),
			Msg::BtScan   => self.start_bt_scan(),
			Msg::ToggleBluetooth =>
			{
				self.bt_on = !self.bt_on;
				system::set_bluetooth_enabled( self.bt_on );
				if self.bt_on
				{
					self.start_bt_scan();
				} else {
					self.bt_devices.clear();
					self.bt_selected = None;
					self.bt_error = None;
				}
			}
			Msg::SelectBt( addr ) =>
			{
				self.bt_selected = if self.bt_selected.as_deref() == Some( addr.as_str() ) { None } else { Some( addr ) };
				self.bt_error = None;
			}
			Msg::BtConnect( addr ) =>
			{
				if self.bt_connecting.is_none()
				{
					let pair_first = self.bt_devices.iter()
						.find( |d| d.addr == addr )
						.map( |d| !d.paired )
						.unwrap_or( false );
					self.bt_connecting = Some( addr.clone() );
					self.bt_error = None;
					let tx = self.scan_tx.clone();
					std::thread::spawn( move ||
					{
						let result = system::bluetooth_connect( &addr, pair_first );
						let _ = tx.send( ScanResult::BtConnect( addr, result ) );
					});
				}
			}
			Msg::BtDisconnect( addr ) =>
			{
				self.bt_selected = None;
				for d in &mut self.bt_devices
				{
					if d.addr == addr { d.connected = false; }
				}
				std::thread::spawn( move || system::bluetooth_disconnect( &addr ) );
				self.refresh_audio_later();
			}
			Msg::BtForget( addr ) =>
			{
				self.bt_selected = None;
				self.bt_error = None;
				self.bt_devices.retain( |d| d.addr != addr );
				std::thread::spawn( move || system::bluetooth_forget( &addr ) );
				self.refresh_audio_later();
			}
			Msg::SetBrightness( v ) =>
			{
				self.brightness = v;
				system::set_brightness( v );
			}
			Msg::SetVolume( v ) =>
			{
				self.volume = v;
				system::set_volume( v );
			}
			Msg::ToggleMute =>
			{
				self.muted = !self.muted;
				system::set_muted( self.muted );
			}
			Msg::SelectSink( id ) =>
			{
				system::set_default_device( id );
				// The master slider now controls the new default sink.
				( self.sinks, self.sources ) = system::audio_devices();
				( self.volume, self.muted ) = system::audio_state();
			}
			Msg::SelectSource( id ) =>
			{
				system::set_default_device( id );
				( self.sinks, self.sources ) = system::audio_devices();
				( self.mic_volume, self.mic_muted ) = system::source_state();
			}
			Msg::SetMicVolume( v ) =>
			{
				self.mic_volume = v;
				system::set_source_volume( v );
			}
			Msg::ToggleMicMute =>
			{
				self.mic_muted = !self.mic_muted;
				system::set_source_muted( self.mic_muted );
			}
			Msg::SetPowerProfile( p ) =>
			{
				system::set_power_profile( &p );
				self.power_profile = p;
			}
			Msg::PreviewBatteryWarn( v ) => self.battery_warn_pct = v,
			Msg::SetBatteryWarn( v ) =>
			{
				self.battery_warn_pct = v;
				system::set_power_policy_u32( "battery_warn_percent", v );
			}
			Msg::PreviewBatterySuspend( v ) => self.battery_suspend_pct = v,
			Msg::SetBatterySuspend( v ) =>
			{
				self.battery_suspend_pct = v;
				system::set_power_policy_u32( "battery_suspend_percent", v );
			}
			Msg::ToggleNightLight =>
			{
				self.night_light_on = !self.night_light_on;
				system::set_night_light_enabled( self.night_light_on );
			}
			Msg::SetNightLightTemp( t ) =>
			{
				self.night_light_temp = t;
				system::set_night_light_temp( t );
			}
			// Dragging only moves the thumb; the gsettings write waits for
			// the release so a sweep does not fire one per motion event.
			Msg::PreviewUiScale( s ) =>
			{
				self.ui_scale = s;
			}
			Msg::SetUiScale( s ) =>
			{
				self.ui_scale = s;
				system::set_ui_scale( s );
			}
			Msg::PreviewInterfaceScale( s ) =>
			{
				self.interface_scale = s;
			}
			// Leaving automatic pins whatever it is applying right now, so the
			// switch never changes the size by itself.
			Msg::ToggleInterfaceAuto =>
			{
				let v = if self.interface_scale > 0.0
				{
					0.0
				} else {
					system::INTERFACE_STEPS[ system::interface_step_index( self.interface_automatic ) ]
				};
				self.interface_scale = v;
				system::set_interface_scale( v );
			}
			Msg::SetInterfaceScale( s ) =>
			{
				self.interface_scale = s;
				system::set_interface_scale( s );
			}
			Msg::SetSleepTimeout( t ) =>
			{
				self.sleep_timeout = t;
				system::set_sleep_timeout_secs( t );
			}
			Msg::SelectMonitor( c ) =>
			{
				let same = self.monitor_selected.as_deref() == Some( c.as_str() );
				self.monitor_selected   = if same { None } else { Some( c.clone() ) };
				self.monitor_scale_drag = None;
				// Seed the resolution picker on the selected monitor's
				// current mode; only one detail section is ever on screen.
				self.monitor_mode_combo = ltk::ComboState::new();
				if let Some( o ) = self.outputs.iter().find( |o| o.connector == c )
				{
					if let Some( idx ) = o.modes.iter().position( |m| *m == o.current_mode )
					{
						self.monitor_mode_combo.select( idx );
					}
				}
			}
			Msg::SetDisplayExtend( extend ) =>
			{
				self.display_extend = extend;
				self.monitors_apply( move || system::set_display_mode( extend ) );
			}
			Msg::MonitorToggleScaleAuto =>
			{
				if let Some( c ) = self.monitor_selected.clone()
				{
					// Turning automatic off pins the scale currently in
					// force, so nothing jumps at the flip of the toggle.
					let pick = self.outputs.iter().find( |o| o.connector == c ).map( |o|
					{
						if o.scale <= 0.0
						{
							if o.scale_applied > 0.0 { o.scale_applied } else { o.auto_scale }
						} else { 0.0 }
					});
					if let Some( pick ) = pick
					{
						if let Some( o ) = self.outputs.iter_mut().find( |o| o.connector == c )
						{
							o.scale = pick;
						}
						self.monitors_apply( move || system::set_output_scale_pick( &c, pick ) );
					}
				}
			}
			Msg::MonitorPreviewScale( v ) => self.monitor_scale_drag = Some( v ),
			Msg::MonitorSetScale( v ) =>
			{
				self.monitor_scale_drag = None;
				if let Some( c ) = self.monitor_selected.clone()
				{
					if let Some( o ) = self.outputs.iter_mut().find( |o| o.connector == c )
					{
						o.scale = v as f64;
					}
					self.monitors_apply( move || system::set_output_scale_pick( &c, v as f64 ) );
				}
			}
			Msg::MonitorModeToggle  => self.monitor_mode_combo.toggle_open(),
			Msg::MonitorModeDismiss => self.monitor_mode_combo.is_open = false,
			Msg::MonitorModeSelect( i ) =>
			{
				self.monitor_mode_combo.selected.clear();
				self.monitor_mode_combo.select( i );
				self.monitor_mode_combo.is_open = false;
				if let Some( c ) = self.monitor_selected.clone()
				{
					let mode = self.outputs.iter()
						.find( |o| o.connector == c )
						.and_then( |o| o.modes.get( i ) )
						.cloned();
					if let Some( mode ) = mode
					{
						self.monitors_apply( move || system::set_output_mode( &c, &mode ) );
					}
				}
			}
			Msg::MonitorPlace( place ) =>
			{
				if let Some( c ) = self.monitor_selected.clone()
				{
					let primary = self.outputs.iter()
						.find( |o| o.primary )
						.and_then( |o| o.logical_size );
					let own = self.outputs.iter()
						.find( |o| o.connector == c )
						.and_then( |o| o.logical_size );
					if let ( Some( ( pw, ph ) ), Some( ( ow, oh ) ) ) = ( primary, own )
					{
						let ( x, y ) = match place
						{
							system::Placement::Left  => ( -ow, 0 ),
							system::Placement::Right => ( pw, 0 ),
							system::Placement::Above => ( 0, -oh ),
							system::Placement::Below => ( 0, ph ),
						};
						self.monitors_apply( move || system::set_output_position( &c, x, y ) );
					}
				}
			}
			Msg::MonitorEnable( enabled ) =>
			{
				if let Some( c ) = self.monitor_selected.clone()
				{
					self.monitors_apply( move || system::set_output_enabled( &c, enabled ) );
				}
			}

			Msg::RotationToggle  => self.rotation_combo.toggle_open(),
			Msg::RotationDismiss => self.rotation_combo.is_open = false,
			Msg::RotationSelect( i ) =>
			{
				self.rotation_combo.selected.clear();
				self.rotation_combo.select( i );
				self.rotation_combo.is_open = false;
				match i
				{
					1 => system::lock_rotation( system::Rotation::Portrait ),
					2 => system::lock_rotation( system::Rotation::Landscape ),
					3 => system::lock_rotation( system::Rotation::InversePortrait ),
					4 => system::lock_rotation( system::Rotation::InverseLandscape ),
					_ => system::unlock_rotation(),
				}
			}

			Msg::ToggleDnd =>
			{
				self.dnd_enabled = !self.dnd_enabled;
				system::set_dnd_enabled( self.dnd_enabled );
			}
			Msg::ToggleLockscreenNotifs =>
			{
				self.lockscreen_show_notifs = !self.lockscreen_show_notifs;
				system::set_lockscreen_show_notifications( self.lockscreen_show_notifs );
			}
			Msg::ToggleLocation =>
			{
				self.location_enabled = !self.location_enabled;
				system::set_location_enabled( self.location_enabled );
			}
			Msg::ToggleAirplane =>
			{
				self.airplane_mode = !self.airplane_mode;
				system::set_airplane_mode( self.airplane_mode );
			}
			Msg::ToggleHotspot =>
			{
				self.hotspot_active = !self.hotspot_active;
				network::set_hotspot_enabled( self.hotspot_active );
			}
			Msg::LaunchApp( exec ) => system::launch_app( &exec ),

			Msg::ToggleNtp =>
			{
				system::set_ntp( !self.ntp_on );
				self.ntp_on = system::ntp_enabled();
				if !self.ntp_on
				{
					self.seed_datetime_inputs();
				}
				self.dt_apply_failed = false;
			}
			Msg::ToggleDateEdit => self.dt_cal_open = !self.dt_cal_open,
			Msg::DateChanged( d ) =>
			{
				self.dt_date = d;
				self.dt_view = ( d.year, d.month );
				self.dt_cal_open = false;
				self.dt_apply_failed = false;
			}
			Msg::DateView( year, month ) => self.dt_view = ( year, month ),
			Msg::TimeChanged( t ) =>
			{
				self.dt_time = t;
				self.dt_apply_failed = false;
			}
			Msg::ApplyDateTime =>
			{
				let when = format!(
					"{:04}-{:02}-{:02} {:02}:{:02}:{:02}",
					self.dt_date.year, self.dt_date.month, self.dt_date.day,
					self.dt_time.hour, self.dt_time.minute, self.dt_time.second,
				);
				self.dt_apply_failed = !system::set_datetime( &when );
				self.refresh_datetime();
			}

			Msg::SetThemePref( pref ) =>
			{
				use chrono::Timelike as _;
				self.theme_pref = pref;
				let hour = chrono::Local::now().hour();
				ltk::set_active_mode( pref.resolve( hour ) );
				config::write_theme_pref( pref );
			}
			Msg::SetWallpaperVariant( mode ) =>
			{
				self.wallpaper_variant = mode;
				config::write_wallpaper_variant( mode );
			}
			Msg::SetLanguage( code ) =>
			{
				self.language = code.to_string();
				i18n::set_locale( code );
				config::write_language( code );
			}

			Msg::ToggleChannelBlocked( app_id, channel_id ) =>
			{
				if let Some( c ) = self.notif_channels.iter_mut().find( | c | c.app_id == app_id && c.channel_id == channel_id )
				{
					c.blocked = !c.blocked;
					notif_channels::set_blocked( &app_id, &channel_id, c.blocked );
				}
			}

			Msg::SearchInput( s )      => self.search_query = s,
			Msg::Refresh               => self.refresh_fast(),
			Msg::TriggerUpdateCheck    =>
			{
				let ( utx, urx ) = mpsc::channel();
				self.updates_rx = urx;
				spawn_updates_check( utx );
			}
		}
	}
}
