//! Top navigation menu emitted with `hx-boost`-friendly anchors.
//!
//! Anchors carry plain `href` attributes — boost on `<body>` upgrades them to
//! HTMX-driven navigations. The current route is computed server-side so the
//! active-item highlight is part of the SSR output (no client JS needed).

use html_escape::encode_double_quoted_attribute;
use serde::{Deserialize, Serialize};

/// One row in the navigation menu.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NavItem {
    /// Localised label already resolved against the active language.
    pub label: String,
    /// Destination URL. Internal routes get `hx-boost` treatment via the
    /// surrounding `<body>`; external links get `target="_blank"`.
    pub route: String,
    /// `true` if the link points outside the site.
    pub is_external: bool,
}

/// Render the navigation menu as a `<nav>` element.
///
/// `active_path` is the current request path used to mark the active anchor
/// with `aria-current="page"`. `items` are pre-localised by the caller.
pub fn render_nav_menu(items: &[NavItem], active_path: &str) -> String {
    let mut out = String::new();
    out.push_str(r#"<nav class="border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-sm" aria-label="Primary">"#);
    out.push_str(r#"<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"><div class="flex justify-between h-16"><div class="hidden lg:flex lg:space-x-8 items-center">"#);
    for item in items {
        let is_active = !item.is_external && paths_match(active_path, &item.route);
        let target_attr = if item.is_external {
            r#" target="_blank" rel="noopener noreferrer""#
        } else {
            ""
        };
        let aria_current = if is_active {
            r#" aria-current="page""#
        } else {
            ""
        };
        let active_class = if is_active {
            " text-gray-900 dark:text-white font-semibold"
        } else {
            ""
        };
        out.push_str(&format!(
            r#"<a href="{href}" class="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium transition-colors no-underline{active_class}"{aria_current}{target_attr}>{label}</a>"#,
            href = encode_double_quoted_attribute(&item.route),
            label = html_escape::encode_text(&item.label),
        ));
    }
    out.push_str("</div></div></div></nav>");
    out
}

fn paths_match(current: &str, route: &str) -> bool {
    if route == "/" {
        current == "/" || current.is_empty()
    } else {
        current == route || current.starts_with(&format!("{route}/"))
    }
}

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

    fn sample_items() -> Vec<NavItem> {
        vec![
            NavItem {
                label: "Home".into(),
                route: "/".into(),
                is_external: false,
            },
            NavItem {
                label: "Blog".into(),
                route: "/blog".into(),
                is_external: false,
            },
            NavItem {
                label: "Rustelo".into(),
                route: "https://rustelo.dev".into(),
                is_external: true,
            },
        ]
    }

    #[test]
    fn renders_all_items() {
        let html = render_nav_menu(&sample_items(), "/");
        assert!(html.contains(r#"href="/""#));
        assert!(html.contains(r#"href="/blog""#));
        assert!(html.contains(r#"href="https://rustelo.dev""#));
        assert_eq!(html.matches("<a ").count(), 3);
    }

    #[test]
    fn marks_active_item_on_root() {
        let html = render_nav_menu(&sample_items(), "/");
        let home_anchor = html.split("<a ").nth(1).expect("first anchor present");
        assert!(home_anchor.contains(r#"aria-current="page""#));
    }

    #[test]
    fn marks_active_item_on_subpath() {
        let html = render_nav_menu(&sample_items(), "/blog/some-post");
        let blog_anchor = html
            .split(r#"href="/blog""#)
            .nth(1)
            .expect("blog anchor present");
        let prefix = &blog_anchor[..blog_anchor.find('>').expect("> found")];
        assert!(prefix.contains(r#"aria-current="page""#));
    }

    #[test]
    fn external_links_get_target_blank() {
        let html = render_nav_menu(&sample_items(), "/");
        assert!(html.contains(r#"target="_blank" rel="noopener noreferrer""#));
    }

    #[test]
    fn escapes_label_text() {
        let items = vec![NavItem {
            label: "<bad>".into(),
            route: "/x".into(),
            is_external: false,
        }];
        let html = render_nav_menu(&items, "/");
        assert!(html.contains("&lt;bad&gt;"));
        assert!(!html.contains("<bad>"));
    }
}
