// Screen 05 — Wireless configuration.

use std::sync::OnceLock;
use std::time::Instant;

use ltk::
{
	ButtonVariant, Color, Corners, Element,
	button, column, container, dialog, flex, list_item, pressable,
	row, scroll, spacer, spinner, stack, text, text_edit,
};

use crate::app::Msg;
use crate::assets;
use crate::flow::Screen;
use crate::i18n::tr;
use crate::state::{ ManualNetworkForm, State, WifiNetwork, WifiPasswordForm };
use crate::ui::chrome;
use crate::ui::theme as t;

const HIGHLIGHT_ALPHA: f32 = 0.18;
const ROW_PAD_V:       f32 = 12.0;
const ROW_PAD_H:       f32 = 16.0;
const ICON_SIZE:       u32 = 16;

pub fn view( state: &State ) -> Element<Msg>
{
	let mut list = column::<Msg>().spacing( 0.0 );
	for ( i, net ) in state.networks.iter().enumerate()
	{
		let selected = state.selected_network == Some( i );
		list = list.push( wifi_row( i, net, selected ) );
	}

	list = list.push(
		list_item::<Msg>( tr!( "wireless.add_manually" ).to_string() )
			.trailing( "›" )
			.on_press( Msg::AddNetworkManually ),
	);

	// Continue gates on whether the system is actually online — not
	// on which row is highlighted. NetworkManager's connectivity
	// status is refreshed once per second from
	// `Msg::CheckConnectivity`, so a pre-connected ethernet / LTE
	// session, or a successful WiFi association via the password
	// dialog, both flip Continue to enabled within ~1 s; if the link
	// drops mid-flow Continue greys out again. Same shape as the
	// disk-management footer so the wizard reads consistently across
	// screens.
	let continue_msg = if state.network_online { Some( Msg::Next ) } else { None };
	let footer = row::<Msg>()
		.spacing( t::ITEM_SPACING )
		.push( spacer() )
		.push(
			button::<Msg>( tr!( "wireless.use_cellular" ).to_string() )
				.variant( ButtonVariant::Tertiary )
				.font_size( t::control_font_size() )
				.on_press( Msg::UseCellular )
				.height( t::control_height() ),
		)
		.push(
			button::<Msg>( tr!( "common.continue" ).to_string() )
				.variant( ButtonVariant::Primary )
				.font_size( t::control_font_size() )
				.on_press_maybe( continue_msg )
				.height( t::control_height() ),
		);

	let body = column::<Msg>()
		.padding( t::PAGE_PADDING_H )
		.spacing( t::SECTION_SPACING )
		.push( chrome::top_bar( Screen::Wireless, true, state.mode ) )
		.push( chrome::title_block(
			&tr!( "wireless.title" ),
			Some( &tr!( "wireless.subtitle" ) ),
		) )
		.push( scroll( list ) )
		.push( footer );

	let page = chrome::page( body );

	// Connecting wins over the password dialog: while nmcli is in
	// flight the loop is blocked anyway, so we swap the password
	// prompt out for a dedicated full-card "Connecting…" overlay
	// that matches the disk-management applying overlay shape — a
	// big spinner + status line, no field, no buttons. When the
	// call returns, the screen flips back to either the network
	// list (success) or the password dialog with the error
	// surfaced inline (failure).
	if state.wifi_connecting
	{
		let ssid = state.password_form.as_ref()
			.and_then( |f| state.networks.get( f.network_idx ) )
			.map( |n| n.ssid.clone() )
			.or_else( || state.manual_form.as_ref().map( |f| f.ssid.clone() ) )
			.unwrap_or_default();
		stack::<Msg>().push( page ).push( connecting_dialog( ssid ) ).into()
	}
	else if let Some( form ) = state.password_form.clone()
	{
		stack::<Msg>().push( page ).push(
			password_dialog( form, &state.networks, state.wifi_error.as_deref() ),
		).into()
	}
	else if let Some( form ) = state.manual_form.clone()
	{
		stack::<Msg>().push( page ).push(
			manual_dialog( form, state.wifi_error.as_deref() ),
		).into()
	}
	else
	{
		page
	}
}

