use adw::prelude::*;
use gtk::glib;
use std::cell::Cell;
use std::rc::Rc;
use crate::app::AppState;

fn get_first_image_aspect(files: &[std::path::PathBuf]) -> f64 {
    let Some(first) = files.first() else { return 0.0 };
    let Ok((w, h)) = image::image_dimensions(first) else { return 0.0 };
    if h == 0 { return 0.0; }
    w as f64 / h as f64
}

fn get_image_dims(path: &std::path::Path) -> (u32, u32) {
    image::image_dimensions(path).unwrap_or((4000, 3000))
}

fn get_first_image_dims(files: &[std::path::PathBuf]) -> (u32, u32) {
    let Some(first) = files.first() else { return (4000, 3000) };
    get_image_dims(first)
}

fn algo_index_to_filter(idx: u32) -> image::imageops::FilterType {
    match idx {
        1 => image::imageops::FilterType::CatmullRom,
        2 => image::imageops::FilterType::Triangle,
        3 => image::imageops::FilterType::Nearest,
        _ => image::imageops::FilterType::Lanczos3,
    }
}

const CATEGORIES: &[&str] = &[
    "Fediverse / Open Platforms",
    "Mainstream Platforms",
    "Common / Web",
];

fn presets_for_category(cat: u32) -> &'static [(&'static str, u32, u32)] {
    match cat {
        0 => &[
            ("Mastodon Post (1920 x 1080)", 1920, 1080),
            ("Mastodon Profile (400 x 400)", 400, 400),
            ("Mastodon Header (1500 x 500)", 1500, 500),
            ("Pixelfed Post (1080 x 1080)", 1080, 1080),
            ("Pixelfed Story (1080 x 1920)", 1080, 1920),
            ("Bluesky Post (1200 x 630)", 1200, 630),
            ("Bluesky Profile (400 x 400)", 400, 400),
            ("Bluesky Banner (1500 x 500)", 1500, 500),
            ("Lemmy Post (1200 x 630)", 1200, 630),
            ("PeerTube Thumbnail (1280 x 720)", 1280, 720),
            ("Friendica Post (1200 x 630)", 1200, 630),
            ("Funkwhale Cover (1400 x 1400)", 1400, 1400),
        ],
        1 => &[
            ("Instagram Post Square (1080 x 1080)", 1080, 1080),
            ("Instagram Post Portrait (1080 x 1350)", 1080, 1350),
            ("Instagram Story/Reel (1080 x 1920)", 1080, 1920),
            ("Facebook Post (1200 x 630)", 1200, 630),
            ("Facebook Cover (820 x 312)", 820, 312),
            ("Facebook Profile (170 x 170)", 170, 170),
            ("YouTube Thumbnail (1280 x 720)", 1280, 720),
            ("YouTube Channel Art (2560 x 1440)", 2560, 1440),
            ("LinkedIn Post (1200 x 627)", 1200, 627),
            ("LinkedIn Cover (1584 x 396)", 1584, 396),
            ("LinkedIn Profile (400 x 400)", 400, 400),
            ("Pinterest Pin (1000 x 1500)", 1000, 1500),
            ("TikTok Video Cover (1080 x 1920)", 1080, 1920),
            ("Threads Post (1080 x 1080)", 1080, 1080),
        ],
        _ => &[
            ("4K UHD (3840 x 2160)", 3840, 2160),
            ("Full HD (1920 x 1080)", 1920, 1080),
            ("HD Ready (1280 x 720)", 1280, 720),
            ("Blog Standard (800 wide)", 800, 0),
            ("Email Header (600 x 200)", 600, 200),
            ("Large Thumbnail (300 x 300)", 300, 300),
            ("Small Thumbnail (150 x 150)", 150, 150),
            ("Favicon (32 x 32)", 32, 32),
        ],
    }
}

