// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2026 ndaal Gesellschaft für Sicherheit in der Informationstechnik mbH & Co KG, Cologne
// SPDX-FileCopyrightText: Author: Pierre Gronau <Pierre.Gronau@ndaal.eu>

//! Server-side HTML rendering with the ndaal Bootstrap house style.
//!
//! Pages are assembled as strings (the ndaal CSAF-CRUD approach) and wrapped by
//! [`page`], which supplies the Bootstrap 5.3 document shell — the same navbar /
//! card / footer layout and `data-bs-theme` colour-mode switcher as the
//! BSI-Grundschutz OSCAL viewer. Bootstrap, Bootstrap Icons and the theme
//! switcher are served from `/static/`; the only JavaScript is the vendored
//! `bootstrap.bundle.min.js` and the external `theme.js`, so the CSP keeps
//! `script-src 'self'` with no inline scripts.

/// The navigation tabs.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Nav {
    /// Dashboard / log list.
    Home,
    /// Import-by-path page.
    Import,
    /// Validate-a-file page.
    Validate,
    /// Export page.
    Export,
    /// No tab highlighted (detail / Info-menu pages).
    None,
}

/// HTML-escape text for safe interpolation into element content or attributes.
#[must_use]
pub fn escape(input: &str) -> String {
    let mut out = String::with_capacity(input.len());
    for ch in input.chars() {
        match ch {
            '&' => out.push_str("&amp;"),
            '<' => out.push_str("&lt;"),
            '>' => out.push_str("&gt;"),
            '"' => out.push_str("&quot;"),
            '\'' => out.push_str("&#39;"),
            other => out.push(other),
        }
    }
    out
}

/// The Bootstrap background class for a SARIF severity level.
#[must_use]
pub fn level_bg(level: &str) -> &'static str {
    match level {
        "error" => "text-bg-danger",
        "note" => "text-bg-info",
        "none" => "text-bg-secondary",
        _ => "text-bg-warning",
    }
}

/// A coloured severity badge.
#[must_use]
pub fn level_badge(level: &str) -> String {
    format!(
        "<span class=\"badge {}\">{}</span>",
        level_bg(level),
        escape(level)
    )
}

/// A severity badge with a count; dimmed (outline) when zero.
#[must_use]
pub fn level_count_badge(level: &str, n: usize) -> String {
    if n == 0 {
        return format!("<span class=\"badge text-bg-light border\">{n} {}</span>", escape(level));
    }
    format!(
        "<span class=\"badge {}\">{n} {}</span>",
        level_bg(level),
        escape(level)
    )
}

/// A Bootstrap alert. `kind` is a Bootstrap contextual name
/// (`success` / `danger` / `warning` / `info`). `inner_html` is inserted as-is
/// (caller escapes any untrusted text).
#[must_use]
pub fn alert(kind: &str, inner_html: &str) -> String {
    let icon = match kind {
        "success" => "bi-check-circle-fill",
        "danger" => "bi-exclamation-octagon-fill",
        "warning" => "bi-exclamation-triangle-fill",
        _ => "bi-info-circle-fill",
    };
    format!(
        "<div class=\"alert alert-{kind} d-flex align-items-start gap-2\" role=\"alert\">\
         <i class=\"bi {icon} mt-1\"></i><div>{inner_html}</div></div>"
    )
}

/// A simple card wrapping `inner_html` (already-built HTML).
#[must_use]
pub fn card(inner_html: &str) -> String {
    format!("<div class=\"card shadow-sm mb-4\"><div class=\"card-body\">{inner_html}</div></div>")
}

/// One navbar link with its icon and active state.
fn nav_link(href: &str, icon: &str, label: &str, active: bool) -> String {
    let class = if active { "nav-link active" } else { "nav-link" };
    let current = if active { " aria-current=\"page\"" } else { "" };
    format!(
        "<li class=\"nav-item\"><a class=\"{class}\" href=\"{href}\"{current}>\
         <i class=\"bi {icon} me-1\"></i>{label}</a></li>"
    )
}

