/* global React */
// ============================================================
// InfraMind — Additional product mockup stages
// StageStats (charts section for Overview), StageList, Stage3D
// ============================================================

const clampPS = (v, a = 0, b = 1) => Math.max(a, Math.min(b, v));

// ============================================================
// Statistical Analysis section (rendered inside Overview)
// Depth Distribution histogram + Defect Depth by Chainage stacked bars
// ============================================================
function StatisticalAnalysis({ local, delay = 1.6 }) {
  const p = clampPS((local - delay) / 1.2);
  if (p <= 0) return null;

  // Histogram: ~14 bins, skewed-right depth distribution
  const hist = [82, 100, 78, 60, 50, 42, 35, 28, 22, 18, 14, 9, 5, 3];
  const histMax = Math.max(...hist);

  // Stacked bars by chainage: each chainage has crit/high/med/low contributions
  // 14 chainage groups; tallest near right (matches heatmap hot zone)
  const chainages = Array.from({length: 14}, (_, i) => {
    const seed = (i + 1) * 9301 % 233280 / 233280;
    const baseHeight = 30 + Math.sin(i * 0.6) * 18 + seed * 25;
    // hot zone bump
    const bump = i >= 9 ? (i - 8) * 14 : 0;
    const total = baseHeight + bump;
    // distribute
    const crit = total * (0.05 + (i >= 11 ? 0.15 : 0));
    const high = total * (0.18 + Math.sin(i * 0.4) * 0.05);
    const med  = total * 0.32;
    const low  = total - crit - high - med;
    return { i, total, crit, high, med, low };
  });
  const chMax = Math.max(...chainages.map(c => c.total));

  return (
    <div className="ov-section ov-stats" style={{opacity: p, transform: `translateY(${(1 - p) * 6}px)`}}>
      <div className="ov-section-head">
        <div>
          <h4>Statistical Analysis</h4>
          <div className="ov-section-sub">Quantitative breakdown of defect measurements and distribution patterns</div>
        </div>
      </div>

      <div className="ov-stats-grid">
        {/* Defect Depth Distribution histogram */}
        <div className="ov-chart">
          <div className="ov-chart-head">
            <span className="ov-chart-title">Defect Depth Distribution</span>
            <span className="ov-chart-ctl">All ▾</span>
          </div>
          <div className="ov-chart-body">
            <svg viewBox="0 0 320 120" preserveAspectRatio="none" style={{width: "100%", height: 120, display: "block"}}>
              {/* Y axis ticks */}
              {[0, 30, 60, 90].map((y, i) => (
                <line key={i} x1="22" y1={110 - i * 28} x2="316" y2={110 - i * 28} stroke="var(--ink-150)" strokeWidth="0.5"/>
              ))}
              {[0, 50, 100, 150].map((v, i) => (
                <text key={i} x="18" y={114 - i * 28} textAnchor="end" fontFamily="var(--font-mono)" fontSize="7" fill="var(--ink-500)">{v}</text>
              ))}
              {/* Bars */}
              {hist.map((v, i) => {
                const w = 18;
                const x = 26 + i * 20;
                const h = (v / histMax) * 90 * p;
                return <rect key={i} x={x} y={110 - h} width={w} height={h} fill="#3B82F6" opacity="0.85"/>;
              })}
              {/* X labels */}
              {[0, 100, 200, 300, 400, 500, 600].map((v, i) => (
                <text key={i} x={26 + i * 47} y="120" textAnchor="middle" fontFamily="var(--font-mono)" fontSize="7" fill="var(--ink-500)">{v}</text>
              ))}
            </svg>
            <div className="ov-chart-axis">Defect depth (mm)</div>
          </div>
        </div>

        {/* Defect Depth by Chainage stacked bars */}
        <div className="ov-chart">
          <div className="ov-chart-head">
            <span className="ov-chart-title">Defect Depth by Chainage</span>
            <span className="ov-chart-ctl-row">
              <span className="ov-chart-toggle">
                <span className="ov-toggle-track on"><span className="ov-toggle-knob"></span></span>
                <span style={{fontSize: 9, color: "var(--ink-600)"}}>Grouped by Size</span>
              </span>
              <span className="ov-chart-ctl">Segment</span>
              <span className="ov-chart-ctl">All ▾</span>
            </span>
          </div>
          <div className="ov-chart-body">
            <svg viewBox="0 0 360 120" preserveAspectRatio="none" style={{width: "100%", height: 120, display: "block"}}>
              {[0, 30, 60, 90].map((y, i) => (
                <line key={i} x1="22" y1={110 - i * 28} x2="356" y2={110 - i * 28} stroke="var(--ink-150)" strokeWidth="0.5"/>
              ))}
              {chainages.map((c, i) => {
                const w = 18;
                const x = 26 + i * 24;
                const scale = 90 / chMax * p;
                let y = 110;
                const segs = [
                  { h: c.low * scale, color: "var(--ink-400)" },
                  { h: c.med * scale, color: "#EAB308" },
                  { h: c.high * scale, color: "#F97316" },
                  { h: c.crit * scale, color: "#DC2626" },
                ];
                return (
                  <g key={i}>
                    {segs.map((s, j) => {
                      y -= s.h;
                      return <rect key={j} x={x} y={y} width={w} height={s.h} fill={s.color}/>;
                    })}
                  </g>
                );
              })}
              {/* X labels — chainage values */}
              {chainages.map((c, i) => i % 2 === 0 && (
                <text key={i} x={26 + i * 24 + 9} y="120" textAnchor="middle" fontFamily="var(--font-mono)" fontSize="7" fill="var(--ink-500)">{i * 60}</text>
              ))}
            </svg>
            <div className="ov-chart-axis">Chainage (m)</div>
          </div>
        </div>
      </div>
    </div>
  );
}