fn rebuild_size_model(size_row: &adw::ComboRow, cat: u32) {
    let presets = presets_for_category(cat);
    let mut names: Vec<&str> = vec!["(select a size)"];
    names.extend(presets.iter().map(|p| p.0));
    size_row.set_model(Some(&gtk::StringList::new(&names)));
    size_row.set_list_factory(Some(&super::full_text_list_factory()));
    size_row.set_selected(0);
}

pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
    let cfg = state.job_config.borrow();

    // === OUTER LAYOUT ===
    let outer = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(0)
        .vexpand(true)
        .build();

    // --- Enable toggle (full width) ---
    let enable_group = adw::PreferencesGroup::builder()
        .margin_start(12)
        .margin_end(12)
        .margin_top(12)
        .build();
    let enable_row = adw::SwitchRow::builder()
        .title("Enable Resize")
        .subtitle("Scale images to new dimensions")
        .active(cfg.resize_enabled)
        .tooltip_text("Toggle resizing of images on or off")
        .build();
    enable_group.add(&enable_row);
    outer.append(&enable_group);

    // --- Horizontal split: Preview (left 60%) | Controls (right 40%) ---
    let split = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(12)
        .margin_start(12)
        .margin_end(12)
        .margin_top(12)
        .margin_bottom(12)
        .vexpand(true)
        .build();

    // ========== LEFT: Preview ==========
    let preview_box = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(8)
        .hexpand(true)
        .valign(gtk::Align::Start)
        .build();

    let thumb_picture = gtk::Picture::builder()
        .content_fit(gtk::ContentFit::Contain)
        .height_request(250)
        .hexpand(true)
        .build();
    thumb_picture.add_css_class("card");
    thumb_picture.update_property(&[
        gtk::accessible::Property::Label("Resize preview - click to cycle images"),
    ]);

    let dims_label = gtk::Label::builder()
        .css_classes(["dim-label", "caption"])
        .halign(gtk::Align::Center)
        .margin_top(4)
        .build();

    let no_preview_label = gtk::Label::builder()
        .label("Add images to see resize preview")
        .css_classes(["dim-label"])
        .halign(gtk::Align::Center)
        .build();

    preview_box.append(&thumb_picture);
    preview_box.append(&dims_label);
    preview_box.append(&no_preview_label);

    // ========== RIGHT: Controls ==========
    let controls_scroll = gtk::ScrolledWindow::builder()
        .hscrollbar_policy(gtk::PolicyType::Never)
        .vexpand(true)
        .width_request(340)
        .build();

    let controls = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(12)
        .build();

    // --- Group 1: Preset (two-level: category then size) ---
    let preset_group = adw::PreferencesGroup::builder()
        .title("Preset")
        .description("Pick a category, then a size")
        .build();

    let category_row = adw::ComboRow::builder()
        .title("Category")
        .use_subtitle(true)
        .tooltip_text("Choose a category of size presets")
        .build();
    category_row.set_model(Some(&gtk::StringList::new(CATEGORIES)));
    category_row.set_list_factory(Some(&super::full_text_list_factory()));

    let size_row = adw::ComboRow::builder()
        .title("Size")
        .subtitle("Select a preset to fill dimensions")
        .use_subtitle(true)
        .tooltip_text("Pick a preset size to fill dimensions")
        .build();
    rebuild_size_model(&size_row, 0);

    preset_group.add(&category_row);
    preset_group.add(&size_row);
    controls.append(&preset_group);

    // --- Group 2: Dimensions ---
    let dims_group = adw::PreferencesGroup::builder()
        .title("Dimensions")
        .build();

    // Custom horizontal row: [W] [width_spin] [lock] [height_spin] [H] [px|%]
    let dim_row = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(6)
        .margin_start(12)
        .margin_end(12)
        .margin_top(10)
        .margin_bottom(10)
        .halign(gtk::Align::Center)
        .build();

    let w_label = gtk::Label::builder()
        .label("W")
        .css_classes(["dim-label"])
        .build();
    w_label.set_accessible_role(gtk::AccessibleRole::Presentation);
    let width_spin = gtk::SpinButton::builder()
        .adjustment(&gtk::Adjustment::new(
            cfg.resize_width as f64, 0.0, 10000.0, 1.0, 100.0, 0.0,
        ))
        .numeric(true)
        .width_chars(6)
        .tooltip_text("Width")
        .build();
    width_spin.update_property(&[
        gtk::accessible::Property::Label("Width"),
    ]);

    let lock_btn = gtk::ToggleButton::builder()
        .icon_name("changes-prevent-symbolic")
        .active(true)
        .tooltip_text("Aspect ratio locked")
        .build();
    lock_btn.update_property(&[
        gtk::accessible::Property::Label("Lock aspect ratio"),
    ]);

    let height_spin = gtk::SpinButton::builder()
        .adjustment(&gtk::Adjustment::new(
            cfg.resize_height as f64, 0.0, 10000.0, 1.0, 100.0, 0.0,
        ))
        .numeric(true)
        .width_chars(6)
        .tooltip_text("Height")
        .build();
    height_spin.update_property(&[
        gtk::accessible::Property::Label("Height"),
    ]);
    let h_label = gtk::Label::builder()
        .label("H")
        .css_classes(["dim-label"])
        .build();
    h_label.set_accessible_role(gtk::AccessibleRole::Presentation);

    // Unit segmented toggle (px / %)
    let unit_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
    unit_box.add_css_class("linked");
    unit_box.update_property(&[
        gtk::accessible::Property::Label("Dimension unit toggle"),
    ]);
    let px_btn = gtk::Button::builder()
        .label("px")
        .tooltip_text("Use pixel dimensions (currently active)")
        .build();
    px_btn.update_property(&[
        gtk::accessible::Property::Label("Pixels - currently active"),
    ]);
    let pct_btn = gtk::Button::builder()
        .label("%")
        .tooltip_text("Use percentage dimensions")
        .build();
    pct_btn.update_property(&[
        gtk::accessible::Property::Label("Percentage"),
    ]);
    px_btn.add_css_class("suggested-action");
    unit_box.append(&px_btn);
    unit_box.append(&pct_btn);

    dim_row.append(&w_label);
    dim_row.append(&width_spin);
    dim_row.append(&lock_btn);
    dim_row.append(&height_spin);
    dim_row.append(&h_label);
    dim_row.append(&unit_box);
    dims_group.add(&dim_row);

    // Mode
    let mode_row = adw::ComboRow::builder()
        .title("Mode")
        .subtitle("How dimensions are applied to images")
        .use_subtitle(true)
        .tooltip_text("Exact stretches to dimensions; Fit keeps aspect ratio")
        .build();
    mode_row.set_model(Some(&gtk::StringList::new(&[
        "Exact Size",
        "Fit Within Box",
    ])));
    mode_row.set_list_factory(Some(&super::full_text_list_factory()));

    // Upscale
    let upscale_row = adw::SwitchRow::builder()
        .title("Allow Upscaling")
        .subtitle("Enlarge images smaller than target size")
        .active(cfg.allow_upscale)
        .tooltip_text("When off, images smaller than target are left as-is")
        .build();

    dims_group.add(&mode_row);
    dims_group.add(&upscale_row);
    controls.append(&dims_group);

    // --- Group 3: Advanced ---
    let advanced_group = adw::PreferencesGroup::builder()
        .title("Advanced")
        .build();
    let advanced_expander = adw::ExpanderRow::builder()
        .title("Advanced Settings")
        .subtitle("Resize algorithm and output DPI")
        .show_enable_switch(false)
        .expanded(state.is_section_expanded("resize-advanced"))
        .build();
    {
        let st = state.clone();
        advanced_expander.connect_expanded_notify(move |row| {
            st.set_section_expanded("resize-advanced", row.is_expanded());
        });
    }

    let algorithm_row = adw::ComboRow::builder()
        .title("Resize Algorithm")
        .use_subtitle(true)
        .build();
    algorithm_row.set_model(Some(&gtk::StringList::new(&[
        "Lanczos3 (Best quality)",
        "CatmullRom (Good, faster)",
        "Bilinear (Fast)",
        "Nearest (Pixelated)",
    ])));
    algorithm_row.set_list_factory(Some(&super::full_text_list_factory()));

    let dpi_row = adw::SpinRow::builder()
        .title("DPI")
        .subtitle("Output resolution in dots per inch")
        .adjustment(&gtk::Adjustment::new(72.0, 72.0, 600.0, 1.0, 10.0, 0.0))
        .build();

    advanced_expander.add_row(&algorithm_row);
    advanced_expander.add_row(&dpi_row);
    advanced_group.add(&advanced_expander);
    controls.append(&advanced_group);

    controls_scroll.set_child(Some(&controls));

    split.append(&preview_box);
    split.append(&controls_scroll);
    outer.append(&split);

    // === SHARED STATE ===
    let preview_width = std::rc::Rc::new(std::cell::RefCell::new(cfg.resize_width));
    let preview_height = std::rc::Rc::new(std::cell::RefCell::new(cfg.resize_height));
    let preview_upscale = std::rc::Rc::new(std::cell::Cell::new(cfg.allow_upscale));
    let preview_mode = std::rc::Rc::new(std::cell::Cell::new(cfg.resize_mode));
    let preview_algo = std::rc::Rc::new(std::cell::Cell::new(0u32));
    let preview_index = std::rc::Rc::new(std::cell::Cell::new(0usize));
    let is_pct = std::rc::Rc::new(std::cell::Cell::new(false));
    let updating = std::rc::Rc::new(std::cell::Cell::new(false));
    let loaded_files = state.loaded_files.clone();

    drop(cfg);

    // === RENDER CLOSURE ===
    let resize_preview_gen: Rc<Cell<u32>> = Rc::new(Cell::new(0));
    let render_thumb = {
        let lf = loaded_files.clone();
        let pw = preview_width.clone();
        let ph = preview_height.clone();
        let pu = preview_upscale.clone();
        let pm = preview_mode.clone();
        let pa = preview_algo.clone();
        let pi = preview_index.clone();
        let pic = thumb_picture.clone();
        let dlbl = dims_label.clone();
        let npl = no_preview_label.clone();
        let bind_gen = resize_preview_gen.clone();
        std::rc::Rc::new(move || {
            let files = lf.borrow();
            if files.is_empty() {
                pic.set_paintable(gtk::gdk::Paintable::NONE);
                pic.set_visible(false);
                npl.set_visible(true);
                npl.set_label("Add images to see resize preview");
                dlbl.set_label("");
                return;
            }
            let idx = pi.get() % files.len();
            let Some(current) = files.get(idx) else { return };
            pic.set_visible(true);
            npl.set_visible(false);

            let tw = *pw.borrow();
            let th = *ph.borrow();
            if tw == 0 && th == 0 {
                pic.set_paintable(gtk::gdk::Paintable::NONE);
                npl.set_visible(true);
                npl.set_label("Set dimensions to preview");
                pic.set_visible(false);
                dlbl.set_label("");
                return;
            }

            let file_count = files.len();
            let (orig_w, orig_h) = get_image_dims(current.as_path());

            let actual_tw = if tw > 0 { tw } else { orig_w };
            let actual_th = if th > 0 {
                th
            } else if tw > 0 {
                let scale = tw as f64 / orig_w as f64;
                (orig_h as f64 * scale).round() as u32
            } else {
                orig_h
            };

            let allow_up = pu.get();
            let (render_tw, render_th) = if !allow_up {
                (actual_tw.min(orig_w), actual_th.min(orig_h))
            } else {
                (actual_tw, actual_th)
            };

            let scale_pct = if orig_w > 0 {
                (render_tw as f64 / orig_w as f64 * 100.0).round() as u32
            } else {
                100
            };

            let counter = if file_count > 1 {
                format!(" [{}/{}]", idx + 1, file_count)
            } else {
                String::new()
            };

            let clamp_note = if !allow_up && (actual_tw > orig_w || actual_th > orig_h) {
                " (clamped)"
            } else {
                ""
            };

            dlbl.set_label(&format!(
                "{} x {} -> {} x {} ({}%){}{}", orig_w, orig_h,
                render_tw, render_th, scale_pct, clamp_note, counter,
            ));

            let my_gen = bind_gen.get().wrapping_add(1);
            bind_gen.set(my_gen);
            let gen_check = bind_gen.clone();

            let path = current.clone();
            let pic = pic.clone();
            let algo = algo_index_to_filter(pa.get());
            let mode = pm.get();
            let (tx, rx) = std::sync::mpsc::channel::<Option<Vec<u8>>>();
            std::thread::spawn(move || {
                let result = (|| -> Option<Vec<u8>> {
                    let img = image::open(&path).ok()?;
                    let target_w = if render_tw > 0 { render_tw } else { img.width().max(1) };
                    let target_h = if render_th > 0 {
                        render_th
                    } else if img.width() > 0 {
                        let scale = target_w as f64 / img.width() as f64;
                        (img.height() as f64 * scale).round().max(1.0) as u32
                    } else {
                        target_w
                    };
                    let resized = if mode == 0 && render_th > 0 {
                        // Exact: stretch to exact dimensions
                        img.resize_exact(target_w.min(1024), target_h.min(1024), algo)
                    } else {
                        // Fit within box (or width-only): maintain aspect ratio
                        img.resize(target_w.min(1024), target_h.min(1024), algo)
                    };
                    let mut buf = Vec::new();
                    resized.write_to(
                        &mut std::io::Cursor::new(&mut buf),
                        image::ImageFormat::Png,
                    ).ok()?;
                    Some(buf)
                })();
                let _ = tx.send(result);
            });
            glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
                if gen_check.get() != my_gen {
                    return glib::ControlFlow::Break;
                }
                match rx.try_recv() {
                    Ok(Some(bytes)) => {
                        let gbytes = glib::Bytes::from(&bytes);
                        if let Ok(texture) = gtk::gdk::Texture::from_bytes(&gbytes) {
                            pic.set_paintable(Some(&texture));
                        }
                        glib::ControlFlow::Break
                    }
                    Ok(None) => glib::ControlFlow::Break,
                    Err(std::sync::mpsc::TryRecvError::Empty) => glib::ControlFlow::Continue,
                    Err(_) => glib::ControlFlow::Break,
                }
            });
        })
    };

    // === WIRE SIGNALS ===

    // Enable toggle
    {
        let jc = state.job_config.clone();
        enable_row.connect_active_notify(move |row| {
            jc.borrow_mut().resize_enabled = row.is_active();
        });
    }

    // Category selection - rebuild the size dropdown
    {
        let sr = size_row.clone();
        category_row.connect_selected_notify(move |row| {
            rebuild_size_model(&sr, row.selected());
        });
    }

    // Size selection - auto-fill dimensions and lock aspect ratio
    {
        let ws = width_spin.clone();
        let hs = height_spin.clone();
        let lb = lock_btn.clone();
        let jc = state.job_config.clone();
        let pw = preview_width.clone();
        let ph = preview_height.clone();
        let upd = updating.clone();
        let ip = is_pct.clone();
        let px = px_btn.clone();
        let pct = pct_btn.clone();
        let rt = render_thumb.clone();
        let cr = category_row.clone();
        size_row.connect_selected_notify(move |row| {
            let sel = row.selected() as usize;
            if sel == 0 {
                return; // "(select a size)" - don't change anything
            }
            let presets = presets_for_category(cr.selected());
            let Some(&(_, w, h)) = presets.get(sel - 1) else { return };

            // Switch to pixels if currently in percentage
            if ip.get() {
                ip.set(false);
                px.add_css_class("suggested-action");
                pct.remove_css_class("suggested-action");
                ws.set_range(0.0, 10000.0);
                ws.set_increments(1.0, 100.0);
                hs.set_range(0.0, 10000.0);
                hs.set_increments(1.0, 100.0);
            }

            upd.set(true);
            ws.set_value(w as f64);
            hs.set_value(h as f64);
            upd.set(false);

            // Lock aspect ratio
            if !lb.is_active() {
                lb.set_active(true);
            }

            let mut c = jc.borrow_mut();
            c.resize_width = w;
            c.resize_height = h;
            *pw.borrow_mut() = w;
            *ph.borrow_mut() = h;

            rt();
        });
    }

    // Lock button icon change
    {
        let lb = lock_btn.clone();
        lock_btn.connect_active_notify(move |btn| {
            if btn.is_active() {
                lb.set_icon_name("changes-prevent-symbolic");
                lb.set_tooltip_text(Some("Aspect ratio locked"));
                lb.update_property(&[
                    gtk::accessible::Property::Label("Aspect ratio locked - click to unlock"),
                ]);
            } else {
                lb.set_icon_name("changes-allow-symbolic");
                lb.set_tooltip_text(Some("Aspect ratio unlocked"));
                lb.update_property(&[
                    gtk::accessible::Property::Label("Aspect ratio unlocked - click to lock"),
                ]);
            }
        });
    }

    // Width spin with aspect lock
    {
        let jc = state.job_config.clone();
        let pw = preview_width.clone();
        let ph = preview_height.clone();
        let rt = render_thumb.clone();
        let lb = lock_btn.clone();
        let hs = height_spin.clone();
        let files = state.loaded_files.clone();
        let upd = updating.clone();
        let ip = is_pct.clone();
        let pr = size_row.clone();
        width_spin.connect_value_notify(move |spin| {
            if upd.get() { return; }
            let val = spin.value();

            let pixel_val = if ip.get() {
                let dims = get_first_image_dims(&files.borrow());
                (val / 100.0 * dims.0 as f64).round() as u32
            } else {
                val as u32
            };

            jc.borrow_mut().resize_width = pixel_val;
            *pw.borrow_mut() = pixel_val;

            // Reset preset to Custom when manually editing
            if pr.selected() != 0 {
                upd.set(true);
                pr.set_selected(0);
                upd.set(false);
            }

            if lb.is_active() && val > 0.0 {
                if ip.get() {
                    upd.set(true);
                    hs.set_value(val);
                    let dims = get_first_image_dims(&files.borrow());
                    let pixel_h = (val / 100.0 * dims.1 as f64).round() as u32;
                    jc.borrow_mut().resize_height = pixel_h;
                    *ph.borrow_mut() = pixel_h;
                    upd.set(false);
                } else {
                    let aspect = get_first_image_aspect(&files.borrow());
                    if aspect > 0.0 {
                        let new_h = (val / aspect).round() as u32;
                        upd.set(true);
                        hs.set_value(new_h as f64);
                        jc.borrow_mut().resize_height = new_h;
                        *ph.borrow_mut() = new_h;
                        upd.set(false);
                    }
                }
            }

            rt();
        });
    }

    // Height spin with aspect lock
    {
        let jc = state.job_config.clone();
        let pw = preview_width.clone();
        let ph = preview_height.clone();
        let rt = render_thumb.clone();
        let lb = lock_btn.clone();
        let ws = width_spin.clone();
        let files = state.loaded_files.clone();
        let upd = updating.clone();
        let ip = is_pct.clone();
        let pr = size_row.clone();
        height_spin.connect_value_notify(move |spin| {
            if upd.get() { return; }
            let val = spin.value();

            let pixel_val = if ip.get() {
                let dims = get_first_image_dims(&files.borrow());
                (val / 100.0 * dims.1 as f64).round() as u32
            } else {
                val as u32
            };

            jc.borrow_mut().resize_height = pixel_val;
            *ph.borrow_mut() = pixel_val;

            // Reset preset to Custom when manually editing
            if pr.selected() != 0 {
                upd.set(true);
                pr.set_selected(0);
                upd.set(false);
            }

            if lb.is_active() && val > 0.0 {
                if ip.get() {
                    upd.set(true);
                    ws.set_value(val);
                    let dims = get_first_image_dims(&files.borrow());
                    let pixel_w = (val / 100.0 * dims.0 as f64).round() as u32;
                    jc.borrow_mut().resize_width = pixel_w;
                    *pw.borrow_mut() = pixel_w;
                    upd.set(false);
                } else {
                    let aspect = get_first_image_aspect(&files.borrow());
                    if aspect > 0.0 {
                        let new_w = (val * aspect).round() as u32;
                        upd.set(true);
                        ws.set_value(new_w as f64);
                        jc.borrow_mut().resize_width = new_w;
                        *pw.borrow_mut() = new_w;
                        upd.set(false);
                    }
                }
            }

            rt();
        });
    }

    // Unit toggle: px button
    {
        let pct = pct_btn.clone();
        let px = px_btn.clone();
        let ip = is_pct.clone();
        let ws = width_spin.clone();
        let hs = height_spin.clone();
        let files = state.loaded_files.clone();
        let upd = updating.clone();
        let jc = state.job_config.clone();
        let pw = preview_width.clone();
        let ph = preview_height.clone();
        let rt = render_thumb.clone();
        px_btn.connect_clicked(move |_| {
            if !ip.get() { return; } // already pixels
            ip.set(false);
            px.add_css_class("suggested-action");
            pct.remove_css_class("suggested-action");
            px.update_property(&[
                gtk::accessible::Property::Label("Pixels - currently active"),
            ]);
            pct.update_property(&[
                gtk::accessible::Property::Label("Percentage"),
            ]);
            px.set_tooltip_text(Some("Use pixel dimensions (currently active)"));
            pct.set_tooltip_text(Some("Use percentage dimensions"));

            let dims = get_first_image_dims(&files.borrow());
            let pct_w = ws.value();
            let pct_h = hs.value();
            let pixel_w = (pct_w / 100.0 * dims.0 as f64).round();
            let pixel_h = (pct_h / 100.0 * dims.1 as f64).round();

            upd.set(true);
            ws.set_range(0.0, 10000.0);
            ws.set_increments(1.0, 100.0);
            hs.set_range(0.0, 10000.0);
            hs.set_increments(1.0, 100.0);
            ws.set_value(pixel_w);
            hs.set_value(pixel_h);
            upd.set(false);

            let mut c = jc.borrow_mut();
            c.resize_width = pixel_w as u32;
            c.resize_height = pixel_h as u32;
            *pw.borrow_mut() = pixel_w as u32;
            *ph.borrow_mut() = pixel_h as u32;

            rt();
        });
    }

    // Unit toggle: % button
    {
        let px = px_btn.clone();
        let pct = pct_btn.clone();
        let ip = is_pct.clone();
        let ws = width_spin.clone();
        let hs = height_spin.clone();
        let files = state.loaded_files.clone();
        let upd = updating.clone();
        let rt = render_thumb.clone();
        let jc = state.job_config.clone();
        let pw = preview_width.clone();
        let ph = preview_height.clone();
        pct_btn.connect_clicked(move |_| {
            if ip.get() { return; } // already percentage
            ip.set(true);
            pct.add_css_class("suggested-action");
            px.remove_css_class("suggested-action");
            pct.update_property(&[
                gtk::accessible::Property::Label("Percentage - currently active"),
            ]);
            px.update_property(&[
                gtk::accessible::Property::Label("Pixels"),
            ]);
            pct.set_tooltip_text(Some("Use percentage dimensions (currently active)"));
            px.set_tooltip_text(Some("Use pixel dimensions"));

            let dims = get_first_image_dims(&files.borrow());
            let cur_w = ws.value();
            let cur_h = hs.value();
            let pct_w = if dims.0 > 0 { (cur_w / dims.0 as f64 * 100.0).round() } else { 100.0 };
            let pct_h = if dims.1 > 0 { (cur_h / dims.1 as f64 * 100.0).round() } else { 100.0 };

            upd.set(true);
            ws.set_range(0.0, 1000.0);
            ws.set_increments(1.0, 10.0);
            hs.set_range(0.0, 1000.0);
            hs.set_increments(1.0, 10.0);
            ws.set_value(pct_w);
            hs.set_value(pct_h);
            upd.set(false);

            // Update job_config with the pixel values
            let pixel_w = (pct_w / 100.0 * dims.0 as f64).round() as u32;
            let pixel_h = (pct_h / 100.0 * dims.1 as f64).round() as u32;
            let mut c = jc.borrow_mut();
            c.resize_width = pixel_w;
            c.resize_height = pixel_h;
            *pw.borrow_mut() = pixel_w;
            *ph.borrow_mut() = pixel_h;

            rt();
        });
    }

    // Mode toggle - update labels and job_config
    {
        let ws = width_spin.clone();
        let hs = height_spin.clone();
        let jc = state.job_config.clone();
        let rt = render_thumb.clone();
        let pmode = preview_mode.clone();
        mode_row.connect_selected_notify(move |row| {
            jc.borrow_mut().resize_mode = row.selected();
            pmode.set(row.selected());
            if row.selected() == 1 {
                ws.set_tooltip_text(Some("Maximum width"));
                hs.set_tooltip_text(Some("Maximum height"));
            } else {
                ws.set_tooltip_text(Some("Width"));
                hs.set_tooltip_text(Some("Height"));
            }
            rt();
        });
    }

    // Upscale toggle
    {
        let jc = state.job_config.clone();
        let pu = preview_upscale.clone();
        let rt = render_thumb.clone();
        upscale_row.connect_active_notify(move |row| {
            jc.borrow_mut().allow_upscale = row.is_active();
            pu.set(row.is_active());
            rt();
        });
    }

    // Algorithm
    {
        let jc = state.job_config.clone();
        let pa = preview_algo.clone();
        let rt = render_thumb.clone();
        algorithm_row.connect_selected_notify(move |row| {
            jc.borrow_mut().resize_algorithm = row.selected();
            pa.set(row.selected());
            rt();
        });
    }

    // DPI
    {
        let jc = state.job_config.clone();
        dpi_row.connect_value_notify(move |row| {
            jc.borrow_mut().output_dpi = row.value() as u32;
        });
    }

    // Click preview to cycle images
    {
        let pi = preview_index.clone();
        let rt = render_thumb.clone();
        let lf = loaded_files.clone();
        let click = gtk::GestureClick::new();
        click.connect_released(move |gesture, _, _, _| {
            let count = lf.borrow().len();
            if count > 1 {
                pi.set((pi.get() + 1) % count);
                rt();
            }
            gesture.set_state(gtk::EventSequenceState::Claimed);
        });
        thumb_picture.set_can_target(true);
        thumb_picture.set_focusable(true);
        thumb_picture.add_controller(click);
        thumb_picture.set_cursor_from_name(Some("pointer"));
    }

    // Keyboard support for preview cycling (Space/Enter)
    {
        let pi = preview_index.clone();
        let rt = render_thumb.clone();
        let lf = loaded_files.clone();
        let key = gtk::EventControllerKey::new();
        key.connect_key_pressed(move |_, keyval, _, _| {
            if keyval == gtk::gdk::Key::space || keyval == gtk::gdk::Key::Return {
                let count = lf.borrow().len();
                if count > 1 {
                    pi.set((pi.get() + 1) % count);
                    rt();
                }
                return glib::Propagation::Stop;
            }
            glib::Propagation::Proceed
        });
        thumb_picture.add_controller(key);
    }

    // Initial render
    {
        let rt = render_thumb.clone();
        glib::idle_add_local_once(move || rt());
    }

    let page = adw::NavigationPage::builder()
        .title("Resize")
        .tag("step-resize")
        .child(&outer)
        .build();

    // Sync enable toggle and re-render on page map
    {
        let rt = render_thumb.clone();
        let jc = state.job_config.clone();
        let er = enable_row.clone();
        page.connect_map(move |_| {
            let enabled = jc.borrow().resize_enabled;
            er.set_active(enabled);
            rt();
        });
    }

    page
}
