use gtk::prelude::*;
use gtk::accessible::Property as AccessibleProperty;

use crate::core::database::CatalogApp;
use super::widgets;

/// Build a catalog tile for the browse grid.
/// Left-aligned layout: icon (48px) at top, name, description, category badge.
/// Card fills its entire FlowBoxChild cell.
/// If `installed` is true, an "Installed" badge is shown on the card.
pub fn build_catalog_tile(app: &CatalogApp, installed: bool) -> gtk::FlowBoxChild {
    let card = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(6)
        .halign(gtk::Align::Fill)
        .valign(gtk::Align::Fill)
        .hexpand(true)
        .vexpand(true)
        .build();
    card.add_css_class("card");
    card.add_css_class("catalog-tile");

    let inner = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(6)
        .margin_top(14)
        .margin_bottom(14)
        .margin_start(14)
        .margin_end(14)
        .vexpand(true)
        .build();

    // Icon (48px) - left aligned
    let icon = widgets::app_icon(None, &app.name, 48);
    icon.add_css_class("icon-dropshadow");
    icon.set_halign(gtk::Align::Start);
    inner.append(&icon);

    // App name - left aligned
    let name_label = gtk::Label::builder()
        .label(&app.name)
        .css_classes(["heading"])
        .ellipsize(gtk::pango::EllipsizeMode::End)
        .max_width_chars(20)
        .xalign(0.0)
        .halign(gtk::Align::Start)
        .build();
    inner.append(&name_label);

    // Description (always 2 lines for uniform height) - prefer OCS summary, then GitHub description
    let plain = app.ocs_summary.as_deref()
        .filter(|d| !d.is_empty())
        .or(app.github_description.as_deref().filter(|d| !d.is_empty()))
        .or(app.description.as_deref().filter(|d| !d.is_empty()))
        .map(|d| strip_html(d))
        .unwrap_or_default();
    let snippet: String = plain.chars().take(80).collect();
    let text = if plain.is_empty() {
        // Non-breaking space placeholder to reserve 2 lines
        "\u{00a0}".to_string()
    } else if snippet.len() < plain.len() {
        format!("{}...", snippet.trim_end())
    } else {
        snippet
    };
    let desc_label = gtk::Label::builder()
        .label(&text)
        .css_classes(["caption", "dim-label"])
        .ellipsize(gtk::pango::EllipsizeMode::End)
        .lines(2)
        .wrap(true)
        .xalign(0.0)
        .max_width_chars(24)
        .halign(gtk::Align::Start)
        .build();
    // Force 2-line height
    desc_label.set_height_request(desc_label.preferred_size().1.height().max(36));
    inner.append(&desc_label);

    // Stats row (downloads + stars + version) - only if data exists
    let has_downloads = app.ocs_downloads.is_some_and(|d| d > 0);
    let has_stars = app.github_stars.is_some_and(|s| s > 0);
    let has_version = app.latest_version.is_some();
    if has_downloads || has_stars || has_version {
        let stats_row = gtk::Box::builder()
            .orientation(gtk::Orientation::Horizontal)
            .spacing(12)
            .halign(gtk::Align::Start)
            .build();
        stats_row.add_css_class("catalog-stats-row");

        if let Some(downloads) = app.ocs_downloads.filter(|&d| d > 0) {
            let dl_box = gtk::Box::builder()
                .orientation(gtk::Orientation::Horizontal)
                .spacing(4)
                .build();
            let dl_icon = gtk::Image::from_icon_name("folder-download-symbolic");
            dl_icon.set_pixel_size(12);
            dl_icon.update_property(&[AccessibleProperty::Label("Downloads")]);
            dl_box.append(&dl_icon);
            let dl_label = gtk::Label::new(Some(&widgets::format_count(downloads)));
            dl_label.add_css_class("caption");
            dl_label.add_css_class("dim-label");
            dl_box.append(&dl_label);
            dl_box.set_tooltip_text(Some(&format!("{} downloads", downloads)));
            stats_row.append(&dl_box);
        }

        if let Some(stars) = app.github_stars.filter(|&s| s > 0) {
            let star_box = gtk::Box::builder()
                .orientation(gtk::Orientation::Horizontal)
                .spacing(4)
                .build();
            let star_icon = gtk::Image::from_icon_name("starred-symbolic");
            star_icon.set_pixel_size(12);
            star_icon.update_property(&[AccessibleProperty::Label("Stars")]);
            star_box.append(&star_icon);
            let star_label = gtk::Label::new(Some(&widgets::format_count(stars)));
            star_box.append(&star_label);
            stats_row.append(&star_box);
        }

        if let Some(ref ver) = app.latest_version {
            let ver_box = gtk::Box::builder()
                .orientation(gtk::Orientation::Horizontal)
                .spacing(4)
                .build();
            let ver_icon = gtk::Image::from_icon_name("tag-symbolic");
            ver_icon.set_pixel_size(12);
            ver_icon.update_property(&[AccessibleProperty::Label("Version")]);
            ver_box.append(&ver_icon);
            let ver_label = gtk::Label::builder()
                .label(ver.as_str())
                .ellipsize(gtk::pango::EllipsizeMode::End)
                .max_width_chars(12)
                .build();
            ver_box.append(&ver_label);
            stats_row.append(&ver_box);
        }

        inner.append(&stats_row);
    }

    // Category badge - left aligned
    if let Some(ref cats) = app.categories {
        let first_cat: String = cats.split(';')
            .next()
            .or_else(|| cats.split(',').next())
            .unwrap_or("")
            .trim()
            .to_string();
        if !first_cat.is_empty() {
            let badge = widgets::status_badge(&first_cat, "neutral");
            badge.set_halign(gtk::Align::Start);
            badge.set_margin_top(2);
            inner.append(&badge);
        }
    }

    // Installed badge
    if installed {
        let installed_badge = widgets::status_badge("Installed", "success");
        installed_badge.set_halign(gtk::Align::Start);
        installed_badge.set_margin_top(4);
        inner.append(&installed_badge);
    }

    card.append(&inner);

    let child = gtk::FlowBoxChild::builder()
        .child(&card)
        .build();
    child.add_css_class("activatable");
    widgets::set_pointer_cursor(&child);

    // Accessible label for screen readers
    let mut a11y_parts = vec![app.name.clone()];
    if !plain.is_empty() {
        a11y_parts.push(plain.chars().take(80).collect());
    }
    if let Some(downloads) = app.ocs_downloads.filter(|&d| d > 0) {
        a11y_parts.push(format!("{} downloads", downloads));
    }
    if let Some(stars) = app.github_stars.filter(|&s| s > 0) {
        a11y_parts.push(format!("{} stars", stars));
    }
    if let Some(ref cats) = app.categories {
        if let Some(cat) = cats.split(';').next().or_else(|| cats.split(',').next()) {
            let cat = cat.trim();
            if !cat.is_empty() {
                a11y_parts.push(format!("category: {}", cat));
            }
        }
    }
    if installed {
        a11y_parts.push("installed".to_string());
    }
    child.update_property(&[AccessibleProperty::Label(&a11y_parts.join(", "))]);

    child
}