// ============================================================
// Stage: List View — defect table + filter panel
// ============================================================
function StageList({ t, t0 }) {
  const local = Math.max(0, t - t0);

  // Defect rows
  const rows = React.useMemo(() => {
    const out = [];
    let s = 11;
    const r = () => { s = (s * 9301 + 49297) % 233280; return s / 233280; };
    for (let i = 0; i < 14; i++) {
      const depth = Math.floor(40 + r() * 230);
      const chainage = (r() * 760).toFixed(2);
      const area = (300 + r() * 6_000_000).toFixed(0);
      const volume = (5_000 + r() * 30_000_000).toFixed(0);
      const sev = depth > 150 ? "Critical" : depth > 80 ? "High" : depth > 40 ? "Medium" : "Low";
      out.push({
        id: `s${(s * 13 % 1e9).toString().padStart(11, "0").slice(0,11)}-001-${(i+1).toString().padStart(2,"0")}`,
        type: r() > 0.4 ? "Spalling" : "Mortar Loss",
        sev, depth, chainage, area, volume,
      });
    }
    return out;
  }, []);

  // Filter panel slides in at ~2.5s, applies at ~3.8s
  const filterIn = clampPS((local - 2.3) / 0.5);
  const filtersApplied = local > 3.0;
  const saveDialogIn  = clampPS((local - 3.2) / 0.25) * clampPS((4.6 - local) / 0.25);
  const shareDialogIn = clampPS((local - 4.6) / 0.25) * clampPS((6.5 - local) / 0.25);
  const toastIn = clampPS((local - 6.5) / 0.3) * clampPS((7.0 - local) / 0.3);

  return (
    <div className="stage-list">
      {/* Title row */}
      <div className="list-titlebar">
        <div>
          <h3 className="list-title">
            {filtersApplied ? "Test — Selected Defects" : "All Defects"}{" "}
            <span className="list-title-caret">▾</span>
          </h3>
          <div className="list-sub">
            {filtersApplied ? "575 defects · Filtered · Unsaved" : "10,805 defects"}
          </div>
        </div>
        <div className="list-actions">
          <span className="list-search">⌕ Search defects…</span>
          <span className="list-act-btn">⛶ Columns</span>
          <span className="list-act-btn">＝ Density</span>
        </div>
      </div>

      {/* Filter chips row */}
      <div className="list-filterbar">
        <span className="list-filter-chip">▽ Add Filter</span>
        {filtersApplied && (
          <>
            <span className="list-filter-chip applied">Severity: Critical ✕</span>
            <span className="list-filter-chip-clear">Clear All</span>
          </>
        )}
        <span style={{marginLeft: "auto"}}></span>
        {filtersApplied && (
          <>
            <span className="list-act-btn">⤴ Apply to 3D View</span>
            <span className="list-act-btn">＋ Save as List</span>
            <button className={`list-generate ${local > 5.4 && local < 5.8 ? "pressed" : ""}`}>Generate ▾</button>
          </>
        )}
      </div>

      {/* Shared list banner */}
      {filtersApplied && (
        <div className="list-shared-banner">
          <span className="list-shared-pin">⌘</span>
          <span><b>Shared List:</b> Test — Selected Defects · Access: View · ⛛ Shared with 0 people</span>
          <span style={{marginLeft: "auto", color: "var(--ink-500)"}}>↗ Notes & Activity · ⚙ Manage Access · 🗑 Delete List</span>
        </div>
      )}

      {/* Table */}
      <div className="list-table">
        <div className="list-row list-row-head">
          <span>☐</span>
          <span>ID</span>
          <span>TYPE</span>
          <span>SEVERITY</span>
          <span>DEPTH MM</span>
          <span>CHAINAGE M</span>
          <span>AREA MM²</span>
          <span>VOLUME MM³</span>
          <span>DEPTH MAP</span>
          <span>IMAGE</span>
          <span>COMMENTS</span>
        </div>
        {rows.map((r, i) => {
          const reveal = clampPS((local - 0.3 - i * 0.05) / 0.4);
          const dimmed = filtersApplied && r.sev !== "Critical";
          return (
            <div key={i} className={`list-row ${dimmed ? "dimmed" : ""}`} style={{opacity: reveal * (dimmed ? 0.35 : 1)}}>
              <span>☐</span>
              <span className="list-id pm-mono">{r.id.slice(0, 16)}…</span>
              <span>{r.type}</span>
              <span className={`list-sev list-sev-${r.sev.toLowerCase()}`}>● {r.sev}</span>
              <span className="list-num">{r.depth}</span>
              <span className="list-num">{r.chainage}</span>
              <span className="list-num">{Number(r.area).toLocaleString("en-GB")}</span>
              <span className="list-num">{Number(r.volume).toLocaleString("en-GB")}</span>
              <span className="list-cell-icon">▦</span>
              <span className="list-cell-icon">⊡</span>
              <span className="list-cell-icon">💬 0</span>
            </div>
          );
        })}
      </div>

      {/* Filter panel slide-in (right side) */}
      <div className="list-filterpanel" style={{transform: `translateX(${(1 - filterIn) * 100}%)`}}>
        <div className="lfp-head">
          <span>Filters</span>
          <span className="lfp-x">✕</span>
        </div>
        <div className="lfp-status">⚠ Draft changes not applied</div>
        <div className="lfp-section">
          <div className="lfp-section-head">Severity <span>▾</span></div>
          <div className="lfp-sev-row">
            <span className={`lfp-sev-chip ${local > 3.0 ? "active" : ""}`}>Critical</span>
            <span className="lfp-sev-chip">High</span>
            <span className="lfp-sev-chip">Medium</span>
            <span className="lfp-sev-chip">Low</span>
          </div>
        </div>
        <div className="lfp-section collapsed">Type <span>▸</span></div>
        <div className="lfp-section collapsed">Depth Range (mm) <span>▸</span></div>
        <div className="lfp-section collapsed">Chainage Range (m) <span>▸</span></div>
        <div className="lfp-section collapsed">Status <span>▸</span></div>
        <div className="lfp-section collapsed">Priority <span>▸</span></div>
        <div className="lfp-section collapsed">Assignment <span>▸</span></div>
        <div className="lfp-foot">
          <div className="lfp-foot-summary">
            <div>Filters configured: <b>1 rule</b></div>
            <div className="pm-dim">Estimated results: <b>~575 defects</b></div>
          </div>
          <div className="lfp-foot-btns">
            <span className="lfp-reset">Reset</span>
            <button className={`lfp-apply ${local > 3.7 && local < 4.1 ? "pressed" : ""}`}>Apply Filters</button>
          </div>
        </div>
      </div>

      {/* Save Filtered View as List dialog */}
      {saveDialogIn > 0 && (
        <div className="list-dialog-backdrop" style={{opacity: saveDialogIn}}>
          <div className="list-dialog" style={{transform: `translateY(${(1 - saveDialogIn) * 12}px)`}}>
            <div className="list-dialog-head">
              <h4>Save Filtered View as List</h4>
              <span className="list-dialog-x">✕</span>
            </div>
            <div className="list-dialog-body">
              <label className="list-dialog-label">List name</label>
              <input className="list-dialog-input" value="Test — Selected Defects" readOnly/>
              <label className="list-dialog-label" style={{marginTop: 8}}>Description (optional)</label>
              <textarea className="list-dialog-textarea" rows="2" value="Critical-severity defects across all chainages" readOnly></textarea>
              <div className="list-dialog-summary">
                <span>📋 575 defects · 1 filter rule</span>
              </div>
            </div>
            <div className="list-dialog-foot">
              <button className="list-dialog-cancel">Cancel</button>
              <button className="list-dialog-primary">Save list →</button>
            </div>
          </div>
        </div>
      )}

      {/* Share List dialog */}
      {shareDialogIn > 0 && (
        <div className="list-dialog-backdrop" style={{opacity: shareDialogIn}}>
          <div className="list-dialog" style={{transform: `translateY(${(1 - shareDialogIn) * 12}px)`}}>
            <div className="list-dialog-head">
              <h4>Share List</h4>
              <span className="list-dialog-x">✕</span>
            </div>
            <div className="list-dialog-body">
              <div className="list-dialog-row">
                <span className="list-dialog-row-icon">🌐</span>
                <div style={{flex: 1}}>
                  <div className="list-dialog-row-title">Test — Selected Defects</div>
                  <div className="list-dialog-row-sub">Anyone with workspace access · View only</div>
                </div>
                <span className="list-dialog-access">View ▾</span>
              </div>
              <label className="list-dialog-label" style={{marginTop: 14}}>Add people by email</label>
              <div className="list-dialog-email">
                <input className="list-dialog-input" placeholder="name@inframind.com" value="" readOnly/>
                <span className="list-dialog-perm">Can view ▾</span>
              </div>
              <label className="list-dialog-checkbox">
                <span className="list-dialog-check">✓</span>
                Notify recipients in workspace
              </label>
            </div>
            <div className="list-dialog-foot">
              <button className="list-dialog-cancel">Copy link</button>
              <button className="list-dialog-primary">Share list →</button>
            </div>
          </div>
        </div>
      )}

      {/* Save-as-list toast */}
      {toastIn > 0 && (
        <div className="list-toast" style={{opacity: toastIn}}>
          <span className="list-toast-dot"></span>
          <div>
            <div className="list-toast-title">List shared &amp; saved</div>
            <div className="list-toast-sub">"Test — Selected Defects" moved from Saved Lists to Shared Lists</div>
          </div>
        </div>
      )}
    </div>
  );
}

