// arbor-diagram — diagram intermediate representation (AST + IR).
//
// Where arbor-core supplies the *.arbor source-language model — Mermaid-safe
// IDs, ArborShape strings, ArborEdgeKind strings, and the lowered "diagram-
// form" map — arbor-diagram exposes the same lowered model as the canonical
// IR for downstream serializers (arbor-render and any future Mermaid-style
// emitter). The two vessels overlap by design: arbor-core is responsible for
// *naming* the schema; arbor-diagram is responsible for *building* values
// against it.
//
// The Rust crate ships small AST builder structs (`DiagramNode::new`,
// `DiagramEdge::with_label`, `DiagramGraph::add_node`). El has no method
// chaining, no Default::default(), no enum types. The El idiom is a stack
// of immutable maps with explicit constructor + with_* helpers that take
// the value and return a freshly-allocated map.
//
// Public surface:
//   make_node(id, label) → DiagramNode
//     with_shape(node, shape)       → DiagramNode
//     with_sublabel(node, sublabel) → DiagramNode
//     with_style(node, fill, stroke, color) → DiagramNode
//
//   make_edge(from, to) → DiagramEdge
//     with_label(edge, label)
//     with_line(edge, line)         // "solid"/"dashed"/"dotted"/"thick"
//     with_arrow(edge, arrow)       // "forward"/"backward"/"both"/"none"
//
//   make_group(id, label) → DiagramGroup
//     with_node(group, node_id)
//     with_nodes(group, [node_id])
//     with_direction(group, dir)
//
//   make_graph(title) → DiagramGraph
//     with_direction(graph, dir)
//     graph_add_node(graph, node)   → DiagramGraph
//     graph_add_edge(graph, edge)   → DiagramGraph
//     graph_add_group(graph, group) → DiagramGraph
//     graph_node(graph, id)         → DiagramNode | empty map
//
// Shape vocabulary (lowered): see arbor-core. The local copy here mirrors
// the table in arbor-core/src/main.el so this vessel is hermetic.

// ── NodeShape vocabulary ────────────────────────────────────────────────────

fn node_shape_rectangle() -> String { "rectangle" }
fn node_shape_rounded_rect() -> String { "rounded_rect" }
fn node_shape_stadium() -> String { "stadium" }
fn node_shape_cylinder() -> String { "cylinder" }
fn node_shape_diamond() -> String { "diamond" }
fn node_shape_parallelogram() -> String { "parallelogram" }
fn node_shape_database() -> String { "database" }
fn node_shape_subroutine() -> String { "subroutine" }

fn node_shape_valid(s: String) -> Bool {
    if str_eq(s, "rectangle") { return true }
    if str_eq(s, "rounded_rect") { return true }
    if str_eq(s, "stadium") { return true }
    if str_eq(s, "cylinder") { return true }
    if str_eq(s, "diamond") { return true }
    if str_eq(s, "parallelogram") { return true }
    if str_eq(s, "database") { return true }
    if str_eq(s, "subroutine") { return true }
    false
}

// ── EdgeLine vocabulary ─────────────────────────────────────────────────────

fn edge_line_solid() -> String { "solid" }
fn edge_line_dashed() -> String { "dashed" }
fn edge_line_dotted() -> String { "dotted" }
fn edge_line_thick() -> String { "thick" }

fn edge_line_valid(s: String) -> Bool {
    if str_eq(s, "solid") { return true }
    if str_eq(s, "dashed") { return true }
    if str_eq(s, "dotted") { return true }
    if str_eq(s, "thick") { return true }
    false
}

// ── EdgeArrow vocabulary ────────────────────────────────────────────────────

fn edge_arrow_forward() -> String { "forward" }
fn edge_arrow_backward() -> String { "backward" }
fn edge_arrow_both() -> String { "both" }
fn edge_arrow_none() -> String { "none" }

