// The channel-draining half of the App: background threads deliver
// their results here, and the poll decides when a redraw is worth it.

use std::time::Duration;

use crate::network;

use super::{ screen_by_name, spawn_slow, Msg, ScanResult, State };

impl State
{
	pub( super ) fn poll_msgs( &mut self ) -> Vec<Msg>
	{
		// NOTE: the ltk runtime calls this on *every* event-loop iteration
		// (frame callbacks, input, timer…), not only when `poll_interval`
		// fires. So this must be cheap and idempotent: never unconditionally
		// spawn work or return a redraw message, or we feed a redraw → wake →
		// poll → spawn loop that pegs the CPU and forks a subprocess storm.
		let mut changed = false;

		// Drain results delivered by the background slow-refresh thread.
		while let Ok( snap ) = self.slow_rx.try_recv()
		{
			self.wifi_on                = snap.wifi_on;
			self.connected_ssid         = snap.connected_ssid;
			self.connectivity           = snap.connectivity;
			self.bt_on                  = snap.bt_on;
			self.volume                 = snap.volume;
			self.muted                  = snap.muted;
			self.battery_pct            = snap.battery.as_ref().filter( |b| b.present ).map( |b| b.percent );
			self.battery_chg            = snap.battery.as_ref().map( |b| b.charging ).unwrap_or( false );
			self.battery_present        = snap.battery.as_ref().map( |b| b.present ).unwrap_or( false );
			self.battery_time_secs      = snap.battery.as_ref().and_then( |b| b.time_remaining_secs );
			self.power_profile          = snap.power_profile;
			self.night_light_on         = snap.night_light_on;
			self.night_light_temp       = snap.night_light_temp;
			self.ui_scale               = snap.ui_scale;
			self.interface_automatic    = snap.interface_automatic;
			self.sleep_timeout          = snap.sleep_timeout;
			self.primary_ip             = snap.primary_ip;
			self.dnd_enabled            = snap.dnd_enabled;
			self.lockscreen_show_notifs = snap.lockscreen_show_notifs;
			self.location_enabled       = snap.location_enabled;
			self.airplane_mode          = snap.airplane_mode;
			self.hotspot_active         = snap.hotspot_active;
			self.firewall_active        = snap.firewall_active;
			self.selinux_state          = snap.selinux_state;
			self.sim_imei               = snap.sim_imei;
			self.sim_operator           = snap.sim_operator;
			self.fprintd_active         = snap.fprintd_active;
			self.fprintd_enrolled       = snap.fprintd_enrolled;
			self.vpn_connections        = snap.vpn_connections;
			self.slow_in_flight         = false;
			changed = true;
		}

		// Drain the one-shot updates check.
		while let Ok( n ) = self.updates_rx.try_recv()
		{
			self.updates_available = n;
			changed = true;
		}

		// Drain on-demand scan results.
		while let Ok( result ) = self.scan_rx.try_recv()
		{
			match result
			{
				ScanResult::Wifi( nets )  => { self.wifi_networks = nets;  self.wifi_scanning  = false; }
				ScanResult::WifiConnect( ssid, ok ) =>
				{
					self.wifi_connecting = None;
					if ok
					{
						let open_net = self.wifi_networks.iter()
							.find( |n| n.ssid == ssid )
							.map( |n| !n.secured )
							.unwrap_or( false );
						self.connected_ssid = Some( ssid.clone() );
						self.wifi_selected  = None;
						self.wifi_password.clear();
						self.wifi_error     = None;
						for n in &mut self.wifi_networks
						{
							n.connected = n.ssid == ssid;
							if n.connected { n.known = true; }
						}
						if open_net
						{
							// The portal window closes itself when there is nothing to sign into.
							let _ = std::process::Command::new( "eydos-portal" ).spawn();
						} else {
							let tx = self.scan_tx.clone();
							std::thread::spawn( move || { let _ = tx.send( ScanResult::Connectivity( network::check_connectivity() ) ); } );
						}
					} else {
						self.wifi_error = Some( ssid );
					}
				}
				ScanResult::Connectivity( c ) =>
				{
					self.connectivity = c;
					// Phone-like behaviour: a portal detected right after
					// connecting opens the sign-in window by itself.
					if c == network::CONNECTIVITY_PORTAL
					{
						let _ = std::process::Command::new( "eydos-portal" ).spawn();
					}
				}
				ScanResult::Bt( devs, done ) =>
				{
					self.bt_devices = devs;
					if done { self.bt_scanning = false; }
				}
				ScanResult::Audio { sinks, sources, volume, muted, mic_volume, mic_muted } =>
				{
					self.sinks      = sinks;
					self.sources    = sources;
					self.volume     = volume;
					self.muted      = muted;
					self.mic_volume = mic_volume;
					self.mic_muted  = mic_muted;
				}
				ScanResult::BtConnect( addr, result ) =>
				{
					self.bt_connecting = None;
					match result
					{
						Ok( () ) =>
						{
							self.bt_selected = None;
							for d in &mut self.bt_devices
							{
								if d.addr == addr { d.connected = true; d.paired = true; }
							}
							self.refresh_audio_later();
						}
						Err( e ) => self.bt_error = Some( ( addr, e ) ),
					}
				}
				ScanResult::Notif( chs )  => { self.notif_channels = chs; self.notif_scanning = false; }
				ScanResult::Outputs( res ) =>
				{
					if let Some( o ) = res
					{
						self.outputs        = o.outputs;
						self.display_extend = o.extend;
					}
					self.monitors_scanning = false;
				}
			}
			changed = true;
		}

		// Kick off the next slow refresh only when the previous one has
		// finished *and* at least the poll interval has elapsed — one
		// background pass at a time, no faster than every 5 s.
		if !self.slow_in_flight
			&& self.last_slow_spawn.elapsed() >= Duration::from_secs( 5 )
		{
			self.last_slow_spawn = std::time::Instant::now();
			self.slow_in_flight  = true;
			spawn_slow( self.slow_tx.clone() );
		}

		// Deep links forwarded by later launches through the single-instance
		// guard — navigate to the requested screen.
		let deep_links = std::mem::take( &mut self.ipc_inbox.lock().unwrap().pending );
		let mut msgs: Vec<Msg> = deep_links.iter()
			.filter_map( |name| screen_by_name( name ) )
			.map( Msg::Open )
			.collect();

		// Only ask for a redraw when fresh data actually arrived. Returning
		// an empty vec lets the event loop go back to sleep.
		if changed && msgs.is_empty() { msgs.push( Msg::Refresh ); }
		msgs
	}
}
