//! Render the workbench as one self-contained HTML page.
//!
//! No network, no CDN, no external assets: the CSS and the tiny tab-switching
//! script are inlined so the page opens from `file://` with no account and no
//! connection. All artifact data is escaped and embedded directly.

use super::model::{CapItem, ResultsSummary, SecItem, TraceEntry, WorkbenchData};

/// Render the gathered data into a single HTML document.
pub fn render_html(data: &WorkbenchData) -> String {
    let mut out = String::new();
    out.push_str("<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\">\n");
    out.push_str("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n");
    out.push_str("<title>mcptest workbench</title>\n");
    out.push_str(STYLE);
    out.push_str("</head><body>\n");
    out.push_str(&format!(
        "<header><h1>mcptest workbench</h1><p class=\"src\">{} {}</p></header>\n",
        if data.live { "live:" } else { "artifacts:" },
        escape(&data.source_label)
    ));

    let tabs = [
        ("capabilities", "Capabilities"),
        ("trace", "Trace"),
        ("results", "Results"),
        ("security", "Security"),
        ("evidence", "Evidence"),
        ("workflow", "Next steps"),
    ];
    out.push_str("<nav class=\"tabs\">\n");
    for (i, (id, label)) in tabs.iter().enumerate() {
        let active = if i == 0 { " active" } else { "" };
        out.push_str(&format!(
            "<button class=\"tab{active}\" data-tab=\"{id}\">{label}</button>\n"
        ));
    }
    out.push_str("</nav>\n");

    push_capabilities(&mut out, data);
    push_trace(&mut out, &data.trace);
    push_results(&mut out, data.results.as_ref());
    push_security(&mut out, &data.security);
    push_evidence(&mut out, data);
    push_workflow(&mut out);

    out.push_str(SCRIPT);
    out.push_str("</body></html>\n");
    out
}

/// The capabilities pane: tools, resources, prompts.
fn push_capabilities(out: &mut String, data: &WorkbenchData) {
    out.push_str("<section id=\"capabilities\" class=\"pane active\">\n");
    push_cap_table(out, "Tools", &data.tools);
    push_cap_table(out, "Resources", &data.resources);
    push_cap_table(out, "Prompts", &data.prompts);
    out.push_str("</section>\n");
}

/// One capability table.
fn push_cap_table(out: &mut String, title: &str, items: &[CapItem]) {
    out.push_str(&format!("<h2>{title} ({})</h2>\n", items.len()));
    if items.is_empty() {
        out.push_str("<p class=\"empty\">none</p>\n");
        return;
    }
    out.push_str("<table><thead><tr><th>name</th><th>description</th></tr></thead><tbody>\n");
    for item in items {
        out.push_str(&format!(
            "<tr><td><code>{}</code></td><td>{}</td></tr>\n",
            escape(&item.name),
            escape(&item.description)
        ));
    }
    out.push_str("</tbody></table>\n");
}

/// The JSON-RPC trace pane.
fn push_trace(out: &mut String, trace: &[TraceEntry]) {
    out.push_str("<section id=\"trace\" class=\"pane\">\n<h2>JSON-RPC trace</h2>\n");
    if trace.is_empty() {
        out.push_str("<p class=\"empty\">no cassette opened</p>\n");
    } else {
        out.push_str(
            "<table><thead><tr><th>dir</th><th>method</th><th>body</th></tr></thead><tbody>\n",
        );
        for entry in trace {
            out.push_str(&format!(
                "<tr><td>{}</td><td><code>{}</code></td><td class=\"mono\">{}</td></tr>\n",
                escape(&entry.direction),
                escape(&entry.method),
                escape(&entry.summary)
            ));
        }
        out.push_str("</tbody></table>\n");
    }
    out.push_str("</section>\n");
}

/// The run-results pane.
fn push_results(out: &mut String, results: Option<&ResultsSummary>) {
    out.push_str("<section id=\"results\" class=\"pane\">\n<h2>Run results</h2>\n");
    match results {
        None => out.push_str("<p class=\"empty\">no run report opened</p>\n"),
        Some(r) => {
            out.push_str(&format!(
                "<p class=\"summary\">{} total, <span class=\"ok\">{} passed</span>, \
                 <span class=\"bad\">{} failed</span>, {} skipped</p>\n",
                r.total, r.passed, r.failed, r.skipped
            ));
            if !r.rows.is_empty() {
                out.push_str(
                    "<table><thead><tr><th>test</th><th>status</th></tr></thead><tbody>\n",
                );
                for row in &r.rows {
                    out.push_str(&format!(
                        "<tr><td>{}</td><td class=\"{}\">{}</td></tr>\n",
                        escape(&row.name),
                        status_class(&row.status),
                        escape(&row.status)
                    ));
                }
                out.push_str("</tbody></table>\n");
            }
        }
    }
    out.push_str("</section>\n");
}