fn edge_arrow_valid(s: String) -> Bool {
    if str_eq(s, "forward") { return true }
    if str_eq(s, "backward") { return true }
    if str_eq(s, "both") { return true }
    if str_eq(s, "none") { return true }
    false
}

// ── Direction vocabulary ────────────────────────────────────────────────────

fn direction_top_down() -> String { "top-down" }
fn direction_left_right() -> String { "left-right" }
fn direction_right_left() -> String { "right-left" }
fn direction_bottom_up() -> String { "bottom-up" }

fn direction_valid(s: String) -> Bool {
    if str_eq(s, "top-down") { return true }
    if str_eq(s, "left-right") { return true }
    if str_eq(s, "right-left") { return true }
    if str_eq(s, "bottom-up") { return true }
    false
}

// ── DiagramNode ─────────────────────────────────────────────────────────────

fn make_node(id: String, label: String) -> Map<String, Any> {
    {
        "id":           id,
        "label":        label,
        "sublabel":     "",
        "shape":        "rectangle",
        "style_fill":   "",
        "style_stroke": "",
        "style_color":  ""
    }
}

fn with_shape(node: Map<String, Any>, shape: String) -> Map<String, Any> {
    {
        "id":           node["id"],
        "label":        node["label"],
        "sublabel":     node["sublabel"],
        "shape":        shape,
        "style_fill":   node["style_fill"],
        "style_stroke": node["style_stroke"],
        "style_color":  node["style_color"]
    }
}

fn with_sublabel(node: Map<String, Any>, sublabel: String) -> Map<String, Any> {
    {
        "id":           node["id"],
        "label":        node["label"],
        "sublabel":     sublabel,
        "shape":        node["shape"],
        "style_fill":   node["style_fill"],
        "style_stroke": node["style_stroke"],
        "style_color":  node["style_color"]
    }
}

fn with_style(node: Map<String, Any>, fill: String, stroke: String, color: String) -> Map<String, Any> {
    {
        "id":           node["id"],
        "label":        node["label"],
        "sublabel":     node["sublabel"],
        "shape":        node["shape"],
        "style_fill":   fill,
        "style_stroke": stroke,
        "style_color":  color
    }
}

// ── DiagramEdge ─────────────────────────────────────────────────────────────

fn make_edge(from: String, to: String) -> Map<String, Any> {
    {
        "from":  from,
        "to":    to,
        "label": "",
        "line":  "solid",
        "arrow": "forward"
    }
}

fn with_label(edge: Map<String, Any>, label: String) -> Map<String, Any> {
    {
        "from":  edge["from"],
        "to":    edge["to"],
        "label": label,
        "line":  edge["line"],
        "arrow": edge["arrow"]
    }
}

fn with_line(edge: Map<String, Any>, line: String) -> Map<String, Any> {
    {
        "from":  edge["from"],
        "to":    edge["to"],
        "label": edge["label"],
        "line":  line,
        "arrow": edge["arrow"]
    }
}

fn with_arrow(edge: Map<String, Any>, arrow: String) -> Map<String, Any> {
    {
        "from":  edge["from"],
        "to":    edge["to"],
        "label": edge["label"],
        "line":  edge["line"],
        "arrow": arrow
    }
}

// ── DiagramGroup ────────────────────────────────────────────────────────────

fn make_group(id: String, label: String) -> Map<String, Any> {
    let empty: [String] = native_list_empty()
    {
        "id":        id,
        "label":     label,
        "node_ids":  empty,
        "direction": ""
    }
}

fn with_node(group: Map<String, Any>, node_id: String) -> Map<String, Any> {
    let cur: [String] = group["node_ids"]
    let next: [String] = native_list_append(cur, node_id)
    {
        "id":        group["id"],
        "label":     group["label"],
        "node_ids":  next,
        "direction": group["direction"]
    }
}

