// editor.el — Round-trip graph editing API.
//
// Provides El functions for mutating the Engram graph (the live knowledge graph
// stored in-process via engram_* builtins). These functions are the mutation
// layer for graph editors — the CGI Studio Engram panel will call these to add,
// remove, and connect nodes without reloading the whole graph.
//
// All mutations write directly to the in-process Engram via engram_* builtins
// (see BOOTSTRAP.md §Engram Knowledge Graph).
//
// Drag interaction is NOT implemented here — that requires pointer-event
// handlers in JavaScript. When el-ui-compiler gains a JS backend, wire the
// move_node() position cache to the layout state keys used in layout.el.
//
// Public API:
//   graph_add_node(content, node_type, label, salience) -> String  // node_id or ""
//   graph_remove_node(node_id) -> String                           // "ok" or error JSON
//   graph_add_edge(from_id, to_id, weight_str, relation) -> String  // "ok" or error JSON
//   graph_remove_edge(from_id, to_id) -> String                    // "ok" or error JSON
//   graph_move_node(node_id, x_str, y_str) -> String               // "ok" (position cache)

// ── graph_add_node ────────────────────────────────────────────────────────────

fn graph_add_node(content: String, node_type: String, label: String, salience_str: String) -> String {
    if str_eq(content, "") {
        return "{\"error\":\"content is required\"}"
    }
    let sal: Float = if str_eq(salience_str, "") { int_to_float(1) } else { str_to_float(salience_str) }
    // engram_node_full: content, type, label, salience, importance, confidence, tier, tags
    let eff_label: String = if str_eq(label, "") { str_slice(content, 0, 40) } else { label }
    let eff_type: String = if str_eq(node_type, "") { "Entity" } else { node_type }
    let node_id: String = engram_node_full(content, eff_type, eff_label, sal, sal, int_to_float(1), "Working", "")
    if str_eq(node_id, "") {
        return "{\"error\":\"engram_node_full returned empty id\"}"
    }
    "{\"id\":\"" + node_id + "\"}"
}

// ── graph_remove_node ─────────────────────────────────────────────────────────

fn graph_remove_node(node_id: String) -> String {
    if str_eq(node_id, "") {
        return "{\"error\":\"node_id is required\"}"
    }
    // Check node exists
    let existing: String = engram_get_node(node_id)
    if str_eq(existing, "") {
        return "{\"error\":\"node not found\",\"id\":\"" + node_id + "\"}"
    }
    engram_forget(node_id)
    "{\"ok\":true,\"id\":\"" + node_id + "\"}"
}

// ── graph_add_edge ────────────────────────────────────────────────────────────

fn graph_add_edge(from_id: String, to_id: String, weight_str: String, relation: String) -> String {
    if str_eq(from_id, "") {
        return "{\"error\":\"from_id is required\"}"
    }
    if str_eq(to_id, "") {
        return "{\"error\":\"to_id is required\"}"
    }
    let w: Float = if str_eq(weight_str, "") { int_to_float(1) } else { str_to_float(weight_str) }
    let rel: String = if str_eq(relation, "") { "relates_to" } else { relation }
    // engram_connect(from, to, weight, relation)
    let edge_id: String = engram_connect(from_id, to_id, w, rel)
    if str_eq(edge_id, "") {
        return "{\"error\":\"engram_connect failed\",\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\"}"
    }
    "{\"ok\":true,\"edge_id\":\"" + edge_id + "\",\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\"}"
}

// ── graph_remove_edge ─────────────────────────────────────────────────────────

fn graph_remove_edge(from_id: String, to_id: String) -> String {
    if str_eq(from_id, "") {
        return "{\"error\":\"from_id is required\"}"
    }
    if str_eq(to_id, "") {
        return "{\"error\":\"to_id is required\"}"
    }
    // Check edge exists
    let existing: String = engram_edge_between(from_id, to_id)
    if str_eq(existing, "") {
        return "{\"error\":\"edge not found\",\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\"}"
    }
    // No engram_remove_edge builtin — use engram_forget on the edge node if
    // an edge ID was returned, otherwise surface a not-implemented note.
    // In practice, engram_forget(node_id) removes a node and its edges;
    // there is no "remove edge only" primitive yet.
    "{\"error\":\"remove_edge not yet supported by engram builtins — remove the node to remove all its edges\",\"from\":\"" + from_id + "\",\"to\":\"" + to_id + "\"}"
}

// ── graph_move_node ───────────────────────────────────────────────────────────
//
// Caches a node's screen position for use by the layout engine.
// When el-ui-compiler ships JS output, drag handlers will call this after
// pointer-up to persist the dragged position so the next render uses it as
// the initial position (preventing snap-back after re-layout).

fn graph_move_node(node_id: String, x_str: String, y_str: String) -> String {
    if str_eq(node_id, "") {
        return "{\"error\":\"node_id is required\"}"
    }
    // Store in process state — layout.el reads these keys as initial positions.
    state_set("node_x_" + node_id, x_str)
    state_set("node_y_" + node_id, y_str)
    // Also zero the velocity so the node doesn't immediately drift.
    state_set("node_vx_" + node_id, "0.0")
    state_set("node_vy_" + node_id, "0.0")
    "{\"ok\":true,\"id\":\"" + node_id + "\",\"x\":" + x_str + ",\"y\":" + y_str + "}"
}

// ── graph_node_info ───────────────────────────────────────────────────────────
//
// Retrieve full node data from Engram (for inspector panels).

fn graph_node_info(node_id: String) -> String {
    if str_eq(node_id, "") {
        return "{\"error\":\"node_id is required\"}"
    }
    let n: String = engram_get_node(node_id)
    if str_eq(n, "") {
        return "{\"error\":\"node not found\",\"id\":\"" + node_id + "\"}"
    }
    n
}

// ── graph_neighbors ───────────────────────────────────────────────────────────
//
// Return the neighbors of a node as a JSON array (for sub-graph drill-down).

fn graph_neighbors_json(node_id: String) -> String {
    if str_eq(node_id, "") {
        return "{\"error\":\"node_id is required\"}"
    }
    engram_neighbors(node_id)
}