/// Manually-entered (possibly hidden) network: SSID field plus an
/// optional password — empty means an open network. Shares the error
/// presentation with [`password_dialog`].
fn manual_dialog( form: ManualNetworkForm, error: Option<&str> ) -> Element<Msg>
{
	let palette = ltk::theme_palette();

	let mut col = column::<Msg>().spacing( 8.0 );
	if let Some( err ) = error
	{
		col = col.push( text( err.to_string() )
			.size( t::SMALL_SIZE )
			.color( palette.danger )
			.wrap( true ) );
	}
	col = col
		.push(
			text_edit::<Msg>(
				tr!( "wireless.manual_ssid_ph" ).to_string(),
				form.ssid.clone(),
			)
				.on_change( Msg::ManualSsidChange )
				.font_size( t::control_font_size() )
				.height( t::control_height() ),
		)
		.push(
			text_edit::<Msg>(
				tr!( "wireless.password_placeholder" ).to_string(),
				form.password.clone(),
			)
				.on_change( Msg::ManualPasswordChange )
				.on_submit( Msg::ConfirmManual )
				.password_toggle( form.password_visible, Msg::ManualTogglePasswordVisibility )
				.font_size( t::control_font_size() )
				.height( t::control_height() ),
		);

	let connect = if form.ssid.trim().is_empty() { None } else { Some( Msg::ConfirmManual ) };
	dialog::<Msg>()
		.title( tr!( "wireless.manual_title" ).to_string() )
		.subtitle( tr!( "wireless.manual_subtitle" ).to_string() )
		.body( col )
		.cancel( Msg::CancelManual )
		.action(
			button::<Msg>( tr!( "disk.dialog_cancel" ).to_string() )
				.variant( ButtonVariant::Tertiary )
				.font_size( t::control_font_size() )
				.on_press( Msg::CancelManual )
				.height( t::control_height() ),
		)
		.action(
			button::<Msg>( tr!( "wireless.connect" ).to_string() )
				.variant( ButtonVariant::Primary )
				.font_size( t::control_font_size() )
				.on_press_maybe( connect )
				.height( t::control_height() ),
		)
		.into()
}

/// Full-card overlay shown while `nmcli` is associating with the
/// AP. Mirrors the applying overlay on the disk-management screen
/// (spinner 48 px + status line, no buttons) so the wizard reads
/// consistently across long-running steps. The spinner's `phase`
/// is driven from a process-lifetime `Instant` so it actually
/// rotates while the worker thread runs `nmcli` in the background
/// — without `.phase( … )` ltk's spinner is stateless and would
/// stay frozen at angle 0.
fn connecting_dialog( ssid: String ) -> Element<Msg>
{
	let palette = ltk::theme_palette();
	let body = column::<Msg>()
		.spacing( t::ITEM_SPACING )
		.align_center_x( true )
		.push( spinner().size( 48.0 ).stroke_width( 4.0 ).phase( spinner_phase() ) )
		.push( text( tr!( "wireless.connecting", ssid = ssid ).to_string() )
			.size( t::BODY_SIZE )
			.color( palette.text_primary )
			.align_center() );
	dialog::<Msg>().body( body ).into()
}

/// Monotonically-increasing seconds-since-process-start, captured
/// from a single `Instant` to keep f32 precision tight.
fn spinner_phase() -> f32
{
	static START: OnceLock<Instant> = OnceLock::new();
	START.get_or_init( Instant::now ).elapsed().as_secs_f32()
}