/// A small inline `<style>` block for the few things Bootstrap utilities don't
/// cover (monospace ids, JSON blocks). Inline styles are allowed by the CSP.
const STYLE: &str = "\
.mono{font-family:var(--bs-font-monospace);font-size:.9em}\
pre.json-block{background:var(--bs-tertiary-bg);border:1px solid var(--bs-border-color);\
border-radius:.375rem;padding:1rem;overflow:auto;max-height:60vh;font-size:.85rem}\
.navbar-blur{background:rgba(var(--bs-body-bg-rgb),.95);backdrop-filter:blur(8px)}\
.brand-icon{color:var(--bs-primary)}\
.table th{font-size:.78rem;text-transform:uppercase;letter-spacing:.04em;opacity:.75}\
main{flex:1}body{min-height:100vh;display:flex;flex-direction:column}\
.stat-value{font-size:1.9rem;font-weight:700;line-height:1.1}\
.stat-label{font-size:.8rem;opacity:.75;text-transform:uppercase;letter-spacing:.04em}";

/// Wrap page `content` (already-built HTML) in the Bootstrap document shell.
#[must_use]
pub fn page(title: &str, active: Nav, content: &str) -> String {
    let tabs = [
        ("/", "bi-speedometer2", "Dashboard", Nav::Home),
        ("/import", "bi-box-arrow-in-down", "Import", Nav::Import),
        ("/validate", "bi-check2-circle", "Validate", Nav::Validate),
        ("/export", "bi-box-arrow-up", "Export", Nav::Export),
    ];
    let nav: String = tabs
        .iter()
        .map(|(href, icon, label, tab)| nav_link(href, icon, label, *tab == active))
        .collect();

    format!(
        "<!DOCTYPE html><html lang=\"en\" data-bs-theme=\"light\"><head>\
         <meta charset=\"utf-8\">\
         <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\
         <title>{title} — SARIF Viewer</title>\
         <link href=\"/static/css/bootstrap.min.css\" rel=\"stylesheet\">\
         <link href=\"/static/css/bootstrap-icons.min.css\" rel=\"stylesheet\">\
         <script src=\"/static/js/theme.js\"></script>\
         <style>{STYLE}</style></head><body>\
         <nav class=\"navbar navbar-expand-lg border-bottom navbar-blur sticky-top\">\
         <div class=\"container-fluid\">\
         <a class=\"navbar-brand fw-bold d-flex align-items-center\" href=\"/\">\
         <i class=\"bi bi-hexagon-half brand-icon me-2 fs-4\"></i>SARIF Viewer</a>\
         <button class=\"navbar-toggler\" type=\"button\" data-bs-toggle=\"collapse\" \
         data-bs-target=\"#mainNav\" aria-controls=\"mainNav\" aria-expanded=\"false\" \
         aria-label=\"Toggle navigation\"><span class=\"navbar-toggler-icon\"></span></button>\
         <div class=\"collapse navbar-collapse\" id=\"mainNav\">\
         <ul class=\"navbar-nav me-auto mb-2 mb-lg-0\">{nav}</ul>\
         <ul class=\"navbar-nav mb-2 mb-lg-0 align-items-lg-center\">\
         <li class=\"nav-item d-none d-lg-flex align-items-center me-1\">\
         <span class=\"badge rounded-pill text-bg-secondary\">SARIF 2.1.0 + 2.2</span></li>\
         <li class=\"nav-item\">\
         <button id=\"theme-toggle\" type=\"button\" class=\"nav-link btn\" \
         aria-label=\"Toggle colour theme\" title=\"Toggle light/dark\">\
         <i id=\"theme-icon\" class=\"bi bi-moon-stars-fill\"></i></button></li>\
         <li class=\"nav-item dropdown\">\
         <a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" \
         data-bs-toggle=\"dropdown\" aria-expanded=\"false\"><i class=\"bi bi-info-circle me-1\"></i>Info</a>\
         <ul class=\"dropdown-menu dropdown-menu-end\">\
         <li><a class=\"dropdown-item\" href=\"/about\"><i class=\"bi bi-info-square me-2\"></i>About</a></li>\
         <li><a class=\"dropdown-item\" href=\"/system-info\"><i class=\"bi bi-cpu me-2\"></i>System info</a></li>\
         <li><hr class=\"dropdown-divider\"></li>\
         <li><a class=\"dropdown-item\" href=\"/privacy\"><i class=\"bi bi-eye-slash me-2\"></i>Privacy</a></li>\
         <li><a class=\"dropdown-item\" href=\"/security\"><i class=\"bi bi-shield-check me-2\"></i>Security</a></li>\
         <li><a class=\"dropdown-item\" href=\"/license\"><i class=\"bi bi-file-earmark-text me-2\"></i>License</a></li>\
         <li><a class=\"dropdown-item\" href=\"/imprint\"><i class=\"bi bi-building me-2\"></i>Imprint</a></li>\
         <li><hr class=\"dropdown-divider\"></li>\
         <li><a class=\"dropdown-item\" href=\"/changelog\"><i class=\"bi bi-card-list me-2\"></i>Changelog</a></li>\
         </ul></li></ul></div></div></nav>\
         <main class=\"container py-4\">{content}</main>\
         <footer class=\"border-top py-3 mt-auto\"><div class=\"container text-center text-body-secondary small\">\
         <i class=\"bi bi-shield-check me-1\"></i>SARIF Viewer — schema-validated SARIF 2.1.0 / 2.2 \
         — © 2026 ndaal GmbH &amp; Co KG — Apache-2.0\
         <span class=\"mx-2\">|</span><a href=\"/privacy\" class=\"text-body-secondary text-decoration-none\">Privacy</a>\
         <span class=\"mx-1\">|</span><a href=\"/security\" class=\"text-body-secondary text-decoration-none\">Security</a>\
         <span class=\"mx-1\">|</span><a href=\"/license\" class=\"text-body-secondary text-decoration-none\">License</a>\
         <span class=\"mx-1\">|</span><a href=\"/imprint\" class=\"text-body-secondary text-decoration-none\">Imprint</a>\
         </div></footer>\
         <script src=\"/static/js/bootstrap.bundle.min.js\"></script>\
         </body></html>",
        title = escape(title),
    )
}

