// pages/system.rs — System info and management page (matches Qt SystemPage layout)

use gtk4::prelude::*;
use gtk4::{
    glib, Align, Box as GBox, Button, DropDown, Label, Orientation, Spinner,
    ScrolledWindow, Separator, StringList, TextBuffer, TextView, Widget,
};
use libadwaita::prelude::*;
use libadwaita::{ActionRow, Dialog, HeaderBar, PreferencesGroup, ToolbarView};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::mpsc;
use std::time::Duration;

use rakuos_updates::{OverlayStatus, SystemStatus};

const DE_LABELS: &[(&str, &str)] = &[
    ("KDE Plasma", "rakuos-kde"),
    ("GNOME",      "rakuos-gnome"),
    ("COSMIC",     "rakuos-cosmic"),
];

const BRANCH_LABELS: &[(&str, &str)] = &[
    ("Stable", "latest"),
    ("Staging", "staging"),
    ("Rawhide", "rawhide"),
];

fn parse_image_ref(image: &str) -> (String, String, bool) {
    let (image_path, tag) = image.rsplit_once(':').unwrap_or((image, ""));
    let path_without_registry = image_path
        .split_once('/')
        .map(|(_, rest)| rest)
        .unwrap_or(image_path);
    let normalized_path = if path_without_registry.contains("/rakuos/") {
        path_without_registry
            .split_once("rakuos/")
            .map(|(_, rest)| rest)
            .unwrap_or(path_without_registry)
    } else {
        path_without_registry
    };
    let is_nvidia = normalized_path.ends_with("/nvidia") || normalized_path.ends_with("-nvidia");
    let base_name = if normalized_path.ends_with("/nvidia") {
        normalized_path
            .trim_end_matches("/nvidia")
            .split('/')
            .last()
            .unwrap_or("")
            .to_string()
    } else {
        normalized_path
            .split('/')
            .last()
            .unwrap_or("")
            .trim_end_matches("-nvidia")
            .to_string()
    };
    (base_name, tag.to_string(), is_nvidia)
}

fn canonical_repo_url(_image: &str, de_id: &str, is_nvidia: bool) -> String {
    if is_nvidia {
        format!("quay.io/rakuos/{}-nvidia", de_id)
    } else {
        format!("quay.io/rakuos/{}", de_id)
    }
}

fn de_label_from_image(image: &str) -> String {
    let (name, _, is_nvidia) = parse_image_ref(image);
    let label = DE_LABELS.iter()
        .find(|(_, id)| *id == name)
        .map(|(lbl, _)| *lbl)
        .unwrap_or(name.as_str());
    if is_nvidia { format!("{} (Nvidia)", label) } else { label.to_string() }
}

fn branch_label_from_tag(tag: &str) -> &'static str {
    let tag = tag.to_ascii_lowercase();
    if tag.starts_with("staging") {
        "Staging"
    } else if tag.starts_with("rawhide") {
        "Rawhide"
    } else {
        "Stable"
    }
}

fn display_image_version(image: &str, version: &str) -> String {
    let (_, tag, _) = parse_image_ref(image);
    let tag_lower = tag.to_ascii_lowercase();
    let date = version
        .split(|c: char| !c.is_ascii_digit())
        .find(|part| part.len() == 8)
        .unwrap_or("");

    if tag.len() == 8 && tag.chars().all(|c| c.is_ascii_digit()) {
        tag
    } else if (tag_lower.starts_with("latest.") || tag_lower.starts_with("staging.") || tag_lower.starts_with("rawhide."))
        && tag_lower.rsplit_once('.')
            .map(|(_, d)| d.len() == 8 && d.chars().all(|c| c.is_ascii_digit()))
            .unwrap_or(false)
    {
        tag_lower
    } else if matches!(tag_lower.as_str(), "latest" | "staging" | "rawhide") && !date.is_empty() {
        format!("{}.{}", tag_lower, date)
    } else if !tag.is_empty() && !date.is_empty() {
        format!("{}.{}", tag, date)
    } else if !version.is_empty() {
        version.to_string()
    } else {
        "Unknown".to_string()
    }
}

fn selected_image_differs(current_image: &str, de_idx: usize, branch_idx: usize) -> bool {
    if current_image.is_empty() {
        return false;
    }

    let Some(&(_, de_id)) = DE_LABELS.get(de_idx) else {
        return false;
    };
    let Some(&(_, branch_tag)) = BRANCH_LABELS.get(branch_idx) else {
        return false;
    };

    let (name, current_tag, _) = parse_image_ref(current_image);
    let current_tag = current_tag.to_ascii_lowercase();

    let branch_matches = match branch_tag {
        "staging" => current_tag.starts_with("staging"),
        "rawhide" => current_tag.starts_with("rawhide"),
        _ => !current_tag.starts_with("staging") && !current_tag.starts_with("rawhide"),
    };

    name != de_id || !branch_matches
}

fn stream_succeeded<I>(stream: I) -> bool
where
    I: Iterator<Item = String>,
{
    let mut saw_done = false;
    let mut exit_code = 1i32;

    for line in stream {
        if let Some(code) = line.strip_prefix("__done__") {
            saw_done = true;
            exit_code = code.trim().parse().unwrap_or(1);
        }
    }

    saw_done && exit_code == 0
}

