use adw::prelude::*;
use std::cell::RefCell;
use std::collections::HashSet;
use std::path::PathBuf;
use std::rc::Rc;

use crate::app::AppState;
use pixstrip_core::types::ImageFormat;

/// All format labels shown in the card grid.
/// Keep Original + 7 common formats = 8 cards.
const CARD_FORMATS: &[(&str, &str, &str, Option<ImageFormat>)] = &[
    ("Keep Original", "No conversion", "edit-copy-symbolic", None),
    ("JPEG", "Universal photo format\nLossy, small files", "image-x-generic-symbolic", Some(ImageFormat::Jpeg)),
    ("PNG", "Lossless, transparency\nGraphics, screenshots", "image-x-generic-symbolic", Some(ImageFormat::Png)),
    ("WebP", "Modern web format\nExcellent compression", "web-browser-symbolic", Some(ImageFormat::WebP)),
    ("AVIF", "Next-gen format\nBest compression", "emblem-favorite-symbolic", Some(ImageFormat::Avif)),
    ("GIF", "Animations, 256 colors\nSimple graphics", "media-playback-start-symbolic", Some(ImageFormat::Gif)),
    ("TIFF", "Archival, lossless\nVery large files", "drive-harddisk-symbolic", Some(ImageFormat::Tiff)),
    ("BMP", "Uncompressed bitmap\nLegacy format", "image-x-generic-symbolic", Some(ImageFormat::Bmp)),
];

/// Extra formats available only in the "Other Formats" dropdown: (short name, dropdown label)
const DROPDOWN_ONLY_FORMATS: &[(&str, &str)] = &[
    ("ICO", "ICO - Favicon and icon format, max 256x256 pixels"),
    ("HDR", "HDR - Radiance HDR, high dynamic range imaging"),
    ("PNM/PPM", "PNM/PPM - Portable anymap, simple uncompressed"),
    ("TGA", "TGA - Targa, legacy game/video format"),
    ("HEIC/HEIF", "HEIC/HEIF - Apple's photo format, excellent compression"),
    ("JXL (JPEG XL)", "JXL (JPEG XL) - Next-gen JPEG successor, lossless and lossy"),
    ("QOI", "QOI - Quite OK Image, fast lossless compression"),
    ("EXR", "EXR - OpenEXR, VFX/film industry HDR standard"),
    ("Farbfeld", "Farbfeld - Minimal 16-bit lossless, suckless format"),
];

/// Ordered list of format labels for the per-format mapping dropdowns.
/// Index 0 = "Same as above", 1 = "Keep Original", then common formats with descriptions.
const MAPPING_CHOICES: &[&str] = &[
    "Same as above - use the global output format",
    "Keep Original - no conversion for this type",
    "JPEG - universal photo format, lossy compression",
    "PNG - lossless with transparency, larger files",
    "WebP - modern web format, excellent compression",
    "AVIF - next-gen, best compression, slower encode",
    "GIF - 256 colors, animation support",
    "TIFF - archival lossless, very large files",
    "BMP - uncompressed bitmap, legacy format",
    "ICO - favicon/icon format, max 256x256",
    "HDR - Radiance high dynamic range",
    "PNM/PPM - portable anymap, uncompressed",
    "TGA - Targa, legacy game/video format",
    "HEIC/HEIF - Apple photo format, great compression",
    "JXL (JPEG XL) - next-gen JPEG successor",
    "QOI - fast lossless compression",
    "EXR - OpenEXR, VFX/film HDR standard",
    "Farbfeld - minimal 16-bit lossless",
];

