use super::render::{short_type, source};
use pipecircuit::{BindingDescription, PipeDescription};
fn escape(value: &str) -> String {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
fn documentation(lines: &[&str]) -> String {
escape(
&lines
.iter()
.map(|line| line.trim())
.collect::<Vec<_>>()
.join("\n"),
)
}
pub(super) fn render(
board: &str,
pipes: &[PipeDescription],
bindings: Option<&[BindingDescription]>,
) -> String {
let title = escape(&short_type(board));
let mut output = format!(
r#"<!doctype html>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'">
<title>{title} — Pipecircuit inspection</title>
<style>
:root {{ color-scheme: light dark; font: 16px/1.5 system-ui, sans-serif; background: #101923; color: #e7eef6; }}
* {{ box-sizing: border-box; }}
body {{ max-width: 1100px; margin: auto; padding: 32px 24px 64px; }}
h1 {{ font-size: clamp(1.5rem, 4vw, 2.5rem); margin: .3rem 0; overflow-wrap: anywhere; }}
h2, h3, p {{ margin: .5rem 0; }}
.eyebrow {{ color: #a7c5de; letter-spacing: .12em; font-size: .8rem; text-transform: uppercase; }}
.intro, .scope {{ color: #bacbda; max-width: 80ch; }}
.board {{ text-align: center; background: #1d3042; border: 2px solid #8eaeca; border-radius: 12px; margin-top: 24px; padding: 18px; }}
.pipes {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 310px), 1fr)); gap: 24px; }}
.connections {{ display: grid; grid-template-columns: 1fr 1fr; gap: 12px; text-align: center; margin: 8px 0; }}
.direction {{ color: #bdeed9; }}
.arrow {{ font-size: 2rem; line-height: 1.2; }}
code {{ font: .88em ui-monospace, monospace; overflow-wrap: anywhere; white-space: pre-wrap; }}
.connections code {{ display: block; }}
.pipe {{ border: 2px solid #75caaa; border-radius: 12px; padding: 18px; background: #172c2a; }}
.note {{ white-space: pre-line; }}
.physical {{ background: #192c42; border: 1px solid #8eb9ea; border-radius: 8px; padding: 14px; margin-top: 14px; }}
summary {{ cursor: pointer; color: #a8cff8; margin-top: 10px; }}
details dl {{ margin: 10px 0 0; }}
dt {{ color: #bacbda; margin-top: 8px; }}
dd {{ margin: 0; }}
input {{ accent-color: #75caaa; width: 1.1rem; height: 1.1rem; }}
input:focus-visible, summary:focus-visible {{ outline: 3px solid #ffce70; outline-offset: 4px; }}
#placement:not(:checked) ~ .pipes .physical {{ display: none; }}
.scope {{ margin-top: 24px; padding-top: 16px; border-top: 1px solid #456076; }}
@media print {{ :root {{ color-scheme: light; background: white; color: black; }} .pipe, .board, .physical {{ background: white; color: black; }} .intro, .scope, .eyebrow, .direction, dt, summary {{ color: #234; }} }}
</style></head><body>
<header><p><strong>Experimental inspection prototype</strong> — development review only; not ready for application consumption.</p><div class="eyebrow">Pipecircuit · declared program</div><h1>{title}</h1>
<p class="intro">Start with the named pipes. Each has an independent family of messages it emits and receives. The switchboard chooses where emitted messages go next.</p></header>
<main><section class="board" aria-label="Switchboard"><div class="eyebrow">Semantic routing</div><h2>Switchboard</h2><code>{title}</code></section>
"#
);
if bindings.is_some() {
output.push_str("<input id=\"placement\" type=\"checkbox\" checked> <label for=\"placement\">Show physical installation</label>\n");
}
output.push_str("<div class=\"pipes\">\n");
for pipe in pipes {
let name = escape(pipe.name);
let input = escape(&short_type(pipe.input));
let emitted = escape(&short_type(pipe.output));
let description = documentation(pipe.documentation);
let protocol = escape(pipe.protocol);
let declaration = escape(&source(pipe.declaration));
let full_input = escape(pipe.input);
let full_output = escape(pipe.output);
output.push_str(&format!(
r#"<section aria-label="Pipe {name}">
<div class="connections">
<div class="direction"><div class="arrow" aria-hidden="true">↑</div>Emits to switchboard<code>{emitted}</code><small>Pipe::Out</small></div>
<div class="direction"><div class="arrow" aria-hidden="true">↓</div>Receives from switchboard<code>{input}</code><small>Pipe::In</small></div>
</div><div class="pipe"><div class="eyebrow">Logical pipe</div><h2>{name}</h2><p class="note">{description}</p>
<details><summary>Protocol and declaration</summary><dl>
<dt>Protocol</dt><dd><code>{protocol}</code></dd>
<dt>Emits</dt><dd><code>{full_output}</code></dd>
<dt>Receives</dt><dd><code>{full_input}</code></dd>
<dt>Declared at</dt><dd><code>{declaration}</code></dd>
</dl></details></div>
"#
));
if let Some(binding) = bindings.and_then(|all| all.iter().find(|b| b.name == pipe.name)) {
let description = if binding.documentation.is_empty() {
escape(&short_type(binding.driver))
} else {
documentation(binding.documentation)
};
let driver = escape(binding.driver);
let binding_type = escape(binding.binding);
let location = escape(&source(binding.declaration));
output.push_str(&format!(
r#"<div class="physical"><div class="eyebrow">Physical installation</div>
<p class="note">{description}</p><details><summary>Transport types and wiring</summary><dl>
<dt>Driver</dt><dd><code>{driver}</code></dd><dt>Binding</dt><dd><code>{binding_type}</code></dd>
<dt>Wired at</dt><dd><code>{location}</code></dd></dl></details></div>
"#
));
}
output.push_str("</section>\n");
}
output.push_str("</div><footer class=\"scope\"><p><strong>How to read this:</strong> arrows identify the declared message families at each boundary. They do not assert a route between two pipes, a request/reply pairing, delivery order or runtime reachability.</p><p>These declarations do not measure transport occupancy, health, remote placement or wire compatibility. This page is a static inspection artifact, not a live monitor. Documentation comes from the Rust declarations; it is author explanation, not a compiler-proven behavior claim.</p></footer></main></body></html>\n");
output
}