/* ATLAS — Asset detail view.
   Headline metric cards (formula + scope) + editable inputs + live calculated outputs.
   Edit mode lets the user (and, via the same ops, the agent) add/delete inputs and
   add/edit/delete output formulas. All structural changes go through ATLAS.ops.* so the
   validation/guards live in one place. */
(function () {
  "use strict";
  const { useState, useEffect, useMemo } = React;
  const h = React.createElement;
  const fmt = ATLAS.fmtNum;

  const SCOPE = {
    1: { label: "SCOPE 1", bg: "#063F39", fg: "#FFFFFF", desc: "Direct — fuel & calcination" },
    2: { label: "SCOPE 2", bg: "#22867C", fg: "#FFFFFF", desc: "Indirect — purchased electricity" },
    3: { label: "SCOPE 3", bg: "#89E4DA", fg: "#063F39", desc: "Value chain" },
  };

  function ScopeChip({ scope, small }) {
    if (!scope) return null;
    const s = SCOPE[scope];
    return h("span", { className: "scope-chip", style: {
      background: s.bg, color: s.fg, fontSize: small ? 9 : 10,
      padding: small ? "2px 6px" : "3px 8px", letterSpacing: ".1em",
      fontFamily: "var(--font-mono)", fontWeight: 600, whiteSpace: "nowrap",
    } }, s.label);
  }

  // editable numeric cell. Commits live on every keystroke so the calculated
  // metrics + headline cards recompute as you type; blur normalises display.
  function InputCell({ value, onCommit }) {
    const [buf, setBuf] = useState(String(value));
    const [editing, setEditing] = useState(false);
    useEffect(() => { if (!editing) setBuf(String(value)); }, [value, editing]);
    return h("input", {
      className: "in-cell", value: editing ? buf : trimNum(value),
      inputMode: "decimal",
      onFocus: (e) => { setEditing(true); setBuf(String(value)); e.target.select(); },
      onChange: (e) => {
        const raw = e.target.value;
        setBuf(raw);
        const n = parseFloat(raw);
        if (!isNaN(n)) onCommit(n);
      },
      onBlur: () => { setEditing(false); const n = parseFloat(buf); onCommit(isNaN(n) ? 0 : n); },
      onKeyDown: (e) => { if (e.key === "Enter") e.target.blur(); },
    });
  }
  function trimNum(x) {
    if (x === 0) return "0";
    if (Number.isInteger(x)) return x.toLocaleString("en-IN");
    return Number(x).toLocaleString("en-IN", { maximumFractionDigits: 4 });
  }

  function MetricCard({ metric, value, driven }) {
    return h("div", { className: "metric-card" + (driven ? " driven" : " passive"), tabIndex: 0 },
      h("div", { className: "mc-top" },
        h("span", { className: "mc-label" }, metric.label),
        h(ScopeChip, { scope: metric.scope }),
      ),
      h("div", { className: "mc-value-row" },
        h("span", { className: "mc-value" }, metric.fmt(value)),
        h("span", { className: "mc-unit" }, metric.unit),
      ),
      h("div", { className: "mc-formula" }, metric.formula),
    );
  }

  function InputRow({ row, value, onCommit, editMode, onDelete }) {
    return h("tr", { className: "in-row" },
      h("td", { className: "io-label" }, row.label),
      h("td", { className: "io-unit" }, row.unit),
      h("td", { className: "io-val" }, h(InputCell, { value, onCommit })),
      editMode ? h("td", { className: "io-del" },
        h("button", { className: "row-del", type: "button", title: "Delete input", onClick: () => onDelete(row.id) }, "×"),
      ) : null,
    );
  }

  function OutputRow({ row, value, editMode, onDelete, onEdit }) {
    return h("tr", { className: "out-row" + (row.drives ? " key-out" : ""), tabIndex: 0 },
      h("td", { className: "io-label" },
        h("div", null, row.label),
        h("div", { className: "out-formula" }, row.formula || row.formulaText),
      ),
      h("td", { className: "io-unit" }, row.unit),
      h("td", { className: "io-val out-val" }, fmt(value)),
      h("td", { className: "io-scope" },
        editMode
          ? h("div", { className: "row-actions" },
              h("button", { className: "row-edit", type: "button", title: "Edit formula", onClick: () => onEdit(row) }, "edit"),
              h("button", { className: "row-del", type: "button", title: "Delete output", onClick: () => onDelete(row.id) }, "×"),
            )
          : h(ScopeChip, { scope: row.scope, small: true }),
      ),
    );
  }

  // small inline form for adding an input to a group
  function AddInputForm({ assetId, group, onAdd }) {
    const [open, setOpen] = useState(false);
    const [f, setF] = useState({ id: "", label: "", unit: "", value: "0" });
    if (!open) return h("button", { className: "add-row-btn", type: "button", onClick: () => setOpen(true) }, "+ add input");
    const set = (k) => (e) => setF(Object.assign({}, f, { [k]: e.target.value }));
    return h("div", { className: "edit-form" },
      h("input", { className: "ef-in", placeholder: "id (e.g. steamFlow)", value: f.id, onChange: set("id") }),
      h("input", { className: "ef-in", placeholder: "label", value: f.label, onChange: set("label") }),
      h("input", { className: "ef-in narrow", placeholder: "unit", value: f.unit, onChange: set("unit") }),
      h("input", { className: "ef-in narrow", placeholder: "value", value: f.value, onChange: set("value") }),
      h("div", { className: "ef-actions" },
        h("button", { className: "ef-ok", type: "button", onClick: () => {
          if (onAdd({ assetId, input: { id: f.id.trim(), label: f.label.trim() || f.id.trim(), unit: f.unit.trim(), group, value: f.value } }))
            { setF({ id: "", label: "", unit: "", value: "0" }); setOpen(false); }
        } }, "add"),
        h("button", { className: "ef-cancel", type: "button", onClick: () => setOpen(false) }, "cancel"),
      ),
    );
  }

  // inline form for adding/editing an output formula
  function OutputForm({ assetId, existing, onSubmit, onClose }) {
    const [f, setF] = useState(existing
      ? { id: existing.id, label: existing.label, unit: existing.unit || "", scope: existing.scope || "", expr: existing.expr || "", formulaText: existing.formulaText || "" }
      : { id: "", label: "", unit: "", scope: "", expr: "", formulaText: "" });
    const set = (k) => (e) => setF(Object.assign({}, f, { [k]: e.target.value }));
    const editing = !!existing;
    return h("div", { className: "edit-form out-form" },
      h("div", { className: "ef-title" }, editing ? "Edit output: " + existing.id : "New output"),
      !editing ? h("input", { className: "ef-in", placeholder: "id (e.g. whrsPower)", value: f.id, onChange: set("id") }) : null,
      h("input", { className: "ef-in", placeholder: "label", value: f.label, onChange: set("label") }),
      h("div", { className: "ef-row" },
        h("input", { className: "ef-in narrow", placeholder: "unit", value: f.unit, onChange: set("unit") }),
        h("select", { className: "ef-in narrow", value: f.scope, onChange: set("scope") },
          h("option", { value: "" }, "no scope"),
          h("option", { value: "1" }, "Scope 1"),
          h("option", { value: "2" }, "Scope 2"),
          h("option", { value: "3" }, "Scope 3"),
        ),
      ),
      h("textarea", { className: "ef-expr", placeholder: "formula, e.g. steamFlow * enthalpy / 860", rows: 2, value: f.expr, onChange: set("expr") }),
      h("input", { className: "ef-in", placeholder: "display formula (optional)", value: f.formulaText, onChange: set("formulaText") }),
      h("div", { className: "ef-actions" },
        h("button", { className: "ef-ok", type: "button", onClick: () => {
          const scope = f.scope === "" ? null : Number(f.scope);
          const payload = editing
            ? { id: existing.id, patch: { label: f.label, unit: f.unit, scope, expr: f.expr, formulaText: f.formulaText || f.expr } }
            : { assetId, id: f.id.trim(), label: f.label.trim() || f.id.trim(), unit: f.unit.trim(), scope, expr: f.expr, formulaText: f.formulaText || f.expr };
          if (onSubmit(editing ? "update_output" : "add_output", payload)) onClose();
        } }, editing ? "save" : "add output"),
        h("button", { className: "ef-cancel", type: "button", onClick: onClose }, "cancel"),
      ),
    );
  }

  function InGroup({ group, inputs, setInput, editMode, assetId, onAddInput, onDeleteInput }) {
    const [open, setOpen] = useState(false);
    return h("div", { className: "in-group" + (open ? "" : " collapsed") },
      h("button", { className: "in-group-title", type: "button", "aria-expanded": open, onClick: () => setOpen((o) => !o) },
        h("span", { className: "igt-chevron", "aria-hidden": true }, "▸"),
        h("span", { className: "igt-label" }, group.title),
        h("span", { className: "igt-count" }, group.rows.length + " inputs"),
      ),
      open && h("div", null,
        h("table", { className: "io-table" }, h("tbody", null,
          group.rows.map((r) => h(InputRow, {
            key: r.id, row: r, value: inputs[r.id], editMode,
            onCommit: (val) => setInput(r.id, val), onDelete: onDeleteInput,
          })),
        )),
        editMode ? h(AddInputForm, { assetId, group: group.title, onAdd: onAddInput }) : null,
      ),
    );
  }

  // the asset's own isometric crystal — the single building from the site, centered and
  // floating, used as the hero on top of its detail page. Crystal shape is read straight
  // from the site layout so it always matches what the asset looks like on the network.
  const C30 = 0.8660254, S30 = 0.5;

  function crystalForAsset(plant, assetId) {
    const site = window.ATLAS_SITE;
    if (!site || !plant) return null;
    const b = (site.layout(plant) || []).find((x) => x.asset === assetId);
    return b ? b.crystal : null;
  }

  // small inline isometric crystal — same shape as the site building, sized to sit on
  // the header line right next to the "Plant Site" button and the asset name. No box.
  function AssetGlyph({ asset, plant }) {
    const ready = plant && window.ATLAS_SITE && window.crystalBounds && window.crystalPolys;
    const key = ready ? crystalForAsset(plant, asset.id) : null;
    if (!key) return null;

    const Q = 12;
    const bb = window.crystalBounds(key);
    const P = (x, y, z) => [(x - z) * C30 * Q, ((x + z) * S30 - y) * Q];
    const proj = (mx, my, mz) => P(mx, my - bb.minY, mz);
    const polys = window.crystalPolys(key, proj, { state: "active" });

    let mnx = 1e9, mny = 1e9, mxx = -1e9, mxy = -1e9;
    polys.forEach((pg) => pg.points.split(" ").forEach((pt) => {
      const c = pt.split(","), x = +c[0], y = +c[1];
      mnx = Math.min(mnx, x); mxx = Math.max(mxx, x); mny = Math.min(mny, y); mxy = Math.max(mxy, y);
    }));
    const pad = 2;
    const vb = `${(mnx - pad).toFixed(0)} ${(mny - pad).toFixed(0)} ${(mxx - mnx + 2 * pad).toFixed(0)} ${(mxy - mny + 2 * pad).toFixed(0)}`;

    return h("svg", { className: "dh-glyph", viewBox: vb, height: 46, preserveAspectRatio: "xMidYMid meet",
      "aria-hidden": "true", style: { display: "block", flex: "0 0 auto", overflow: "visible" } },
      polys.map((pg, i) => h("polygon", {
        key: i, points: pg.points, fill: pg.fill, fillOpacity: pg.fillOpacity,
        stroke: pg.stroke, strokeOpacity: pg.strokeOpacity, strokeWidth: pg.strokeWidth,
        strokeLinejoin: "miter", strokeLinecap: "square",
      })),
    );
  }

  // ============ Formula-lineage graph ============
  // Design language ported from the "Formula Ontology" handoff: input-group cards (left)
  // and output/formula cards (right), wired by animated bezier "lineage" edges. Hovering a
  // card traces its full up/down lineage (connected edges flow, the rest dims); clicking
  // pins the trace AND expands the card — input groups become inline-editable, output cards
  // reveal their formula. Scoped to a single asset (2 columns: inputs → outputs).
  const LIN = {
    LEFT: { x: 0, w: 256 }, RIGHT: { x: 476, w: 300 },
    TOPPAD: 30, GAP: 14, GH: 54, OH: 60, ROWH: 40, GPAD: 12, OEXTRA: 44,
    FN: { min: 1, max: 1, abs: 1, floor: 1, ceil: 1, round: 1, sqrt: 1, pow: 1, countPos: 1, if: 1 },
  };

  // Static structure for one asset: nodes (input groups + visible outputs) and the edges
  // between them, derived by tokenising each output's expr (constants/funcs skipped, hidden
  // intermediates folded through to the inputs/visible-outputs they ultimately depend on).
  function buildLineage(asset, model) {
    const raw = (model.assets || []).find((a) => a.id === asset.id) || asset;
    const constSet = {};
    Object.keys(model.constants || {}).forEach((k) => { constSet[k] = true; });
    const inputMeta = {}, outMeta = {};
    (raw.inputs || []).forEach((i) => { inputMeta[i.id] = i.group; });
    (raw.outputs || []).forEach((o) => { outMeta[o.id] = o; });
    const tokens = (expr) => (String(expr).match(/[A-Za-z_][A-Za-z0-9_]*/g) || []);
    const depNodes = (expr, seen) => {
      const acc = {};
      tokens(expr).forEach((t) => {
        if (constSet[t] || LIN.FN[t]) return;
        if (inputMeta[t] != null) { acc["g:" + inputMeta[t]] = true; return; }
        const om = outMeta[t];
        if (!om) return; // cross-asset / unknown id — no node in this asset's graph
        if (om.hidden) {
          if (seen[t]) return; seen[t] = true;
          const sub = depNodes(om.expr, seen);
          Object.keys(sub).forEach((k) => { acc[k] = true; });
        } else acc["o:" + t] = true;
      });
      return acc;
    };

    const groups = asset.inputGroups;
    const visOutputs = (raw.outputs || []).filter((o) => !o.hidden);
    const edges = [], ekeys = {};
    const addEdge = (src, dst) => {
      const k = src + ">" + dst;
      if (ekeys[k] || src === dst) return;
      ekeys[k] = true; edges.push({ src, dst });
    };
    visOutputs.forEach((o) => {
      Object.keys(depNodes(o.expr, {})).forEach((src) => addEdge(src, "o:" + o.id));
    });

    const upAdj = {}, downAdj = {};
    edges.forEach((e) => {
      (upAdj[e.dst] = upAdj[e.dst] || []).push(e.src);
      (downAdj[e.src] = downAdj[e.src] || []).push(e.dst);
    });
    const closure = (start, adj) => {
      const seen = {}, stack = [start];
      while (stack.length) {
        const n = stack.pop();
        if (seen[n]) continue;
        seen[n] = true;
        (adj[n] || []).forEach((x) => stack.push(x));
      }
      return seen;
    };
    return { groups, visOutputs, edges, upAdj, downAdj, closure };
  }

  // Deterministic positions: stack each column top-down; the pinned card grows and pushes
  // the cards below it. Edge anchors use each card's header centre so lines stay put on expand.
  function layoutLineage(G, pinNode) {
    const pos = {};
    let ly = LIN.TOPPAD;
    G.groups.forEach((g) => {
      const id = "g:" + g.title;
      const hgt = pinNode === id ? LIN.GH + g.rows.length * LIN.ROWH + LIN.GPAD : LIN.GH;
      pos[id] = { x: LIN.LEFT.x, y: ly, w: LIN.LEFT.w, h: hgt, cy: ly + LIN.GH / 2 };
      ly += hgt + LIN.GAP;
    });
    const leftBottom = ly;
    let ry = LIN.TOPPAD;
    G.visOutputs.forEach((o) => {
      const id = "o:" + o.id;
      const hgt = pinNode === id ? LIN.OH + LIN.OEXTRA : LIN.OH;
      pos[id] = { x: LIN.RIGHT.x, y: ry, w: LIN.RIGHT.w, h: hgt, cy: ry + LIN.OH / 2 };
      ry += hgt + LIN.GAP;
    });
    const graphH = Math.max(leftBottom, ry) - LIN.GAP + 8;
    return { pos, graphH, width: LIN.RIGHT.x + LIN.RIGHT.w };
  }

  function LineageGraph({ asset, model, inputs, setInput, derived }) {
    const [hoverNode, setHoverNode] = useState(null);
    const [pinNode, setPinNode] = useState(null);
    // re-collapse any pinned card when switching assets
    useEffect(() => { setPinNode(null); setHoverNode(null); }, [asset.id]);

    const G = useMemo(() => buildLineage(asset, model), [model, asset.id]);
    const L = useMemo(() => layoutLineage(G, pinNode), [G, pinNode]);

    const focus = pinNode || hoverNode;
    const hiSet = focus ? Object.assign({}, G.closure(focus, G.upAdj), G.closure(focus, G.downAdj)) : null;
    const dimOf = (id) => (hiSet && !hiSet[id] ? 0.14 : 1);
    const onEnter = (id) => () => { if (!pinNode) setHoverNode(id); };
    const onLeave = () => { if (!pinNode) setHoverNode(null); };
    const onClick = (id) => () => { setHoverNode(null); setPinNode((p) => (p === id ? null : id)); };

    // ---- edges
    const edgeEls = G.edges.map((e, i) => {
      const S = L.pos[e.src], D = L.pos[e.dst];
      if (!S || !D) return null;
      let d;
      if (S.x === D.x) { // output -> output (same column): bulge out to the right
        const sx = S.x + S.w, tx = D.x + D.w;
        d = "M " + sx + " " + S.cy + " C " + (sx + 60) + " " + S.cy + ", " + (tx + 60) + " " + D.cy + ", " + tx + " " + D.cy;
      } else {
        const sx = S.x + S.w, tx = D.x, dx = (tx - sx) * 0.5;
        d = "M " + sx + " " + S.cy + " C " + (sx + dx) + " " + S.cy + ", " + (tx - dx) + " " + D.cy + ", " + tx + " " + D.cy;
      }
      let stroke = "#D8DEDC", sw = 1.3, dash = "none", anim = "none", op = 1;
      if (hiSet) {
        if (hiSet[e.src] && hiSet[e.dst]) { stroke = "#22867C"; sw = 2; dash = "7 5"; anim = "ontFlow .8s linear infinite"; }
        else op = 0.05;
      }
      return h("path", { key: i, d, style: { fill: "none", stroke, strokeWidth: sw, strokeDasharray: dash, opacity: op, animation: anim } });
    });

    // ---- input-group cards
    const groupEls = G.groups.map((g) => {
      const id = "g:" + g.title, P = L.pos[id], expanded = pinNode === id, isFocus = id === focus;
      return h("div", {
        key: id, onMouseEnter: onEnter(id), onMouseLeave: onLeave, onClick: onClick(id),
        style: {
          position: "absolute", left: P.x, top: P.y, width: P.w, opacity: dimOf(id),
          background: "#FFFFFF", border: "1px solid " + (isFocus ? "#074D47" : "#E5E7EB"),
          cursor: "pointer", transition: "opacity .15s, border-color .15s",
        },
      },
        h("div", { style: { height: LIN.GH, padding: "0 12px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10 } },
          h("div", { style: { minWidth: 0 } },
            h("div", { style: { fontSize: 12, fontWeight: 600, color: "#1F2937", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, g.title),
            h("div", { style: { fontFamily: "var(--font-mono)", fontSize: 8.5, letterSpacing: ".1em", color: "#22867C", marginTop: 2 } }, asset.name.toUpperCase()),
          ),
          h("span", { style: { fontFamily: "var(--font-mono)", fontSize: 8.5, letterSpacing: ".08em", color: "#6B7280", whiteSpace: "nowrap" } }, g.rows.length + " INPUTS"),
        ),
        expanded ? h("div", { style: { borderTop: "1px solid #EEF1F0", padding: "4px 12px 8px" } },
          g.rows.map((r) => h("div", {
            key: r.id, className: "lin-row", onClick: (e) => e.stopPropagation(),
            style: { display: "flex", alignItems: "center", gap: 10, padding: "5px 0" },
          },
            h("div", { style: { flex: 1, minWidth: 0 } },
              h("div", { style: { fontSize: 11.5, color: "#374151", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, r.label),
              r.unit ? h("div", { style: { fontFamily: "var(--font-mono)", fontSize: 8.5, color: "#9CA3AF", marginTop: 1 } }, r.unit) : null,
            ),
            h(InputCell, { value: inputs[r.id], onCommit: (v) => setInput(r.id, v) }),
          )),
        ) : null,
      );
    });

    // ---- output / formula cards
    const outEls = G.visOutputs.map((o) => {
      const id = "o:" + o.id, P = L.pos[id], expanded = pinNode === id, isFocus = id === focus, key = !!o.drives;
      return h("div", {
        key: id, onMouseEnter: onEnter(id), onMouseLeave: onLeave, onClick: onClick(id),
        style: {
          position: "absolute", left: P.x, top: P.y, width: P.w, opacity: dimOf(id),
          background: key ? "#F1FBF9" : "#FFFFFF",
          border: "1px solid " + (isFocus ? "#074D47" : (key ? "#BFE0DB" : "#E5E7EB")),
          cursor: "pointer", transition: "opacity .15s, border-color .15s",
        },
      },
        h("div", { style: { height: LIN.OH, padding: "0 14px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 } },
          h("div", { style: { minWidth: 0 } },
            h("div", { style: { fontSize: 12, fontWeight: 600, color: "#1F2937", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, o.label),
            h("div", { style: { fontFamily: "var(--font-mono)", fontSize: 9, color: "#9CA3AF", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", marginTop: 2 } }, o.formulaText),
          ),
          h("div", { style: { textAlign: "right", flex: "0 0 auto" } },
            h("div", { style: { fontFamily: "var(--font-mono)", fontSize: 13, fontWeight: 600, color: "#074D47" } }, fmt(derived[o.id])),
            h("div", { style: { fontFamily: "var(--font-mono)", fontSize: 8, color: "#9CA3AF", marginTop: 1 } }, o.unit),
          ),
        ),
        expanded ? h("div", { style: { borderTop: "1px solid #EEF1F0", padding: "8px 14px 10px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10 } },
          h("div", { style: { fontFamily: "var(--font-mono)", fontSize: 10.5, color: "#4B5563", lineHeight: 1.5 } }, o.formulaText),
          o.scope ? h(ScopeChip, { scope: o.scope, small: true }) : null,
        ) : null,
      );
    });

    const label = (x, text) => h("div", { style: { position: "absolute", left: x, top: 4,
      fontFamily: "var(--font-mono)", fontSize: 10, fontWeight: 600, letterSpacing: ".14em", color: "#000000" } }, text);

    const legendSq = (bg, bd, text) => h("div", { style: { display: "flex", alignItems: "center", gap: 6 } },
      h("span", { style: { width: 9, height: 9, background: bg, border: bd ? "1px solid " + bd : "none" } }),
      h("span", { style: { fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: ".08em", color: "#6B7280" } }, text));

    return h("div", { className: "lin-card", "data-screen-label": "Formula Lineage",
      style: { background: "#FFFFFF", border: "1px solid #E5E7EB", marginTop: 4 } },
      h("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16, padding: "13px 18px", borderBottom: "1px solid #E5E7EB", flexWrap: "wrap" } },
        h("div", { style: { fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: ".14em", color: "#9CA3AF" } }, "FORMULA LINEAGE — INPUTS → OUTPUTS"),
        h("div", { style: { display: "flex", alignItems: "center", gap: 18, flexWrap: "wrap" } },
          legendSq("#FFFFFF", "#D1D5DB", "INPUT GROUP"),
          legendSq("#F1FBF9", "#BFE0DB", "KEY OUTPUT"),
          h("span", { style: { fontFamily: "var(--font-mono)", fontSize: 9, letterSpacing: ".08em", color: pinNode ? "#074D47" : "#22867C" } },
            pinNode ? "▣ LINEAGE PINNED — CLICK THE CARD AGAIN TO CLEAR" : "HOVER OR CLICK A CARD TO TRACE ITS LINEAGE"),
        ),
      ),
      h("div", { className: "lin-wrap" },
        h("div", { style: { position: "relative", width: L.width, height: L.graphH, margin: "0 auto" } },
          h("svg", { width: L.width, height: L.graphH, style: { position: "absolute", left: 0, top: 0, pointerEvents: "none", overflow: "visible" } }, edgeEls),
          label(LIN.LEFT.x, "INPUT GROUPS"),
          label(LIN.RIGHT.x, "OUTPUT FORMULAS · LIVE VALUES"),
          groupEls, outEls,
        ),
      ),
    );
  }

  function DetailView({ asset, plant, model, setModel, inputs, derived, setInput, onBack, onOpenAsset }) {
    const [editMode, setEditMode] = useState(false);
    const [opError, setOpError] = useState("");
    const [outForm, setOutForm] = useState(null); // null | {existing?} | "new"

    if (!asset) return h("div", { className: "detail" }, h("button", { className: "back-btn", onClick: onBack }, "← Plant Site"), h("p", null, "Asset not found."));

    // run an op; on success commit the new model and clear errors, on failure surface the message
    const run = (opName, args) => {
      const r = ATLAS.ops.apply(model, opName, args);
      if (!r.ok) { setOpError(r.error); return false; }
      setOpError("");
      setModel(r.model);
      return true;
    };

    const finalMetrics = ATLAS.finalMetricsWithFmt(model);

    return h("div", { className: "detail", "data-screen-label": asset.name },
      h("div", { className: "detail-head" },
        h("div", { className: "dh-lead" },
          h("button", { className: "back-btn", onClick: onBack }, "← Plant Site"),
          h(AssetGlyph, { asset, plant }),
          h("h1", { className: "dh-title" }, asset.name),
        ),
        h("button", {
          className: "edit-toggle" + (editMode ? " on" : ""), type: "button",
          onClick: () => { setEditMode((e) => !e); setOpError(""); setOutForm(null); },
        }, editMode ? "DONE" : "EDIT"),
      ),

      opError ? h("div", { className: "op-error" }, opError) : null,

      h("div", { className: "metrics-band" },
        h("div", { className: "metrics-grid" },
          finalMetrics.map((m) => h(MetricCard, {
            key: m.id, metric: m, value: derived._final[m.id],
            driven: (asset.drives || []).indexOf(m.id) !== -1,
          })),
        ),
      ),

      editMode ? h("div", { className: "io-wrap" },
        // inputs
        h("div", { className: "io-col io-inputs" },
          h("div", { className: "io-col-head" },
            h("span", { className: "ioh-title" }, "INPUTS"),
            h("span", { className: "ioh-tag white-tag" }, "EDITABLE"),
          ),
          asset.inputGroups.map((g) => h(InGroup, {
            key: g.title, group: g, inputs, setInput, editMode, assetId: asset.id,
            onAddInput: (args) => run("add_input", args),
            onDeleteInput: (id) => run("delete_input", { id }),
          })),
          editMode ? h(AddInputForm, { assetId: asset.id, group: "New Group",
            onAdd: (args) => run("add_input", args) }) : null,
        ),
        // outputs
        h("div", { className: "io-col io-outputs" },
          h("div", { className: "io-col-head" },
            h("span", { className: "ioh-title" }, "CALCULATED"),
            h("span", { className: "ioh-tag pink-tag" }, "FORMULA-DRIVEN"),
          ),
          h("table", { className: "io-table out-table" }, h("tbody", null,
            asset.outputs.map((o) => h(OutputRow, {
              key: o.id, row: o, value: derived[o.id], editMode,
              onDelete: (id) => run("delete_output", { id }),
              onEdit: (row) => setOutForm({ existing: row }),
            })),
          )),
          editMode && outForm ? h(OutputForm, {
            assetId: asset.id, existing: outForm.existing,
            onSubmit: (op, args) => run(op, args), onClose: () => setOutForm(null),
          }) : null,
          editMode && !outForm ? h("button", { className: "add-row-btn", type: "button",
            onClick: () => setOutForm({ existing: null }) }, "+ add output") : null,
          h("div", { className: "scope-legend" },
            Object.keys(SCOPE).map((k) => h("div", { className: "sl-item", key: k },
              h(ScopeChip, { scope: +k, small: true }),
              h("span", { className: "sl-desc" }, SCOPE[k].desc),
            )),
          ),
        ),
      ) : h(LineageGraph, { asset, model, inputs, setInput, derived }),
    );
  }

  window.DetailView = DetailView;
})();