// ============================================================
// Stage: 3D View — interactive 3D tunnel with defect inspection
// ============================================================
function Stage3D({ t, t0 }) {
  const local = Math.max(0, t - t0);

  // Camera swing: starts at angle A (one-point straight-down-the-tunnel view),
  // swings to angle B (offset/oblique view from the right) around t≈3.0s,
  // then a subtle drift takes over.
  const swingP = clampPS((local - 2.6) / 1.4);          // 0→1 between 2.6s and 4.0s
  const drift  = Math.sin(local * 0.4) * 0.04;          // gentle ambient drift

  // Tunnel geometry (one-point perspective)
  // Foreground arch — large, at the front of the view
  const FRONT = { cx: 360, cy: 270, rx: 290, ry: 195, baseY: 410 };
  // Background arch — small, at the vanishing point. Animates with camera swing.
  // Angle A: VP at (530, 200) — straight ahead.
  // Angle B: VP shifted right and slightly up — viewer "turned" toward the right wall.
  const BACK_A = { cx: 530, cy: 200, rx: 22, ry: 14, baseY: 220 };
  const BACK_B = { cx: 620, cy: 180, rx: 22, ry: 14, baseY: 200 };
  const lerp = (a, b, p) => a + (b - a) * p;
  const BACK = {
    cx:    lerp(BACK_A.cx,    BACK_B.cx,    swingP) + drift * 20,
    cy:    lerp(BACK_A.cy,    BACK_B.cy,    swingP),
    rx:    lerp(BACK_A.rx,    BACK_B.rx,    swingP),
    ry:    lerp(BACK_A.ry,    BACK_B.ry,    swingP),
    baseY: lerp(BACK_A.baseY, BACK_B.baseY, swingP),
  };

  const archPt = (arch, theta) => [
    arch.cx + arch.rx * Math.cos(theta),
    arch.cy - arch.ry * Math.sin(theta)
  ];
  const archAt = (u) => ({
    cx:    FRONT.cx    + (BACK.cx    - FRONT.cx)    * u,
    cy:    FRONT.cy    + (BACK.cy    - FRONT.cy)    * u,
    rx:    FRONT.rx    + (BACK.rx    - FRONT.rx)    * u,
    ry:    FRONT.ry    + (BACK.ry    - FRONT.ry)    * u,
    baseY: FRONT.baseY + (BACK.baseY - FRONT.baseY) * u,
  });
  const surfacePt = (theta, u) => archPt(archAt(u), theta);

  const NSEG = 20;     // arch segments (around the half-arch from springline to springline)
  const NDEPTH = 7;    // depth bands (front → back)

  // Build wall quads (back to front for proper z-order). Each quad is a tunnel "tile".
  const strips = [];
  for (let d = NDEPTH - 1; d >= 0; d--) {
    for (let s = 0; s < NSEG; s++) {
      const u0 = d / NDEPTH;
      const u1 = (d + 1) / NDEPTH;
      const th0 = Math.PI * s / NSEG;
      const th1 = Math.PI * (s + 1) / NSEG;
      const p1 = surfacePt(th0, u0);
      const p2 = surfacePt(th1, u0);
      const p3 = surfacePt(th1, u1);
      const p4 = surfacePt(th0, u1);
      // Color: brick tan, varied by position around the arch + atmospheric fade toward white at the back
      const crownness = Math.sin((th0 + th1) / 2);           // 0 at sides, 1 at crown
      const distU = (u0 + u1) / 2;
      // Per-brick color variation (deterministic) — makes the masonry feel real
      const seed = ((s * 73 + d * 41) % 100) / 100;
      const variation = (seed - 0.5) * 0.16;
      const baseShade = 0.76 + crownness * 0.15 + variation;
      // Mild hue shift per brick
      const hueShift = (seed - 0.5) * 14;
      const lightR = lerp(214, 252, distU) + hueShift;
      const lightG = lerp(196, 248, distU) + hueShift * 0.5;
      const lightB = lerp(166, 240, distU) - hueShift * 0.3;
      const fill = `rgb(${Math.round(lightR * baseShade)},${Math.round(lightG * baseShade)},${Math.round(lightB * baseShade)})`;
      strips.push({ path: `M${p1[0]},${p1[1]} L${p2[0]},${p2[1]} L${p3[0]},${p3[1]} L${p4[0]},${p4[1]} Z`, fill });
    }
  }

  // Side walls (vertical, from springline down to floor) — left and right
  // Without these the area between the arch and the floor reads as empty space.
  // Subdivided into HEIGHT_BANDS × NDEPTH bricks per side for proper masonry feel.
  const HEIGHT_BANDS = 4;
  const sideStrips = [];
  for (const side of [-1, 1]) {       // -1 = left, +1 = right
    for (let d = NDEPTH - 1; d >= 0; d--) {
      for (let h = 0; h < HEIGHT_BANDS; h++) {
        const u0 = d / NDEPTH;
        const u1 = (d + 1) / NDEPTH;
        const h0 = h / HEIGHT_BANDS;
        const h1 = (h + 1) / HEIGHT_BANDS;
        const a0 = archAt(u0);
        const a1 = archAt(u1);
        const x0t = a0.cx + side * a0.rx, y0t = a0.cy + (a0.baseY - a0.cy) * h0;
        const x0b = a0.cx + side * a0.rx, y0b = a0.cy + (a0.baseY - a0.cy) * h1;
        const x1t = a1.cx + side * a1.rx, y1t = a1.cy + (a1.baseY - a1.cy) * h0;
        const x1b = a1.cx + side * a1.rx, y1b = a1.cy + (a1.baseY - a1.cy) * h1;
        const path = `M${x0t},${y0t} L${x0b},${y0b} L${x1b},${y1b} L${x1t},${y1t} Z`;
        // Side-wall colour: darker than crown (sides are in shadow), atmospheric fade with depth
        const distU = (u0 + u1) / 2;
        // Per-brick color variation
        const seed = ((d * 41 + h * 67 + (side > 0 ? 7 : 19) * 23) % 100) / 100;
        const variation = (seed - 0.5) * 0.14;
        const baseShade = 0.80 + side * -0.015 + variation - h * 0.01;   // brighter, blends with arch + floor
        const hueShift = (seed - 0.5) * 14;
        const lightR = lerp(214, 252, distU) + hueShift;
        const lightG = lerp(196, 248, distU) + hueShift * 0.5;
        const lightB = lerp(166, 240, distU) - hueShift * 0.3;
        const fill = `rgb(${Math.round(lightR * baseShade)},${Math.round(lightG * baseShade)},${Math.round(lightB * baseShade)})`;
        sideStrips.push({ path, fill });
      }
    }
  }

  // Side-wall brick courses (horizontal-ish lines that follow depth direction)
  const sideCourses = [];
  for (const side of [-1, 1]) {
    for (let h = 1; h < HEIGHT_BANDS; h++) {            // courses align with brick boundaries
      const heightFrac = h / HEIGHT_BANDS;
      let path = "";
      for (let dep = 0; dep <= NDEPTH; dep++) {
        const u = dep / NDEPTH;
        const a = archAt(u);
        const x = a.cx + side * a.rx;
        const y = a.cy + (a.baseY - a.cy) * heightFrac;
        path += (dep === 0 ? "M" : "L") + x + "," + y;
      }
      sideCourses.push({ path });
    }
  }

  // Side-wall vertical joints (perpendicular short lines on each strip)
  const sideJoints = [];
  let sideRng = 23;
  const sRand = () => { sideRng = (sideRng * 9301 + 49297) % 233280; return sideRng / 233280; };
  for (const side of [-1, 1]) {
    for (let d = 0; d < NDEPTH; d++) {
      for (let h = 0; h < HEIGHT_BANDS; h++) {
        const u0 = d / NDEPTH;
        const u1 = (d + 1) / NDEPTH;
        const heightFrac = (h + 0.3 + sRand() * 0.4) / HEIGHT_BANDS;
        const a0 = archAt(u0);
        const a1 = archAt(u1);
        const x0 = a0.cx + side * a0.rx, y0 = a0.cy + (a0.baseY - a0.cy) * heightFrac;
        const x1 = a1.cx + side * a1.rx, y1 = a1.cy + (a1.baseY - a1.cy) * heightFrac;
        sideJoints.push({ x1: x0, y1: y0, x2: x1, y2: y1, opacity: 0.35 - d * 0.04 });
      }
    }
  }

  // Brick course lines (concentric arches at each depth)
  const courses = [];
  for (let d = 1; d <= NDEPTH; d++) {
    const u = d / NDEPTH;
    let path = "";
    for (let s = 0; s <= NSEG; s++) {
      const theta = Math.PI * s / NSEG;
      const pt = surfacePt(theta, u);
      path += (s === 0 ? "M" : "L") + pt[0] + "," + pt[1];
    }
    courses.push({ path, opacity: 0.5 - u * 0.35 });
  }

  // Brick joints (short radial lines within each strip, randomly offset)
  const joints = [];
  let rng = 11;
  const rand = () => { rng = (rng * 9301 + 49297) % 233280; return rng / 233280; };
  for (let s = 0; s < NSEG; s++) {
    for (let d = 0; d < NDEPTH; d++) {
      const u0 = d / NDEPTH;
      const u1 = (d + 1) / NDEPTH;
      const offset = 0.2 + rand() * 0.6;
      const theta = Math.PI * (s + offset) / NSEG;
      const pTop = surfacePt(theta, u0);
      const pBot = surfacePt(theta, u1);
      joints.push({ x1: pTop[0], y1: pTop[1], x2: pBot[0], y2: pBot[1], opacity: 0.4 - d * 0.04 });
    }
  }

  // Floor polygon (trapezoid from front-base to back-base)
  const FA = archAt(0);
  const BA = archAt(1);
  const floorPath = `M${FA.cx - FA.rx},${FA.baseY} L${FA.cx + FA.rx},${FA.baseY} L${BA.cx + BA.rx},${BA.baseY} L${BA.cx - BA.rx},${BA.baseY} Z`;

  // Floor tiles — subdivide the floor into NDEPTH × NCROSS tiles so it gets the same
  // per-brick colour variation as the walls. Eliminates the flat-mask look.
  const NCROSS = 6;
  const floorTiles = [];
  for (let d = NDEPTH - 1; d >= 0; d--) {
    for (let c = 0; c < NCROSS; c++) {
      const u0 = d / NDEPTH;
      const u1 = (d + 1) / NDEPTH;
      const c0 = c / NCROSS;
      const c1 = (c + 1) / NCROSS;
      const a0 = archAt(u0);
      const a1 = archAt(u1);
      const xL0 = a0.cx - a0.rx + (a0.rx * 2) * c0;
      const xR0 = a0.cx - a0.rx + (a0.rx * 2) * c1;
      const xL1 = a1.cx - a1.rx + (a1.rx * 2) * c0;
      const xR1 = a1.cx - a1.rx + (a1.rx * 2) * c1;
      const path = `M${xL0},${a0.baseY} L${xR0},${a0.baseY} L${xR1},${a1.baseY} L${xL1},${a1.baseY} Z`;
      // Match wall tones, slight cooler bias for the floor
      const distU = (u0 + u1) / 2;
      const seed = ((d * 31 + c * 53) % 100) / 100;
      const variation = (seed - 0.5) * 0.14;
      const baseShade = 0.82 + variation;
      const hueShift = (seed - 0.5) * 12;
      const lightR = lerp(214, 252, distU) + hueShift;
      const lightG = lerp(196, 248, distU) + hueShift * 0.5;
      const lightB = lerp(166, 240, distU) - hueShift * 0.3;
      const fill = `rgb(${Math.round(lightR * baseShade)},${Math.round(lightG * baseShade)},${Math.round(lightB * baseShade)})`;
      floorTiles.push({ path, fill });
    }
  }

  // Defect markers scattered on tunnel surfaces
  const wallDefects = React.useMemo(() => {
    const out = [];
    let r = 7;
    const ra = () => { r = (r * 9301 + 49297) % 233280; return r / 233280; };
    for (let i = 0; i < 32; i++) {
      const theta = Math.PI * (0.08 + ra() * 0.84);
      const u = 0.05 + ra() * 0.75;
      const sev = ra();
      const color = sev > 0.85 ? "#DC2626" : sev > 0.6 ? "#F97316" : sev > 0.3 ? "#EAB308" : "#737373";
      out.push({ theta, u, color, sev });
    }
    return out;
  }, []);

  // Featured defect — patch on the right wall at moderate depth
  const featTheta = Math.PI * 0.22;       // right wall, below crown
  const featU = 0.30;
  const featSize = 0.07;                   // angular half-extent
  const featDepth = 0.09;                  // depth half-extent
  const fp1 = surfacePt(featTheta - featSize, featU - featDepth);
  const fp2 = surfacePt(featTheta + featSize, featU - featDepth);
  const fp3 = surfacePt(featTheta + featSize, featU + featDepth);
  const fp4 = surfacePt(featTheta - featSize, featU + featDepth);
  const featRevealed = local > 0.8;
  const cardIn = clampPS((local - 1.6) / 0.5);

  // Top chainage strip
  const segments = Array.from({length: 28}, (_, i) => {
    const seed = (i * 9301 % 233280) / 233280;
    const v = 0.2 + Math.sin(i * 0.4) * 0.25 + seed * 0.4 + (i > 16 ? 0.25 : 0);
    return clampPS(v);
  });

  return (
    <div className="stage-3d">
      {/* Top header bar: Spatial Distribution + filters */}
      <div className="s3-topbar">
        <div>
          <div className="s3-topbar-title">Spatial Distribution <span className="pm-dim">158 segments</span></div>
        </div>
        <div className="s3-topbar-ctls">
          <span className="s3-ctl-pill">⊕ Layers ▾ <span className="pm-dim">0 active</span></span>
          <span className="s3-ctl-pill"><span className="lfp-sev-chip active" style={{padding: "1px 6px"}}>Filters (1)</span></span>
        </div>
      </div>

      {/* Heatmap row */}
      <div className="s3-heatmap">
        <div className="s3-heatmap-labels">
          <div>Tunnel</div>
          <div>Findings</div>
          <div>Manual Inspection</div>
        </div>
        <div className="s3-heatmap-cells">
          {segments.map((v, i) => (
            <div key={i} className="s3-heat-cell" style={{background: `rgba(220, 38, 38, ${Math.max(0.06, v)})`}}></div>
          ))}
        </div>
        <div className="s3-heatmap-axis">
          <span>0m</span><span>200m</span><span>400m</span><span>600m</span><span>760m</span>
        </div>
      </div>

      {/* Main immersive tunnel canvas */}
      <div className="s3-canvas s3-canvas-light">
        {/* Real product screenshot — actual rendered 3D digital twin */}
        <img
          src="assets/tunnel-wall.png"
          alt="Real 3D digital twin of Demo Tunnel — brick wall surface with AI-classified defects"
          className="s3-tunnel-real"
          style={{
            opacity: clampPS((local - 0.2) / 0.4),
          }}
        />
        <svg className="s3-tunnel-svg s3-tunnel-svg--hidden" viewBox="0 0 760 460" preserveAspectRatio="xMidYMid slice" style={{display: "none"}}>
          {/* Light background */}
          <rect width="760" height="460" fill="#fafaf7"/>

          {/* Floor — subdivided tiles for per-brick variation */}
          {floorTiles.map((tile, i) => (
            <path key={"ft" + i} d={tile.path} fill={tile.fill}/>
          ))}
          <defs>
            <linearGradient id="floorFade" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" stopColor="rgba(80,60,30,0.0)"/>
              <stop offset="1" stopColor="rgba(80,60,30,0.45)"/>
            </linearGradient>
            <radialGradient id="vpGlow" cx="50%" cy="50%" r="50%">
              <stop offset="0"   stopColor="rgba(255,250,235,0.95)"/>
              <stop offset="0.4" stopColor="rgba(255,235,200,0.55)"/>
              <stop offset="1"   stopColor="rgba(255,235,200,0)"/>
            </radialGradient>
            <linearGradient id="ceilingShadow" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0" stopColor="rgba(0,0,0,0.35)"/>
              <stop offset="1" stopColor="rgba(0,0,0,0)"/>
            </linearGradient>
          </defs>

          {/* Side walls (vertical, between springline and floor) */}
          {sideStrips.map((s, i) => (
            <path key={"sw" + i} d={s.path} fill={s.fill}/>
          ))}

          {/* Wall strips (colored quads, back-to-front) */}
          {strips.map((s, i) => (
            <path key={i} d={s.path} fill={s.fill}/>
          ))}

          {/* Top ceiling shadow (subtle band) */}
          <path d={`M ${archPt(FRONT, 0).join(",")} A ${FRONT.rx},${FRONT.ry} 0 0 1 ${archPt(FRONT, Math.PI).join(",")}`}
                fill="url(#ceilingShadow)" stroke="none"/>

          {/* Brick course arcs (circumferential) */}
          {courses.map((c, i) => (
            <path key={i} d={c.path} fill="none" stroke="#5b4838" strokeWidth="0.55" opacity={c.opacity}/>
          ))}

          {/* Side-wall course lines (run along depth direction) */}
          {sideCourses.map((c, i) => (
            <path key={"sc" + i} d={c.path} fill="none" stroke="#5b4838" strokeWidth="0.5" opacity="0.4"/>
          ))}

          {/* Brick joints (perpendicular ticks) */}
          {joints.map((j, i) => (
            <line key={i} x1={j.x1} y1={j.y1} x2={j.x2} y2={j.y2}
                  stroke="#5b4838" strokeWidth="0.45" opacity={j.opacity}/>
          ))}

          {/* Side-wall joints */}
          {sideJoints.map((j, i) => (
            <line key={"sj" + i} x1={j.x1} y1={j.y1} x2={j.x2} y2={j.y2}
                  stroke="#5b4838" strokeWidth="0.4" opacity={j.opacity}/>
          ))}

          {/* Vanishing-point ambient light (creates the "end of the tunnel" feel) */}
          <ellipse cx={BACK.cx} cy={BACK.cy} rx={BACK.rx * 4.5} ry={BACK.ry * 4.5} fill="url(#vpGlow)"/>
          <ellipse cx={BACK.cx} cy={BACK.cy} rx={BACK.rx * 1.4} ry={BACK.ry * 1.4} fill="rgba(255,253,245,1)"/>

          {/* Tunnel floor brick joints (depth-direction lines) */}
          {(() => {
            const lines = [];
            for (let i = 1; i < 5; i++) {
              const frac = i / 5;
              const FAA = archAt(0);
              const BAA = archAt(1);
              const x0 = FAA.cx - FAA.rx + (FAA.rx * 2) * frac;
              const x1 = BAA.cx - BAA.rx + (BAA.rx * 2) * frac;
              lines.push(<line key={"fl" + i} x1={x0} y1={FAA.baseY} x2={x1} y2={BAA.baseY}
                                stroke="#5b4838" strokeWidth="0.4" opacity="0.3"/>);
            }
            // perpendicular floor courses at different depths
            for (let d = 1; d <= 4; d++) {
              const u = d / 5;
              const a = archAt(u);
              lines.push(<line key={"fc" + d} x1={a.cx - a.rx} y1={a.baseY} x2={a.cx + a.rx} y2={a.baseY}
                                stroke="#5b4838" strokeWidth="0.4" opacity={0.35 - u * 0.2}/>);
            }
            return lines;
          })()}

          {/* Defect markers on tunnel surface */}
          {wallDefects.map((d, i) => {
            const pt = surfacePt(d.theta, d.u);
            const reveal = clampPS((local - 0.3 - i * 0.03) / 0.4);
            const size = (3 + d.sev * 4) * (1 - d.u * 0.5);
            const opacity = (0.85 - d.u * 0.4) * reveal;
            return (
              <g key={i} opacity={opacity}>
                <circle cx={pt[0]} cy={pt[1]} r={size + 1.5} fill={d.color} opacity="0.25"/>
                <circle cx={pt[0]} cy={pt[1]} r={size} fill={d.color}
                        stroke="rgba(255,255,255,0.85)" strokeWidth="0.7"/>
              </g>
            );
          })}

          {/* Featured defect bounding patch */}
          {featRevealed && (
            <g opacity={clampPS((local - 0.8) / 0.4)}>
              <polygon
                points={`${fp1.join(",")} ${fp2.join(",")} ${fp3.join(",")} ${fp4.join(",")}`}
                fill="rgba(220, 38, 38, 0.22)"
                stroke="#F97316" strokeWidth="1.6" strokeDasharray="4 2"
              />
              {/* Hatched fill for damage feel */}
              <defs>
                <pattern id="hatch" width="6" height="6" patternUnits="userSpaceOnUse">
                  <line x1="0" y1="6" x2="6" y2="0" stroke="rgba(180, 40, 20, 0.4)" strokeWidth="1"/>
                </pattern>
              </defs>
              <polygon
                points={`${fp1.join(",")} ${fp2.join(",")} ${fp3.join(",")} ${fp4.join(",")}`}
                fill="url(#hatch)" stroke="none"
              />
              {/* Label */}
              <g transform={`translate(${Math.min(fp1[0], fp4[0]) - 4} ${Math.min(fp1[1], fp2[1]) - 18})`}>
                <rect x="0" y="0" width="80" height="14" fill="#F97316"/>
                <text x="6"  y="10" fontFamily="JetBrains Mono, monospace" fontSize="9" fill="#fff" fontWeight="700">SP-114</text>
                <text x="46" y="10" fontFamily="JetBrains Mono, monospace" fontSize="9" fill="#fff" opacity="0.9">114mm</text>
              </g>
              {/* Pin at center */}
              {(() => {
                const cx = (fp1[0] + fp3[0]) / 2;
                const cy = (fp1[1] + fp3[1]) / 2;
                return <circle cx={cx} cy={cy} r="2.5" fill="#F97316" stroke="#fff" strokeWidth="0.8"/>;
              })()}
            </g>
          )}
        </svg>

        {/* Axis indicator (bottom-left overlay) */}
        <div className="s3-axis-overlay">
          <svg width="40" height="40" viewBox="0 0 40 40">
            <line x1="20" y1="20" x2="34" y2="20" stroke="#DC2626" strokeWidth="1.6"/>
            <line x1="20" y1="20" x2="20" y2="6"  stroke="#16A34A" strokeWidth="1.6"/>
            <line x1="20" y1="20" x2="10" y2="28" stroke="#3B82F6" strokeWidth="1.6"/>
            <text x="36" y="22" fontSize="8" fontFamily="JetBrains Mono, monospace" fill="#525252">x</text>
            <text x="18" y="4"  fontSize="8" fontFamily="JetBrains Mono, monospace" fill="#525252">z</text>
            <text x="2"  y="32" fontSize="8" fontFamily="JetBrains Mono, monospace" fill="#525252">y</text>
          </svg>
        </div>

        {/* Focus card (defect info) — slides in */}
        {cardIn > 0 && (
          <div className="s3-focus-card" style={{opacity: cardIn, transform: `translateY(${(1 - cardIn) * 6}px)`}}>
            <div className="s3-focus-head">
              <span className="s3-focus-arrow">↗ Focus</span>
              <span className="s3-focus-label">spalling</span>
              <span className="s3-focus-x">✕</span>
            </div>
            <div className="s3-focus-rows">
              <div><span>Depth</span><b>114 mm</b></div>
              <div><span>Chainage</span><b>11.78 m</b></div>
              <div><span>Area</span><b>10,433.5 mm²</b></div>
              <div><span>Volume</span><b>1,189,883.7 mm³</b></div>
              <div><span>Location</span><b>Left wall</b></div>
            </div>
            <div className="s3-focus-thumb"></div>
          </div>
        )}

        {/* Right panel: defects list */}
        <div className="s3-defects-panel">
          <div className="s3-dp-head">
            <span>Defects <span className="pm-dim">36/575</span></span>
            <span className="s3-dp-clear">Clear</span>
          </div>
          <div className="s3-dp-filtered">
            <span className="s3-dp-dot"></span>
            <div>
              <div className="s3-dp-title">All Defects</div>
              <div className="pm-dim" style={{fontSize: 9}}>575 defects across all chainage</div>
            </div>
          </div>
          <div className="s3-dp-tabs">
            <span className="s3-dp-tab active">AI Model</span>
            <span className="s3-dp-tab">Manual Report</span>
          </div>
          <div className="s3-dp-rows">
            {Array.from({length: 14}, (_, i) => {
              const sev = i === 0 ? "C" : i % 4 === 0 ? "H" : i % 3 === 0 ? "M" : "L";
              const sevColor = sev === "C" ? "#DC2626" : sev === "H" ? "#F97316" : sev === "M" ? "#EAB308" : "#A3A3A3";
              const reveal = clampPS((local - 0.8 - i * 0.05) / 0.4);
              return (
                <div key={i} className={`s3-dp-row ${i === 0 ? "selected" : ""}`} style={{opacity: reveal}}>
                  <span>☐</span>
                  <span className="s3-dp-sev" style={{background: sevColor, color: "#fff"}}>{sev}</span>
                  <span>Mortar Loss</span>
                  <span className="pm-mono pm-dim" style={{fontSize: 9}}>{(11 + i * 0.3).toFixed(1)}</span>
                  <span className="pm-mono pm-dim" style={{fontSize: 9}}>{20 + i * 4}</span>
                  <span className="pm-mono pm-dim" style={{fontSize: 9}}>{(6 + i * 0.6).toFixed(2)}</span>
                </div>
              );
            })}
          </div>
        </div>

        {/* Bottom-right view controls */}
        <div className="s3-view-ctls">
          <div className="s3-vc-label">RESOLUTION</div>
          <div className="s3-vc-views">
            <span>Front</span><span className="active">Side</span><span>Top</span>
          </div>
          <div className="s3-vc-pad">
            <span>↑</span>
            <span>← <span className="s3-vc-center">●</span> →</span>
            <span>↓</span>
          </div>
          <div className="s3-vc-zoom">
            <span>−</span><span className="pm-dim">100%</span><span>+</span>
          </div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { StatisticalAnalysis, StageList, Stage3D });
