//! Mermaid flowchart formatter for `Network`.

use std::fmt::Write as _;

use super::{Edges, indent};
use crate::interpreter::network::{Network, NodeId, WalkEvent};

/// Render a `Network` as a Mermaid `flowchart` diagram.
///
/// Leaf nodes render as a plain box (`node_<id>["name: type"]`); every composite; the
/// root group included; becomes a `subgraph cluster_<id>["name"]` block, mirroring
/// DOT's clustering, so the encompassing group's own box is visible and nesting depth
/// reads directly off the diagram.
///
/// Mermaid has no record/port-anchor shape like DOT, so port-level detail rides on the
/// edges instead: each connection is labeled `from_port ➔ to_port`
/// (`node_1 -->|output ➔ input| node_2`). A DOT-style boundary-port pin node was tried
/// and rejected: Mermaid's auto-layout places small pin nodes wherever its layout
/// engine sees fit, which reads as "weirdly placed" for anything short and numerous.
///
/// A group's own boundary ports have no visual identity: a connection touching one is
/// resolved straight through to the real node it ultimately connects to inside the
/// group (a leaf, or a deeper nested group's leaf), following the D19 proxy chain (see
/// [`Edges`]). Terminating edges at the group's own `cluster_<id>` instead; which
/// Mermaid does support, attaching an edge directly to a subgraph's boundary; was
/// tried first and rejected: a group with several boundary ports rendered as a pile of
/// arrows all converging on the same box edge, which reads as "the group" without
/// showing *which* member each one is actually headed to. The group is still drawn as
/// a containing box, but connections resolve to the leaves inside it, so the group's
/// grouping and the connection's real endpoint are both visible at once.
pub(crate) fn render(network: &Network) -> String {
    let mut out = String::from("flowchart LR\n");

    if network.root.is_some() {
        render_nodes(&mut out, network);
        render_edges(&mut out, network);
    }

    out
}

fn render_nodes(out: &mut String, network: &Network) {
    let mut depth = 1;

    for event in network.walk() {
        match event {
            WalkEvent::Enter(node) => {
                let pad = indent(depth);
                writeln!(
                    out,
                    "{pad}subgraph cluster_{} [\"{}\"]",
                    node.id,
                    escape(&node.name)
                )
                .unwrap();
                writeln!(out, "{pad}    direction LR").unwrap();
                depth += 1;
            }
            WalkEvent::Leaf(node) => {
                writeln!(
                    out,
                    "{}node_{}[\"{}: {}\"]",
                    indent(depth),
                    node.id,
                    escape(&node.name),
                    escape(&node.type_name)
                )
                .unwrap();
            }
            WalkEvent::Leave(_) => {
                depth -= 1;
                writeln!(out, "{}end", indent(depth)).unwrap();
            }
        }
    }
}

fn render_edges(out: &mut String, network: &Network) {
    let edges = Edges::new(network);

    let mut wrote_any = false;
    for conn in edges.visible() {
        if !wrote_any {
            out.push('\n');
            wrote_any = true;
        }
        let (from_id, from_port) = edges.resolve(&conn.from);
        let (to_id, to_port) = edges.resolve(&conn.to);
        writeln!(
            out,
            "    {} -->|{} ➔ {}| {}",
            anchor(from_id, &edges),
            escape(&from_port),
            escape(&to_port),
            anchor(to_id, &edges)
        )
        .unwrap();
    }
}

/// Mermaid identifier a `NodeId` renders as: its own box for a leaf, its `subgraph` for
/// a group (groups never get a box of their own, only the cluster; a group endpoint is
/// a defensive fallback only, see [`Edges::resolve`]).
fn anchor(node_id: NodeId, edges: &Edges<'_>) -> String {
    if edges.is_group(node_id) {
        format!("cluster_{node_id}")
    } else {
        format!("node_{node_id}")
    }
}

/// Escape a string for embedding in a Mermaid quoted label. Unlike DOT/PlantUML,
/// Mermaid does not support backslash-escaping inside quoted text; a literal `"`
/// needs the HTML-entity form `#quot;`, and `#` itself needs escaping first (as
/// `#35;`) since it's what introduces entity syntax in the first place. Real
/// newlines are replaced with Mermaid's `<br/>` line-break tag, since a bare newline
/// inside a label breaks the diagram's line-oriented syntax. `|` becomes `#124;` so
/// a label can never terminate an edge-label (`-->|…|`) early.
fn escape(s: &str) -> String {
    s.replace('#', "#35;")
        .replace('"', "#quot;")
        .replace('|', "#124;")
        .replace('\n', "<br/>")
}