/// One row in the network list. Shows the SSID on the left and a
/// padlock icon on the right when the AP requires a key. Hand-rolled
/// (rather than via [`list_item`]) because `list_item.trailing` only
/// takes a string — Sora doesn't ship the `🔒` emoji codepoint and
/// the system fallback chain doesn't either, so the previous version
/// rendered as a `[?]` tofu box. A theme-icon `safety/padlock` reads
/// correctly under any font config.
fn wifi_row( idx: usize, net: &WifiNetwork, selected: bool ) -> Element<Msg>
{
	let palette = ltk::theme_palette();

	let label = text( net.ssid.clone() )
		.size( t::BODY_SIZE )
		.color( palette.text_primary );

	let mut inner = row::<Msg>()
		.spacing( 12.0 )
		.push( flex( label ) );
	if net.locked
	{
		if let Some( icon ) = assets::theme_icon::<Msg>( "safety/padlock", ICON_SIZE )
		{
			inner = inner.push( icon );
		}
	}

	let padded = container::<Msg>( inner )
		.padding_h( ROW_PAD_H )
		.padding_v( ROW_PAD_V );

	let row_el: Element<Msg> = pressable::<Msg>( padded )
		.on_press( Msg::SelectNetwork( idx ) )
		.into();

	if selected
	{
		let a = palette.accent;
		let bg = Color { r: a.r, g: a.g, b: a.b, a: HIGHLIGHT_ALPHA };
		container::<Msg>( row_el )
			.background( bg )
			.radius( Corners::all( 0.0 ) )
			.into()
	}
	else
	{
		row_el
	}
}

/// WPA-password prompt for a locked network. The text field is
/// `secure( !password_visible )` so the bullets toggle on the eye
/// icon click, and the title embeds the SSID so the user can see
/// which AP they are about to authenticate against.
///
/// Two states share the dialog shell:
///
/// * **Idle** — text edit (with eye toggle) + `[ Cancel ] [ Connect ]`.
///   Connect fires `nmcli connection add … connection up …` and
///   pivots into the dedicated [`connecting_dialog`] overlay.
/// * **Error** — Idle layout plus a red error line above the text
///   edit (the trimmed nmcli stderr — wrong password, AP
///   unreachable, timeout). The user retries by editing the
///   password, which clears the error.
///
/// Connect only marks the network as the *configured* choice on
/// success: a failed attempt keeps the form open and the wizard's
/// Continue button stays disabled, so the user cannot advance past
/// a network they could not actually associate with.
fn password_dialog(
	form:     WifiPasswordForm,
	networks: &[ WifiNetwork ],
	error:    Option<&str>,
) -> Element<Msg>
{
	let palette = ltk::theme_palette();
	let ssid    = networks.get( form.network_idx )
		.map( |n| n.ssid.clone() )
		.unwrap_or_default();

	let mut col = column::<Msg>().spacing( 8.0 );
	if let Some( err ) = error
	{
		col = col.push( text( err.to_string() )
			.size( t::SMALL_SIZE )
			.color( palette.danger )
			.wrap( true ) );
	}
	col = col.push(
		text_edit::<Msg>(
			tr!( "wireless.password_placeholder" ).to_string(),
			form.password.clone(),
		)
			.on_change( Msg::PasswordChange )
			.on_submit( Msg::ConfirmPassword )
			.password_toggle( form.password_visible, Msg::TogglePasswordVisibility )
			.font_size( t::control_font_size() )
			.height( t::control_height() ),
	);

	dialog::<Msg>()
		.title( tr!( "wireless.password_title", ssid = ssid ).to_string() )
		.subtitle( tr!( "wireless.password_subtitle" ).to_string() )
		.body( col )
		.cancel( Msg::CancelPassword )
		.action(
			button::<Msg>( tr!( "disk.dialog_cancel" ).to_string() )
				.variant( ButtonVariant::Tertiary )
				.font_size( t::control_font_size() )
				.on_press( Msg::CancelPassword )
				.height( t::control_height() ),
		)
		.action(
			button::<Msg>( tr!( "wireless.connect" ).to_string() )
				.variant( ButtonVariant::Primary )
				.font_size( t::control_font_size() )
				.on_press( Msg::ConfirmPassword )
				.height( t::control_height() ),
		)
		.into()
}