/// Build a compact list-row tile for the browse grid in list mode.
/// Horizontal layout: icon (32px) | name | description snippet | stats.
pub fn build_catalog_row(app: &CatalogApp, installed: bool) -> gtk::FlowBoxChild {
    let row = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(12)
        .halign(gtk::Align::Fill)
        .hexpand(true)
        .build();
    row.add_css_class("card");
    row.add_css_class("catalog-row");

    let inner = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(12)
        .margin_top(8)
        .margin_bottom(8)
        .margin_start(12)
        .margin_end(12)
        .hexpand(true)
        .build();

    // Icon (32px)
    let icon = widgets::app_icon(None, &app.name, 32);
    icon.set_valign(gtk::Align::Center);
    inner.append(&icon);

    // Name
    let name_label = gtk::Label::builder()
        .label(&app.name)
        .css_classes(["heading"])
        .ellipsize(gtk::pango::EllipsizeMode::End)
        .max_width_chars(18)
        .xalign(0.0)
        .width_chars(14)
        .build();
    inner.append(&name_label);

    // Description (single line)
    let plain = app.ocs_summary.as_deref()
        .filter(|d| !d.is_empty())
        .or(app.github_description.as_deref().filter(|d| !d.is_empty()))
        .or(app.description.as_deref().filter(|d| !d.is_empty()))
        .map(|d| strip_html(d))
        .unwrap_or_default();
    let desc_label = gtk::Label::builder()
        .label(&plain)
        .css_classes(["dim-label"])
        .ellipsize(gtk::pango::EllipsizeMode::End)
        .hexpand(true)
        .xalign(0.0)
        .build();
    inner.append(&desc_label);

    // Stats (compact)
    if let Some(downloads) = app.ocs_downloads.filter(|&d| d > 0) {
        let dl_label = gtk::Label::builder()
            .label(&format!("{} dl", widgets::format_count(downloads)))
            .css_classes(["caption", "dim-label"])
            .build();
        inner.append(&dl_label);
    } else if let Some(stars) = app.github_stars.filter(|&s| s > 0) {
        let star_label = gtk::Label::builder()
            .label(&format!("{} stars", widgets::format_count(stars)))
            .css_classes(["caption", "dim-label"])
            .build();
        inner.append(&star_label);
    }

    // Installed badge
    if installed {
        let badge = widgets::status_badge("Installed", "success");
        badge.set_valign(gtk::Align::Center);
        inner.append(&badge);
    }

    row.append(&inner);

    let child = gtk::FlowBoxChild::builder()
        .child(&row)
        .build();
    child.add_css_class("activatable");
    widgets::set_pointer_cursor(&child);

    // Accessible label for screen readers
    let mut a11y_parts = vec![app.name.clone()];
    if !plain.is_empty() {
        a11y_parts.push(plain.chars().take(60).collect());
    }
    if let Some(downloads) = app.ocs_downloads.filter(|&d| d > 0) {
        a11y_parts.push(format!("{} downloads", downloads));
    } else if let Some(stars) = app.github_stars.filter(|&s| s > 0) {
        a11y_parts.push(format!("{} stars", stars));
    }
    if installed {
        a11y_parts.push("installed".to_string());
    }
    child.update_property(&[AccessibleProperty::Label(&a11y_parts.join(", "))]);

    child
}

