use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{
    atomic::{AtomicBool, AtomicI32, Ordering},
    Arc, Mutex,
};
use std::collections::{HashMap, HashSet};

mod localize;
mod page;
use page::Page as _;

use cosmic::app::{Core, Settings, Task};
use cosmic::iced::{self, window, Alignment, Length, Subscription};
use cosmic::iced_core::layout::Limits;
use cosmic::{executor, theme, widget, Application, ApplicationExt, Element};
use serde::Deserialize;

const APP_ID: &str = "org.rakuos.Welcome";
const WELCOME_DONE_FILE: &str = ".config/rakuos/welcome-done";
const LOGO_DARK: &str = "/usr/share/pixmaps/fedora_whitelogo_med.png";
const LOGO_LIGHT: &str = "/usr/share/pixmaps/fedora_logo_med.png";

fn load_apps_json() -> std::borrow::Cow<'static, str> {
    std::fs::read_to_string("/usr/share/rakuos-welcome/apps.json")
        .map(std::borrow::Cow::Owned)
        .unwrap_or(std::borrow::Cow::Borrowed(include_str!("../../../data/apps.json")))
}

// ── Catalog types ─────────────────────────────────────────────────────────────

#[derive(Debug, Deserialize, Clone)]
struct AppEntry {
    id: String,
    name: String,
    summary: String,
    #[serde(default)]
    icon: String,
    #[serde(rename = "type")]
    kind: String,
    package: String,
    #[serde(default)]
    remote: String,
    #[serde(default)]
    packages: Vec<String>,
    #[serde(skip)]
    icon_path: String,
    #[serde(skip)]
    icon_url: String,
}

impl AppEntry {
    fn unit_weight(&self) -> usize { 1 + self.packages.len() }
}

#[derive(Debug, Deserialize, Clone)]
struct Category {
    id: String,
    title: String,
    icon: String,
    description: String,
    apps: Vec<AppEntry>,
    #[serde(default)]
    exclusive: bool,
}

#[derive(Debug, Deserialize, Clone)]
struct Catalog { categories: Vec<Category> }

// ── WiFi types ────────────────────────────────────────────────────────────────

#[derive(Debug, Clone)]
struct WifiNetwork { ssid: String, signal: u8, secured: bool }

// ── Install progress types ────────────────────────────────────────────────────

#[derive(Debug, Clone, PartialEq)]
enum TaskProgress { Pending, Installing, Done(bool) }

#[derive(Debug, Clone)]
struct InstallTask { app: AppEntry, remove: bool }

// ── Icon widget ───────────────────────────────────────────────────────────────

fn app_icon_widget<'a, Msg: 'static>(path: &str, url: &str, size: u16) -> Element<'a, Msg> {
    if !path.is_empty() {
        if let Ok(bytes) = std::fs::read(path) {
            let handle = if is_svg(path, &bytes) {
                widget::icon::from_svg_bytes(bytes)
            } else {
                widget::icon::from_raster_bytes(bytes)
            };
            return widget::icon::icon(handle).size(size).into();
        }
    }
    if url.starts_with("http://") || url.starts_with("https://") {
        return widget::image(url.to_string())
            .width(Length::Fixed(size as f32))
            .height(Length::Fixed(size as f32))
            .into();
    }
    widget::icon::from_name("package-x-generic").size(size).into()
}

fn is_svg(path: &str, bytes: &[u8]) -> bool {
    if Path::new(path).extension().and_then(|e| e.to_str()).is_some_and(|e| e.eq_ignore_ascii_case("svg")) {
        return true;
    }
    let sniff = String::from_utf8_lossy(&bytes[..bytes.len().min(512)]).to_lowercase();
    sniff.contains("<svg") || sniff.contains("<?xml")
}

// ── Installed-package detection ───────────────────────────────────────────────

fn query_installed_rpms() -> HashSet<String> {
    std::process::Command::new("rpm")
        .args(["-qa", "--qf", "%{NAME}\\n"])
        .output()
        .map(|o| String::from_utf8_lossy(&o.stdout).lines().map(|s| s.to_string()).collect())
        .unwrap_or_default()
}

fn query_installed_flatpaks() -> HashSet<String> {
    std::process::Command::new("flatpak")
        .args(["list", "--app", "--columns=application"])
        .output()
        .map(|o| String::from_utf8_lossy(&o.stdout).lines().map(|s| s.to_string()).collect())
        .unwrap_or_default()
}

fn is_installed(app: &AppEntry, rpms: &HashSet<String>, flatpaks: &HashSet<String>) -> bool {
    match app.kind.as_str() {
        "flatpak" => flatpaks.contains(&app.package),
        "script"  => false,
        _         => rpms.contains(&app.package),
    }
}

// ── Network helpers ───────────────────────────────────────────────────────────

fn check_connectivity() -> bool {
    std::process::Command::new("nmcli")
        .args(["networking", "connectivity", "check"])
        .output()
        .map(|o| String::from_utf8_lossy(&o.stdout).trim() == "full")
        .unwrap_or(false)
}