/// The security findings pane.
fn push_security(out: &mut String, findings: &[SecItem]) {
    out.push_str("<section id=\"security\" class=\"pane\">\n<h2>Security findings</h2>\n");
    if findings.is_empty() {
        out.push_str("<p class=\"empty\">no security report opened</p>\n");
    } else {
        out.push_str(
            "<table><thead><tr><th>id</th><th>severity</th><th>finding</th></tr></thead><tbody>\n",
        );
        for f in findings {
            out.push_str(&format!(
                "<tr><td><code>{}</code></td><td>{}</td><td>{}</td></tr>\n",
                escape(&f.id),
                escape(&f.severity),
                escape(&f.title)
            ));
        }
        out.push_str("</tbody></table>\n");
    }
    out.push_str("</section>\n");
}

/// The evidence metadata pane.
fn push_evidence(out: &mut String, data: &WorkbenchData) {
    out.push_str("<section id=\"evidence\" class=\"pane\">\n<h2>Evidence</h2>\n");
    match &data.evidence {
        None => out.push_str("<p class=\"empty\">no evidence pack opened</p>\n"),
        Some(e) => {
            out.push_str(&format!(
                "<ul><li>schema: <code>{}</code></li><li>generated: {}</li>\
                 <li>digest: <code>{}</code></li><li>sections: {}</li></ul>\n",
                escape(&e.schema),
                escape(&e.generated_at),
                escape(&e.digest),
                escape(&e.sections.join(", "))
            ));
        }
    }
    out.push_str("</section>\n");
}

/// The workflow pane: inspect to evidence, all local.
fn push_workflow(out: &mut String) {
    out.push_str(
        "<section id=\"workflow\" class=\"pane\">\n<h2>Inspect to evidence, locally</h2>\n",
    );
    out.push_str("<ol>\n");
    out.push_str(
        "<li><b>Inspect</b>: the Capabilities and Trace tabs show what the server exposes.</li>\n",
    );
    out.push_str("<li><b>Propose / assert</b>: <code>mcptest propose --tool &lt;name&gt;</code> drafts an expect block.</li>\n");
    out.push_str("<li><b>Run</b>: <code>mcptest run --config tests.yaml</code> fills the Results tab.</li>\n");
    out.push_str("<li><b>Evidence</b>: <code>mcptest evidence pack report.json</code> fills the Evidence tab.</li>\n");
    out.push_str("</ol>\n");
    out.push_str(
        "<p class=\"note\">No account, no network, no telemetry. Hosted dashboards, \
                  org history, RBAC, and shared workbenches are enterprise features.</p>\n",
    );
    out.push_str("</section>\n");
}

/// Map a status label to a CSS class.
fn status_class(status: &str) -> &'static str {
    match status {
        "passed" | "pass" => "ok",
        "failed" | "fail" => "bad",
        _ => "muted",
    }
}

/// HTML-escape text so embedded artifact data cannot break the page.
fn escape(text: &str) -> String {
    text.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
}

const STYLE: &str = "<style>\
body{font-family:system-ui,sans-serif;margin:0;color:#1a1a1a;background:#fafafa}\
header{padding:1rem 1.5rem;background:#1a1a2e;color:#fff}\
header h1{margin:0;font-size:1.2rem}.src{margin:.2rem 0 0;color:#aab;font-size:.85rem}\
.tabs{display:flex;gap:.25rem;padding:.5rem 1rem;background:#eee;flex-wrap:wrap}\
.tab{border:0;background:#ddd;padding:.4rem .8rem;cursor:pointer;border-radius:4px}\
.tab.active{background:#1a1a2e;color:#fff}\
.pane{display:none;padding:1.5rem}.pane.active{display:block}\
h2{font-size:1rem;margin:1.2rem 0 .4rem}table{border-collapse:collapse;width:100%;font-size:.9rem}\
th,td{border:1px solid #ddd;padding:.3rem .5rem;text-align:left;vertical-align:top}\
th{background:#f0f0f0}code{background:#f0f0f0;padding:.05rem .3rem;border-radius:3px}\
.mono{font-family:ui-monospace,monospace;font-size:.8rem;word-break:break-all}\
.empty,.muted{color:#999}.ok{color:#1a7f37}.bad{color:#c1121f}\
.note{color:#555;font-size:.85rem;margin-top:1rem}.summary{font-size:.95rem}\
</style>\n";

const SCRIPT: &str = "<script>\
document.querySelectorAll('.tab').forEach(function(t){\
t.addEventListener('click',function(){\
document.querySelectorAll('.tab').forEach(function(x){x.classList.remove('active')});\
document.querySelectorAll('.pane').forEach(function(x){x.classList.remove('active')});\
t.classList.add('active');\
document.getElementById(t.dataset.tab).classList.add('active');\
})});\
</script>\n";