/// Build a featured banner card for the carousel.
/// Layout: screenshot preview on top, then icon + name + description + badge below.
/// Width is set dynamically by the carousel layout.
pub fn build_featured_tile(app: &CatalogApp) -> gtk::Box {
    let card = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(0)
        .halign(gtk::Align::Fill)
        .valign(gtk::Align::Fill)
        .hexpand(true)
        .vexpand(true)
        .build();
    card.add_css_class("card");
    card.add_css_class("catalog-featured-card");
    card.add_css_class("activatable");
    widgets::set_pointer_cursor(&card);
    card.set_widget_name(&format!("featured-{}", app.id));

    // Accessible label for screen readers
    let mut a11y_parts = vec![app.name.clone()];
    if let Some(desc) = app.ocs_summary.as_deref()
        .filter(|d| !d.is_empty())
        .or(app.github_description.as_deref().filter(|d| !d.is_empty()))
    {
        a11y_parts.push(strip_html(desc).chars().take(60).collect());
    }
    if let Some(ref cats) = app.categories {
        if let Some(cat) = cats.split(';').next().or_else(|| cats.split(',').next()) {
            let cat = cat.trim();
            if !cat.is_empty() {
                a11y_parts.push(format!("category: {}", cat));
            }
        }
    }
    card.update_property(&[AccessibleProperty::Label(&a11y_parts.join(", "))]);

    // Screenshot preview area (top)
    let screenshot_frame = gtk::Frame::new(None);
    screenshot_frame.add_css_class("catalog-featured-screenshot");
    screenshot_frame.set_height_request(160);
    screenshot_frame.set_hexpand(true);

    // Spinner placeholder until image loads
    let spinner = gtk::Spinner::builder()
        .halign(gtk::Align::Center)
        .valign(gtk::Align::Center)
        .spinning(true)
        .width_request(32)
        .height_request(32)
        .build();
    spinner.update_property(&[gtk::accessible::Property::Label("Loading screenshot")]);
    screenshot_frame.set_child(Some(&spinner));
    card.append(&screenshot_frame);

    // Info section below screenshot
    let info_box = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(12)
        .margin_top(10)
        .margin_bottom(10)
        .margin_start(12)
        .margin_end(12)
        .build();

    // Icon (48px)
    let icon = widgets::app_icon(None, &app.name, 48);
    icon.add_css_class("icon-dropshadow");
    icon.set_valign(gtk::Align::Start);
    info_box.append(&icon);

    // Text column
    let text_box = gtk::Box::builder()
        .orientation(gtk::Orientation::Vertical)
        .spacing(2)
        .valign(gtk::Align::Center)
        .hexpand(true)
        .build();

    // App name
    let name_label = gtk::Label::builder()
        .label(&app.name)
        .css_classes(["heading"])
        .ellipsize(gtk::pango::EllipsizeMode::End)
        .max_width_chars(28)
        .xalign(0.0)
        .halign(gtk::Align::Start)
        .build();
    text_box.append(&name_label);

    // Description (1 line in featured since space is tight) - prefer OCS summary
    let feat_desc = app.ocs_summary.as_deref()
        .filter(|d| !d.is_empty())
        .or(app.github_description.as_deref().filter(|d| !d.is_empty()))
        .or(app.description.as_deref().filter(|d| !d.is_empty()));
    if let Some(desc) = feat_desc {
        let plain = strip_html(desc);
        let snippet: String = plain.chars().take(60).collect();
        let text = if snippet.len() < plain.len() {
            format!("{}...", snippet.trim_end())
        } else {
            snippet
        };
        let desc_label = gtk::Label::builder()
            .label(&text)
            .css_classes(["caption", "dim-label"])
            .ellipsize(gtk::pango::EllipsizeMode::End)
            .lines(1)
            .xalign(0.0)
            .max_width_chars(35)
            .halign(gtk::Align::Start)
            .build();
        text_box.append(&desc_label);
    }

    // Badge row: category + downloads/stars
    let badge_row = gtk::Box::builder()
        .orientation(gtk::Orientation::Horizontal)
        .spacing(6)
        .margin_top(2)
        .build();

    if let Some(ref cats) = app.categories {
        let first_cat: String = cats.split(';')
            .next()
            .or_else(|| cats.split(',').next())
            .unwrap_or("")
            .trim()
            .to_string();
        if !first_cat.is_empty() {
            let badge = widgets::status_badge(&first_cat, "info");
            badge.set_halign(gtk::Align::Start);
            badge_row.append(&badge);
        }
    }

    if let Some(downloads) = app.ocs_downloads.filter(|&d| d > 0) {
        let dl_badge = widgets::status_badge_with_icon(
            "folder-download-symbolic",
            &widgets::format_count(downloads),
            "neutral",
        );
        dl_badge.set_halign(gtk::Align::Start);
        badge_row.append(&dl_badge);
    } else if let Some(stars) = app.github_stars.filter(|&s| s > 0) {
        let star_badge = widgets::status_badge_with_icon(
            "starred-symbolic",
            &widgets::format_count(stars),
            "neutral",
        );
        star_badge.set_halign(gtk::Align::Start);
        badge_row.append(&star_badge);
    }

    text_box.append(&badge_row);

    info_box.append(&text_box);
    card.append(&info_box);
    card
}