fn scan_wifi() -> Vec<WifiNetwork> {
    let out = match std::process::Command::new("nmcli")
        .args(["-t", "-f", "SSID,SIGNAL,SECURITY", "device", "wifi", "list", "--rescan", "yes"])
        .output()
    {
        Ok(o) => o,
        Err(_) => return vec![],
    };
    let mut seen = HashSet::new();
    String::from_utf8_lossy(&out.stdout)
        .lines()
        .filter_map(|line| {
            let parts: Vec<&str> = line.splitn(3, ':').collect();
            let ssid = parts.first().map(|s| s.replace("\\:", ":")).unwrap_or_default();
            if ssid.is_empty() || !seen.insert(ssid.clone()) { return None; }
            let signal: u8 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(0);
            let secured = parts.get(2).map_or(false, |s| !s.is_empty() && *s != "--");
            Some(WifiNetwork { ssid, signal, secured })
        })
        .collect()
}

fn connect_wifi(ssid: &str, password: &str) -> bool {
    let mut cmd = std::process::Command::new("nmcli");
    cmd.args(["device", "wifi", "connect", ssid]);
    if !password.is_empty() { cmd.args(["password", password]); }
    cmd.status().map(|s| s.success()).unwrap_or(false)
}

// ── App state ─────────────────────────────────────────────────────────────────

fn done_file() -> PathBuf {
    PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| "/root".to_string()))
        .join(WELCOME_DONE_FILE)
}

fn log_file_path() -> PathBuf {
    std::env::temp_dir().join("rakuos-welcome-cosmic.log")
}

struct SharedState {
    running: AtomicBool,
    current_task: AtomicI32,
}
impl Default for SharedState {
    fn default() -> Self {
        Self { running: AtomicBool::new(false), current_task: AtomicI32::new(-1) }
    }
}

pub struct WelcomeApp {
    core: Core,
    catalog: Catalog,
    current_page: usize,
    selections: HashMap<String, bool>,
    initial_installed: HashMap<String, bool>,
    force: bool,
    test_setup: bool,
    // Install
    install_tasks: Vec<InstallTask>,
    install_progress: Vec<TaskProgress>,
    task_results: Arc<Mutex<Vec<Option<bool>>>>,
    total_units: usize,
    units_done: usize,
    log_text: String,
    last_log_len: usize,
    shared: Arc<SharedState>,
    // WiFi / network
    wifi_networks: Vec<WifiNetwork>,
    wifi_scanning: bool,
    wifi_scan_results: Arc<Mutex<Option<Vec<WifiNetwork>>>>,
    wifi_selected: String,
    wifi_password: String,
    wifi_connecting: bool,
    wifi_connect_result: Arc<Mutex<Option<bool>>>,
    wifi_status_msg: String,
    // Pages
    a11y_page: page::a11y::Page,
    language_page: page::language::Page,
    keyboard_page: page::keyboard::Page,
    location_page: page::location::Page,
    appearance_page: page::appearance::Page,
    layout_page: page::layout::Page,
}

#[derive(Debug, Clone)]
pub enum Message {
    NextPage,
    BackPage,
    ToggleApp(String, bool),
    ScanWifi,
    SelectWifi(String),
    WifiPasswordChanged(String),
    ConnectWifi,
    StartInstall,
    PollState,
    OpenUrl(String),
    Finish,
    PageMessage(page::Message),
}

impl Application for WelcomeApp {
    type Executor = executor::Default;
    type Flags = ();
    type Message = Message;
    const APP_ID: &'static str = APP_ID;

    fn core(&self) -> &Core { &self.core }
    fn core_mut(&mut self) -> &mut Core { &mut self.core }

    fn on_close_requested(&self, _id: cosmic::iced::window::Id) -> Option<Message> {
        if self.force { std::process::exit(0); }
        None
    }

    fn init(mut core: Core, (): ()) -> (Self, Task<Message>) {
        let force = std::env::args_os().any(|a| a == "--force" || a == "-f");
        let test_setup = std::env::var("RAKUOS_WELCOME_TEST_SETUP").is_ok();

        if !force {
            core.window.show_headerbar = false;
            core.window.show_close    = false;
            core.window.show_maximize = false;
            core.window.show_minimize = false;
        }
        let json = load_apps_json();
        let mut catalog: Catalog = serde_json::from_str(&json).expect("invalid apps.json");
        for cat in &mut catalog.categories {
            for app in &mut cat.apps {
                let (path, url) = if let Some(bundled) = rakuos_appstream::bundled_icon_path(&app.id) {
                    (bundled, String::new())
                } else {
                    rakuos_appstream::resolve_icon_for_package(&app.kind, &app.package, &app.icon)
                };
                app.icon_path = path;
                app.icon_url = url;
            }
        }
        let rpms = query_installed_rpms();
        let flatpaks = query_installed_flatpaks();
        let mut selections = HashMap::new();
        for cat in &catalog.categories {
            for app in &cat.apps {
                selections.insert(app.id.clone(), is_installed(app, &rpms, &flatpaks));
            }
        }
        let initial_installed = selections.clone();

        localize::localize();

        let mut a11y_page = page::a11y::Page::new();
        let mut language_page = page::language::Page::new();
        let mut keyboard_page = page::keyboard::Page::new();
        let mut location_page = page::location::Page::new();
        let appearance_page = page::appearance::Page::new();
        let mut layout_page = page::layout::Page::default();

        let mut app = Self {
            core,
            catalog,
            current_page: 0,
            selections,
            initial_installed,
            force,
            test_setup,
            install_tasks: vec![],
            install_progress: vec![],
            task_results: Arc::new(Mutex::new(vec![])),
            total_units: 0,
            units_done: 0,
            log_text: String::new(),
            last_log_len: 0,
            shared: Arc::new(SharedState::default()),
            wifi_networks: vec![],
            wifi_scanning: false,
            wifi_scan_results: Arc::new(Mutex::new(None)),
            wifi_selected: String::new(),
            wifi_password: String::new(),
            wifi_connecting: false,
            wifi_connect_result: Arc::new(Mutex::new(None)),
            wifi_status_msg: String::new(),
            a11y_page,
            language_page,
            keyboard_page,
            location_page,
            appearance_page,
            layout_page,
        };
        app.set_header_title("Welcome to RakuOS".into());

        let icon_task = app.core().main_window_id()
            .and_then(|id| {
                let img = image::open("/usr/share/pixmaps/rakuos-logo.png").ok()?;
                let rgba = img.to_rgba8();
                let (w, h) = rgba.dimensions();
                let icon = iced::window::icon::from_rgba(rgba.into_raw(), w, h).ok()?;
                Some(window::set_icon(id, icon))
            })
            .unwrap_or(Task::none());

        let a11y_init = app.a11y_page.init()
            .map(Message::PageMessage)
            .map(cosmic::Action::App);

        let lang_init = app.language_page.init()
            .map(Message::PageMessage)
            .map(cosmic::Action::App);

        let kb_init = app.keyboard_page.init()
            .map(Message::PageMessage)
            .map(cosmic::Action::App);

        let loc_init = app.location_page.init()
            .map(Message::PageMessage)
            .map(cosmic::Action::App);

        let layout_init = app.layout_page.init()
            .map(Message::PageMessage)
            .map(cosmic::Action::App);

        (app, Task::batch([icon_task, a11y_init, lang_init, kb_init, loc_init, layout_init]))
    }

