provenance-cli 0.2.3

Command-line interface for Provenance requirements traceability
use crate::wiki::model::{LineageEntry, PageKind};
use crate::wiki::routes::WikiRoute;
use crate::wiki::theme::{ICON_DEFS, SEARCH_SCRIPT, THEME_SCRIPT};
use std::fmt::Write as _;

use super::html::{escape_attr, escape_html, icon_svg, PageLinksRenderer};
use super::labels::{kind_class, kind_icon, kind_label};

pub(in crate::wiki::render) fn page_shell(
    scope: &str,
    kind_class: &str,
    title: &str,
    breadcrumb: &str,
    container: &str,
    field_notes: &str,
) -> String {
    page_shell_with_script(
        scope,
        kind_class,
        title,
        breadcrumb,
        container,
        field_notes,
        "",
    )
}

pub(in crate::wiki::render) fn search_page_shell(
    scope: &str,
    kind_class: &str,
    title: &str,
    breadcrumb: &str,
    container: &str,
) -> String {
    page_shell_with_script(
        scope,
        kind_class,
        title,
        breadcrumb,
        container,
        "",
        SEARCH_SCRIPT,
    )
}

#[allow(clippy::too_many_arguments)]
fn page_shell_with_script(
    scope: &str,
    kind_class: &str,
    title: &str,
    breadcrumb: &str,
    container: &str,
    field_notes: &str,
    extra_script: &str,
) -> String {
    let mut html = String::new();
    html.push_str("<!doctype html>\n<html lang=\"en\" data-theme=\"statesman\">\n<head>\n");
    html.push_str("<meta charset=\"utf-8\">\n");
    html.push_str("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n");
    writeln!(
        html,
        "<title>{} - Provenance Wiki</title>",
        escape_html(title)
    )
    .expect("writing to a String should not fail");
    writeln!(
        html,
        "<link rel=\"stylesheet\" href=\"{}\">",
        WikiRoute::Stylesheet.path()
    )
    .expect("writing to a String should not fail");
    html.push_str("</head>\n<body>\n");
    html.push_str(ICON_DEFS.trim_start_matches('\n'));
    html.push_str("<header class=\"chrome\">\n<div class=\"chrome-inner\">\n");
    writeln!(
        html,
        "<span class=\"wordmark\">Provenance<span class=\"scope\">{}</span></span>",
        escape_html(scope)
    )
    .expect("writing to a String should not fail");
    writeln!(html, "<nav aria-label=\"Breadcrumb\">{breadcrumb}</nav>")
        .expect("writing to a String should not fail");
    writeln!(
        html,
        "<nav class=\"global-nav\" aria-label=\"Wiki\">\
         <a href=\"{}\">Home</a><a href=\"{}\">Domains</a>\
         <a href=\"{}\">Decisions</a><a href=\"{}\">Search</a></nav>",
        WikiRoute::Index.path(),
        WikiRoute::Domains.path(),
        WikiRoute::Decisions.path(),
        WikiRoute::Search.path(),
    )
    .expect("writing to a String should not fail");
    html.push_str(
        "<label class=\"theme-select\">Theme\n<select id=\"theme-select\">\n\
         <option value=\"statesman\" selected>Statesman</option>\n\
         <option value=\"piano\">Piano</option>\n\
         <option value=\"latte\">Catppuccin Latte</option>\n\
         <option value=\"mocha\">Catppuccin Mocha</option>\n\
         <option value=\"dracula\">Dracula</option>\n\
         </select>\n</label>\n",
    );
    html.push_str("</div>\n</header>\n");
    writeln!(
        html,
        "<div class=\"accent-bar {kind_class}\" aria-hidden=\"true\"></div>"
    )
    .expect("writing to a String should not fail");
    write!(html, "<div class=\"container\">\n{container}</div>\n")
        .expect("writing to a String should not fail");
    html.push_str(field_notes);
    html.push_str("<script>");
    html.push_str(THEME_SCRIPT);
    html.push_str(extra_script);
    html.push_str("</script>\n</body>\n</html>\n");
    html
}

pub(in crate::wiki::render) fn container_html(
    back: Option<(PageKind, (String, String))>,
    title_row: &str,
    main: &str,
    margin: &str,
) -> String {
    let mut html = String::new();
    if let Some((kind, (route, title))) = back {
        write!(
            html,
            "<a class=\"back-link {}\" href=\"{}\">\n{}{}\n</a>\n",
            kind_class(kind),
            escape_attr(&route),
            icon_svg("i-arrow-left"),
            escape_html(&title)
        )
        .expect("writing to a String should not fail");
    }
    html.push_str(title_row);
    write!(
        html,
        "<div class=\"body-grid\">\n<div class=\"body-main\">\n{main}</div>\n\
         <aside class=\"body-margin\">\n{margin}</aside>\n</div>\n"
    )
    .expect("writing to a String should not fail");
    html
}

pub(in crate::wiki::render) fn title_row(
    kind: PageKind,
    title: &str,
    status_badge: Option<&str>,
    chips: &[String],
    id_chip: &str,
) -> String {
    let mut html = String::new();
    write!(
        html,
        "<div class=\"title-row\">\n<svg class=\"icon {}\" aria-hidden=\"true\"><use href=\"#{}\"/></svg>\n<div>\n<h1>{}</h1>\n<div class=\"badge-row\">\n",
        kind_class(kind),
        kind_icon(kind),
        escape_html(title)
    )
    .expect("writing to a String should not fail");
    if kind != PageKind::ScopeIndex {
        writeln!(
            html,
            "<span class=\"type-badge {}\">{}{}</span>",
            kind_class(kind),
            icon_svg(kind_icon(kind)),
            kind_label(kind)
        )
        .expect("writing to a String should not fail");
    }
    if let Some(badge) = status_badge {
        html.push_str(badge);
        html.push('\n');
    }
    for chip in chips {
        html.push_str(chip);
        html.push('\n');
    }
    writeln!(
        html,
        "<span class=\"id-chip\">{}</span>",
        escape_html(id_chip)
    )
    .expect("writing to a String should not fail");
    html.push_str("</div>\n</div>\n</div>\n");
    html
}

pub(in crate::wiki::render) fn breadcrumb_from_lineage(
    links: &PageLinksRenderer,
    lineage: &[LineageEntry],
) -> String {
    let ancestors: Vec<String> = lineage
        .iter()
        .filter(|entry| !entry.is_current)
        .map(|entry| links.link(&entry.link, None))
        .collect();
    ancestors.join(" <span class=\"sep\">›</span> ")
}

pub(in crate::wiki::render) fn index_breadcrumb(scope: &str) -> String {
    format!(
        "<a href=\"{}\">{}</a>",
        WikiRoute::Index.path(),
        escape_html(scope)
    )
}