pub fn build_convert_page(state: &AppState) -> adw::NavigationPage {
    let scrolled = gtk::ScrolledWindow::builder()
        .hscrollbar_policy(gtk::PolicyType::Never)
        .vexpand(true)
        .build();

    let content = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(12)
        .margin_top(12)
        .margin_bottom(12)
        .margin_start(24)
        .margin_end(24)
        .build();

    let cfg = state.job_config.borrow();

    // Enable toggle
    let enable_row = adw::SwitchRow::builder()
        .title("Enable Format Conversion")
        .subtitle("Convert images to a different format")
        .active(cfg.convert_enabled)
        .tooltip_text("Toggle format conversion on or off")
        .build();

    let enable_group = adw::PreferencesGroup::new();
    enable_group.add(&enable_row);
    content.append(&enable_group);

    // --- Visual format cards grid ---
    let cards_group = adw::PreferencesGroup::builder()
        .title("Output Format")
        .description("Choose the format all images will be converted to")
        .build();

    let flow = gtk::FlowBox::builder()
        .selection_mode(gtk::SelectionMode::Single)
        .max_children_per_line(4)
        .min_children_per_line(2)
        .row_spacing(8)
        .column_spacing(8)
        .homogeneous(true)
        .margin_top(4)
        .margin_bottom(4)
        .build();

    flow.update_property(&[
        gtk::accessible::Property::Label("Output format selection grid"),
    ]);

    let initial_format = cfg.convert_format;

    for (name, desc, icon_name, _fmt) in CARD_FORMATS {
        let card = gtk::Box::builder()
            .orientation(gtk::Orientation::Vertical)
            .spacing(4)
            .hexpand(true)
            .vexpand(false)
            .build();
        card.add_css_class("card");
        card.set_size_request(130, 110);
        card.update_property(&[
            gtk::accessible::Property::Label(&format!("{}: {}", name, desc.replace('\n', ", "))),
        ]);

        let inner = gtk::Box::builder()
            .orientation(gtk::Orientation::Vertical)
            .spacing(4)
            .margin_top(6)
            .margin_bottom(6)
            .margin_start(4)
            .margin_end(4)
            .halign(gtk::Align::Center)
            .valign(gtk::Align::Center)
            .vexpand(true)
            .build();

        let icon = gtk::Image::builder()
            .icon_name(*icon_name)
            .pixel_size(28)
            .build();
        icon.set_accessible_role(gtk::AccessibleRole::Presentation);

        let name_label = gtk::Label::builder()
            .label(*name)
            .css_classes(["heading"])
            .build();

        let desc_label = gtk::Label::builder()
            .label(*desc)
            .css_classes(["caption", "dim-label"])
            .wrap(true)
            .justify(gtk::Justification::Center)
            .max_width_chars(18)
            .build();

        inner.append(&icon);
        inner.append(&name_label);
        inner.append(&desc_label);
        card.append(&inner);
        flow.append(&card);
    }

    // Select the initial card (only if format matches a card)
    let initial_idx = card_index_for_format(initial_format);
    if let Some(idx) = initial_idx
        && let Some(child) = flow.child_at_index(idx)
    {
        flow.select_child(&child);
    }

    // Wrap FlowBox in a Clamp so cards don't stretch too wide when maximized
    let clamp = adw::Clamp::builder()
        .maximum_size(800)
        .child(&flow)
        .build();

    // Format info label (updates based on selection)
    let info_label = gtk::Label::builder()
        .label(format_info(cfg.convert_format))
        .css_classes(["dim-label"])
        .halign(gtk::Align::Start)
        .wrap(true)
        .margin_top(8)
        .margin_bottom(4)
        .margin_start(4)
        .build();

    cards_group.add(&clamp);
    cards_group.add(&info_label);

    // --- "Other Formats" dropdown for less common formats ---
    let other_group = adw::PreferencesGroup::builder()
        .title("Other Formats")
        .description("Less common formats not shown in the card grid")
        .build();

    let other_combo = adw::ComboRow::builder()
        .title("Select format")
        .subtitle("Choosing a format here deselects the card grid")
        .use_subtitle(true)
        .build();

    // Build model: first entry is "(none)" for no selection, then extra formats with descriptions
    let mut other_items: Vec<&str> = vec!["(none)"];
    for (_short, label) in DROPDOWN_ONLY_FORMATS {
        other_items.push(label);
    }
    other_combo.set_model(Some(&gtk::StringList::new(&other_items)));
    other_combo.set_list_factory(Some(&super::full_text_list_factory()));
    other_combo.set_selected(0);

    other_group.add(&other_combo);
    content.append(&cards_group);
    content.append(&other_group);

    // --- JPEG encoding options (only visible when JPEG or Keep Original is selected) ---
    let jpeg_group = adw::PreferencesGroup::builder()
        .title("JPEG Encoding")
        .build();

    let progressive_row = adw::SwitchRow::builder()
        .title("Progressive JPEG")
        .subtitle("Loads gradually in browsers, slightly larger file size")
        .active(cfg.progressive_jpeg)
        .tooltip_text("Creates JPEG files that load gradually in web browsers")
        .build();

    jpeg_group.add(&progressive_row);

    // Show only for JPEG (card index 1) or Keep Original (card index 0)
    let shows_jpeg = matches!(initial_format, None | Some(ImageFormat::Jpeg));
    jpeg_group.set_visible(shows_jpeg);

    content.append(&jpeg_group);

    // --- Format mapping group (dynamic, rebuilt on page map) ---
    let mapping_group = adw::PreferencesGroup::builder()
        .title("Format Mapping")
        .description("Override the output format for specific input types")
        .build();

    // Container for dynamically added mapping rows
    let mapping_list = gtk::ListBox::builder()
        .selection_mode(gtk::SelectionMode::None)
        .css_classes(["boxed-list"])
        .build();

    mapping_group.add(&mapping_list);
    content.append(&mapping_group);

    drop(cfg);

    // --- Wire signals ---

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

    // Card grid selection
    {
        let jc = state.job_config.clone();
        let label = info_label.clone();
        let combo = other_combo.clone();
        let jg = jpeg_group.clone();
        flow.connect_child_activated(move |_flow, child| {
            let idx = child.index() as usize;
            let mut c = jc.borrow_mut();
            c.convert_format = format_for_card_index(idx);
            label.set_label(&format_info(c.convert_format));
            // Deselect the "Other Formats" dropdown when a card is picked
            combo.set_selected(0);
            // Progressive JPEG only relevant for JPEG or Keep Original
            jg.set_visible(idx == 0 || idx == 1);
        });
    }

    // "Other Formats" dropdown selection
    {
        let jc = state.job_config.clone();
        let label = info_label;
        let fl = flow.clone();
        let jg = jpeg_group.clone();
        other_combo.connect_selected_notify(move |row| {
            let selected = row.selected();
            if selected == 0 {
                // "(none)" selected - do nothing, cards take priority
                return;
            }
            // Deselect all cards
            fl.unselect_all();
            // Hide progressive JPEG - none of the "other" formats are JPEG
            jg.set_visible(false);
            // These formats are not in the ImageFormat enum,
            // so set convert_format to None and show a note
            let mut c = jc.borrow_mut();
            c.convert_format = None;
            let name = DROPDOWN_ONLY_FORMATS
                .get((selected - 1) as usize)
                .map(|(short, _)| *short)
                .unwrap_or("Unknown");
            label.set_label(&format!(
                "{}: This format is not yet supported by the processing engine. \
                 Support is planned for a future release.",
                name
            ));
        });
    }

    // Progressive JPEG toggle
    {
        let jc = state.job_config.clone();
        progressive_row.connect_active_notify(move |row| {
            jc.borrow_mut().progressive_jpeg = row.is_active();
        });
    }

    scrolled.set_child(Some(&content));

    let page = adw::NavigationPage::builder()
        .title("Convert")
        .tag("step-convert")
        .child(&scrolled)
        .build();

    // Sync enable toggle and rebuild format mapping rows when navigating to this page
    {
        let files = state.loaded_files.clone();
        let list = mapping_list;
        let jc = state.job_config.clone();
        let er = enable_row.clone();
        page.connect_map(move |_| {
            let enabled = jc.borrow().convert_enabled;
            er.set_active(enabled);
            rebuild_format_mapping(&list, &files.borrow(), &jc);
        });
    }

    page
}

