//! `PlantUML` component-diagram formatter for `Network`.

use std::fmt::Write as _;

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

/// Render a `Network` as a `PlantUML` component diagram.
///
/// Leaf nodes become `component "name: type" as node_<id>`; every composite; the root
/// group included; becomes a `rectangle "name" { … }` block, mirroring DOT's clusters
/// and Mermaid's subgraphs. `left to right direction` matches the left-to-right flow the
/// other two formatters set with `rankdir=LR` / `flowchart LR`.
///
/// Port-level detail rides on the edges, as in Mermaid: each connection is labeled
/// `: from_port ➔ to_port` (`node_1 --> node_3 : output ➔ input`). `PlantUML` `rectangle`
/// blocks carry no port anchors, so a group's own boundary ports have no visual identity:
/// each drawn connection is resolved through the D19 proxy chain to the real leaf
/// `component` it ultimately reaches (see [`Edges`]) and drawn as an ordinary
/// `node_<id> --> node_<id>` edge, exactly like a plain leaf-to-leaf connection.
pub(crate) fn render(network: &Network) -> String {
    let mut out = String::from("@startuml\nleft to right direction\n");

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

    out.push_str("@enduml\n");
    out
}

fn render_nodes(out: &mut String, network: &Network) {
    let mut depth = 0;
    let mut events = network.walk().peekable();

    while let Some(event) = events.next() {
        match event {
            WalkEvent::Enter(node) => {
                writeln!(
                    out,
                    "{}rectangle \"{}\" {{",
                    indent(depth),
                    escape(&node.name)
                )
                .unwrap();
                depth += 1;
            }
            WalkEvent::Leaf(node) => {
                writeln!(
                    out,
                    "{}component \"{}: {}\" as node_{}",
                    indent(depth),
                    escape(&node.name),
                    escape(&node.type_name),
                    node.id
                )
                .unwrap();
            }
            WalkEvent::Leave(_) => {
                depth -= 1;
                writeln!(out, "{}}}", indent(depth)).unwrap();
                // Blank line after a closed rectangle, unless the next line is another
                // closing brace; mirrors the repo's own "empty line after `}` unless the
                // next line is also `}`" layout convention.
                if matches!(
                    events.peek(),
                    Some(WalkEvent::Enter(_) | WalkEvent::Leaf(_))
                ) {
                    out.push('\n');
                }
            }
        }
    }
}

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,
            "node_{from_id} --> node_{to_id} : {} ➔ {}",
            escape(&from_port),
            escape(&to_port)
        )
        .unwrap();
    }
}
