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

use crate::app::AppState;

pub fn build_adjustments_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 Adjustments")
        .subtitle("Rotate, flip, brightness, contrast, effects")
        .active(cfg.adjustments_enabled)
        .tooltip_text("Toggle image adjustments on or off")
        .build();
    enable_group.add(&enable_row);
    outer.append(&enable_group);

    // === LEFT SIDE: Preview ===

    let preview_picture = gtk::Picture::builder()
        .content_fit(gtk::ContentFit::Contain)
        .hexpand(true)
        .vexpand(true)
        .build();
    preview_picture.set_can_target(true);
    preview_picture.set_focusable(true);
    preview_picture.update_property(&[
        gtk::accessible::Property::Label("Adjustments preview - press Space to cycle images"),
    ]);

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

    let preview_frame = gtk::Frame::builder()
        .hexpand(true)
        .vexpand(true)
        .build();
    preview_frame.set_child(Some(&preview_picture));

    let preview_box = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(4)
        .hexpand(true)
        .vexpand(true)
        .build();
    preview_box.append(&preview_frame);
    preview_box.append(&info_label);

    // === RIGHT SIDE: Controls (scrollable) ===

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

    // --- Orientation group ---
    let orient_group = adw::PreferencesGroup::builder()
        .title("Orientation")
        .build();

    let rotate_row = adw::ComboRow::builder()
        .title("Rotate")
        .subtitle("Rotation applied to all images")
        .use_subtitle(true)
        .tooltip_text("Rotate all images by a fixed angle or auto-orient from EXIF")
        .build();
    rotate_row.set_model(Some(&gtk::StringList::new(&[
        "None",
        "90 clockwise",
        "180",
        "270 clockwise",
        "Auto-orient (from EXIF)",
    ])));
    rotate_row.set_list_factory(Some(&super::full_text_list_factory()));
    rotate_row.set_selected(cfg.rotation);

    let flip_row = adw::ComboRow::builder()
        .title("Flip")
        .subtitle("Mirror the image")
        .use_subtitle(true)
        .tooltip_text("Mirror images horizontally or vertically")
        .build();
    flip_row.set_model(Some(&gtk::StringList::new(&["None", "Horizontal", "Vertical"])));
    flip_row.set_list_factory(Some(&super::full_text_list_factory()));
    flip_row.set_selected(cfg.flip);

    orient_group.add(&rotate_row);
    orient_group.add(&flip_row);
    controls.append(&orient_group);

    // --- Color adjustments group ---
    let color_group = adw::PreferencesGroup::builder()
        .title("Color")
        .build();

    // Helper to build a slider row with reset button
    let make_slider_row = |title: &str, label_text: &str, value: i32| -> (adw::ActionRow, gtk::Scale, gtk::Button) {
        let row = adw::ActionRow::builder()
            .title(title)
            .subtitle(&format!("{}", value))
            .build();
        let scale = gtk::Scale::with_range(gtk::Orientation::Horizontal, -100.0, 100.0, 1.0);
        scale.set_value(value as f64);
        scale.set_draw_value(false);
        scale.set_hexpand(false);
        scale.set_valign(gtk::Align::Center);
        scale.set_width_request(180);
        scale.set_tooltip_text(Some(label_text));
        scale.update_property(&[
            gtk::accessible::Property::Label(label_text),
        ]);

        let reset_btn = gtk::Button::builder()
            .icon_name("edit-undo-symbolic")
            .valign(gtk::Align::Center)
            .tooltip_text("Reset to 0")
            .has_frame(false)
            .build();
        reset_btn.update_property(&[
            gtk::accessible::Property::Label(&format!("Reset {} to 0", title)),
        ]);
        reset_btn.set_sensitive(value != 0);

        row.add_suffix(&scale);
        row.add_suffix(&reset_btn);
        (row, scale, reset_btn)
    };

    let (brightness_row, brightness_scale, brightness_reset) =
        make_slider_row("Brightness", "Brightness adjustment, -100 to +100", cfg.brightness);
    let (contrast_row, contrast_scale, contrast_reset) =
        make_slider_row("Contrast", "Contrast adjustment, -100 to +100", cfg.contrast);
    let (saturation_row, saturation_scale, saturation_reset) =
        make_slider_row("Saturation", "Saturation adjustment, -100 to +100", cfg.saturation);

    color_group.add(&brightness_row);
    color_group.add(&contrast_row);
    color_group.add(&saturation_row);
    controls.append(&color_group);

    // --- Effects group (compact toggle buttons) ---
    let effects_group = adw::PreferencesGroup::builder()
        .title("Effects")
        .build();

    let effects_box = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(8)
        .margin_top(4)
        .margin_bottom(4)
        .halign(gtk::Align::Start)
        .build();

    let grayscale_btn = gtk::ToggleButton::builder()
        .label("Grayscale")
        .active(cfg.grayscale)
        .tooltip_text("Convert to grayscale")
        .build();
    grayscale_btn.update_property(&[
        gtk::accessible::Property::Label("Grayscale effect toggle"),
    ]);

    let sepia_btn = gtk::ToggleButton::builder()
        .label("Sepia")
        .active(cfg.sepia)
        .tooltip_text("Apply sepia tone")
        .build();
    sepia_btn.update_property(&[
        gtk::accessible::Property::Label("Sepia effect toggle"),
    ]);

    let sharpen_btn = gtk::ToggleButton::builder()
        .label("Sharpen")
        .active(cfg.sharpen)
        .tooltip_text("Sharpen the image")
        .build();
    sharpen_btn.update_property(&[
        gtk::accessible::Property::Label("Sharpen effect toggle"),
    ]);

    effects_box.append(&grayscale_btn);
    effects_box.append(&sepia_btn);
    effects_box.append(&sharpen_btn);
    effects_group.add(&effects_box);
    controls.append(&effects_group);

    // --- Crop & Canvas group ---
    let crop_group = adw::PreferencesGroup::builder()
        .title("Crop and Canvas")
        .build();

    let crop_row = adw::ComboRow::builder()
        .title("Crop to Aspect Ratio")
        .subtitle("Crop from center to a specific ratio")
        .use_subtitle(true)
        .tooltip_text("Crop from center to a specific aspect ratio")
        .build();
    crop_row.set_model(Some(&gtk::StringList::new(&[
        "None",
        "1:1 (Square)",
        "4:3",
        "3:2",
        "16:9",
        "9:16 (Portrait)",
        "3:4 (Portrait)",
        "2:3 (Portrait)",
    ])));
    crop_row.set_list_factory(Some(&super::full_text_list_factory()));
    crop_row.set_selected(cfg.crop_aspect_ratio);

    let trim_row = adw::SwitchRow::builder()
        .title("Trim Whitespace")
        .subtitle("Remove uniform borders around the image")
        .active(cfg.trim_whitespace)
        .tooltip_text("Detect and remove uniform borders around the image")
        .build();

    let padding_row = adw::SpinRow::builder()
        .title("Canvas Padding")
        .subtitle("Add uniform padding (pixels)")
        .adjustment(&gtk::Adjustment::new(cfg.canvas_padding as f64, 0.0, 500.0, 1.0, 10.0, 0.0))
        .tooltip_text("Add a white border around each image in pixels")
        .build();

    crop_group.add(&crop_row);
    crop_group.add(&trim_row);
    crop_group.add(&padding_row);
    controls.append(&crop_group);

    // Scrollable controls
    let controls_scrolled = gtk::ScrolledWindow::builder()
        .hscrollbar_policy(gtk::PolicyType::Never)
        .vexpand(true)
        .width_request(360)
        .child(&controls)
        .build();

    // === Main layout: 60/40 side-by-side ===
    let main_box = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(12)
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(12)
        .margin_end(12)
        .vexpand(true)
        .build();

    preview_box.set_width_request(400);
    main_box.append(&preview_box);
    main_box.append(&controls_scrolled);
    outer.append(&main_box);

    // Preview state
    let preview_index: Rc<Cell<usize>> = Rc::new(Cell::new(0));

    drop(cfg);

    // === Preview update closure ===
    let preview_gen: Rc<Cell<u32>> = Rc::new(Cell::new(0));
    let update_preview = {
        let files = state.loaded_files.clone();
        let jc = state.job_config.clone();
        let pic = preview_picture.clone();
        let info = info_label.clone();
        let pidx = preview_index.clone();
        let bind_gen = preview_gen.clone();

        Rc::new(move || {
            let loaded = files.borrow();
            if loaded.is_empty() {
                info.set_label("No images loaded");
                pic.set_paintable(gtk::gdk::Paintable::NONE);
                return;
            }

            let idx = pidx.get().min(loaded.len().saturating_sub(1));
            pidx.set(idx);
            let path = loaded[idx].clone();
            let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("image");
            info.set_label(&format!("[{}/{}] {}", idx + 1, loaded.len(), name));

            let cfg = jc.borrow();
            let rotation = cfg.rotation;
            let flip = cfg.flip;
            let brightness = cfg.brightness;
            let contrast = cfg.contrast;
            let saturation = cfg.saturation;
            let grayscale = cfg.grayscale;
            let sepia = cfg.sepia;
            let sharpen = cfg.sharpen;
            let crop_aspect = cfg.crop_aspect_ratio;
            let trim_ws = cfg.trim_whitespace;
            let padding = cfg.canvas_padding;
            drop(cfg);

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

            let pic = pic.clone();
            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 img = img.resize(1024, 1024, image::imageops::FilterType::Triangle);

                    // Rotation
                    let mut img = match rotation {
                        1 => img.rotate90(),
                        2 => img.rotate180(),
                        3 => img.rotate270(),
                        // 4 = auto-orient from EXIF - skip in preview (would need exif crate)
                        _ => img,
                    };

                    // Flip
                    match flip {
                        1 => img = img.fliph(),
                        2 => img = img.flipv(),
                        _ => {}
                    }

                    // Crop to aspect ratio
                    if crop_aspect > 0 {
                        let (target_w, target_h): (f64, f64) = match crop_aspect {
                            1 => (1.0, 1.0),   // 1:1
                            2 => (4.0, 3.0),   // 4:3
                            3 => (3.0, 2.0),   // 3:2
                            4 => (16.0, 9.0),  // 16:9
                            5 => (9.0, 16.0),  // 9:16
                            6 => (3.0, 4.0),   // 3:4
                            7 => (2.0, 3.0),   // 2:3
                            _ => (1.0, 1.0),
                        };
                        let iw = img.width() as f64;
                        let ih = img.height() as f64;
                        let target_ratio = target_w / target_h;
                        let current_ratio = iw / ih;
                        let (crop_w, crop_h) = if current_ratio > target_ratio {
                            ((ih * target_ratio) as u32, img.height())
                        } else {
                            (img.width(), (iw / target_ratio) as u32)
                        };
                        let cx = (img.width().saturating_sub(crop_w)) / 2;
                        let cy = (img.height().saturating_sub(crop_h)) / 2;
                        img = img.crop_imm(cx, cy, crop_w, crop_h);
                    }

                    // Trim whitespace (matches core algorithm with threshold)
                    if trim_ws {
                        let rgba = img.to_rgba8();
                        let (w, h) = (rgba.width(), rgba.height());
                        if w > 2 && h > 2 {
                            let bg = *rgba.get_pixel(0, 0);
                            let threshold = 30u32;
                            let is_bg = |p: &image::Rgba<u8>| -> bool {
                                let dr = (p[0] as i32 - bg[0] as i32).unsigned_abs();
                                let dg = (p[1] as i32 - bg[1] as i32).unsigned_abs();
                                let db = (p[2] as i32 - bg[2] as i32).unsigned_abs();
                                dr + dg + db < threshold
                            };
                            let mut top = 0u32;
                            let mut bottom = h - 1;
                            let mut left = 0u32;
                            let mut right = w - 1;
                            'top: for y in 0..h {
                                for x in 0..w {
                                    if !is_bg(rgba.get_pixel(x, y)) { top = y; break 'top; }
                                }
                            }
                            'bottom: for y in (0..h).rev() {
                                for x in 0..w {
                                    if !is_bg(rgba.get_pixel(x, y)) { bottom = y; break 'bottom; }
                                }
                            }
                            'left: for x in 0..w {
                                for y in top..=bottom {
                                    if !is_bg(rgba.get_pixel(x, y)) { left = x; break 'left; }
                                }
                            }
                            'right: for x in (0..w).rev() {
                                for y in top..=bottom {
                                    if !is_bg(rgba.get_pixel(x, y)) { right = x; break 'right; }
                                }
                            }
                            let cw = right.saturating_sub(left).saturating_add(1);
                            let ch = bottom.saturating_sub(top).saturating_add(1);
                            if cw > 0 && ch > 0 && (cw < w || ch < h) {
                                img = img.crop_imm(left, top, cw, ch);
                            }
                        }
                    }

                    // Brightness
                    if brightness != 0 {
                        img = img.brighten(brightness);
                    }
                    // Contrast
                    if contrast != 0 {
                        img = img.adjust_contrast(contrast as f32);
                    }
                    // Saturation
                    if saturation != 0 {
                        let sat = saturation.clamp(-100, 100);
                        let factor = 1.0 + (sat as f64 / 100.0);
                        let mut rgba = img.into_rgba8();
                        for pixel in rgba.pixels_mut() {
                            let r = pixel[0] as f64;
                            let g = pixel[1] as f64;
                            let b = pixel[2] as f64;
                            let gray = 0.2126 * r + 0.7152 * g + 0.0722 * b;
                            pixel[0] = (gray + (r - gray) * factor).clamp(0.0, 255.0) as u8;
                            pixel[1] = (gray + (g - gray) * factor).clamp(0.0, 255.0) as u8;
                            pixel[2] = (gray + (b - gray) * factor).clamp(0.0, 255.0) as u8;
                        }
                        img = image::DynamicImage::ImageRgba8(rgba);
                    }
                    // Sharpen
                    if sharpen {
                        img = img.unsharpen(1.0, 5);
                    }
                    // Grayscale
                    if grayscale {
                        img = image::DynamicImage::ImageLuma8(img.to_luma8());
                    }
                    // Sepia
                    if sepia {
                        let mut rgba = img.into_rgba8();
                        for pixel in rgba.pixels_mut() {
                            let r = pixel[0] as f64;
                            let g = pixel[1] as f64;
                            let b = pixel[2] as f64;
                            pixel[0] = (0.393 * r + 0.769 * g + 0.189 * b).clamp(0.0, 255.0) as u8;
                            pixel[1] = (0.349 * r + 0.686 * g + 0.168 * b).clamp(0.0, 255.0) as u8;
                            pixel[2] = (0.272 * r + 0.534 * g + 0.131 * b).clamp(0.0, 255.0) as u8;
                        }
                        img = image::DynamicImage::ImageRgba8(rgba);
                    }

                    // Canvas padding
                    if padding > 0 {
                        let pad = padding.min(200); // cap for preview
                        let new_w = img.width().saturating_add(pad.saturating_mul(2));
                        let new_h = img.height().saturating_add(pad.saturating_mul(2));
                        let mut canvas = image::RgbaImage::from_pixel(
                            new_w, new_h,
                            image::Rgba([255, 255, 255, 255]),
                        );
                        image::imageops::overlay(&mut canvas, &img.to_rgba8(), pad as i64, pad as i64);
                        img = image::DynamicImage::ImageRgba8(canvas);
                    }

                    let mut buf = Vec::new();
                    img.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,
                }
            });
        })
    };

    // Click-to-cycle on preview
    {
        let click = gtk::GestureClick::new();
        let pidx = preview_index.clone();
        let files = state.loaded_files.clone();
        let up = update_preview.clone();
        click.connect_released(move |_, _, _, _| {
            let loaded = files.borrow();
            if loaded.len() > 1 {
                let next = (pidx.get() + 1) % loaded.len();
                pidx.set(next);
                up();
            }
        });
        preview_picture.add_controller(click);
    }

    // Keyboard support for preview cycling (Space/Enter)
    {
        let key = gtk::EventControllerKey::new();
        let pidx = preview_index.clone();
        let files = state.loaded_files.clone();
        let up = update_preview.clone();
        key.connect_key_pressed(move |_, keyval, _, _| {
            if keyval == gtk::gdk::Key::space || keyval == gtk::gdk::Key::Return {
                let loaded = files.borrow();
                if loaded.len() > 1 {
                    let next = (pidx.get() + 1) % loaded.len();
                    pidx.set(next);
                    up();
                }
                return glib::Propagation::Stop;
            }
            glib::Propagation::Proceed
        });
        preview_picture.add_controller(key);
    }

    // === Wire signals ===

    {
        let jc = state.job_config.clone();
        enable_row.connect_active_notify(move |row| {
            jc.borrow_mut().adjustments_enabled = row.is_active();
        });
    }
    {
        let jc = state.job_config.clone();
        let up = update_preview.clone();
        rotate_row.connect_selected_notify(move |row| {
            jc.borrow_mut().rotation = row.selected();
            up();
        });
    }
    {
        let jc = state.job_config.clone();
        let up = update_preview.clone();
        flip_row.connect_selected_notify(move |row| {
            jc.borrow_mut().flip = row.selected();
            up();
        });
    }

    // Per-slider debounce counters (separate to avoid cross-slider cancellation)
    let brightness_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));
    let contrast_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));
    let saturation_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));
    let padding_debounce: Rc<Cell<u32>> = Rc::new(Cell::new(0));

    // Brightness
    {
        let jc = state.job_config.clone();
        let row = brightness_row.clone();
        let up = update_preview.clone();
        let rst = brightness_reset.clone();
        let did = brightness_debounce.clone();
        brightness_scale.connect_value_changed(move |scale| {
            let val = scale.value().round() as i32;
            jc.borrow_mut().brightness = val;
            row.set_subtitle(&format!("{}", val));
            rst.set_sensitive(val != 0);
            let up = up.clone();
            let did = did.clone();
            let id = did.get().wrapping_add(1);
            did.set(id);
            glib::timeout_add_local_once(std::time::Duration::from_millis(150), move || {
                if did.get() == id {
                    up();
                }
            });
        });
    }
    {
        let scale = brightness_scale.clone();
        brightness_reset.connect_clicked(move |_| {
            scale.set_value(0.0);
        });
    }

    // Contrast
    {
        let jc = state.job_config.clone();
        let row = contrast_row.clone();
        let up = update_preview.clone();
        let rst = contrast_reset.clone();
        let did = contrast_debounce.clone();
        contrast_scale.connect_value_changed(move |scale| {
            let val = scale.value().round() as i32;
            jc.borrow_mut().contrast = val;
            row.set_subtitle(&format!("{}", val));
            rst.set_sensitive(val != 0);
            let up = up.clone();
            let did = did.clone();
            let id = did.get().wrapping_add(1);
            did.set(id);
            glib::timeout_add_local_once(std::time::Duration::from_millis(150), move || {
                if did.get() == id {
                    up();
                }
            });
        });
    }
    {
        let scale = contrast_scale.clone();
        contrast_reset.connect_clicked(move |_| {
            scale.set_value(0.0);
        });
    }

    // Saturation
    {
        let jc = state.job_config.clone();
        let row = saturation_row.clone();
        let up = update_preview.clone();
        let rst = saturation_reset.clone();
        let did = saturation_debounce.clone();
        saturation_scale.connect_value_changed(move |scale| {
            let val = scale.value().round() as i32;
            jc.borrow_mut().saturation = val;
            row.set_subtitle(&format!("{}", val));
            rst.set_sensitive(val != 0);
            let up = up.clone();
            let did = did.clone();
            let id = did.get().wrapping_add(1);
            did.set(id);
            glib::timeout_add_local_once(std::time::Duration::from_millis(150), move || {
                if did.get() == id {
                    up();
                }
            });
        });
    }
    {
        let scale = saturation_scale.clone();
        saturation_reset.connect_clicked(move |_| {
            scale.set_value(0.0);
        });
    }

    // Effects toggle buttons
    {
        let jc = state.job_config.clone();
        let up = update_preview.clone();
        grayscale_btn.connect_toggled(move |btn| {
            jc.borrow_mut().grayscale = btn.is_active();
            up();
        });
    }
    {
        let jc = state.job_config.clone();
        let up = update_preview.clone();
        sepia_btn.connect_toggled(move |btn| {
            jc.borrow_mut().sepia = btn.is_active();
            up();
        });
    }
    {
        let jc = state.job_config.clone();
        let up = update_preview.clone();
        sharpen_btn.connect_toggled(move |btn| {
            jc.borrow_mut().sharpen = btn.is_active();
            up();
        });
    }

    // Crop & Canvas
    {
        let jc = state.job_config.clone();
        let up = update_preview.clone();
        crop_row.connect_selected_notify(move |row| {
            jc.borrow_mut().crop_aspect_ratio = row.selected();
            up();
        });
    }
    {
        let jc = state.job_config.clone();
        let up = update_preview.clone();
        trim_row.connect_active_notify(move |row| {
            jc.borrow_mut().trim_whitespace = row.is_active();
            up();
        });
    }
    {
        let jc = state.job_config.clone();
        let up = update_preview.clone();
        let did = padding_debounce.clone();
        padding_row.connect_value_notify(move |row| {
            jc.borrow_mut().canvas_padding = row.value() as u32;
            let up = up.clone();
            let did = did.clone();
            let id = did.get().wrapping_add(1);
            did.set(id);
            glib::timeout_add_local_once(std::time::Duration::from_millis(150), move || {
                if did.get() == id {
                    up();
                }
            });
        });
    }

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

    // Sync enable toggle, refresh preview and sensitivity when navigating to this page
    {
        let up = update_preview.clone();
        let lf = state.loaded_files.clone();
        let ctrl = controls.clone();
        let jc = state.job_config.clone();
        let er = enable_row.clone();
        page.connect_map(move |_| {
            let enabled = jc.borrow().adjustments_enabled;
            er.set_active(enabled);
            ctrl.set_sensitive(!lf.borrow().is_empty());
            up();
        });
    }

    page
}