fn with_nodes(group: Map<String, Any>, ids: [String]) -> Map<String, Any> {
    let cur: [String] = group["node_ids"]
    let n: Int = el_list_len(ids)
    let i = 0
    while i < n {
        let cur = native_list_append(cur, get(ids, i))
        let i = i + 1
    }
    {
        "id":        group["id"],
        "label":     group["label"],
        "node_ids":  cur,
        "direction": group["direction"]
    }
}

fn with_group_direction(group: Map<String, Any>, dir: String) -> Map<String, Any> {
    {
        "id":        group["id"],
        "label":     group["label"],
        "node_ids":  group["node_ids"],
        "direction": dir
    }
}

// ── DiagramGraph ────────────────────────────────────────────────────────────

fn make_graph(title: String) -> Map<String, Any> {
    let empty_n: [Map<String, Any>] = native_list_empty()
    let empty_e: [Map<String, Any>] = native_list_empty()
    let empty_g: [Map<String, Any>] = native_list_empty()
    {
        "title":     title,
        "direction": "top-down",
        "nodes":     empty_n,
        "edges":     empty_e,
        "groups":    empty_g
    }
}

fn with_direction(graph: Map<String, Any>, dir: String) -> Map<String, Any> {
    {
        "title":     graph["title"],
        "direction": dir,
        "nodes":     graph["nodes"],
        "edges":     graph["edges"],
        "groups":    graph["groups"]
    }
}

fn graph_add_node(graph: Map<String, Any>, node: Map<String, Any>) -> Map<String, Any> {
    let cur: [Map<String, Any>] = graph["nodes"]
    let next: [Map<String, Any>] = native_list_append(cur, node)
    {
        "title":     graph["title"],
        "direction": graph["direction"],
        "nodes":     next,
        "edges":     graph["edges"],
        "groups":    graph["groups"]
    }
}

fn graph_add_edge(graph: Map<String, Any>, edge: Map<String, Any>) -> Map<String, Any> {
    let cur: [Map<String, Any>] = graph["edges"]
    let next: [Map<String, Any>] = native_list_append(cur, edge)
    {
        "title":     graph["title"],
        "direction": graph["direction"],
        "nodes":     graph["nodes"],
        "edges":     next,
        "groups":    graph["groups"]
    }
}

fn graph_add_group(graph: Map<String, Any>, group: Map<String, Any>) -> Map<String, Any> {
    let cur: [Map<String, Any>] = graph["groups"]
    let next: [Map<String, Any>] = native_list_append(cur, group)
    {
        "title":     graph["title"],
        "direction": graph["direction"],
        "nodes":     graph["nodes"],
        "edges":     graph["edges"],
        "groups":    next
    }
}

// Find a node by id. Returns an empty map (no "id" field) when not present.
fn graph_node(graph: Map<String, Any>, id: String) -> Map<String, Any> {
    let nodes: [Map<String, Any>] = graph["nodes"]
    let n: Int = el_list_len(nodes)
    let i = 0
    while i < n {
        let nd: Map<String, Any> = get(nodes, i)
        let nid: String = nd["id"]
        if str_eq(nid, id) { return nd }
        let i = i + 1
    }
    let empty: Map<String, Any> = el_map_new(0)
    empty
}

// ── Smoke test ──────────────────────────────────────────────────────────────

fn fail(label: String, got: String, want: String) -> Int {
    println("FAIL " + label + " got=[" + got + "] want=[" + want + "]")
    state_set("smoke_failures", "1")
    0
}

fn check_eq(label: String, got: String, want: String) -> Int {
    if got == want {
        println("ok   " + label + " = " + got)
        return 1
    }
    fail(label, got, want)
}

// Vocabulary self-checks
check_eq("shape rectangle valid",
    bool_to_str(node_shape_valid("rectangle")), "true")
check_eq("shape hexagon invalid",
    bool_to_str(node_shape_valid("hexagon")), "false")
check_eq("line dashed valid",
    bool_to_str(edge_line_valid("dashed")), "true")