/// Convert HTML to readable formatted plain text, preserving paragraph breaks,
/// line breaks, and list structure. Suitable for detail page descriptions.
pub fn html_to_description(html: &str) -> String {
    let mut result = String::with_capacity(html.len());
    let mut in_tag = false;
    let mut tag_buf = String::new();

    for ch in html.chars() {
        match ch {
            '<' => {
                in_tag = true;
                tag_buf.clear();
            }
            '>' if in_tag => {
                in_tag = false;
                let tag = tag_buf.trim().to_lowercase();
                let tag_name = tag.split_whitespace().next().unwrap_or("");
                match tag_name {
                    "br" | "br/" => result.push('\n'),
                    "/p" => result.push_str("\n\n"),
                    "li" => result.push_str("\n  - "),
                    "/ul" | "/ol" => result.push('\n'),
                    s if s.starts_with("/h") => result.push_str("\n\n"),
                    _ => {}
                }
            }
            _ if in_tag => tag_buf.push(ch),
            _ => result.push(ch),
        }
    }

    // Decode HTML entities
    let decoded = result
        .replace("&amp;", "&")
        .replace("&lt;", "<")
        .replace("&gt;", ">")
        .replace("&quot;", "\"")
        .replace("&#39;", "'")
        .replace("&apos;", "'")
        .replace("&nbsp;", " ");

    // Clean up: trim lines, collapse multiple blank lines
    let trimmed: Vec<&str> = decoded.lines().map(|l| l.trim()).collect();
    let mut cleaned = String::new();
    let mut prev_blank = false;
    for line in &trimmed {
        if line.is_empty() {
            if !prev_blank && !cleaned.is_empty() {
                cleaned.push('\n');
                prev_blank = true;
            }
        } else {
            if prev_blank {
                cleaned.push('\n');
            }
            cleaned.push_str(line);
            cleaned.push('\n');
            prev_blank = false;
        }
    }

    cleaned.trim().to_string()
}