pub fn build() -> Widget {
    let scroll = ScrolledWindow::builder()
        .hscrollbar_policy(gtk4::PolicyType::Never)
        .vscrollbar_policy(gtk4::PolicyType::Automatic)
        .vexpand(true)
        .build();

    let outer = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(16)
        .margin_top(20)
        .margin_bottom(20)
        .margin_start(20)
        .margin_end(20)
        .build();
    scroll.set_child(Some(&outer));

    let title = Label::builder()
        .label("System")
        .halign(Align::Start)
        .css_classes(vec!["title-1".to_string()])
        .build();
    outer.append(&title);

    // ── Booted Image ──────────────────────────────────────────────────────────
    let image_group = PreferencesGroup::builder()
        .title("Booted Image")
        .build();

    let image_row     = ActionRow::builder().title("OS Image").subtitle("Loading…").build();
    let version_row   = ActionRow::builder().title("Version").subtitle("Loading…").build();
    let digest_row    = ActionRow::builder().title("Digest").subtitle("Loading…").build();
    let timestamp_row = ActionRow::builder().title("Timestamp").subtitle("Loading…").build();
    let nvidia_row    = ActionRow::builder().title("Nvidia").subtitle("No").build();
    image_group.add(&image_row);
    image_group.add(&version_row);
    image_group.add(&digest_row);
    image_group.add(&timestamp_row);
    image_group.add(&nvidia_row);

    // Action buttons row
    let img_btn_row = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(8)
        .margin_top(8)
        .margin_bottom(4)
        .build();

    let rollback_btn = Button::builder()
        .label("Rollback")
        .css_classes(vec!["pill".to_string()])
        .build();
    let kargs_btn = Button::builder()
        .label("Kernel Parameters…")
        .css_classes(vec!["pill".to_string()])
        .build();
    let img_reboot_btn = Button::builder()
        .label("Reboot to Apply")
        .css_classes(vec!["suggested-action".to_string(), "pill".to_string()])
        .visible(false)
        .build();
    let img_status_lbl = Label::builder()
        .halign(Align::Start)
        .hexpand(true)
        .css_classes(vec!["caption".to_string()])
        .visible(false)
        .build();

    img_btn_row.append(&rollback_btn);
    img_btn_row.append(&kargs_btn);
    img_btn_row.append(&img_reboot_btn);
    img_btn_row.append(&img_status_lbl);
    image_group.add(&img_btn_row);

    outer.append(&image_group);

    // ── Desktop Environment ───────────────────────────────────────────────────
    let de_group = PreferencesGroup::builder()
        .title("Desktop Environment")
        .build();

    let current_de_row = ActionRow::builder()
        .title("Current")
        .subtitle("Loading…")
        .build();
    let current_branch_row = ActionRow::builder()
        .title("Branch")
        .subtitle("Loading…")
        .build();
    de_group.add(&current_de_row);
    de_group.add(&current_branch_row);

    let de_switch_row = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(8)
        .margin_top(4)
        .margin_bottom(4)
        .build();

    let de_lbl = Label::builder()
        .label("Switch to:")
        .halign(Align::Start)
        .css_classes(vec!["dim-label".to_string()])
        .build();
    let de_list = StringList::new(&DE_LABELS.iter().map(|(l, _)| *l).collect::<Vec<_>>());
    let de_combo = DropDown::builder()
        .model(&de_list)
        .hexpand(true)
        .build();
    let branch_list = StringList::new(&BRANCH_LABELS.iter().map(|(l, _)| *l).collect::<Vec<_>>());
    let branch_combo = DropDown::builder()
        .model(&branch_list)
        .hexpand(true)
        .build();
    let switch_btn = Button::builder()
        .label("Switch Image")
        .css_classes(vec!["pill".to_string()])
        .sensitive(false)
        .build();
    let switch_status_lbl = Label::builder()
        .halign(Align::Start)
        .css_classes(vec!["caption".to_string(), "dim-label".to_string()])
        .visible(false)
        .build();

    de_switch_row.append(&de_lbl);
    de_switch_row.append(&de_combo);
    de_switch_row.append(&branch_combo);
    de_switch_row.append(&switch_btn);
    de_switch_row.append(&switch_status_lbl);
    de_group.add(&de_switch_row);
    outer.append(&de_group);

    // ── Overlay Packages ──────────────────────────────────────────────────────
    let overlay_group = PreferencesGroup::builder()
        .title("Overlay Packages")
        .description("Packages installed on top of the base OS image")
        .build();

    // Header row: count + reset button
    let overlay_hdr = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(8)
        .margin_top(4)
        .margin_bottom(4)
        .build();
    let overlay_count_lbl = Label::builder()
        .label("Loading…")
        .halign(Align::Start)
        .hexpand(true)
        .css_classes(vec!["dim-label".to_string(), "caption".to_string()])
        .build();
    let reset_btn = Button::builder()
        .label("Reset Overlay…")
        .css_classes(vec!["pill".to_string(), "destructive-action".to_string()])
        .halign(Align::End)
        .build();
    let overlay_status_lbl = Label::builder()
        .halign(Align::Start)
        .css_classes(vec!["caption".to_string()])
        .visible(false)
        .build();
    let overlay_reboot_btn = Button::builder()
        .label("Reboot to Apply")
        .css_classes(vec!["suggested-action".to_string(), "pill".to_string()])
        .halign(Align::End)
        .visible(false)
        .build();

    overlay_hdr.append(&overlay_count_lbl);
    overlay_hdr.append(&overlay_status_lbl);
    overlay_hdr.append(&overlay_reboot_btn);
    overlay_hdr.append(&reset_btn);
    overlay_group.add(&overlay_hdr);
    overlay_group.add(&Separator::new(Orientation::Horizontal));

    // Package list box — populated after load
    let pkg_list_box = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(2)
        .margin_top(4)
        .build();
    let no_pkgs_lbl = Label::builder()
        .label("No overlay packages installed.")
        .halign(Align::Start)
        .css_classes(vec!["dim-label".to_string()])
        .build();
    pkg_list_box.append(&no_pkgs_lbl);
    overlay_group.add(&pkg_list_box);

    outer.append(&overlay_group);

    // ── Firmware ──────────────────────────────────────────────────────────────
    let fw_group = PreferencesGroup::builder()
        .title("Firmware Updates")
        .description("Check for firmware updates via LVFS")
        .build();

    let fw_row = ActionRow::builder()
        .title("Check for Firmware Updates")
        .subtitle("Uses fwupdmgr to query LVFS")
        .build();
    let fw_btn = Button::builder()
        .label("Refresh")
        .valign(Align::Center)
        .css_classes(vec!["pill".to_string()])
        .build();

    fw_btn.connect_clicked(move |btn| {
        btn.set_sensitive(false);
        btn.set_label("Checking…");
        let (tx, rx) = mpsc::channel::<()>();
        std::thread::spawn(move || {
            let _ = std::process::Command::new("fwupdmgr").arg("refresh").status();
            let _ = tx.send(());
        });
        let btn_c = btn.clone();
        glib::timeout_add_local(Duration::from_millis(50), move || {
            match rx.try_recv() {
                Ok(_) => { btn_c.set_label("Refresh"); btn_c.set_sensitive(true); glib::ControlFlow::Break }
                Err(mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
                Err(_) => glib::ControlFlow::Break,
            }
        });
    });

    fw_row.add_suffix(&fw_btn);
    fw_group.add(&fw_row);
    outer.append(&fw_group);

    // ── Wire Rollback ─────────────────────────────────────────────────────────
    {
        let reboot_b  = img_reboot_btn.clone();
        let status_l  = img_status_lbl.clone();
        let outer_w   = outer.clone();

        rollback_btn.connect_clicked(move |btn| {
            // Show warning dialog before proceeding — rollback also triggers a
            // soft overlay reset so the user knows both operations will run.
            let dlg = Dialog::builder()
                .title("Roll Back System?")
                .build();

            let toolbar = ToolbarView::new();
            let header = HeaderBar::new();
            toolbar.add_top_bar(&header);

            let body = GBox::builder()
                .orientation(Orientation::Vertical)
                .spacing(12)
                .margin_top(12)
                .margin_bottom(20)
                .margin_start(24)
                .margin_end(24)
                .build();

            let msg = Label::builder()
                .label("This will roll back to the previous OS image and perform a soft overlay reset.\n\nYour installed packages list is preserved — the overlay will be rebuilt from it on next boot.\n\npkexec will prompt for your password twice.")
                .wrap(true)
                .xalign(0.0)
                .build();

            let btn_row = GBox::builder()
                .orientation(Orientation::Horizontal)
                .spacing(8)
                .halign(Align::End)
                .margin_top(8)
                .build();

            let cancel_btn = Button::builder()
                .label("Cancel")
                .build();
            let confirm_btn = Button::builder()
                .label("Roll Back & Reset Overlay")
                .css_classes(vec!["destructive-action".to_string()])
                .build();

            btn_row.append(&cancel_btn);
            btn_row.append(&confirm_btn);
            body.append(&msg);
            body.append(&btn_row);
            toolbar.set_content(Some(&body));
            dlg.set_child(Some(&toolbar));

            let dlg_c = dlg.clone();
            cancel_btn.connect_clicked(move |_| { let _ = dlg_c.close(); });

            let dlg_c2   = dlg.clone();
            let btn_c    = btn.clone();
            let reboot_c = reboot_b.clone();
            let status_c = status_l.clone();

            confirm_btn.connect_clicked(move |_| {
                let _ = dlg_c2.close();
                btn_c.set_sensitive(false);
                btn_c.set_label("Rolling back…");
                status_c.set_visible(false);

                let (tx, rx) = mpsc::channel::<()>();
                std::thread::spawn(move || {
                    let _: Vec<_> = rakuos_updates::rollback_stream().collect();
                    let _ = tx.send(());
                });
                let btn_c2    = btn_c.clone();
                let reboot_c2 = reboot_c.clone();
                let status_c2 = status_c.clone();

                glib::timeout_add_local(Duration::from_millis(50), move || {
                    match rx.try_recv() {
                        Ok(_) => {
                            btn_c2.set_label("Rollback");
                            btn_c2.set_sensitive(true);
                            reboot_c2.set_visible(true);
                            status_c2.set_label("Rollback staged — reboot to apply.");
                            status_c2.set_visible(true);
                            glib::ControlFlow::Break
                        }
                        Err(mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
                        Err(_) => glib::ControlFlow::Break,
                    }
                });
            });

            dlg.present(Some(&outer_w));
        });
    }

    // ── Wire Kernel Parameters ────────────────────────────────────────────────
    {
        let outer_w = outer.clone();
        let reboot_b = img_reboot_btn.clone();
        kargs_btn.connect_clicked(move |_| {
            show_kargs_dialog(outer_w.upcast_ref::<gtk4::Widget>(), reboot_b.clone());
        });
    }

    img_reboot_btn.connect_clicked(|_| { rakuos_updates::schedule_reboot(); });
    overlay_reboot_btn.connect_clicked(|_| { rakuos_updates::schedule_reboot(); });

    // ── Wire Image Switch ────────────────────────────────────────────────────
    // current_image shared state populated after load
    let current_image_state: Rc<RefCell<String>> = Rc::new(RefCell::new(String::new()));
    let syncing_switch_controls: Rc<RefCell<bool>> = Rc::new(RefCell::new(true));
    {
        let img_state = Rc::clone(&current_image_state);
        let sync_state = Rc::clone(&syncing_switch_controls);
        let branch_combo_c = branch_combo.clone();
        let switch_btn_c = switch_btn.clone();
        de_combo.connect_selected_notify(move |combo| {
            if *sync_state.borrow() {
                return;
            }
            let enabled = selected_image_differs(
                &img_state.borrow(),
                combo.selected() as usize,
                branch_combo_c.selected() as usize,
            );
            switch_btn_c.set_sensitive(enabled);
        });
    }
    {
        let img_state = Rc::clone(&current_image_state);
        let sync_state = Rc::clone(&syncing_switch_controls);
        let de_combo_c = de_combo.clone();
        let switch_btn_c = switch_btn.clone();
        branch_combo.connect_selected_notify(move |combo| {
            if *sync_state.borrow() {
                return;
            }
            let enabled = selected_image_differs(
                &img_state.borrow(),
                de_combo_c.selected() as usize,
                combo.selected() as usize,
            );
            switch_btn_c.set_sensitive(enabled);
        });
    }
    {
        let img_state = Rc::clone(&current_image_state);
        let combo_c   = de_combo.clone();
        let branch_c  = branch_combo.clone();
        let outer_w   = outer.clone();
        let status_c  = switch_status_lbl.clone();
        let switch_c  = switch_btn.clone();
        let reboot_c  = img_reboot_btn.clone();

        switch_btn.connect_clicked(move |btn| {
            let img = img_state.borrow().clone();
            if img.is_empty() { return; }
            let idx = combo_c.selected() as usize;
            let branch_idx = branch_c.selected() as usize;
            let Some(&(de_label, de_id)) = DE_LABELS.get(idx) else { return; };
            let Some(&(branch_label, branch_tag)) = BRANCH_LABELS.get(branch_idx) else { return; };
            let (_, _, is_nvidia) = parse_image_ref(&img);
            let target = format!(
                "{}:{}",
                canonical_repo_url(&img, de_id, is_nvidia),
                branch_tag
            );
            show_switch_dialog(
                outer_w.upcast_ref::<gtk4::Widget>(),
                &target,
                de_label,
                branch_label,
                status_c.clone(),
                switch_c.clone(),
                reboot_c.clone(),
            );
            btn.set_sensitive(false);
        });
    }

    // ── Wire Reset Overlay ────────────────────────────────────────────────────
    {
        let reset_b      = reset_btn.clone();
        let ovl_status   = overlay_status_lbl.clone();
        let ovl_reboot   = overlay_reboot_btn.clone();

        reset_btn.connect_clicked(move |_| {
            show_reset_dialog(
                reset_b.upcast_ref::<gtk4::Widget>(),
                ovl_status.clone(),
                ovl_reboot.clone(),
            );
        });
    }

    // ── Load system status ────────────────────────────────────────────────────
    {
        let (tx, rx) = mpsc::channel::<(SystemStatus, OverlayStatus)>();
        std::thread::spawn(move || {
            let status  = rakuos_updates::get_system_status();
            let overlay = rakuos_updates::get_overlay_status();
            let _ = tx.send((status, overlay));
        });

        let img_state = Rc::clone(&current_image_state);
        let sync_state = Rc::clone(&syncing_switch_controls);
        let switch_btn_c = switch_btn.clone();

        glib::timeout_add_local(Duration::from_millis(80), move || {
            match rx.try_recv() {
                Ok((status, overlay)) => {
                    img_state.replace(status.image.clone());
                    *sync_state.borrow_mut() = true;

                    image_row.set_subtitle(if status.image.is_empty() { "Unknown" } else { &status.image });
                    version_row.set_subtitle(&display_image_version(&status.image, &status.version));
                    digest_row.set_subtitle(if status.digest.is_empty() {
                        "Unknown".to_string()
                    } else {
                        format!("{}…", &status.digest[..status.digest.len().min(24)])
                    }.as_str());
                    timestamp_row.set_subtitle(if status.timestamp.is_empty() { "Unknown" } else { &status.timestamp });
                    let (_, tag, is_nvidia) = parse_image_ref(&status.image);
                    let tag_lower = tag.to_ascii_lowercase();
                    nvidia_row.set_subtitle(if is_nvidia { "Yes" } else { "No" });

                    // Set DE combo to current DE
                    let (name, _, _) = parse_image_ref(&status.image);
                    if let Some(idx) = DE_LABELS.iter().position(|(_, id)| *id == name) {
                        de_combo.set_selected(idx as u32);
                    }
                    if let Some(idx) = BRANCH_LABELS.iter().position(|(_, branch_tag)| {
                        match *branch_tag {
                            "staging" => tag_lower.starts_with("staging"),
                            "rawhide" => tag_lower.starts_with("rawhide"),
                            _ => !tag_lower.starts_with("staging") && !tag_lower.starts_with("rawhide"),
                        }
                    }) {
                        branch_combo.set_selected(idx as u32);
                    }
                    switch_btn_c.set_sensitive(false);
                    *sync_state.borrow_mut() = false;
                    current_de_row.set_subtitle(&de_label_from_image(&status.image));
                    current_branch_row.set_subtitle(branch_label_from_tag(&tag_lower));

                    // Overlay packages
                    overlay_count_lbl.set_label(&format!(
                        "{} package{}", overlay.package_count,
                        if overlay.package_count == 1 { "" } else { "s" }
                    ));

                    // Remove placeholder
                    if let Some(c) = pkg_list_box.first_child() {
                        pkg_list_box.remove(&c);
                    }
                    if overlay.packages.is_empty() {
                        pkg_list_box.append(&no_pkgs_lbl);
                    } else {
                        for pkg in &overlay.packages {
                            let row = ActionRow::builder()
                                .title(pkg.as_str())
                                .build();
                            pkg_list_box.append(&row);
                        }
                    }

                    glib::ControlFlow::Break
                }
                Err(mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
                Err(_) => glib::ControlFlow::Break,
            }
        });
    }

    scroll.upcast()
}

// ── Reset Overlay dialog ──────────────────────────────────────────────────────

fn show_reset_dialog(
    parent_widget: &gtk4::Widget,
    status_lbl:    Label,
    reboot_btn:    Button,
) {
    let dialog = Dialog::builder()
        .title("Reset Overlay")
        .build();

    let content = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(16)
        .margin_top(16)
        .margin_bottom(16)
        .margin_start(20)
        .margin_end(20)
        .build();

    let header = HeaderBar::new();
    let toolbar = ToolbarView::builder().content(&content).build();
    toolbar.add_top_bar(&header);
    dialog.set_child(Some(&toolbar));

    let intro = Label::builder()
        .label("Choose how to reset the overlay. Both options require a reboot.\npkexec will prompt for your password.")
        .halign(Align::Start)
        .wrap(true)
        .css_classes(vec!["dim-label".to_string()])
        .build();
    content.append(&intro);

    // Soft Reset card
    let soft_card = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(6)
        .css_classes(vec!["card".to_string()])
        .margin_top(4)
        .build();
    let soft_inner = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(12)
        .margin_top(12).margin_bottom(12).margin_start(12).margin_end(12)
        .build();
    let soft_text = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(2)
        .hexpand(true)
        .build();
    let soft_title = Label::builder()
        .label("Soft Reset (Rebuild)")
        .halign(Align::Start)
        .css_classes(vec!["heading".to_string()])
        .build();
    let soft_desc = Label::builder()
        .label("Wipes the overlay and reinstalls all packages\nfrom your packages list on next boot.\nYour installed package list is preserved.\n\nA network connection is required during\nthe next boot to complete the rebuild.")
        .halign(Align::Start)
        .wrap(true)
        .css_classes(vec!["dim-label".to_string()])
        .build();
    let soft_btn = Button::builder()
        .label("Soft Reset")
        .valign(Align::Center)
        .css_classes(vec!["pill".to_string(), "suggested-action".to_string()])
        .build();
    soft_text.append(&soft_title);
    soft_text.append(&soft_desc);
    soft_inner.append(&soft_text);
    soft_inner.append(&soft_btn);
    soft_card.append(&soft_inner);
    content.append(&soft_card);

    // Full Reset card
    let full_card = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(6)
        .css_classes(vec!["card".to_string()])
        .build();
    let full_inner = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(12)
        .margin_top(12).margin_bottom(12).margin_start(12).margin_end(12)
        .build();
    let full_text = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(2)
        .hexpand(true)
        .build();
    let full_title = Label::builder()
        .label("Full Reset (Stock)")
        .halign(Align::Start)
        .css_classes(vec!["heading".to_string()])
        .build();
    let full_desc = Label::builder()
        .label("Completely wipes the overlay and your packages list.\nThe system returns to the base image state.\nAll installed packages will be removed.")
        .halign(Align::Start)
        .wrap(true)
        .css_classes(vec!["dim-label".to_string()])
        .build();
    let full_btn = Button::builder()
        .label("Full Reset")
        .valign(Align::Center)
        .css_classes(vec!["pill".to_string(), "destructive-action".to_string()])
        .build();
    full_text.append(&full_title);
    full_text.append(&full_desc);
    full_inner.append(&full_text);
    full_inner.append(&full_btn);
    full_card.append(&full_inner);
    content.append(&full_card);

    // Wire Soft Reset button
    {
        let d      = dialog.clone();
        let s_lbl  = status_lbl.clone();
        let r_btn  = reboot_btn.clone();

        soft_btn.connect_clicked(move |_| {
            let _ = d.close();
            run_overlay_reset("soft", s_lbl.clone(), r_btn.clone());
        });
    }

    // Wire Full Reset button
    {
        let d     = dialog.clone();
        let s_lbl = status_lbl.clone();
        let r_btn = reboot_btn.clone();

        full_btn.connect_clicked(move |_| {
            let _ = d.close();
            run_overlay_reset("full", s_lbl.clone(), r_btn.clone());
        });
    }

    let parent_window = parent_widget
        .root()
        .and_then(|r| r.downcast::<gtk4::Window>().ok());
    dialog.present(parent_window.as_ref());
}

fn run_overlay_reset(mode: &'static str, status_lbl: Label, reboot_btn: Button) {
    status_lbl.set_label("Running reset…");
    status_lbl.remove_css_class("success");
    status_lbl.remove_css_class("error");
    status_lbl.set_visible(true);
    reboot_btn.set_visible(false);

    let (tx, rx) = mpsc::channel::<bool>();
    std::thread::spawn(move || {
        let success = rakuos_updates::reset_overlay_stream(mode)
            .last()
            .map(|last| !last.starts_with("__done__2"))
            .unwrap_or(true);
        let _ = tx.send(success);
    });

    glib::timeout_add_local(Duration::from_millis(80), move || {
        match rx.try_recv() {
            Ok(ok) => {
                if ok {
                    status_lbl.set_label("Reset scheduled — reboot to apply.");
                    status_lbl.add_css_class("success");
                    reboot_btn.set_visible(true);
                } else {
                    status_lbl.set_label("Reset failed. Check system logs.");
                    status_lbl.add_css_class("error");
                }
                glib::ControlFlow::Break
            }
            Err(mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
            Err(_) => glib::ControlFlow::Break,
        }
    });
}

fn show_switch_dialog(
    parent_widget: &gtk4::Widget,
    target: &str,
    de_label: &str,
    branch_label: &str,
    status_lbl: Label,
    switch_btn: Button,
    reboot_btn: Button,
) {
    let dialog = Dialog::builder()
        .title("Switch Image?")
        .can_close(true)
        .build();

    let content = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(16)
        .margin_top(16)
        .margin_bottom(16)
        .margin_start(20)
        .margin_end(20)
        .build();

    let header = HeaderBar::new();
    let toolbar = ToolbarView::builder().content(&content).build();
    toolbar.add_top_bar(&header);
    dialog.set_child(Some(&toolbar));

    let intro = Label::builder()
        .label(&format!(
            "{}",
            if branch_label == "Rawhide" {
                format!(
                    "Switching to {} on {} performs a full overlay reset before the new image is staged.\n\nWARNING: Rawhide is an experimental rolling development branch. It can break unexpectedly, may fail to boot, may contain unfinished changes, and is not recommended for normal use.\n\nThis wipes the current overlay and saved packages list, then runs the image switch with pkexec.",
                    de_label, branch_label
                )
            } else {
                format!(
                    "Switching to {} on {} performs a full overlay reset before the new image is staged.\n\nThis wipes the current overlay and saved packages list, then runs the image switch with pkexec.",
                    de_label, branch_label
                )
            }
        ))
        .halign(Align::Start)
        .wrap(true)
        .css_classes(vec!["dim-label".to_string()])
        .build();
    content.append(&intro);

    let warning_card = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(8)
        .css_classes(vec!["card".to_string()])
        .build();
    let warning_inner = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(6)
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .build();
    let warning_title = Label::builder()
        .label("Full Overlay Reset Required")
        .halign(Align::Start)
        .css_classes(vec!["heading".to_string(), "error".to_string()])
        .build();
    let warning_desc = Label::builder()
        .label(
            if branch_label == "Rawhide" {
                "Keep this window open while the switch runs. Rawhide is for testing only and should be expected to be unstable."
            } else {
                "Keep this window open while the switch runs. You will be able to reboot once the operation completes."
            }
        )
        .halign(Align::Start)
        .wrap(true)
        .css_classes(vec!["dim-label".to_string()])
        .build();
    warning_inner.append(&warning_title);
    warning_inner.append(&warning_desc);
    warning_card.append(&warning_inner);
    content.append(&warning_card);

    let actions = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(8)
        .halign(Align::End)
        .build();
    let cancel_btn = Button::builder()
        .label("Cancel")
        .build();
    let proceed_btn = Button::builder()
        .label("Proceed")
        .css_classes(vec!["suggested-action".to_string()])
        .build();
    actions.append(&cancel_btn);
    actions.append(&proceed_btn);
    content.append(&actions);

    let dialog_c = dialog.clone();
    let switch_btn_c = switch_btn.clone();
    cancel_btn.connect_clicked(move |_| {
        switch_btn_c.set_label("Switch Image");
        switch_btn_c.set_sensitive(true);
        let _ = dialog_c.close();
    });

    let parent_window = parent_widget
        .root()
        .and_then(|r| r.downcast::<gtk4::Window>().ok());

    {
        let dialog_c = dialog.clone();
        let target = target.to_string();
        let de_label = de_label.to_string();
        let branch_label = branch_label.to_string();
        let status_lbl_c = status_lbl.clone();
        let switch_btn_c = switch_btn.clone();
        let reboot_btn_c = reboot_btn.clone();
        let parent_window_c = parent_window.clone();
        proceed_btn.connect_clicked(move |_| {
            let _ = dialog_c.close();
            show_switch_progress_dialog(
                parent_window_c.clone(),
                target.clone(),
                de_label.clone(),
                branch_label.clone(),
                status_lbl_c.clone(),
                switch_btn_c.clone(),
                reboot_btn_c.clone(),
            );
        });
    }

    dialog.present(parent_window.as_ref());
}

fn show_switch_progress_dialog(
    parent_window: Option<gtk4::Window>,
    target: String,
    de_label: String,
    branch_label: String,
    status_lbl: Label,
    switch_btn: Button,
    reboot_btn: Button,
) {
    let dialog = Dialog::builder()
        .title("Switching Image")
        .can_close(false)
        .build();

    let content = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(16)
        .margin_top(16)
        .margin_bottom(16)
        .margin_start(20)
        .margin_end(20)
        .build();

    let header = HeaderBar::new();
    header.set_show_end_title_buttons(false);
    let toolbar = ToolbarView::builder().content(&content).build();
    toolbar.add_top_bar(&header);
    dialog.set_child(Some(&toolbar));

    let intro = Label::builder()
        .label(&format!(
            "Please wait while RakuOS fully resets the overlay and switches to {} on {}.",
            de_label, branch_label
        ))
        .halign(Align::Start)
        .wrap(true)
        .css_classes(vec!["dim-label".to_string()])
        .build();
    content.append(&intro);

    let progress_row = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(10)
        .build();
    let spinner = Spinner::builder()
        .spinning(true)
        .build();
    let progress_lbl = Label::builder()
        .label("Please wait…")
        .halign(Align::Start)
        .build();
    progress_row.append(&spinner);
    progress_row.append(&progress_lbl);
    content.append(&progress_row);

    let result_lbl = Label::builder()
        .label("")
        .halign(Align::Start)
        .wrap(true)
        .visible(false)
        .build();
    content.append(&result_lbl);

    let log_buffer = TextBuffer::new(None);
    let log_view = TextView::builder()
        .buffer(&log_buffer)
        .editable(false)
        .cursor_visible(false)
        .monospace(true)
        .wrap_mode(gtk4::WrapMode::WordChar)
        .top_margin(8)
        .bottom_margin(8)
        .left_margin(8)
        .right_margin(8)
        .build();
    let log_scroll = ScrolledWindow::builder()
        .min_content_height(180)
        .hscrollbar_policy(gtk4::PolicyType::Never)
        .vscrollbar_policy(gtk4::PolicyType::Automatic)
        .child(&log_view)
        .build();
    content.append(&log_scroll);

    let actions = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(8)
        .halign(Align::End)
        .build();
    let close_btn = Button::builder()
        .label("Close")
        .visible(false)
        .build();
    let reboot_action_btn = Button::builder()
        .label("Reboot to Apply")
        .css_classes(vec!["suggested-action".to_string()])
        .visible(false)
        .build();
    actions.append(&close_btn);
    actions.append(&reboot_action_btn);
    content.append(&actions);

    let (tx, rx) = mpsc::channel::<Result<String, bool>>();
    std::thread::spawn(move || {
        let mut saw_done = false;
        let mut exit_code = 1i32;

        for line in rakuos_updates::pkexec_reset_overlay_and_switch_stream(&target) {
            if let Some(code) = line.strip_prefix("__done__") {
                saw_done = true;
                exit_code = code.trim().parse().unwrap_or(1);
            } else {
                let _ = tx.send(Ok(line));
            }
        }

        let _ = tx.send(Err(saw_done && exit_code == 0));
    });

    let dialog_c = dialog.clone();
    close_btn.connect_clicked(move |_| {
        let _ = dialog_c.close();
    });

    reboot_action_btn.connect_clicked(|_| {
        rakuos_updates::schedule_reboot();
    });

    dialog.present(parent_window.as_ref());

    glib::timeout_add_local(Duration::from_millis(80), move || {
        let mut finished = None;

        loop {
            match rx.try_recv() {
                Ok(Ok(line)) => {
                    let mut end = log_buffer.end_iter();
                    log_buffer.insert(&mut end, &(line + "\n"));
                }
                Ok(Err(ok)) => {
                    finished = Some(ok);
                    break;
                }
                Err(mpsc::TryRecvError::Empty) => break,
                Err(_) => {
                    finished = Some(false);
                    break;
                }
            }
        }

        if let Some(ok) = finished {
            spinner.set_spinning(false);
            spinner.set_visible(false);
            progress_lbl.set_visible(false);
            result_lbl.set_visible(true);
            switch_btn.set_label("Switch Image");
            switch_btn.set_sensitive(true);

            if ok {
                dialog.set_can_close(true);
                result_lbl.set_label("Image switch staged — reboot to apply.");
                result_lbl.add_css_class("success");
                reboot_action_btn.set_visible(true);
                reboot_btn.set_visible(true);
                status_lbl.set_label("Switch staged — reboot to apply.");
                status_lbl.remove_css_class("error");
                status_lbl.add_css_class("success");
                status_lbl.set_visible(true);
            } else {
                dialog.set_can_close(true);
                result_lbl.set_label("Image switch failed. Review the system logs and try again.");
                result_lbl.add_css_class("error");
                close_btn.set_visible(true);
                status_lbl.set_label("Image switch failed. Check system logs.");
                status_lbl.remove_css_class("success");
                status_lbl.add_css_class("error");
                status_lbl.set_visible(true);
            }
            glib::ControlFlow::Break
        } else {
            glib::ControlFlow::Continue
        }
    });
}

// ── Kernel Parameters dialog ──────────────────────────────────────────────────

fn show_kargs_dialog(parent_widget: &gtk4::Widget, reboot_btn: Button) {
    use gtk4::EntryBuffer;

    let user_args = rakuos_updates::get_user_kernel_args();
    let cmdline   = rakuos_updates::get_current_cmdline();

    let dialog = Dialog::builder()
        .title("Kernel Parameters")
        .content_width(560)
        .build();

    let content = GBox::builder()
        .orientation(Orientation::Vertical)
        .spacing(12)
        .margin_top(16)
        .margin_bottom(16)
        .margin_start(20)
        .margin_end(20)
        .build();

    let header = HeaderBar::new();
    let toolbar = ToolbarView::builder().content(&content).build();
    toolbar.add_top_bar(&header);
    dialog.set_child(Some(&toolbar));

    // Current cmdline (read-only)
    let cmdline_heading = Label::builder()
        .label("Current kernel command line:")
        .halign(Align::Start)
        .css_classes(vec!["heading".to_string()])
        .build();
    content.append(&cmdline_heading);

    let cmdline_lbl = Label::builder()
        .label(&cmdline)
        .halign(Align::Start)
        .wrap(true)
        .selectable(true)
        .css_classes(vec!["monospace".to_string(), "dim-label".to_string()])
        .build();
    content.append(&cmdline_lbl);

    content.append(&Separator::new(Orientation::Horizontal));

    // User-managed args entry
    let entry_heading = Label::builder()
        .label("User-managed parameters (rakuos-user source):")
        .halign(Align::Start)
        .css_classes(vec!["heading".to_string()])
        .build();
    content.append(&entry_heading);

    let hint = Label::builder()
        .label("Space-separated kernel arguments. Leave empty to remove all user-set args.")
        .halign(Align::Start)
        .wrap(true)
        .css_classes(vec!["caption".to_string(), "dim-label".to_string()])
        .build();
    content.append(&hint);

    let entry_buf = EntryBuffer::new(Some(&user_args));
    let entry = gtk4::Entry::builder()
        .buffer(&entry_buf)
        .hexpand(true)
        .css_classes(vec!["monospace".to_string()])
        .placeholder_text("e.g. mitigations=off quiet splash")
        .build();
    content.append(&entry);

    // Status + log
    let status_lbl = Label::builder()
        .halign(Align::Start)
        .visible(false)
        .wrap(true)
        .css_classes(vec!["caption".to_string()])
        .build();
    content.append(&status_lbl);

    let log_buf = TextBuffer::new(None);
    let log_view = gtk4::TextView::builder()
        .buffer(&log_buf)
        .editable(false)
        .cursor_visible(false)
        .monospace(true)
        .wrap_mode(gtk4::WrapMode::WordChar)
        .top_margin(6)
        .bottom_margin(6)
        .left_margin(6)
        .right_margin(6)
        .build();
    let log_scroll = ScrolledWindow::builder()
        .child(&log_view)
        .min_content_height(100)
        .max_content_height(100)
        .hscrollbar_policy(gtk4::PolicyType::Never)
        .visible(false)
        .build();
    content.append(&log_scroll);

    // Action buttons
    let btn_row = GBox::builder()
        .orientation(Orientation::Horizontal)
        .spacing(8)
        .halign(Align::End)
        .build();

    let cancel_btn = Button::builder().label("Cancel").build();
    let clear_btn = Button::builder()
        .label("Clear All")
        .css_classes(vec!["destructive-action".to_string()])
        .build();
    let apply_btn = Button::builder()
        .label("Apply")
        .css_classes(vec!["suggested-action".to_string()])
        .build();

    btn_row.append(&cancel_btn);
    btn_row.append(&clear_btn);
    btn_row.append(&apply_btn);
    content.append(&btn_row);

    let dlg_c = dialog.clone();
    cancel_btn.connect_clicked(move |_| { let _ = dlg_c.close(); });

    // Clear = empty the entry and apply
    {
        let entry_c = entry.clone();
        clear_btn.connect_clicked(move |_| {
            entry_c.buffer().set_text("");
        });
    }

    // Apply
    {
        let entry_c   = entry.clone();
        let log_buf_c = log_buf.clone();
        let log_scroll_c = log_scroll.clone();
        let status_c  = status_lbl.clone();
        let apply_c   = apply_btn.clone();
        let cancel_c  = cancel_btn.clone();
        let clear_c   = clear_btn.clone();
        let reboot_c  = reboot_btn.clone();

        apply_btn.connect_clicked(move |_| {
            let options = entry_c.buffer().text().to_string();
            apply_c.set_sensitive(false);
            apply_c.set_label("Applying…");
            clear_c.set_sensitive(false);
            cancel_c.set_sensitive(false);
            log_scroll_c.set_visible(true);
            log_buf_c.set_text("Running pkexec bootc loader-entries set-options-for-source…\n");
            status_c.set_visible(false);

            let (tx, rx) = mpsc::channel::<Result<String, bool>>();
            std::thread::spawn(move || {
                let mut exit_code = 1i32;
                for line in rakuos_updates::set_kernel_args_stream(&options) {
                    if let Some(code) = line.strip_prefix("__done__") {
                        exit_code = code.trim().parse().unwrap_or(1);
                    } else {
                        let _ = tx.send(Ok(line));
                    }
                }
                let _ = tx.send(Err(exit_code == 0));
            });

            let apply_c2   = apply_c.clone();
            let cancel_c2  = cancel_c.clone();
            let clear_c2   = clear_c.clone();
            let status_c2  = status_c.clone();
            let log_buf_c2 = log_buf_c.clone();
            let log_sc2    = log_scroll_c.clone();
            let reboot_c2  = reboot_c.clone();

            glib::timeout_add_local(Duration::from_millis(80), move || {
                loop {
                    match rx.try_recv() {
                        Ok(Ok(line)) => {
                            let mut end = log_buf_c2.end_iter();
                            log_buf_c2.insert(&mut end, &(line + "\n"));
                            let adj = log_sc2.vadjustment();
                            adj.set_value(adj.upper());
                        }
                        Ok(Err(ok)) => {
                            apply_c2.set_label("Apply");
                            apply_c2.set_sensitive(true);
                            cancel_c2.set_sensitive(true);
                            clear_c2.set_sensitive(true);
                            if ok {
                                status_c2.set_label("Kernel args staged — reboot to apply.");
                                status_c2.add_css_class("success");
                                status_c2.remove_css_class("error");
                                reboot_c2.set_visible(true);
                            } else {
                                status_c2.set_label("Failed to apply kernel args. Check the log above.");
                                status_c2.add_css_class("error");
                                status_c2.remove_css_class("success");
                            }
                            status_c2.set_visible(true);
                            return glib::ControlFlow::Break;
                        }
                        Err(mpsc::TryRecvError::Empty) => return glib::ControlFlow::Continue,
                        Err(_) => return glib::ControlFlow::Break,
                    }
                }
            });
        });
    }

    let parent_window = parent_widget
        .root()
        .and_then(|r| r.downcast::<gtk4::Window>().ok());
    dialog.present(parent_window.as_ref());
}

fn parse_bootc_progress(line: &str) -> Option<f64> {
    // "[N/M] ..." style from bootc/skopeo output
    let trimmed = line.trim();
    let bracket_start = trimmed.find('[')?;
    let bracket_end   = trimmed[bracket_start..].find(']')?;
    let inner = &trimmed[bracket_start + 1..bracket_start + bracket_end];
    let slash = inner.find('/')?;
    let n: f64 = inner[..slash].trim().parse().ok()?;
    let total: f64 = inner[slash + 1..].trim().parse().ok()?;
    if total > 0.0 { Some((n / total).min(1.0)) } else { None }
}