#[cfg(test)]
mod tests {
    use super::{alert, escape, level_bg, level_count_badge, page, Nav};

    #[test]
    fn escapes_html_metacharacters() {
        assert_eq!(escape("<a>&\"'"), "&lt;a&gt;&amp;&quot;&#39;");
    }

    #[test]
    fn level_backgrounds() {
        assert_eq!(level_bg("error"), "text-bg-danger");
        assert_eq!(level_bg("whatever"), "text-bg-warning");
    }

    #[test]
    fn zero_count_badge_is_dimmed() {
        assert!(level_count_badge("error", 0).contains("text-bg-light"));
        assert!(level_count_badge("error", 3).contains("text-bg-danger"));
    }

    #[test]
    fn alert_carries_kind_and_icon() {
        let html = alert("danger", "boom");
        assert!(html.contains("alert-danger"));
        assert!(html.contains("bi-exclamation-octagon-fill"));
    }

    #[test]
    fn page_marks_active_tab_links_bootstrap_and_escapes_title() {
        let html = page("<x>", Nav::Import, "<p>hi</p>");
        assert!(html.contains("&lt;x&gt; — SARIF Viewer"));
        assert!(html.contains("/static/css/bootstrap.min.css"));
        assert!(html.contains("href=\"/import\" aria-current=\"page\""));
        assert!(html.contains("<p>hi</p>"));
        assert!(html.contains("data-bs-theme=\"light\""));
        assert!(html.contains("id=\"theme-toggle\""));
    }
}