    fn subscription(&self) -> Subscription<Message> {
        let needs_poll = self.shared.running.load(Ordering::Relaxed)
            || self.wifi_scanning
            || self.wifi_connecting
            || self.install_progress.iter().any(|p| !matches!(p, TaskProgress::Done(_)));

        let poll = if needs_poll {
            iced::time::every(std::time::Duration::from_millis(250))
                .map(|_| Message::PollState)
        } else {
            Subscription::none()
        };

        let a11y_bus = cosmic_settings_accessibility_subscription::subscription()
            .map(|m| Message::PageMessage(page::Message::A11y(page::a11y::Message::A11yBus(m))));

        Subscription::batch([poll, a11y_bus])
    }

    fn update(&mut self, message: Message) -> Task<Message> {
        let n_cats           = self.catalog.categories.len();
        let a11y_page        = n_cats + 1;
        let language_page    = n_cats + 2;
        let keyboard_page    = n_cats + 3;
        let location_page    = n_cats + 4;
        let appearance_page  = n_cats + 5;
        let layout_page      = n_cats + 6;
        let network_page     = n_cats + 7;
        let install_page     = n_cats + 8;
        let done_page        = n_cats + 9;
        let total            = n_cats + 10;

        match message {
            Message::NextPage => {
                if self.force && !self.test_setup && self.current_page == n_cats {
                    self.current_page = install_page;
                    return Task::none();
                }
                if self.current_page == n_cats {
                    self.current_page = a11y_page;
                    return Task::none();
                }
                if self.current_page == a11y_page {
                    self.current_page = language_page;
                    return self.language_page.open()
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                if self.current_page == language_page {
                    self.current_page = keyboard_page;
                    return self.keyboard_page.open()
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                if self.current_page == keyboard_page {
                    self.current_page = location_page;
                    return self.location_page.open()
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                if self.current_page == location_page {
                    self.current_page = appearance_page;
                    return Task::none();
                }
                if self.current_page == appearance_page {
                    self.current_page = layout_page;
                    return self.layout_page.open()
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                if self.current_page == layout_page {
                    // layout → connectivity check → network or install
                    if check_connectivity() {
                        self.current_page = install_page;
                    } else {
                        self.current_page = network_page;
                        self.spawn_wifi_scan();
                    }
                    return Task::none();
                }
                if self.current_page == network_page {
                    self.current_page = install_page;
                    return Task::none();
                }
                if self.current_page < total - 1 { self.current_page += 1; }
            }
            Message::StartInstall => { self.begin_install(); }
            Message::PageMessage(page_msg) => match page_msg {
                page::Message::A11y(msg) => {
                    return self.a11y_page.update(msg)
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                page::Message::Appearance(msg) => {
                    return self.appearance_page.update(msg)
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                page::Message::Keyboard(msg) => {
                    return self.keyboard_page.update(msg)
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                page::Message::Language(msg) => {
                    return self.language_page.update(msg)
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                page::Message::Layout(msg) => {
                    return self.layout_page.update(msg)
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                page::Message::Location(msg) => {
                    return self.location_page.update(msg)
                        .map(Message::PageMessage)
                        .map(cosmic::Action::App);
                }
                page::Message::SetTheme(theme) => {
                    return cosmic::command::set_theme(theme);
                }
            },
            Message::BackPage => {
                if self.current_page > 0 && !self.shared.running.load(Ordering::Relaxed) {
                    if self.force && !self.test_setup && self.current_page == install_page {
                        self.current_page = n_cats;
                        self.install_tasks.clear();
                        self.install_progress.clear();
                        self.units_done = 0;
                        self.total_units = 0;
                        self.log_text.clear();
                        self.last_log_len = 0;
                    } else {
                        self.current_page -= 1;
                        if self.current_page < install_page {
                            self.install_tasks.clear();
                            self.install_progress.clear();
                            self.units_done = 0;
                            self.total_units = 0;
                            self.log_text.clear();
                            self.last_log_len = 0;
                        }
                    }
                }
            }
            Message::ToggleApp(id, checked) => {
                if checked {
                    if let Some(cat) = self.catalog.categories.iter()
                        .find(|c| c.apps.iter().any(|a| a.id == id))
                    {
                        if cat.exclusive {
                            for app in &cat.apps {
                                if app.id != id { self.selections.insert(app.id.clone(), false); }
                            }
                        }
                    }
                }
                self.selections.insert(id, checked);
            }
            Message::ScanWifi => self.spawn_wifi_scan(),
            Message::SelectWifi(ssid) => {
                self.wifi_selected = ssid;
                self.wifi_password.clear();
                self.wifi_status_msg.clear();
            }
            Message::WifiPasswordChanged(pw) => { self.wifi_password = pw; }
            Message::ConnectWifi => {
                if self.wifi_selected.is_empty() { return Task::none(); }
                let ssid = self.wifi_selected.clone();
                let pw   = self.wifi_password.clone();
                self.wifi_connecting = true;
                self.wifi_status_msg.clear();
                let result = Arc::clone(&self.wifi_connect_result);
                std::thread::spawn(move || {
                    let ok = connect_wifi(&ssid, &pw);
                    *result.lock().unwrap() = Some(ok);
                });
            }
            Message::PollState => {
                // Check WiFi scan completion
                if self.wifi_scanning {
                    if let Some(nets) = self.wifi_scan_results.lock().unwrap().take() {
                        self.wifi_networks = nets;
                        self.wifi_scanning = false;
                    }
                }
                // Check WiFi connect completion
                if self.wifi_connecting {
                    if let Some(ok) = self.wifi_connect_result.lock().unwrap().take() {
                        self.wifi_connecting = false;
                        self.wifi_status_msg = if ok {
                            "✓ Connected! Click Next to continue.".into()
                        } else {
                            "✗ Connection failed — check the password.".into()
                        };
                    }
                }
                // Update install progress
                {
                    let results = self.task_results.lock().unwrap();
                    let current_idx = self.shared.current_task.load(Ordering::Relaxed);
                    for (i, prog) in self.install_progress.iter_mut().enumerate() {
                        match prog {
                            TaskProgress::Pending => {
                                if i as i32 == current_idx {
                                    *prog = TaskProgress::Installing;
                                } else if let Some(Some(ok)) = results.get(i) {
                                    self.units_done += self.install_tasks[i].app.unit_weight();
                                    *prog = TaskProgress::Done(*ok);
                                }
                            }
                            TaskProgress::Installing => {
                                if let Some(Some(ok)) = results.get(i) {
                                    self.units_done += self.install_tasks[i].app.unit_weight();
                                    *prog = TaskProgress::Done(*ok);
                                }
                            }
                            TaskProgress::Done(_) => {}
                        }
                    }
                }
                // Read log
                if let Ok(content) = std::fs::read_to_string(log_file_path()) {
                    if content.len() != self.last_log_len {
                        self.log_text = content;
                        self.last_log_len = self.log_text.len();
                    }
                }
            }
            Message::OpenUrl(url) => {
                let _ = std::process::Command::new("xdg-open").arg(&url).spawn();
            }
            Message::Finish => {
                let df = done_file();
                let _ = std::fs::create_dir_all(df.parent().unwrap());
                let _ = std::fs::File::create(&df);
                std::process::exit(0);
            }
        }
        Task::none()
    }

    fn view(&self) -> Element<Message> {
        let n_cats           = self.catalog.categories.len();
        let a11y_page        = n_cats + 1;
        let language_page    = n_cats + 2;
        let keyboard_page    = n_cats + 3;
        let location_page    = n_cats + 4;
        let appearance_page  = n_cats + 5;
        let layout_page      = n_cats + 6;
        let network_page     = n_cats + 7;
        let install_page     = n_cats + 8;
        let done_page        = n_cats + 9;
        let total            = n_cats + 10;
        let busy = self.shared.running.load(Ordering::Relaxed);

        let page: Element<Message> = match self.current_page {
            0 => self.view_welcome(),
            p if p == a11y_page       => self.a11y_page.view().map(Message::PageMessage),
            p if p == language_page   => self.language_page.view().map(Message::PageMessage),
            p if p == keyboard_page   => self.keyboard_page.view().map(Message::PageMessage),
            p if p == location_page   => self.location_page.view().map(Message::PageMessage),
            p if p == appearance_page => self.appearance_page.view().map(Message::PageMessage),
            p if p == layout_page     => self.layout_page.view().map(Message::PageMessage),
            p if p == network_page    => self.view_network(),
            p if p == install_page    => self.view_install(),
            p if p == done_page       => self.view_done(),
            p => self.view_category(p - 1),
        };

        let install_all_done = !self.install_tasks.is_empty()
            && self.install_progress.iter().all(|p| matches!(p, TaskProgress::Done(_)));

        let dots = (0..total).fold(widget::row::with_capacity(total), |row, i| {
            row.push(widget::text(if i == self.current_page { "●" } else { "○" }).size(10))
               .spacing(4)
        });

        let back_btn: Option<Element<Message>> =
            if self.current_page > 0 && !busy {
                Some(widget::button::standard("← Back").on_press(Message::BackPage).into())
            } else { None };

        let next_label = if self.current_page == done_page { "Finish" } else { "Next →" };
        let next_msg   = if self.current_page == done_page { Message::Finish } else { Message::NextPage };
        let install_started = busy || !self.install_tasks.is_empty();
        let can_next = !busy && !self.wifi_scanning && !self.wifi_connecting
            && if self.current_page == install_page {
                !install_started || install_all_done
            } else if self.current_page == a11y_page {
                self.a11y_page.completed()
            } else if self.current_page == language_page {
                self.language_page.completed()
            } else if self.current_page == keyboard_page {
                self.keyboard_page.completed()
            } else if self.current_page == location_page {
                self.location_page.completed()
            } else {
                true
            };
        let next_btn: Element<Message> = widget::button::suggested(next_label)
            .on_press_maybe(can_next.then_some(next_msg))
            .into();

        let mut nav_row = widget::row::with_capacity(4)
            .push(widget::container(dots).width(Length::Fill))
            .spacing(8)
            .padding(iced::Padding::from([12, 24]));
        if let Some(b) = back_btn { nav_row = nav_row.push(b); }
        nav_row = nav_row.push(next_btn);

        let is_setup_page = matches!(self.current_page,
            p if p == a11y_page || p == language_page || p == keyboard_page
                || p == location_page || p == appearance_page || p == layout_page
        );
        let top_gap = Length::Fixed(if is_setup_page { 32.0 } else { 0.0 });

        let page_container = if is_setup_page {
            widget::container(page)
                .width(Length::Fill)
                .height(Length::Fill)
                .padding(iced::Padding::from([0, 24]))
        } else {
            widget::container(page)
                .width(Length::Fill)
                .height(Length::Fill)
        };

        widget::column::with_capacity(4)
            .push(widget::Space::new().height(top_gap))
            .push(page_container)
            .push(widget::divider::horizontal::default())
            .push(nav_row)
            .into()
    }
}

// ── Page views ────────────────────────────────────────────────────────────────

impl WelcomeApp {
    fn logo_path(&self) -> &'static str {
        if cosmic::theme::is_dark() { LOGO_DARK } else { LOGO_LIGHT }
    }

    fn view_welcome(&self) -> Element<'_, Message> {
        let content = widget::column()
            .push(widget::container(
                widget::image(widget::image::Handle::from_path(self.logo_path()))
                    .width(Length::Fixed(240.0)).height(Length::Fixed(80.0))
            ).width(Length::Fill).center_x(Length::Fill))
            .push(widget::container(widget::text::title1("Welcome to RakuOS Linux")).width(Length::Fill).center_x(Length::Fill))
            .push(widget::container(widget::text("The Hybrid Atomic Linux Desktop").size(14)).width(Length::Fill).center_x(Length::Fill))
            .push(widget::Space::new().height(Length::Fixed(8.0)))
            .push(widget::container(
                widget::text("RakuOS combines the stability and security of an atomic immutable base \
                              with the flexibility of a traditional Linux distribution. \
                              Your system can never be broken by a bad update.")
                    .width(Length::Fixed(520.0))
            ).width(Length::Fill).center_x(Length::Fill))
            .push(widget::container(widget::divider::horizontal::default()).width(Length::Fixed(480.0)).center_x(Length::Fill))
            .push(widget::container(widget::text("Find us online:")).width(Length::Fill).center_x(Length::Fill))
            .push(widget::container(
                widget::row()
                    .push(widget::button::link("🌐 Website").on_press(Message::OpenUrl("https://rakuos.org".into())))
                    .push(widget::button::link("💻 GitLab").on_press(Message::OpenUrl("https://gitlab.com/rakuos".into())))
                    .push(widget::button::link("📦 SourceForge").on_press(Message::OpenUrl("https://sourceforge.net/projects/rakuos/".into())))
                    .spacing(8)
            ).width(Length::Fill).center_x(Length::Fill))
            .spacing(12).padding(40);

        widget::container(content).width(Length::Fill).height(Length::Fill)
            .center_x(Length::Fill).center_y(Length::Fill).into()
    }

    fn view_category(&self, cat_idx: usize) -> Element<'_, Message> {
        let cat = &self.catalog.categories[cat_idx];
        let spacing = theme::active().cosmic().spacing;

        let mut col = widget::column()
            .spacing(spacing.space_s)
            .padding(iced::Padding::from([spacing.space_s as u16, spacing.space_m as u16]));

        col = col.push(
            widget::row()
                .push(widget::icon::from_name(cat.icon.as_str()).size(24).icon())
                .push(widget::text::title2(cat.title.as_str()))
                .spacing(spacing.space_xs).align_y(Alignment::Center),
        );
        col = col.push(widget::text(cat.description.as_str()).size(13));
        if cat.exclusive {
            col = col.push(widget::text::caption("Select one option."));
        }
        col = col.push(widget::Space::new().height(Length::Fixed(4.0)));

        let mut grid = widget::grid();
        let mut grid_col = 0usize;
        const COLS: usize = 2;

        for app in &cat.apps {
            if grid_col >= COLS { grid = grid.insert_row(); grid_col = 0; }
            let selected = *self.selections.get(&app.id).unwrap_or(&false);
            let id = app.id.clone();

            let name_row: Element<_> = if app.kind == "flatpak" {
                widget::row::with_children(vec![
                    widget::text::body(app.name.as_str()).into(),
                    widget::container(widget::text::caption("Flatpak"))
                        .padding([1, 5]).class(theme::Container::Primary).into(),
                ])
                .align_y(Alignment::Center).spacing(spacing.space_xxs)
                .height(Length::Fixed(20.0)).into()
            } else {
                widget::text::body(app.name.as_str()).height(Length::Fixed(20.0)).into()
            };

            let card = widget::container(
                widget::row::with_children(vec![
                    app_icon_widget(&app.icon_path, &app.icon_url, 48),
                    widget::column::with_children(vec![
                        name_row,
                        widget::text::caption(app.summary.as_str())
                            .height(Length::Fixed(36.0)).into(),
                    ]).width(Length::Fill).into(),
                    widget::checkbox(selected)
                        .on_toggle_maybe(Some(move |v| Message::ToggleApp(id.clone(), v)))
                        .into(),
                ])
                .align_y(Alignment::Center).spacing(spacing.space_s),
            )
            .align_y(Alignment::Center)
            .width(Length::Fixed(340.0))
            .height(Length::Fixed(56.0 + spacing.space_xxs as f32 * 2.0))
            .padding([spacing.space_xxs, spacing.space_s])
            .class(theme::Container::Card);

            grid = grid.push(card);
            grid_col += 1;
        }

        col = col.push(grid.column_spacing(spacing.space_xs).row_spacing(spacing.space_xs));
        widget::scrollable(col).height(Length::Fill).width(Length::Fill).into()
    }

    fn view_network(&self) -> Element<'_, Message> {
        let spacing = theme::active().cosmic().spacing;
        let mut col = widget::column()
            .spacing(spacing.space_s)
            .padding(iced::Padding::from([spacing.space_m as u16, spacing.space_m as u16]));

        col = col.push(
            widget::row()
                .push(widget::icon::from_name("network-wireless-symbolic").size(24).icon())
                .push(widget::text::title2("Network Setup"))
                .spacing(spacing.space_xs).align_y(Alignment::Center),
        );
        col = col.push(widget::text(
            "No internet connection detected. Connect to WiFi to install apps, \
             or click Next to skip and install later."
        ).size(13));
        col = col.push(widget::Space::new().height(Length::Fixed(8.0)));

        // Scan row
        col = col.push(
            widget::row()
                .push(widget::text("Available Networks:").width(Length::Fill))
                .push(widget::button::standard("↺ Scan")
                    .on_press_maybe((!self.wifi_scanning).then_some(Message::ScanWifi)))
                .align_y(Alignment::Center),
        );

        if self.wifi_scanning {
            col = col.push(
                widget::container(widget::text("⟳ Scanning for networks…").size(13)).padding(12),
            );
        } else if self.wifi_networks.is_empty() {
            col = col.push(
                widget::container(widget::text("No networks found. Click Scan.").size(12)).padding(12),
            );
        } else {
            let mut list = widget::column().spacing(4);
            for net in &self.wifi_networks {
                let bars = match net.signal {
                    80..=100 => "▪▪▪▪",
                    60..=79  => "▪▪▪░",
                    40..=59  => "▪▪░░",
                    _        => "▪░░░",
                };
                let lock = if net.secured { " 🔒" } else { "" };
                let label = format!("{}{} {}  {}%", bars, lock, net.ssid, net.signal);
                let selected = self.wifi_selected == net.ssid;
                let ssid = net.ssid.clone();
                let btn: Element<_> = widget::button::standard(label)
                    .on_press(Message::SelectWifi(ssid))
                    .into();
                let item = if selected {
                    widget::container(btn).class(theme::Container::Primary)
                } else {
                    widget::container(btn)
                };
                list = list.push(item);
            }
            col = col.push(
                widget::container(widget::scrollable(list).height(Length::Fixed(200.0))).padding(4),
            );
        }

        if !self.wifi_selected.is_empty() {
            col = col.push(widget::Space::new().height(Length::Fixed(8.0)));
            col = col.push(widget::text(format!("Password for \"{}\":", self.wifi_selected)).size(12));
            col = col.push(
                widget::text_input("Password", &self.wifi_password)
                    .password()
                    .on_input(Message::WifiPasswordChanged),
            );
            col = col.push(
                widget::container(
                    widget::button::suggested("Connect")
                        .on_press_maybe((!self.wifi_connecting).then_some(Message::ConnectWifi)),
                ).width(Length::Fill).center_x(Length::Fill),
            );
        }

        if !self.wifi_status_msg.is_empty() {
            col = col.push(widget::text(&self.wifi_status_msg).size(12));
        }
        if self.wifi_connecting {
            col = col.push(widget::text("⟳ Connecting…").size(12));
        }

        widget::scrollable(col).height(Length::Fill).width(Length::Fill).into()
    }

    fn view_install(&self) -> Element<'_, Message> {
        let spacing = theme::active().cosmic().spacing;
        let busy = self.shared.running.load(Ordering::Relaxed);
        let has_started = busy || !self.install_tasks.is_empty();

        // ── Pre-install: centered call-to-action ──────────────────────────────
        if !has_started {
            return widget::container(
                widget::column()
                    .push(widget::container(
                        widget::text::title2("Ready to Install")
                    ).width(Length::Fill).center_x(Length::Fill))
                    .push(widget::container(
                        widget::text("Your selected apps will now be installed. This may take a few minutes.")
                            .width(Length::Fixed(400.0))
                            .size(14)
                    ).width(Length::Fill).center_x(Length::Fill))
                    .push(widget::Space::new().height(Length::Fixed(16.0)))
                    .push(widget::container(
                        widget::button::suggested("Install Apps")
                            .on_press(Message::StartInstall)
                    ).width(Length::Fill).center_x(Length::Fill))
                    .spacing(spacing.space_s)
                    .padding(iced::Padding::from([spacing.space_m as u16, spacing.space_m as u16])),
            )
            .width(Length::Fill).height(Length::Fill)
            .center_y(Length::Fill)
            .into();
        }

        // ── In-progress / finished: progress view ─────────────────────────────
        let all_done = !self.install_tasks.is_empty()
            && self.install_progress.iter().all(|p| matches!(p, TaskProgress::Done(_)));

        let overall = if self.total_units > 0 {
            self.units_done.min(self.total_units) as f32 / self.total_units as f32
        } else if all_done { 1.0 } else { 0.0 };

        let done_count = self.install_progress.iter()
            .filter(|p| matches!(p, TaskProgress::Done(_))).count();

        let mut col = widget::column()
            .spacing(spacing.space_s)
            .padding(iced::Padding::from([spacing.space_m as u16, spacing.space_m as u16]));

        let title = if all_done { "Installation Complete" } else { "Installing Your Apps" };
        col = col.push(widget::text::title2(title));

        let status = if all_done {
            format!("Done — {}/{} tasks completed", done_count, self.install_tasks.len())
        } else {
            format!("Installing… {}/{} units", self.units_done, self.total_units)
        };
        col = col.push(widget::text(status).size(12));
        col = col.push(widget::progress_bar(0.0..=1.0, overall));
        col = col.push(widget::Space::new().height(Length::Fixed(8.0)));

        for (i, task) in self.install_tasks.iter().enumerate() {
            let app = &task.app;
            let action = if task.remove { "Remove" } else { "Install" };
            let pkg_count = app.unit_weight();
            let prog_val: f32 = match self.install_progress.get(i) {
                Some(TaskProgress::Pending)     => 0.0,
                Some(TaskProgress::Installing)  => 0.5,
                Some(TaskProgress::Done(_))     => 1.0,
                None                            => 0.0,
            };
            let status_text = match self.install_progress.get(i) {
                Some(TaskProgress::Installing)   => "⟳",
                Some(TaskProgress::Done(true))   => "✓",
                Some(TaskProgress::Done(false))  => "✗",
                _                                => "○",
            };
            let pkg_label = if pkg_count > 1 {
                format!("{} {} ({} pkgs)", action, app.name, pkg_count)
            } else {
                format!("{} {}", action, app.name)
            };

            let row = widget::row::with_children(vec![
                widget::text(status_text).size(16).width(Length::Fixed(22.0)).into(),
                widget::text(pkg_label).width(Length::Fixed(280.0)).size(13).into(),
                widget::container(widget::progress_bar(0.0..=1.0, prog_val))
                    .width(Length::Fill).into(),
            ])
            .align_y(Alignment::Center)
            .spacing(spacing.space_s);
            col = col.push(row);
        }

        if !self.log_text.is_empty() {
            col = col.push(widget::Space::new().height(Length::Fixed(8.0)));
            col = col.push(widget::divider::horizontal::default());
            col = col.push(widget::text("Details:").size(11));
            col = col.push(
                widget::container(
                    widget::scrollable(
                        widget::text(&self.log_text).font(iced::Font::MONOSPACE).size(10),
                    ).height(Length::Fixed(100.0)),
                ).width(Length::Fill).padding(6),
            );
        }

        widget::scrollable(col).height(Length::Fill).width(Length::Fill).into()
    }

    fn view_done(&self) -> Element<'_, Message> {
        widget::container(
            widget::column()
                .push(widget::container(widget::icon::from_name("object-select-symbolic").size(56).icon())
                    .width(Length::Fill).center_x(Length::Fill))
                .push(widget::container(widget::text::title1("You're All Set!"))
                    .width(Length::Fill).center_x(Length::Fill))
                .push(widget::container(
                    widget::text("RakuOS is ready to use. You can install more software\nfrom the RakuOS Software Center.")
                        .width(Length::Fixed(480.0))
                ).width(Length::Fill).center_x(Length::Fill))
                .spacing(16).padding(40),
        )
        .width(Length::Fill).height(Length::Fill).center_y(Length::Fill).into()
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    fn spawn_wifi_scan(&mut self) {
        self.wifi_scanning = true;
        let results = Arc::clone(&self.wifi_scan_results);
        std::thread::spawn(move || {
            let nets = scan_wifi();
            *results.lock().unwrap() = Some(nets);
        });
    }

    fn begin_install(&mut self) {
        let mut tasks: Vec<InstallTask> = vec![];
        for cat in &self.catalog.categories {
            if cat.exclusive && !self.force {
                for app in &cat.apps {
                    if *self.initial_installed.get(&app.id).unwrap_or(&false)
                        && !*self.selections.get(&app.id).unwrap_or(&false)
                        && app.kind != "script"
                    {
                        tasks.push(InstallTask { app: app.clone(), remove: true });
                    }
                }
            }
            for app in &cat.apps {
                if *self.selections.get(&app.id).unwrap_or(&false) {
                    tasks.push(InstallTask { app: app.clone(), remove: false });
                }
            }
        }

        if tasks.is_empty() {
            self.install_tasks = vec![];
            self.install_progress = vec![];
            return;
        }

        let total_units: usize = tasks.iter().map(|t| t.app.unit_weight()).sum();
        let n = tasks.len();
        self.install_tasks = tasks.clone();
        self.install_progress = vec![TaskProgress::Pending; n];
        self.task_results = Arc::new(Mutex::new(vec![None; n]));
        self.total_units = total_units;
        self.units_done = 0;
        self.log_text.clear();
        self.last_log_len = 0;

        let log_path = log_file_path();
        let _ = std::fs::write(&log_path, "Starting…\n");
        self.shared.running.store(true, Ordering::Relaxed);
        self.shared.current_task.store(-1, Ordering::Relaxed);

        let shared  = Arc::clone(&self.shared);
        let results = Arc::clone(&self.task_results);

        std::thread::spawn(move || {
            let append = |text: &str| {
                if let Ok(mut f) = std::fs::OpenOptions::new().append(true).open(&log_path) {
                    let _ = f.write_all(text.as_bytes());
                    let _ = f.write_all(b"\n");
                }
            };
            for (i, task) in tasks.iter().enumerate() {
                shared.current_task.store(i as i32, Ordering::Relaxed);
                let app = &task.app;
                if task.remove {
                    append(&format!("→ Removing {}…", app.name));
                    let ok = remove_pkg(app, &append);
                    let msg = if ok { format!("✓ {} removed", app.name) } else { format!("✗ {} removal failed", app.name) };
                    append(&msg);
                    results.lock().unwrap()[i] = Some(ok);
                } else {
                    append(&format!("→ Installing {}…", app.name));
                    let ok = match app.kind.as_str() {
                        "flatpak" => install_flatpak(app, &append),
                        "script"  => install_script(&app.package, &append),
                        _         => install_rpm(app, &append),
                    };
                    let msg = if ok { format!("✓ {} done", app.name) } else { format!("✗ {} failed", app.name) };
                    append(&msg);
                    results.lock().unwrap()[i] = Some(ok);
                }
            }
            shared.current_task.store(tasks.len() as i32, Ordering::Relaxed);
            shared.running.store(false, Ordering::Relaxed);
            append("All done.");
        });
    }
}

// ── Install runners ───────────────────────────────────────────────────────────

fn remove_pkg(app: &AppEntry, append: &impl Fn(&str)) -> bool {
    match app.kind.as_str() {
        "flatpak" => run_cmd("flatpak", &["uninstall", "--user", "-y", &app.package], append),
        _         => run_cmd("pkexec", &["/usr/libexec/rakuos/rakuos-remove", &app.package], append),
    }
}

fn install_flatpak(app: &AppEntry, append: &impl Fn(&str)) -> bool {
    let remote = if app.remote.is_empty() { "flathub" } else { &app.remote };
    let mut args = vec!["install", "--user", "--noninteractive", "-y", remote, &app.package];
    let extras: Vec<&str> = app.packages.iter().map(|s| s.as_str()).collect();
    args.extend_from_slice(&extras);
    run_cmd("flatpak", &args, append)
}

fn install_rpm(app: &AppEntry, append: &impl Fn(&str)) -> bool {
    let mut args = vec!["/usr/libexec/rakuos/rakuos-install", &app.package];
    let extras: Vec<&str> = app.packages.iter().map(|s| s.as_str()).collect();
    args.extend_from_slice(&extras);
    run_cmd("pkexec", &args, append)
}

fn install_script(script: &str, append: &impl Fn(&str)) -> bool {
    let path = format!("/usr/libexec/rakuos/{}", script);
    run_cmd("pkexec", &[path.as_str()], append)
}

fn run_cmd(cmd: &str, args: &[&str], append: &impl Fn(&str)) -> bool {
    use std::io::BufRead;
    use std::process::{Command, Stdio};
    match Command::new(cmd).args(args).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn() {
        Ok(mut child) => {
            if let Some(out) = child.stdout.take() {
                for line in std::io::BufReader::new(out).lines().map_while(Result::ok) {
                    if !line.is_empty() { append(&line); }
                }
            }
            if let Some(err) = child.stderr.take() {
                for line in std::io::BufReader::new(err).lines().map_while(Result::ok) {
                    if !line.is_empty() { append(&line); }
                }
            }
            child.wait().map(|s| s.success()).unwrap_or(false)
        }
        Err(e) => { append(&e.to_string()); false }
    }
}

// ── Entry point ───────────────────────────────────────────────────────────────

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let force = std::env::args_os().any(|a| a == "--force" || a == "-f");
    if done_file().exists() && !force { return Ok(()); }

    if std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default() != "COSMIC" {
        cosmic::icon_theme::set_default("Adwaita");
    }

    let settings = Settings::default()
        .size(iced::Size::new(800.0, 640.0))
        .size_limits(Limits::NONE.min_width(600.0).min_height(500.0))
        .is_daemon(false);

    cosmic::app::run::<WelcomeApp>(settings, ())?;
    Ok(())
}