check_eq("arrow both valid",
    bool_to_str(edge_arrow_valid("both")), "true")
check_eq("dir top-down valid",
    bool_to_str(direction_valid("top-down")), "true")

// Node builder
let n0: Map<String, Any> = make_node("svc", "Service")
check_eq("node default shape", n0["shape"], "rectangle")
check_eq("node default sublabel empty", n0["sublabel"], "")

let n1: Map<String, Any> = with_shape(n0, "cylinder")
check_eq("node with_shape", n1["shape"], "cylinder")
check_eq("node id preserved", n1["id"], "svc")

let n2: Map<String, Any> = with_sublabel(n1, "v0.1.0")
check_eq("node with_sublabel", n2["sublabel"], "v0.1.0")

let n3: Map<String, Any> = with_style(n2, "#0052A0", "#0052A0", "#ffffff")
check_eq("node style fill", n3["style_fill"], "#0052A0")
check_eq("node style color", n3["style_color"], "#ffffff")

// Edge builder
let e0: Map<String, Any> = make_edge("a", "b")
check_eq("edge default line", e0["line"], "solid")
check_eq("edge default arrow", e0["arrow"], "forward")
let e1: Map<String, Any> = with_line(e0, "dashed")
let e2: Map<String, Any> = with_arrow(e1, "both")
let e3: Map<String, Any> = with_label(e2, "calls")
check_eq("edge line", e3["line"], "dashed")
check_eq("edge arrow", e3["arrow"], "both")
check_eq("edge label", e3["label"], "calls")

// Group builder
let g0: Map<String, Any> = make_group("core", "Application Core")
let g1: Map<String, Any> = with_node(g0, "api")
let g2: Map<String, Any> = with_node(g1, "svc")
let ids2: [String] = g2["node_ids"]
check_eq("group with two nodes", int_to_str(el_list_len(ids2)), "2")

let g3: Map<String, Any> = make_group("infra", "Infrastructure")
let extras: [String] = native_list_empty()
let extras = native_list_append(extras, "db")
let extras = native_list_append(extras, "cache")
let g4: Map<String, Any> = with_nodes(g3, extras)
let ids4: [String] = g4["node_ids"]
check_eq("group with_nodes appends", int_to_str(el_list_len(ids4)), "2")

// Graph builder + lookup
let G0: Map<String, Any> = make_graph("System")
let G1: Map<String, Any> = with_direction(G0, "left-right")
let G2: Map<String, Any> = graph_add_node(G1, n3)
let nb: Map<String, Any> = make_node("b", "Backend")
let G3: Map<String, Any> = graph_add_node(G2, nb)
let G4: Map<String, Any> = graph_add_edge(G3, e3)
let G5: Map<String, Any> = graph_add_group(G4, g4)

check_eq("graph title", G5["title"], "System")
check_eq("graph direction", G5["direction"], "left-right")
let gn: [Map<String, Any>] = G5["nodes"]
let ge: [Map<String, Any>] = G5["edges"]
let gg: [Map<String, Any>] = G5["groups"]
check_eq("graph nodes count", int_to_str(el_list_len(gn)), "2")
check_eq("graph edges count", int_to_str(el_list_len(ge)), "1")
check_eq("graph groups count", int_to_str(el_list_len(gg)), "1")

let found: Map<String, Any> = graph_node(G5, "svc")
check_eq("graph_node found", found["id"], "svc")
let missing: Map<String, Any> = graph_node(G5, "nonexistent")
let missing_id: String = missing["id"]
if str_len(missing_id) == 0 {
    println("ok   graph_node missing returns empty")
} else {
    println("FAIL graph_node missing returned: " + missing_id)
    state_set("smoke_failures", "1")
}

println("")
let failures: String = state_get("smoke_failures")
if str_eq(failures, "1") {
    println("arbor-diagram: FAILED")
    exit_program(1)
} else {
    println("arbor-diagram: ok")
}
