// view.el — SVG renderer for the force-directed graph.
//
// Takes layout positions + node/edge data and produces a complete SVG string.
// Rendering is purely server-side — no DOM, no JavaScript.
//
// Public API:
//   graph_render_svg(nodes_json, edges_json, positions_json, width, height) -> String
//     Returns a complete <svg>...</svg> string ready for embedding or serving.
//
// Visual conventions:
//   - Background: #0d1117 (dark, matching Studio theme)
//   - Edges drawn first (below nodes)
//   - Nodes: filled circle with stroke, radius by salience
//   - Labels: truncated to 30 chars, below node, 10px IBM Plex Mono

// ── SVG helpers ───────────────────────────────────────────────────────────────

fn svg_open(width: Int, height: Int) -> String {
    "<svg xmlns=\"http://www.w3.org/2000/svg\" " +
    "width=\"" + int_to_str(width) + "\" " +
    "height=\"" + int_to_str(height) + "\" " +
    "viewBox=\"0 0 " + int_to_str(width) + " " + int_to_str(height) + "\" " +
    "style=\"background:#0d1117;font-family:'IBM Plex Mono',monospace\">"
}

fn svg_close() -> String { "</svg>" }

fn svg_defs() -> String {
    "<defs>" +
    "<filter id=\"glow\"><feGaussianBlur stdDeviation=\"2\" result=\"blur\"/>" +
    "<feMerge><feMergeNode in=\"blur\"/><feMergeNode in=\"SourceGraphic\"/></feMerge></filter>" +
    "</defs>"
}

// ── Edge rendering ────────────────────────────────────────────────────────────

fn svg_edge(x1: Float, y1: Float, x2: Float, y2: Float, weight: Float) -> String {
    let sw: Float = edge_stroke_width(weight)
    let sw_str: String = format_float(sw, 1)
    let x1s: String = format_float(x1, 1)
    let y1s: String = format_float(y1, 1)
    let x2s: String = format_float(x2, 1)
    let y2s: String = format_float(y2, 1)
    "<line " +
    "x1=\"" + x1s + "\" y1=\"" + y1s + "\" " +
    "x2=\"" + x2s + "\" y2=\"" + y2s + "\" " +
    "stroke=\"" + edge_stroke_color() + "\" " +
    "stroke-width=\"" + sw_str + "\" " +
    "stroke-opacity=\"0.7\"/>"
}

// ── Node rendering ────────────────────────────────────────────────────────────

fn svg_node(x: Float, y: Float, radius: Int, color: String, label: String) -> String {
    let xs: String = format_float(x, 1)
    let ys: String = format_float(y, 1)
    let rs: String = int_to_str(radius)
    let label_trunc: String = node_label_truncate(label)
    // Escape XML special chars in label
    let label_safe: String = str_replace(str_replace(str_replace(label_trunc, "&", "&amp;"), "<", "&lt;"), ">", "&gt;")
    let label_y: String = format_float(y + int_to_float(radius) + int_to_float(12), 1)
    "<circle cx=\"" + xs + "\" cy=\"" + ys + "\" r=\"" + rs + "\" " +
    "fill=\"" + color + "\" fill-opacity=\"0.85\" " +
    "stroke=\"" + color + "\" stroke-width=\"1.5\" filter=\"url(#glow)\"/>" +
    "<text x=\"" + xs + "\" y=\"" + label_y + "\" " +
    "text-anchor=\"middle\" font-size=\"9\" fill=\"#8b9aaa\" " +
    "font-family=\"IBM Plex Mono,monospace\">" + label_safe + "</text>"
}

// ── Position lookup ───────────────────────────────────────────────────────────
//
// Build a flat map from node_id -> position JSON in process state.
// Key: "pos_<id>" -> "{\"x\":...,\"y\":...}"

fn build_position_index(positions_json: String) -> Bool {
    let count: Int = json_array_len(positions_json)
    let i: Int = 0
    while i < count {
        let pos: String = json_array_get(positions_json, i)
        let id: String = json_get_string(pos, "id")
        if !str_eq(id, "") {
            state_set("pos_" + id, pos)
        }
        let i = i + 1
    }
    true
}

fn get_pos_x(node_id: String) -> Float {
    let pos: String = state_get("pos_" + node_id)
    if str_eq(pos, "") { return int_to_float(0) }
    json_get_float(pos, "x")
}

fn get_pos_y(node_id: String) -> Float {
    let pos: String = state_get("pos_" + node_id)
    if str_eq(pos, "") { return int_to_float(0) }
    json_get_float(pos, "y")
}

// ── Public: graph_render_svg ──────────────────────────────────────────────────

fn graph_render_svg(nodes_json: String, edges_json: String, positions_json: String, width: Int, height: Int) -> String {
    // Index positions by node id
    build_position_index(positions_json)

    let out: String = svg_open(width, height)
    let out = out + svg_defs()

    // ── Draw edges (behind nodes) ─────────────────────────────────────────────
    let edge_count: Int = json_array_len(edges_json)
    let i: Int = 0
    while i < edge_count {
        let e: String = json_array_get(edges_json, i)
        let src: String = edge_source(e)
        let tgt: String = edge_target(e)
        let w: Float = edge_weight(e)
        // Only draw if both endpoints have positions
        let src_pos: String = state_get("pos_" + src)
        let tgt_pos: String = state_get("pos_" + tgt)
        if !str_eq(src_pos, "") {
            if !str_eq(tgt_pos, "") {
                let x1: Float = get_pos_x(src)
                let y1: Float = get_pos_y(src)
                let x2: Float = get_pos_x(tgt)
                let y2: Float = get_pos_y(tgt)
                let out = out + svg_edge(x1, y1, x2, y2, w)
            }
        }
        let i = i + 1
    }

    // ── Draw nodes (over edges) ───────────────────────────────────────────────
    let node_count: Int = json_array_len(nodes_json)
    let j: Int = 0
    while j < node_count {
        let n: String = json_array_get(nodes_json, j)
        let id: String = node_id(n)
        let lbl: String = node_label(n)
        let ntype: String = node_type_field(n)
        let sal: Float = node_salience(n)
        let color: String = node_color(ntype)
        let radius: Int = node_radius_int(sal)
        let pos: String = state_get("pos_" + id)
        if !str_eq(pos, "") {
            let x: Float = get_pos_x(id)
            let y: Float = get_pos_y(id)
            let out = out + svg_node(x, y, radius, color, lbl)
        }
        let j = j + 1
    }

    let out = out + svg_close()
    out
}