/// Returns the card grid index for a given ImageFormat, or None if not in the card grid.
fn card_index_for_format(format: Option<ImageFormat>) -> Option<i32> {
    match format {
        None => Some(0),
        Some(ImageFormat::Jpeg) => Some(1),
        Some(ImageFormat::Png) => Some(2),
        Some(ImageFormat::WebP) => Some(3),
        Some(ImageFormat::Avif) => Some(4),
        Some(ImageFormat::Gif) => Some(5),
        Some(ImageFormat::Tiff) => Some(6),
        Some(ImageFormat::Bmp) => Some(7),
    }
}

/// Returns the ImageFormat for a given card grid index.
fn format_for_card_index(idx: usize) -> Option<ImageFormat> {
    match idx {
        1 => Some(ImageFormat::Jpeg),
        2 => Some(ImageFormat::Png),
        3 => Some(ImageFormat::WebP),
        4 => Some(ImageFormat::Avif),
        5 => Some(ImageFormat::Gif),
        6 => Some(ImageFormat::Tiff),
        7 => Some(ImageFormat::Bmp),
        _ => None, // 0 = Keep Original
    }
}

fn format_info(format: Option<ImageFormat>) -> String {
    match format {
        None => "Images will keep their original format. No conversion applied.".into(),
        Some(ImageFormat::Jpeg) => "JPEG: Best for photographs. Lossy compression, \
            no transparency support. Universally compatible with all devices and browsers."
            .into(),
        Some(ImageFormat::Png) => "PNG: Best for graphics, screenshots, and logos. \
            Lossless compression, supports full transparency. Produces larger files \
            than JPEG or WebP."
            .into(),
        Some(ImageFormat::WebP) => "WebP: Modern format with excellent lossy and lossless \
            compression. Supports transparency and animation. Widely supported in modern browsers."
            .into(),
        Some(ImageFormat::Avif) => "AVIF: Next-generation format based on AV1 video codec. \
            Best compression ratios available. Supports transparency and HDR. Slower to encode, \
            growing browser support."
            .into(),
        Some(ImageFormat::Gif) => "GIF: Limited to 256 colors per frame. Supports basic \
            animation and binary transparency. Best for simple graphics and short animations."
            .into(),
        Some(ImageFormat::Tiff) => "TIFF: Professional archival format. Lossless compression, \
            supports layers and rich metadata. Very large files. Not suitable for web."
            .into(),
        Some(ImageFormat::Bmp) => "BMP: Uncompressed bitmap format. Very large files, no \
            compression. Legacy format mainly used for compatibility with older software."
            .into(),
    }
}

