/* ATLAS — Isometric plant schematic.
   Pure-SVG extruded blocks on a 2:1 dimetric grid. Sharp edges only.
   Production + Kiln are active (teal, clickable); the rest are muted context. */
(function () {
  "use strict";
  const { useState } = React;
  const h = React.createElement;

  // ---- isometric projection (camera from +x/+y, looking down) ----
  // NOTE: this is a 2:1 dimetric used for the ground GRID only. Crystals are NOT drawn with
  // it — its up-axis (ZH) is compressed relative to its ground axes, which squashes/tips tall
  // solids. Crystals use the engine's TRUE isometric below so they match the design 1:1.
  const TW = 26, TH = 13, ZH = 22;
  const iso = (x, y, z) => ({ x: (x - y) * TW, y: (x + y) * TH - z * ZH });
  const pt = (p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`;

  // ---- engine-native TRUE isometric (same as crystals.js / the design asset cards) ----
  // x,z = ground plane, y = up. Uniform scale Q, so proportions are preserved exactly.
  const EC = 0.8660254, ES = 0.5; // cos30 / sin30
  // project crystal build-space (mx,my,mz) to screen, resting its base centre at (ox,oy).
  function crystalProject(ox, oy, Q, minY) {
    return (mx, my, mz) => [ox + (mx - mz) * EC * Q, oy + ((mx + mz) * ES - (my - minY)) * Q];
  }

  // Extruded box: footprint corner (x0,y0), size (w,d), height ht.
  // Returns {top,left,right,frontEdge,topCenter,baseFront}
  function box(x0, y0, w, d, ht) {
    const A = iso(x0, y0, 0), B = iso(x0 + w, y0, 0), C = iso(x0 + w, y0 + d, 0), D = iso(x0, y0 + d, 0);
    const A2 = iso(x0, y0, ht), B2 = iso(x0 + w, y0, ht), C2 = iso(x0 + w, y0 + d, ht), D2 = iso(x0, y0 + d, ht);
    return {
      top: [A2, B2, C2, D2].map(pt).join(" "),
      right: [B2, C2, C, B].map(pt).join(" "),   // +x face
      left: [D2, C2, C, D].map(pt).join(" "),     // +y face
      topMid: iso(x0 + w / 2, y0 + d / 2, ht),
      baseFront: iso(x0 + w / 2, y0 + d, 0),
      C2, C,
    };
  }

  // colour sets per state
  const PAL = {
    active:   { top: "#2C9488", left: "#0C5249", right: "#1B7B70", stroke: "#063F39" },
    hover:    { top: "#3CB3A4", left: "#157065", right: "#249C8E", stroke: "#063F39" },
    mute:     { top: "#E3E7E6", left: "#BFC8C6", right: "#D2D9D7", stroke: "#A9B3B1" },
  };

  // each machine renders as a curated faceted crystal (crystals.js), shaded + painter-sorted
  // by the engine, projected through the scene's own isometric so it sits on the grid.
  function Machine({ node, onClick, hovered, setHover }) {
    const isActive = node.active;
    const isHover = hovered === node.id;
    // Always render crystals in their true library colouring (never the grey "mute" ramp):
    // a greyed translucent gem like the garnet washes out to an invisible flat shard, and the
    // user curated these shapes to look like the asset library. Interactivity (hover-lift +
    // click) still depends on `isActive`, so data-backed nodes remain the only clickable ones.
    const state = isActive && isHover ? "hover" : "active";

    let body;
    if (window.crystalPolys && node.crystal && node.qscale) {
      // engine-native TRUE isometric (uniform scale) so the crystal matches its design asset
      // exactly — base centre rests at (groundX, groundY) on the shared baseline.
      const project = crystalProject(node.groundX, node.groundY, node.qscale, node.cminY);
      const polys = window.crystalPolys(node.crystal, project, { state });
      body = h("g", null, 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",
      })));
    } else {
      // fallback: original extruded box (only if the crystal engine is unavailable)
      const pal = isActive ? (isHover ? PAL.hover : PAL.active) : PAL.mute;
      const b = box(node.gx, node.gy, node.w, node.d, node.ht);
      body = h("g", null,
        h("polygon", { points: b.left, fill: pal.left, stroke: pal.stroke, strokeWidth: 1, strokeLinejoin: "miter" }),
        h("polygon", { points: b.right, fill: pal.right, stroke: pal.stroke, strokeWidth: 1, strokeLinejoin: "miter" }),
        h("polygon", { points: b.top, fill: pal.top, stroke: pal.stroke, strokeWidth: 1, strokeLinejoin: "miter" }),
        decoFor(node, b, pal, isActive),
      );
    }

    const lift = isHover && isActive ? -6 : 0;

    // soft ground shadow (stays put + pulses) and the crystal body (floats above it),
    // mirroring the ambient motion of the asset-library tiles.
    const shadow = node.shadowRx ? h("ellipse", {
      className: "plant-shadow", cx: node.groundX, cy: node.groundY,
      rx: node.shadowRx, ry: node.shadowRy != null ? node.shadowRy : node.shadowRx * 0.4,
      fill: "url(#crystalShadow)", style: { animationDelay: node.floatDelay + "s" },
    }) : null;
    const floatBody = h("g", { className: "plant-float", style: { animationDelay: node.floatDelay + "s" } }, body);

    return h("g", {
      transform: `translate(0,${lift})`,
      style: { cursor: isActive ? "pointer" : "default", transition: "transform .18s cubic-bezier(.22,1,.36,1)" },
      onMouseEnter: () => isActive && setHover(node.id),
      onMouseLeave: () => isActive && setHover(null),
      onClick: () => isActive && onClick(node.id),
    },
      shadow,
      floatBody,
    );
  }

  // little seam decals to differentiate machine types (kept minimal).
  // bands evenly divide the height: n lines at i/(n+1) → equal gaps + equal top/bottom margins.
  // each seam wraps the front vertical edge across both visible faces and is inset from the
  // outer edges, so it reads as a real ring on the equipment rather than a stripe on a border.
  function decoFor(node, b, pal, isActive) {
    const stroke = isActive ? "rgba(255,255,255,.26)" : "rgba(255,255,255,.22)";
    const STROKE_W = 1.4, INS = 0; // 0 = seams run full width, edge to edge
    const els = [];
    const band = (frac) => {
      const zz = node.ht * frac;
      const xR = node.gx + node.w, yF = node.gy + node.d; // shared front edge (x=xR, y=yF)
      const p1 = iso(xR, node.gy + node.d * INS, zz);     // +x face, inset from back edge
      const p2 = iso(xR, yF, zz);                          // front corner (wrap point)
      const p3 = iso(node.gx + node.w * INS, yF, zz);      // +y face, inset from left edge
      els.push(h("polyline", { points: [p1, p2, p3].map(pt).join(" "), fill: "none",
        stroke, strokeWidth: STROKE_W, strokeLinecap: "round", strokeLinejoin: "round" }));
    };
    const bands = (n) => { for (let i = 1; i <= n; i++) band(i / (n + 1)); };
    const COUNT = { kiln: 3, cooler: 2, mill: 2, stockpile: 3, power: 3, fuel: 2,
      turbine: 2, whr: 2, cpp: 3, boiler: 2, process: 2, liquid: 2 };
    if (COUNT[node.glyph]) bands(COUNT[node.glyph]);
    return h("g", null, ...els);
  }

  // conveyor connector: a low raised bar from a->b (grid coords at z=barZ)
  function Conveyor({ x1, y1, x2, y2 }) {
    const z = 0.55, wdt = 0.42;
    // build a thin box along the segment — approximate with a quad on top + side
    const a1 = iso(x1, y1 - wdt, z), a2 = iso(x1, y1 + wdt, z);
    const b1 = iso(x2, y2 - wdt, z), b2 = iso(x2, y2 + wdt, z);
    const a1b = iso(x1, y1 + wdt, 0), b1b = iso(x2, y2 + wdt, 0);
    return h("g", null,
      h("polygon", { points: [a2, b2, b1b, a1b].map(pt).join(" "), fill: "#C2CBC9" }),
      h("polygon", { points: [a1, b1, b2, a2].map(pt).join(" "), fill: "#D8DEDC" }),
    );
  }

  // bounds of all node geometry (in screen space) for auto-fit viewBox — crystals are placed
  // directly in screen space (n.sx0..n.syBot), so we just union their projected boxes + labels.
  function sceneBounds(nodes) {
    let minX = 1e9, minY = 1e9, maxX = -1e9, maxY = -1e9;
    nodes.forEach((n) => {
      minX = Math.min(minX, n.sx0); maxX = Math.max(maxX, n.sx1);
      minY = Math.min(minY, n.syTop); maxY = Math.max(maxY, n.syBot);
      if (n.labelY != null) maxY = Math.max(maxY, n.labelY + 14);
    });
    return { minX, minY, maxX, maxY };
  }

  // key figures shown in the hover overview — the asset's top computed outputs,
  // pulled live from the derived store. Returns [] for muted/placeholder nodes.
  function overviewRows(id, derived) {
    const asset = ATLAS.ASSETS && ATLAS.ASSETS[id];
    if (!asset || !derived) return [];
    return (asset.outputs || []).slice(0, 3).map((o) => ({
      label: o.label,
      value: ATLAS.fmtNum(derived[o.id]),
      unit: o.unit,
    }));
  }

  // minimal data-overview card that follows the cursor over an active node.
  function HoverCard({ node, pos, derived }) {
    const asset = ATLAS.ASSETS && ATLAS.ASSETS[node.id];
    if (!asset) return null;
    const rows = overviewRows(node.id, derived);
    // flip to the left of the cursor when near the right edge to stay on-screen
    const flip = pos.x > pos.w - 240;
    return h("div", {
      style: {
        position: "absolute", left: pos.x, top: pos.y,
        transform: `translate(${flip ? "calc(-100% - 16px)" : "16px"}, 12px)`,
        width: 220, pointerEvents: "none", zIndex: 20,
        background: "#FFFFFF", border: "1px solid #063F39",
        padding: "10px 12px", fontFamily: "var(--font-mono)",
      },
    },
      h("div", { style: { fontSize: 11, fontWeight: 700, color: "#063F39",
        letterSpacing: ".06em", textTransform: "uppercase", marginBottom: 8 } }, node.name),
      h("div", { style: { display: "flex", flexDirection: "column", gap: 5 } },
        rows.map((r) => h("div", { key: r.label, style: {
            display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 } },
          h("span", { style: { fontSize: 9.5, color: "#5A6664",
            whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, r.label),
          h("span", { style: { flex: "0 0 auto", fontSize: 11, fontWeight: 600, color: "#063F39" } },
            r.value,
            h("span", { style: { fontSize: 8.5, fontWeight: 400, color: "#9AA4A2", marginLeft: 3 } },
              r.unit),
          ),
        )),
      ),
    );
  }

  function PlantScene({ nodes, onSelect, showGrid, connectors, padX, derived }) {
    const [hovered, setHover] = useState(null);
    const [pos, setPos] = useState({ x: 0, y: 0, w: 0 });
    const bb = sceneBounds(nodes);
    const px = padX || 200, padTop = 72, padBot = 92; // generous margin → zoomed-out, airy composition
    const vb = `${(bb.minX - px).toFixed(0)} ${(bb.minY - padTop).toFixed(0)} ${(bb.maxX - bb.minX + px * 2).toFixed(0)} ${(bb.maxY - bb.minY + padTop + padBot).toFixed(0)}`;

    // ground grid (2:1 dimetric) filling the visible viewBox. Crystals live in screen space,
    // so we derive the grid's coordinate range by inverse-projecting the viewBox corners.
    const gridLines = [];
    const X0 = bb.minX - px, X1 = bb.maxX + px, Y0 = bb.minY - padTop, Y1 = bb.maxY + padBot;
    const invG = (X, Y) => ({ gx: (X / TW + Y / TH) / 2, gy: (Y / TH - X / TW) / 2 });
    let gxMin = 1e9, gxMax = -1e9, gyMin = 1e9, gyMax = -1e9;
    [[X0, Y0], [X1, Y0], [X1, Y1], [X0, Y1]].forEach(([X, Y]) => {
      const g = invG(X, Y);
      gxMin = Math.min(gxMin, g.gx); gxMax = Math.max(gxMax, g.gx);
      gyMin = Math.min(gyMin, g.gy); gyMax = Math.max(gyMax, g.gy);
    });
    const gx0 = Math.floor(gxMin) - 2, gx1 = Math.ceil(gxMax) + 2, gyLo = Math.floor(gyMin) - 2, gyHi = Math.ceil(gyMax) + 2;
    for (let i = gx0; i <= gx1; i += 2) {
      const a = iso(i, gyLo, 0), b = iso(i, gyHi, 0);
      gridLines.push(h("line", { key: "gx" + i, x1: a.x, y1: a.y, x2: b.x, y2: b.y, stroke: "#EEF1F0", strokeWidth: 1 }));
    }
    for (let j = gyLo; j <= gyHi; j += 2) {
      const a = iso(gx0, j, 0), b = iso(gx1, j, 0);
      gridLines.push(h("line", { key: "gy" + j, x1: a.x, y1: a.y, x2: b.x, y2: b.y, stroke: "#EEF1F0", strokeWidth: 1 }));
    }

    // connectors between consecutive nodes (front-centre to front-centre)
    const conv = [];
    for (let i = 0; i < nodes.length - 1; i++) {
      const a = nodes[i], c = nodes[i + 1];
      conv.push(h(Conveyor, {
        key: "c" + i,
        x1: a.gx + a.w, y1: a.gy + a.d / 2,
        x2: c.gx, y2: c.gy + c.d / 2,
      }));
    }

    // labels (rendered last, flat, in screen space)
    const labels = nodes.map((n) => {
      // all labels share one baseline (n.labelY), each centred under its model (n.lblX)
      const lblX = n.lblX != null ? n.lblX : iso(n.gx + n.w / 2, n.gy + n.d, 0).x;
      const lblY = n.labelY != null ? n.labelY : iso(n.gx + n.w / 2, n.gy + n.d, 0).y + 30;
      return h("g", { key: "l" + n.id, transform: `translate(${lblX},${lblY})`,
        style: { cursor: n.active ? "pointer" : "default" },
        onMouseEnter: () => n.active && setHover(n.id),
        onMouseLeave: () => n.active && setHover(null),
        onClick: () => n.active && onSelect(n.id) },
        h("text", { textAnchor: "middle", fontFamily: "var(--font-mono)", fontSize: 11.5,
          fontWeight: n.active ? 600 : 500, letterSpacing: ".04em",
          fill: n.active ? "#063F39" : "#9CA3AF", style: { textTransform: "uppercase" } }, n.name),
      );
    });

    const svg = h("svg", { viewBox: vb, width: "100%", height: "100%",
        style: { display: "block", maxHeight: "100%" }, preserveAspectRatio: "xMidYMid meet" },
      h("defs", null,
        h("radialGradient", { id: "crystalShadow" },
          h("stop", { offset: "0%", stopColor: "#074D47", stopOpacity: 0.28 }),
          h("stop", { offset: "65%", stopColor: "#074D47", stopOpacity: 0.10 }),
          h("stop", { offset: "100%", stopColor: "#074D47", stopOpacity: 0 }),
        ),
      ),
      showGrid !== false ? h("g", null, ...gridLines) : null,
      connectors !== false ? h("g", null, ...conv) : null,
      h("g", null, ...nodes.map((n) => h(Machine, { key: n.id, node: n, onClick: onSelect, hovered, setHover }))),
      h("g", null, ...labels),
    );

    const hoverNode = hovered ? nodes.find((n) => n.id === hovered) : null;

    return h("div", {
      style: { position: "relative", width: "100%", height: "100%" },
      onMouseMove: (e) => {
        const r = e.currentTarget.getBoundingClientRect();
        setPos({ x: e.clientX - r.left, y: e.clientY - r.top, w: r.width });
      },
    },
      svg,
      hoverNode ? h(HoverCard, { node: hoverNode, pos, derived }) : null,
    );
  }

  // Each asset renders as a curated crystalline solid (see crystals.js) keyed by `crystal`.
  // Footprints (w/d the crystal is fit into; ht is a fallback) are looked up per crystal key;
  // new/unknown crystals fall back to a sensible generic footprint.
  const FOOTPRINT = {
    kiln_crystal:       { w: 4.2, d: 1.6, ht: 1.7 },
    cementmill_crystal: { w: 2.2, d: 1.8, ht: 2.0 },
    whrs:               { w: 2.0, d: 1.8, ht: 1.6 },
    cementmill_hybrid:  { w: 2.8, d: 2.0, ht: 2.6 },
    capstone:           { w: 1.8, d: 1.8, ht: 2.8 },
    production_faceted: { w: 2.8, d: 2.0, ht: 3.0 },
    cube_step:          { w: 2.4, d: 2.0, ht: 2.0 },
    cooler_crystal:     { w: 2.6, d: 2.2, ht: 1.4 },
    fuelliquid:         { w: 2.2, d: 2.0, ht: 1.8 },
    cube_cluster:       { w: 2.4, d: 1.8, ht: 2.2 },
  };
  const DEFAULT_FOOTPRINT = { w: 2.6, d: 2.0, ht: 2.2 };

  // build the layout spec for an asset/placeholder from its crystal + glyph
  function specFor(item) {
    const fp = FOOTPRINT[item.crystal] || DEFAULT_FOOTPRINT;
    return { w: fp.w, d: fp.d, ht: fp.ht, glyph: item.glyph || "process", crystal: item.crystal || "production_faceted" };
  }

  // muted context shapes for equipment not yet modelled (suppressed once a real asset
  // with the same id exists in the model).
  const PLACEHOLDERS = {
    plant_assets: [
      { id: "whrs", name: "WHRS", crystal: "whrs", glyph: "whr" },
      { id: "stg", name: "STG", crystal: "cementmill_crystal", glyph: "turbine" },
      { id: "cpp", name: "CPP", crystal: "cementmill_hybrid", glyph: "cpp" },
      { id: "boilers", name: "Boilers", crystal: "capstone", glyph: "boiler" },
    ],
    process: [],
  };

  // Row layout: every crystal is drawn with the engine's TRUE isometric (uniform scale Q) so
  // it matches the design exactly, with its base centre resting on one shared screen-y line.
  // Models march along screen-x by their projected widths; all labels share one baseline.
  function layout(list) {
    const GROUND_Y = 150;   // shared baseline (screen px) all crystals rest their base on
    const GAP_PX = 132;     // horizontal gap between adjacent crystals, in screen px

    // 1) measure each crystal and pick a uniform scale Q that fits the node footprint.
    //    Q = (footprint-fit scale) * TW / EC keeps the previous horizontal sizing while the
    //    vertical axis is now correctly proportioned (no more squash).
    const measured = list.map(({ id, name, active, s }) => {
      const b = window.crystalBounds && s.crystal ? window.crystalBounds(s.crystal) : null;
      const fit = b ? Math.min(s.w / (2 * b.hx), s.d / (2 * b.hz)) : 1;
      const Q = fit * TW / EC;
      const span = b ? (b.hx + b.hz) : Math.max(s.w, s.d);  // half-diagonal in build units
      const halfW = span * EC * Q;                          // projected horizontal half-width
      const lowOff = span * ES * Q;                         // base front corner drop below baseline
      const rise = b ? ((b.maxY - b.minY) + span * ES) * Q : (s.ht * 8); // top above baseline
      return { id, name, active, s, b, Q, halfW, lowOff, rise };
    });

    // 2) one shared label baseline, below the lowest-reaching crystal in the row.
    const labelY = GROUND_Y + (measured.length ? Math.max(...measured.map((m) => m.lowOff)) : 0) + 28;

    // 3) place the models left-to-right, bases on GROUND_Y, spaced by their half-widths.
    let cx = 0;
    return measured.map((m, idx) => {
      if (idx > 0) cx += measured[idx - 1].halfW + GAP_PX + m.halfW;
      const n = { id: m.id, assetId: m.active ? m.id : null, name: m.name, glyph: m.s.glyph,
        active: m.active, gx: 0, gy: 0, w: m.s.w, d: m.s.d, ht: m.s.ht, crystal: m.s.crystal };
      n.qscale = m.Q;
      n.cminY = m.b ? m.b.minY : 0;
      n.groundX = cx; n.groundY = GROUND_Y;            // crystal base centre (screen px)
      // precomputed projected screen bbox, for auto-fit viewBox + label clearance
      n.sx0 = cx - m.halfW; n.sx1 = cx + m.halfW;
      n.syTop = GROUND_Y - m.rise; n.syBot = GROUND_Y + m.lowOff;
      n.shadowRx = m.halfW * 0.82; n.shadowRy = m.lowOff * 0.9;
      n.lblX = cx;                                     // label centred under the model
      n.labelY = labelY;                               // shared baseline for every label
      n.floatDelay = ((idx % 3) * 0.8).toFixed(2);     // staggered float, like the asset library
      return n;
    });
  }

  // build nodes for one overview section: real (clickable) model assets first, then any
  // muted placeholders whose id isn't yet modelled.
  function buildSection(model, section) {
    const m = model || (window.ATLAS && window.ATLAS.model) || { assets: [] };
    const real = (m.assets || []).filter((a) => a.section === section).map((a) => ({
      id: a.id, name: a.name, active: true, s: specFor(a),
    }));
    const have = {};
    real.forEach((n) => { have[n.id] = true; });
    const placeholders = (PLACEHOLDERS[section] || []).filter((p) => !have[p.id]).map((p) => ({
      id: p.id, name: p.name, active: false, s: specFor(p),
    }));
    return layout(real.concat(placeholders));
  }

  // Graph 1 — physical plant assets (equipment).
  function buildAssetNodes(model) { return buildSection(model, "plant_assets"); }
  // Graph 2 — process / data categories (Production, Fuels, Electricity).
  function buildProcessNodes(model) { return buildSection(model, "process"); }

  window.PlantScene = PlantScene;
  window.buildAssetNodes = buildAssetNodes;
  window.buildProcessNodes = buildProcessNodes;
})();