/// Strip all HTML tags and collapse whitespace. Suitable for short descriptions on tiles.
pub fn strip_html(html: &str) -> String {
    let mut result = String::with_capacity(html.len());
    let mut in_tag = false;
    for ch in html.chars() {
        match ch {
            '<' => in_tag = true,
            '>' => in_tag = false,
            _ if !in_tag => result.push(ch),
            _ => {}
        }
    }
    // Collapse whitespace
    let collapsed: String = result.split_whitespace().collect::<Vec<&str>>().join(" ");
    // Decode common HTML entities
    collapsed
        .replace("&amp;", "&")
        .replace("&lt;", "<")
        .replace("&gt;", ">")
        .replace("&quot;", "\"")
        .replace("&#39;", "'")
        .replace("&apos;", "'")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_strip_html_basic() {
        assert_eq!(strip_html("<p>Hello world</p>"), "Hello world");
    }

    #[test]
    fn test_strip_html_nested() {
        assert_eq!(
            strip_html("<p>Hello <b>bold</b> world</p>"),
            "Hello bold world"
        );
    }

    #[test]
    fn test_strip_html_entities() {
        assert_eq!(strip_html("&amp; &lt; &gt; &quot;"), "& < > \"");
    }

    #[test]
    fn test_strip_html_multiline() {
        let input = "<p>Line one</p>\n<p>Line two</p>";
        assert_eq!(strip_html(input), "Line one Line two");
    }

    #[test]
    fn test_strip_html_list() {
        let input = "<ul>\n  <li>Item 1</li>\n  <li>Item 2</li>\n</ul>";
        assert_eq!(strip_html(input), "Item 1 Item 2");
    }

    #[test]
    fn test_strip_html_plain_text() {
        assert_eq!(strip_html("No HTML here"), "No HTML here");
    }
}