/// Rebuild the per-format mapping rows based on which file types are actually loaded.
/// Uses a dedicated ListBox container that can be easily cleared and rebuilt.
fn rebuild_format_mapping(
    list: &gtk::ListBox,
    loaded_files: &[PathBuf],
    job_config: &Rc<RefCell<crate::app::JobConfig>>,
) {
    // Clear all existing rows from the list
    list.remove_all();

    // Detect which file extensions are present in loaded files
    let mut seen_extensions: HashSet<String> = HashSet::new();
    for path in loaded_files {
        if let Some(ext) = path.extension()
            && let Some(ext_str) = ext.to_str()
        {
            seen_extensions.insert(ext_str.to_lowercase());
        }
    }

    if seen_extensions.is_empty() {
        // No files loaded yet - add a placeholder row
        let placeholder = adw::ActionRow::builder()
            .title("No files loaded")
            .subtitle("Load images first to configure per-format mappings")
            .build();
        placeholder.add_prefix(&gtk::Image::from_icon_name("dialog-information-symbolic"));
        list.append(&placeholder);
        return;
    }

    // Normalize extensions to canonical display names, maintaining a stable order
    let mut format_entries: Vec<(String, String)> = Vec::new(); // (canonical ext, display name)
    let ext_to_name: &[(&[&str], &str)] = &[
        (&["jpg", "jpeg"], "JPEG"),
        (&["png"], "PNG"),
        (&["webp"], "WebP"),
        (&["avif"], "AVIF"),
        (&["gif"], "GIF"),
        (&["tiff", "tif"], "TIFF"),
        (&["bmp"], "BMP"),
        (&["ico"], "ICO"),
        (&["hdr"], "HDR"),
        (&["pnm", "ppm", "pgm", "pbm"], "PNM/PPM"),
        (&["tga"], "TGA"),
        (&["heic", "heif"], "HEIC/HEIF"),
        (&["jxl"], "JXL (JPEG XL)"),
        (&["qoi"], "QOI"),
        (&["exr"], "EXR"),
        (&["ff", "farbfeld"], "Farbfeld"),
    ];

    let mut added_names: HashSet<String> = HashSet::new();
    for (exts, display_name) in ext_to_name {
        for ext in *exts {
            if seen_extensions.contains(*ext) && added_names.insert(display_name.to_string()) {
                // Use the first extension as canonical key
                format_entries.push((exts[0].to_string(), display_name.to_string()));
                break;
            }
        }
    }

    // Also handle any unknown extensions in sorted order
    let mut unknown: Vec<String> = Vec::new();
    for ext in &seen_extensions {
        let known = ext_to_name
            .iter()
            .any(|(exts, _)| exts.contains(&ext.as_str()));
        if !known {
            unknown.push(ext.clone());
        }
    }
    unknown.sort();
    for ext in unknown {
        let upper = ext.to_uppercase();
        if added_names.insert(upper.clone()) {
            format_entries.push((ext, upper));
        }
    }

    let cfg = job_config.borrow();

    for (canonical_ext, display_name) in &format_entries {
        let combo = adw::ComboRow::builder()
            .title(format!("{} inputs", display_name))
            .subtitle(format!("Output format for {} source files", display_name))
            .use_subtitle(true)
            .build();
        combo.set_model(Some(&gtk::StringList::new(MAPPING_CHOICES)));
        combo.set_list_factory(Some(&super::full_text_list_factory()));

        // Restore saved selection if any
        let saved = cfg.format_mappings.get(canonical_ext).copied().unwrap_or(0);
        combo.set_selected(saved);

        // Wire signal to save selection
        let jc = job_config.clone();
        let ext_key = canonical_ext.clone();
        combo.connect_selected_notify(move |row| {
            jc.borrow_mut()
                .format_mappings
                .insert(ext_key.clone(), row.selected());
        });

        list.append(&combo);
    }
}
