// 3D fleet view — abstract glowing cubes with orbit controls
const { useRef: use3DRef, useState: use3DState, useEffect: use3DEffect } = React;

function ThreeD({ nodes, onSelect, style3d }) {
  const canvasRef = use3DRef(null);
  const wrapRef = use3DRef(null);
  const [cam, setCam] = use3DState({ yaw: -0.5, pitch: 0.45, zoom: 1, tx: 0, ty: 0 });
  const [hover, setHover] = use3DState(null);
  const rafRef = use3DRef(0);
  const dragRef = use3DRef(null);

  // group nodes by their group (Websites / Lovable / Cloudflare / Dgx-spark / Godento)
  const layout = React.useMemo(() => {
    const groups = window.GROUPS;
    const byGroup = {};
    nodes.forEach((n, idx) => {
      if (!byGroup[n.group]) byGroup[n.group] = [];
      byGroup[n.group].push({ n, origIdx: idx });
    });

    const positions = new Array(nodes.length);

    if (style3d === 'lattice') {
      nodes.forEach((n, i) => {
        const phi = Math.acos(1 - 2 * (i + 0.5) / nodes.length);
        const theta = Math.PI * (1 + Math.sqrt(5)) * i;
        const R = 9;
        positions[i] = { x: R * Math.sin(phi) * Math.cos(theta), y: R * Math.cos(phi), z: R * Math.sin(phi) * Math.sin(theta) };
      });
      return positions;
    }

    if (style3d === 'grid') {
      // flat grid organized by group columns
      groups.forEach((g, gi) => {
        const list = byGroup[g] || [];
        list.forEach((item, i) => {
          const row = Math.floor(i / 3);
          const col = i % 3;
          positions[item.origIdx] = {
            x: (gi - 2) * 7 + (col - 1) * 1.8,
            y: 0,
            z: (row - 2) * 2,
          };
        });
      });
      return positions;
    }

    // default: cube clusters — one per group
    groups.forEach((g, gi) => {
      const list = byGroup[g] || [];
      const cx = (gi - 2) * 9;
      list.forEach((item, i) => {
        const cols = 3;
        const rows = Math.ceil(list.length / cols);
        const col = i % cols;
        const row = Math.floor(i / cols);
        positions[item.origIdx] = {
          x: cx + (col - 1) * 2.2,
          y: (row - rows/2) * 2.2,
          z: 0,
        };
      });
    });
    return positions;
  }, [nodes, style3d]);

  function project(p, cw, ch, c) {
    const { yaw, pitch, zoom, tx, ty } = c;
    let x = p.x * Math.cos(yaw) - p.z * Math.sin(yaw);
    let z = p.x * Math.sin(yaw) + p.z * Math.cos(yaw);
    let y = p.y;
    const y2 = y * Math.cos(pitch) - z * Math.sin(pitch);
    const z2 = y * Math.sin(pitch) + z * Math.cos(pitch);
    const f = 500 * zoom;
    const d = 34;
    const sx = cw / 2 + (x * f) / (d + z2) + tx;
    const sy = ch / 2 - (y2 * f) / (d + z2) + ty;
    return { sx, sy, depth: z2 };
  }

  function draw() {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const wrap = wrapRef.current;
    const dpr = window.devicePixelRatio || 1;
    const cw = wrap.clientWidth, ch = wrap.clientHeight;
    if (canvas.width !== cw * dpr) {
      canvas.width = cw * dpr; canvas.height = ch * dpr;
      canvas.style.width = cw + 'px'; canvas.style.height = ch + 'px';
    }
    const ctx = canvas.getContext('2d');
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.clearRect(0, 0, cw, ch);

    drawFloor(ctx, cw, ch, cam);
    if (style3d === 'cubes') drawGroupLabels(ctx, cw, ch, cam);
    if (style3d === 'grid') drawGroupLabels(ctx, cw, ch, cam, true);

    const projected = nodes.map((n, i) => ({ n, p: project(layout[i], cw, ch, cam), pos: layout[i] }))
      .sort((a, b) => b.p.depth - a.p.depth);

    drawConnections(ctx, cw, ch, cam, projected);

    for (const item of projected) drawNode(ctx, item, cam, hover === item.n.id);
  }

  function drawFloor(ctx, cw, ch, c) {
    const grid = 20;
    const size = 2;
    ctx.strokeStyle = 'rgba(40, 60, 80, 0.22)';
    ctx.lineWidth = 1;
    for (let i = -grid; i <= grid; i += size) {
      const a = project({x: i, y: -5, z: -grid}, cw, ch, c);
      const b = project({x: i, y: -5, z: grid}, cw, ch, c);
      ctx.beginPath(); ctx.moveTo(a.sx, a.sy); ctx.lineTo(b.sx, b.sy); ctx.stroke();
      const c1 = project({x: -grid, y: -5, z: i}, cw, ch, c);
      const c2 = project({x: grid, y: -5, z: i}, cw, ch, c);
      ctx.beginPath(); ctx.moveTo(c1.sx, c1.sy); ctx.lineTo(c2.sx, c2.sy); ctx.stroke();
    }
  }

  function drawGroupLabels(ctx, cw, ch, c, flat) {
    window.GROUPS.forEach((g, gi) => {
      const p = project({ x: (gi - 2) * 9, y: flat ? -4.5 : 5, z: 0 }, cw, ch, c);
      ctx.fillStyle = 'rgba(150, 180, 200, 0.55)';
      ctx.font = '10px JetBrains Mono, monospace';
      ctx.textAlign = 'center';
      ctx.fillText(g.toUpperCase(), p.sx, p.sy);
    });
  }

  function drawConnections(ctx, cw, ch, c, projected) {
    ctx.lineWidth = 1;
    for (const g of window.GROUPS) {
      const group = projected.filter(p => p.n.group === g);
      for (let i = 0; i < group.length - 1; i++) {
        const a = group[i], b = group[i+1];
        const grad = ctx.createLinearGradient(a.p.sx, a.p.sy, b.p.sx, b.p.sy);
        grad.addColorStop(0, 'rgba(100, 200, 220, 0.04)');
        grad.addColorStop(0.5, 'rgba(100, 200, 220, 0.16)');
        grad.addColorStop(1, 'rgba(100, 200, 220, 0.04)');
        ctx.strokeStyle = grad;
        ctx.beginPath(); ctx.moveTo(a.p.sx, a.p.sy); ctx.lineTo(b.p.sx, b.p.sy); ctx.stroke();
      }
    }
  }

  function drawNode(ctx, item, c, isHover) {
    const { n, p, pos } = item;
    const size = 0.8;
    const colors = {
      ok:      { core: 'oklch(0.72 0.16 150)', glow: 'rgba(50, 220, 140, 0.35)' },
      warn:    { core: 'oklch(0.75 0.15 85)',  glow: 'rgba(255, 200, 80, 0.35)' },
      crit:    { core: 'oklch(0.66 0.22 27)',  glow: 'rgba(255, 80, 80, 0.55)' },
      off:     { core: 'oklch(0.35 0 0)',      glow: 'rgba(100, 100, 100, 0.15)' },
      maint:   { core: 'oklch(0.75 0.15 85)',  glow: 'rgba(255, 200, 80, 0.35)' },
      unknown: { core: 'oklch(0.45 0 0)',      glow: 'rgba(120, 120, 120, 0.2)'  },
    };
    // Fallback ensures an unexpected backend-emitted status can never crash the render.
    const col = colors[n.status] || colors.unknown;
    const scale = 500 * c.zoom / (34 + p.depth);
    const pix = size * scale;

    const haloR = pix * (n.status === 'crit' ? 3.5 : 2.2) * (isHover ? 1.5 : 1);
    const pulse = n.status === 'crit' ? (0.7 + Math.sin(performance.now() * 0.004) * 0.3) : 1;
    const g = ctx.createRadialGradient(p.sx, p.sy, 0, p.sx, p.sy, haloR);
    g.addColorStop(0, col.glow.replace(/[\d.]+\)$/, (0.45 * pulse) + ')'));
    g.addColorStop(1, col.glow.replace(/[\d.]+\)$/, '0)'));
    ctx.fillStyle = g;
    ctx.beginPath(); ctx.arc(p.sx, p.sy, haloR, 0, Math.PI * 2); ctx.fill();

    if (style3d === 'lattice') {
      const g2 = ctx.createRadialGradient(p.sx - pix*0.3, p.sy - pix*0.3, 0, p.sx, p.sy, pix * 1.4);
      g2.addColorStop(0, col.core);
      g2.addColorStop(1, col.core.replace(')', ' / 0.35)'));
      ctx.fillStyle = g2;
      ctx.beginPath(); ctx.arc(p.sx, p.sy, pix * 1.1, 0, Math.PI * 2); ctx.fill();
      ctx.fillStyle = '#fff';
      ctx.beginPath(); ctx.arc(p.sx, p.sy, Math.max(1, pix * 0.25), 0, Math.PI * 2); ctx.fill();
    } else {
      const half = size / 2;
      const corners = [
        {x: pos.x - half, y: pos.y - half, z: pos.z - half},
        {x: pos.x + half, y: pos.y - half, z: pos.z - half},
        {x: pos.x + half, y: pos.y - half, z: pos.z + half},
        {x: pos.x - half, y: pos.y - half, z: pos.z + half},
        {x: pos.x - half, y: pos.y + half, z: pos.z - half},
        {x: pos.x + half, y: pos.y + half, z: pos.z - half},
        {x: pos.x + half, y: pos.y + half, z: pos.z + half},
        {x: pos.x - half, y: pos.y + half, z: pos.z + half},
      ].map(cc => project(cc, canvasRef.current.clientWidth, canvasRef.current.clientHeight, c));
      const fill = (idx, alpha) => {
        ctx.beginPath();
        ctx.moveTo(corners[idx[0]].sx, corners[idx[0]].sy);
        for (let i = 1; i < idx.length; i++) ctx.lineTo(corners[idx[i]].sx, corners[idx[i]].sy);
        ctx.closePath();
        ctx.fillStyle = col.core.replace(')', ` / ${alpha})`);
        ctx.fill();
        ctx.strokeStyle = col.core;
        ctx.lineWidth = isHover ? 1.5 : 0.8;
        ctx.stroke();
      };
      fill([4,5,6,7], 0.85);
      fill([1,5,6,2], 0.55);
      fill([3,2,6,7], 0.7);
    }

    if (pix > 6 || isHover) {
      ctx.fillStyle = isHover ? '#7fe3f5' : 'rgba(200, 220, 240, 0.7)';
      ctx.font = `${isHover ? 'bold ' : ''}10px JetBrains Mono, monospace`;
      ctx.textAlign = 'center';
      const label = n.displayName.length > 20 ? n.displayName.slice(0,18) + '…' : n.displayName;
      ctx.fillText(label, p.sx, p.sy - pix - 10);
      if (isHover) {
        ctx.fillStyle = 'rgba(150, 170, 190, 0.8)';
        ctx.font = '9px JetBrains Mono, monospace';
        ctx.fillText(`${n.latency.toFixed(0)}ms · ${n.group}`, p.sx, p.sy - pix - 22);
      }
    }
  }

  use3DEffect(() => {
    function tick() {
      draw();
      rafRef.current = requestAnimationFrame(tick);
    }
    tick();
    return () => cancelAnimationFrame(rafRef.current);
  }, [nodes, layout, cam, style3d, hover]);

  function onDown(e) {
    dragRef.current = { x: e.clientX, y: e.clientY, yaw: cam.yaw, pitch: cam.pitch, button: e.button };
  }
  function onMove(e) {
    const rect = canvasRef.current.getBoundingClientRect();
    const mx = e.clientX - rect.left, my = e.clientY - rect.top;
    let best = null; let bestDist = 22;
    for (let i = 0; i < nodes.length; i++) {
      const p = project(layout[i], rect.width, rect.height, cam);
      const d = Math.hypot(p.sx - mx, p.sy - my);
      if (d < bestDist) { bestDist = d; best = nodes[i].id; }
    }
    setHover(best);
    if (!dragRef.current) return;
    const dx = e.clientX - dragRef.current.x;
    const dy = e.clientY - dragRef.current.y;
    setCam({ ...cam, yaw: dragRef.current.yaw + dx * 0.006, pitch: Math.max(-1.3, Math.min(1.3, dragRef.current.pitch + dy * 0.006)) });
  }
  function onUp(e) {
    const moved = dragRef.current && (Math.abs(e.clientX - dragRef.current.x) > 4 || Math.abs(e.clientY - dragRef.current.y) > 4);
    dragRef.current = null;
    if (!moved && hover) {
      const n = nodes.find(nn => nn.id === hover);
      if (n) onSelect(n);
    }
  }
  function onWheel(e) {
    e.preventDefault();
    const d = e.deltaY > 0 ? 0.9 : 1.1;
    setCam(c => ({ ...c, zoom: Math.max(0.35, Math.min(2.5, c.zoom * d)) }));
  }

  const styleInfo = { cubes: 'Group clusters', lattice: 'Lattice sphere', grid: 'Flat grid' }[style3d];
  const critical = nodes.filter(n => n.status === 'crit');
  const offline = nodes.filter(n => n.status === 'off');

  return (
    <div ref={wrapRef} className="threed-wrap" onContextMenu={e => e.preventDefault()}>
      <canvas ref={canvasRef} className="threed-canvas"
        onMouseDown={onDown} onMouseMove={onMove} onMouseUp={onUp}
        onMouseLeave={() => { dragRef.current = null; setHover(null); }}
        onWheel={onWheel}
      />
      <div className="threed-hud">
        <div className="hud-panel" style={{maxWidth: 260}}>
          <h4>Camera</h4>
          <div className="hud-row"><span>style</span><span>{styleInfo}</span></div>
          <div className="hud-row"><span>yaw</span><span>{(cam.yaw * 57.3).toFixed(0)}°</span></div>
          <div className="hud-row"><span>pitch</span><span>{(cam.pitch * 57.3).toFixed(0)}°</span></div>
          <div className="hud-row"><span>zoom</span><span>{cam.zoom.toFixed(2)}×</span></div>
          <div style={{marginTop: 6, fontSize: 10, color: 'var(--text-mute)'}}>drag · rotate · scroll · zoom</div>
        </div>
        <div className="hud-panel">
          <h4>Fleet</h4>
          <div className="hud-row"><span>endpoints</span><span>{nodes.length}</span></div>
          <div className="hud-row"><span>up</span><span style={{color:'var(--ok)'}}>{nodes.filter(n=>n.status==='ok').length}</span></div>
          <div className="hud-row"><span>down</span><span style={{color:'var(--crit)'}}>{critical.length}</span></div>
          <div className="hud-row"><span>offline</span><span style={{color:'var(--text-dim)'}}>{offline.length}</span></div>
          {(critical.length > 0 || offline.length > 0) && (
            <div style={{marginTop: 6, fontSize: 10, maxWidth: 220}}>
              <div style={{color:'var(--crit)', letterSpacing:'0.04em', wordBreak:'break-all'}}>▲ {[...critical, ...offline].slice(0,3).map(n=>n.displayName).join(', ')}</div>
            </div>
          )}
        </div>
      </div>
      <div className="threed-controls">
        <button className="btn" onClick={() => setCam({ yaw: -0.5, pitch: 0.45, zoom: 1, tx: 0, ty: 0 })}>reset</button>
        <button className="btn" onClick={() => setCam(c => ({ ...c, zoom: Math.min(2.5, c.zoom * 1.2) }))}>zoom+</button>
        <button className="btn" onClick={() => setCam(c => ({ ...c, zoom: Math.max(0.35, c.zoom * 0.8) }))}>zoom−</button>
        <button className="btn" onClick={() => setCam(c => ({ ...c, yaw: c.yaw - 0.3 }))}>◀ rot</button>
        <button className="btn" onClick={() => setCam(c => ({ ...c, yaw: c.yaw + 0.3 }))}>rot ▶</button>
      </div>
    </div>
  );
}

Object.assign(window, { ThreeD });
