"use client";

import React, { Suspense, useEffect, useMemo, useRef, useState } from "react";
import { Canvas, useFrame, useThree } from "@react-three/fiber";
import { RoundedBox, Line, Sparkles, Html, useGLTF, useProgress, ContactShadows, Environment, Lightformer, Preload } from "@react-three/drei";
import { EffectComposer, Bloom } from "@react-three/postprocessing";
import { KernelSize } from "postprocessing";
import * as THREE from "three";
import { N, districtPosition, cameraFor } from "./theme";

type Props = {
  progress: React.RefObject<number>;
  reduced?: boolean;
  isMobile?: boolean;
  active?: boolean;
  stage?: number;
  ready?: boolean;
  onReady?: () => void;
};

const BG = "#eef1f6";
const FLOOR = "#e7ebf3";

// Per-district accent colours (mirror theme.ts / the side cards).
const GREEN = "#22C55E";  // 01 intake
const PURPLE = "#8B5CF6"; // 02 parallel
const BLUE = "#3B82F6";   // 03 optimization
const ORANGE = "#F59E0B"; // 04 grading
const RED = "#C01227";    // 05 winner

/* ===========================================================================
   Dev metrics HUD — enable by appending ?perf to the URL. Samples the live
   WebGL renderer (draw calls, triangles, resident geometries/textures, linked
   shader programs) plus a rolling FPS, twice a second, into a fixed overlay.
   Zero cost when not enabled (component simply isn't mounted).
=========================================================================== */
function PerfHud() {
  const gl = useThree((s) => s.gl);
  const el = useRef<HTMLDivElement | null>(null);
  // Rolling FPS + one-shot timings: page navigation load, and time from this
  // canvas mounting to its first rendered frame (≈ Workflow-3 activation cost).
  const acc = useRef({ t: 0, frames: 0, mount: 0, firstFrame: 0, nav: 0 });
  useEffect(() => {
    const a = acc.current;
    a.mount = performance.now();
    const navEntry = performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming | undefined;
    a.nav = navEntry ? Math.round(navEntry.loadEventEnd || navEntry.domContentLoadedEventEnd) : 0;
    const d = document.createElement("div");
    d.style.cssText =
      "position:fixed;top:10px;left:10px;z-index:99999;font:11px/1.5 ui-monospace,monospace;" +
      "background:rgba(0,0,0,.82);color:#39ff14;padding:8px 11px;border-radius:7px;white-space:pre;pointer-events:none";
    document.body.appendChild(d);
    el.current = d;
    return () => { d.remove(); };
  }, []);
  useFrame((_s, dt) => {
    const a = acc.current;
    if (!a.firstFrame && a.mount) a.firstFrame = performance.now() - a.mount;
    a.frames++; a.t += dt;
    if (a.t >= 0.5 && el.current) {
      const r = gl.info.render;
      const m = gl.info.memory;
      el.current.textContent =
        `FPS        ${(a.frames / a.t).toFixed(0)}\n` +
        `draws      ${r.calls}${r.calls < 100 ? " ✓" : " ⚠"}\n` +
        `triangles  ${r.triangles.toLocaleString()}\n` +
        `geometries ${m.geometries}\n` +
        `textures   ${m.textures}\n` +
        `programs   ${gl.info.programs?.length ?? 0}\n` +
        `page load  ${a.nav}ms\n` +
        `WF3 1st fr ${Math.round(a.firstFrame)}ms`;
      a.frames = 0; a.t = 0;
    }
  });
  return null;
}

/* ===========================================================================
   Camera — fully scroll-driven (reads a ref, never React state). Dollies forward
   through the world, riding behind+above the active district and looking slightly
   forward+down so the next district eases into frame (see cameraFor in theme.ts).
=========================================================================== */
function CameraRig({ progress, reduced, isMobile }: { progress: React.RefObject<number>; reduced: boolean; isMobile: boolean }) {
  const look = useRef(new THREE.Vector3(...cameraFor(0).look));
  useFrame((state, dt) => {
    const idx = (progress.current ?? 0) * (N - 1);
    const { pos, look: lk } = cameraFor(idx);
    const t = state.clock.elapsedTime;
    const mob = isMobile ? 1 : 0; // portrait pulls camera back+up for framing

    const camX = pos[0] * (1 - 0.25 * mob) + Math.sin(t * 0.22) * 0.22;
    const camY = pos[1] + 1.1 * mob + Math.sin(t * 0.3) * 0.1;
    const camZ = pos[2] + 3.0 * mob;

    // Frame-rate-independent smoothing so the dolly stays Apple-smooth even if FPS dips.
    const k = reduced ? 1 : 1 - Math.pow(0.0025, dt);
    const cam = state.camera;
    cam.position.x += (camX - cam.position.x) * k;
    cam.position.y += (camY - cam.position.y) * k;
    cam.position.z += (camZ - cam.position.z) * k;

    look.current.x += (lk[0] - look.current.x) * k;
    look.current.y += (lk[1] - look.current.y) * k;
    look.current.z += (lk[2] - look.current.z) * k;
    cam.lookAt(look.current);
  });
  return null;
}

/* ---------------------------------------------------------------------------
   Shared helpers
--------------------------------------------------------------------------- */

/** Fade a district's drei <Html> chips in/out by camera proximity (ref-driven).
 *  No-ops entirely when the district is asleep (its labels are unmounted then). */
function useLabelFade(i: number, progress: React.RefObject<number>, awake: boolean) {
  const labels = useRef<HTMLElement[]>([]);
  const register = (el: HTMLElement | null) => {
    if (el && !labels.current.includes(el)) labels.current.push(el);
  };
  useEffect(() => {
    // Labels are conditionally rendered, so their refs churn; drop stale ones each time.
    if (!awake) labels.current = [];
  }, [awake]);
  useFrame(() => {
    if (!awake) return;
    const idx = (progress.current ?? 0) * (N - 1);
    // Full within ±0.2 of the district centre, fading to 0 by ±0.5 — i.e. exactly
    // at the stage boundary where `focused` flips and the labels unmount, so the
    // mount/unmount is never visible (labels are already at 0 opacity by then).
    const op = THREE.MathUtils.clamp(1 - (Math.abs(idx - i) - 0.2) / 0.3, 0, 1);
    // eslint-disable-next-line react-hooks/immutability
    for (const el of labels.current) el.style.opacity = String(op);
  });
  return register;
}

/** Glowing data node: emissive sphere + optional pulsing ground ring. */
function GlowNode({ position, color, size = 0.1, pulse = false }: { position: [number, number, number]; color: string; size?: number; pulse?: boolean }) {
  const ring = useRef<THREE.Mesh>(null);
  useFrame((state) => {
    if (ring.current) ring.current.scale.setScalar(1 + Math.sin(state.clock.elapsedTime * 2 + position[0]) * 0.2);
  });
  return (
    <group position={position}>
      <mesh><sphereGeometry args={[size, 16, 16]} /><meshStandardMaterial color={color} emissive={color} emissiveIntensity={1} toneMapped={false} /></mesh>
      {pulse && (
        <mesh ref={ring} rotation={[-Math.PI / 2, 0, 0]} position={[0, -size * 0.6, 0]}>
          <ringGeometry args={[size * 1.5, size * 1.8, 24]} />
          <meshBasicMaterial color={color} transparent opacity={0.5} side={THREE.DoubleSide} toneMapped={false} />
        </mesh>
      )}
    </group>
  );
}

/** A travelling light packet along a curve (or straight line). */
function Packet({ curve, color, speed = 0.4, offset = 0, size = 0.06 }: { curve: THREE.Curve<THREE.Vector3>; color: string; speed?: number; offset?: number; size?: number }) {
  const dot = useRef<THREE.Mesh>(null);
  useFrame((state) => {
    if (dot.current) dot.current.position.copy(curve.getPoint((state.clock.elapsedTime * speed + offset) % 1));
  });
  return <mesh ref={dot}><sphereGeometry args={[size, 8, 8]} /><meshBasicMaterial color={color} toneMapped={false} /></mesh>;
}

function lineGeom(a: [number, number, number], b: [number, number, number]) {
  return new THREE.LineCurve3(new THREE.Vector3(...a), new THREE.Vector3(...b));
}

/* ---------------------------------------------------------------------------
   Real GLB fleet models (loaded with useGLTF, served from /public/models).
   Each is bounding-box-normalized so it sits on the pad at a consistent size,
   with a per-kind idle animation. Preloaded + memoized for smooth scrolling.
--------------------------------------------------------------------------- */
const MODELS: Record<string, string> = {
  bike: "/models/scooter.glb",
  auto: "/models/auto_rickshaw.glb",
  truck: "/models/truck.glb",
  van: "/models/van.glb",
};
// Per-kind target footprint (largest dimension, world units) + facing rotation.
// rotY gives a 3/4 ISOMETRIC view (not pure side/front) so every vehicle is
// instantly recognizable. `size` keeps relative proportions sane (truck>van>auto>bike).
const VEH_TUNE: Record<string, { size: number; rotY: number }> = {
  bike: { size: 1.0, rotY: Math.PI * 0.72 },
  auto: { size: 0.92, rotY: Math.PI * 0.72 },
  truck: { size: 1.2, rotY: Math.PI * 0.72 },
  van: { size: 1.02, rotY: Math.PI * 0.72 },
};
Object.values(MODELS).forEach((u) => useGLTF.preload(u));

/**
 * Clone a loaded GLB, enable shadows, flatten the costly transmission/clearcoat
 * passes, then bounding-box-NORMALIZE it: uniform `targetSize`, centred in x/z,
 * base sitting exactly on y=0 (so wheels rest on the node, nothing clips/floats).
 * Also collects wheel meshes (by name) so callers can spin them.
 */
function buildModel(scene: THREE.Object3D, targetSize: number) {
  const root = scene.clone(true);
  const wheels: THREE.Object3D[] = [];
  root.traverse((o) => {
    if (/wheel|tyre|tire|\brim\b/i.test(o.name)) wheels.push(o);
    const mesh = o as THREE.Mesh;
    if (!mesh.isMesh) return;
    mesh.castShadow = true;
    mesh.receiveShadow = false;
    const mat = mesh.material as THREE.MeshPhysicalMaterial | THREE.MeshPhysicalMaterial[];
    const t1 = (m: THREE.MeshPhysicalMaterial) => {
      if ("transmission" in m && m.transmission > 0) { m.transmission = 0; m.transparent = false; m.opacity = 1; }
      if ("envMapIntensity" in m) m.envMapIntensity = 0.4;
      m.needsUpdate = true;
    };
    if (Array.isArray(mat)) mat.forEach(t1); else t1(mat);
  });
  const box = new THREE.Box3().setFromObject(root);
  const size = new THREE.Vector3(); box.getSize(size);
  const center = new THREE.Vector3(); box.getCenter(center);
  const s = targetSize / Math.max(size.x, size.y, size.z, 0.0001);
  root.scale.setScalar(s);
  root.position.set(-center.x * s, -box.min.y * s, -center.z * s);
  return { root, wheels };
}

function GLBVehicle({ kind, awake = true }: { kind: string; awake?: boolean }) {
  const tune = VEH_TUNE[kind];
  const { scene } = useGLTF(MODELS[kind]);
  const { root } = useMemo(() => buildModel(scene, tune.size), [scene, tune]);
  const g = useRef<THREE.Group>(null);
  useFrame((state) => {
    const grp = g.current;
    if (!grp || !awake) return;
    const t = state.clock.elapsedTime;
    switch (kind) {
      case "bike": grp.position.y = Math.abs(Math.sin(t * 3)) * 0.025; grp.rotation.z = Math.sin(t * 3) * 0.012; break;
      case "auto": grp.rotation.z = Math.sin(t * 1.6) * 0.045; grp.position.x = Math.sin(t * 1.6) * 0.02; break;
      case "truck": grp.position.y = Math.sin(t * 1.0) * 0.012; grp.rotation.x = Math.sin(t * 0.8) * 0.006; break;
      default: grp.position.y = 0.05 + Math.sin(t * 1.3) * 0.03; break;
    }
  });
  return (
    <group ref={g} rotation={[0, tune.rotY, 0]}>
      <primitive object={root} />
    </group>
  );
}

// Model forward axis is +X, so the tangent heading needs no flip (no backwards driving).
const TRUCK_FWD_YAW = 0;

/**
 * truck.glb driving the route — SCROLL-DRIVEN (the network is static; the truck is
 * the only thing that moves). Scroll index → local t (0 = Dispatch Hub … 1 = Delivery)
 * sampled by arc length (constant velocity). Heading comes from the route tangent and
 * is smoothed with quaternion slerp (always faces travel, never snaps). Wheels spin in
 * proportion to distance travelled; a glow ring marks the active segment under the truck.
 */
function RouteTruck({ route, reduced, i, progress, awake = true }: { route: THREE.CatmullRomCurve3; reduced: boolean; i: number; progress: React.RefObject<number>; awake?: boolean }) {
  const { scene } = useGLTF(MODELS.truck);
  const built = useMemo(() => buildModel(scene, 0.85), [scene]);
  const wheels = useRef<THREE.Object3D[]>([]);
  useEffect(() => { wheels.current = built.wheels; }, [built]);
  const g = useRef<THREE.Group>(null);
  const body = useRef<THREE.Group>(null);
  const qTarget = useMemo(() => new THREE.Quaternion(), []);
  const eTmp = useMemo(() => new THREE.Euler(), []);
  const prevT = useRef(0);
  useFrame((state, dt) => {
    const grp = g.current;
    if (!grp || !awake) return;
    const idx = (progress.current ?? 0) * (N - 1);
    const t = THREE.MathUtils.clamp(idx - (i - 0.5), 0, 1); // 0 → hub … 1 → delivery
    const pos = route.getPointAt(t);
    grp.position.set(pos.x, 0.02, pos.z);
    const ahead = route.getPointAt(Math.min(1, t + 0.02));
    const dx = ahead.x - pos.x, dz = ahead.z - pos.z;
    if (dx * dx + dz * dz > 1e-7) {
      eTmp.set(0, Math.atan2(-dz, dx) + TRUCK_FWD_YAW, 0);
      qTarget.setFromEuler(eTmp);
      grp.quaternion.slerp(qTarget, reduced ? 1 : 1 - Math.pow(0.0006, dt)); // smooth heading
    }
    if (!reduced) {
      const moved = Math.abs(t - prevT.current);
      const ws = wheels.current;
      for (let k = 0; k < ws.length; k++) ws[k].rotation.x += moved * 60; // spin with distance
      if (body.current) body.current.position.y = Math.sin(state.clock.elapsedTime * 7) * 0.01;
    }
    prevT.current = t;
  });
  return (
    <group ref={g}>
      <group ref={body}><primitive object={built.root} /></group>
      <mesh position={[0, -0.005, 0]} rotation={[-Math.PI / 2, 0, 0]}>
        <ringGeometry args={[0.22, 0.5, 28]} />
        <meshBasicMaterial color={BLUE} transparent opacity={0.4} side={THREE.DoubleSide} toneMapped={false} />
      </mesh>
    </group>
  );
}

/* ===========================================================================
   DISTRICT 01 — INTAKE HUB (green): orders ride a conveyor into an AI intake
   gate, then get assigned out to four DISTINCT vehicles (label ↔ vehicle match).
=========================================================================== */
const RIDERS = [
  { id: "A", veh: "EV Bike", icon: "🚲", x: -2.0, kind: "bike" },
  { id: "B", veh: "Auto", icon: "🛺", x: -0.67, kind: "auto" },
  { id: "C", veh: "Cargo Truck", icon: "🚚", x: 0.67, kind: "truck" },
  { id: "D", veh: "EV Van", icon: "🚐", x: 2.0, kind: "van" },
];
const ROW_Z = 0.1; // fleet row sits just in front of the hub — framed centre, above the side card

/** A glowing order packet streaming along the intake route into the hub. */
function OrderPacket({ curve, offset }: { curve: THREE.Curve<THREE.Vector3>; offset: number }) {
  const ref = useRef<THREE.Mesh>(null);
  const mat = useRef<THREE.MeshStandardMaterial>(null);
  useFrame((state) => {
    const t = (state.clock.elapsedTime * 0.3 + offset) % 1;
    if (ref.current) { ref.current.position.copy(curve.getPoint(t)); ref.current.rotation.y = t * 6; }
    if (mat.current) mat.current.opacity = t > 0.9 ? (1 - t) * 10 : t < 0.08 ? t * 12 : 1;
  });
  return (
    <mesh ref={ref}>
      <boxGeometry args={[0.16, 0.12, 0.16]} />
      <meshStandardMaterial ref={mat} color="#ffffff" emissive={GREEN} emissiveIntensity={0.6} transparent metalness={0.1} roughness={0.5} toneMapped={false} />
    </mesh>
  );
}

/** A vehicle parked on its own glowing route node (flat on the floor, no white pad). */
function RiderAvatar({ rider, register, awake, focused }: { rider: typeof RIDERS[number]; register: (el: HTMLElement | null) => void; awake: boolean; focused: boolean }) {
  return (
    <group position={[rider.x, 0, ROW_Z]}>
      <mesh position={[0, 0.014, 0]} rotation={[-Math.PI / 2, 0, 0]}><ringGeometry args={[0.52, 0.64, 36]} /><meshBasicMaterial color={GREEN} transparent opacity={0.6} side={THREE.DoubleSide} toneMapped={false} /></mesh>
      <mesh position={[0, 0.008, 0]} rotation={[-Math.PI / 2, 0, 0]}><circleGeometry args={[0.52, 36]} /><meshBasicMaterial color={GREEN} transparent opacity={0.08} side={THREE.DoubleSide} toneMapped={false} /></mesh>
      <group position={[0, 0.02, 0]}>
        <Suspense fallback={null}>
          <GLBVehicle kind={rider.kind} awake={awake} />
        </Suspense>
      </group>
      {focused && (
        <Html center distanceFactor={9} position={[0, 1.5, 0]} zIndexRange={[20, 0]} pointerEvents="none">
          <div className="dm-st3d-chip" ref={register}>
            <span className="dm-st3d-chip__ico">{rider.icon}</span>
            <span className="dm-st3d-chip__txt"><b>Rider {rider.id}</b>{rider.veh}</span>
          </div>
        </Html>
      )}
    </group>
  );
}

const ORDERS_SRC: [number, number, number] = [3.3, 0.45, -0.7];
const HUB_CORE: [number, number, number] = [0, 0.55, -1.4];

const IntakeHub = React.memo(function IntakeHub({ i, progress, reduced, awake, focused }: { i: number; progress: React.RefObject<number>; reduced: boolean; awake: boolean; focused: boolean }) {
  const register = useLabelFade(i, progress, awake);
  const counter = useRef<HTMLSpanElement>(null);
  const halo = useRef<THREE.Mesh>(null);
  useFrame((state, dt) => {
    if (!awake) return;
    if (counter.current) {
      if (reduced) counter.current.textContent = "59";
      else {
        const cyc = state.clock.elapsedTime % 3.6;
        counter.current.textContent = String(Math.min(59, Math.round((cyc / 2.4) * 59)));
      }
    }
    if (halo.current && !reduced) halo.current.rotation.z += dt * 0.5;
  });
  const intake = useMemo(() => lineGeom(ORDERS_SRC, HUB_CORE), []);
  const routes = useMemo(() => RIDERS.map((r) => lineGeom(HUB_CORE, [r.x, 0.12, ROW_Z])), []);
  return (
    <group position={districtPosition(i)} visible={awake}>
      {/* orders.csv source node → intake route → hub (order packets flowing in) */}
      <GlowNode position={ORDERS_SRC} color={GREEN} size={0.12} />
      <Line points={intake.getPoints(2)} color={GREEN} lineWidth={1.8} transparent opacity={0.5} toneMapped={false} />
      {awake && !reduced && [0, 0.5].map((o) => <OrderPacket key={o} curve={intake} offset={o} />)}

      {/* AI Assignment Hub — a grounded dispatch hub (rim ring + hex dais + glowing core + halo) */}
      <group position={[0, 0, -1.4]}>
        <mesh position={[0, 0.02, 0]} rotation={[-Math.PI / 2, 0, 0]}><ringGeometry args={[0.95, 1.12, 44]} /><meshBasicMaterial color={GREEN} transparent opacity={0.5} side={THREE.DoubleSide} toneMapped={false} /></mesh>
        <mesh position={[0, 0.1, 0]}><cylinderGeometry args={[0.72, 0.86, 0.2, 6]} /><meshStandardMaterial color="#102a1d" metalness={0.4} roughness={0.45} emissive={GREEN} emissiveIntensity={0.12} /></mesh>
        <mesh position={[0, 0.55, 0]}><icosahedronGeometry args={[0.32, 1]} /><meshStandardMaterial color={GREEN} emissive={GREEN} emissiveIntensity={1.2} toneMapped={false} flatShading /></mesh>
        <mesh ref={halo} position={[0, 0.55, 0]} rotation={[Math.PI / 2.4, 0, 0]}><torusGeometry args={[0.5, 0.025, 10, 40]} /><meshStandardMaterial color={GREEN} emissive={GREEN} emissiveIntensity={1} toneMapped={false} /></mesh>
      </group>

      {/* assignment routes hub → each vehicle node */}
      {routes.map((c, j) => (
        <group key={j}>
          <Line points={c.getPoints(2)} color={GREEN} lineWidth={1.6} transparent opacity={0.45} toneMapped={false} />
          {awake && !reduced && <Packet curve={c} color={GREEN} speed={0.55} offset={j * 0.22} />}
        </group>
      ))}

      {/* soft contact shadow grounding the fleet (baked once for performance) */}
      <ContactShadows position={[0, 0.02, ROW_Z]} scale={[7.5, 2.6]} resolution={512} blur={2.6} far={1.2} opacity={0.4} frames={1} color="#1e293b" />

      {RIDERS.map((r) => <RiderAvatar key={r.id} rider={r} register={register} awake={awake} focused={focused} />)}

      {focused && (
        <>
          <Html center distanceFactor={9} position={[ORDERS_SRC[0], ORDERS_SRC[1] + 0.5, ORDERS_SRC[2]]} zIndexRange={[20, 0]} pointerEvents="none">
            <div className="dm-st3d-tag" style={{ ["--tc" as string]: GREEN }} ref={register}>📄 orders.csv</div>
          </Html>
          <Html center distanceFactor={9} position={[0, 1.8, -1.4]} zIndexRange={[20, 0]} pointerEvents="none">
            <div className="dm-st3d-count" ref={register}><span ref={counter}>0</span> Orders</div>
          </Html>
          <Html center distanceFactor={9} position={[0, 1.32, -1.4]} zIndexRange={[20, 0]} pointerEvents="none">
            <div className="dm-st3d-tag" style={{ ["--tc" as string]: GREEN }} ref={register}>🤖 AI Assignment Hub</div>
          </Html>
        </>
      )}
    </group>
  );
});

/* ===========================================================================
   DISTRICT 02 — STRATEGY NETWORK (purple): an AI core fanning six glowing lanes
   out to six labelled strategy pods — packets flow through all six at once.
=========================================================================== */
const STRATS = [
  { name: "EV Aware", unified: true },
  { name: "Multi Trip", unified: true },
  { name: "Time Aware", unified: true },
  { name: "Balanced", unified: false },
  { name: "Fuel Saver", unified: false },
  { name: "Proximity", unified: false },
];
const CORE: [number, number, number] = [0, 1.45, -2.7];

const StrategyNetwork = React.memo(function StrategyNetwork({ i, progress, reduced, awake, focused }: { i: number; progress: React.RefObject<number>; reduced: boolean; awake: boolean; focused: boolean }) {
  const register = useLabelFade(i, progress, awake);
  const ring = useRef<THREE.Mesh>(null);
  const coreMesh = useRef<THREE.Mesh>(null);
  useFrame((state, dt) => {
    if (reduced || !awake) return;
    if (ring.current) ring.current.rotation.z += dt * 0.6;
    if (coreMesh.current) coreMesh.current.rotation.y += dt * 0.4;
  });
  const lanes = useMemo(
    () =>
      STRATS.map((s, j) => {
        const x = -3.4 + j * 1.36;
        const z = -0.3 + Math.abs(j - 2.5) * 0.18;
        const end: [number, number, number] = [x, 0.62, z];
        return { ...s, end, curve: lineGeom(CORE, end) };
      }),
    [],
  );
  return (
    <group position={districtPosition(i)} visible={awake}>
      {/* dark tech dais + glowing rim (reads as a control platform, not a white slab) */}
      <mesh position={[0, 0.06, -0.6]}><cylinderGeometry args={[5.4, 5.6, 0.12, 56]} /><meshStandardMaterial color="#161b30" metalness={0.5} roughness={0.42} emissive={PURPLE} emissiveIntensity={0.07} /></mesh>
      <mesh position={[0, 0.13, -0.6]} rotation={[-Math.PI / 2, 0, 0]}><ringGeometry args={[5.2, 5.42, 64]} /><meshBasicMaterial color={PURPLE} transparent opacity={0.4} side={THREE.DoubleSide} toneMapped={false} /></mesh>

      <mesh position={[0, 0.65, -2.7]}><cylinderGeometry args={[0.4, 0.62, 1.2, 24]} /><meshStandardMaterial color="#2a3350" metalness={0.5} roughness={0.4} /></mesh>
      <mesh ref={coreMesh} position={CORE}><icosahedronGeometry args={[0.6, 1]} /><meshStandardMaterial color={PURPLE} emissive={PURPLE} emissiveIntensity={1.3} toneMapped={false} flatShading /></mesh>
      <mesh ref={ring} position={CORE} rotation={[Math.PI / 2.4, 0, 0]}><torusGeometry args={[0.98, 0.03, 10, 44]} /><meshStandardMaterial color={PURPLE} emissive={PURPLE} emissiveIntensity={1.1} toneMapped={false} /></mesh>
      {focused && (
        <Html center distanceFactor={9} position={[0, 2.5, -2.7]} zIndexRange={[20, 0]} pointerEvents="none">
          <div className="dm-st3d-tag" style={{ ["--tc" as string]: PURPLE }} ref={register}>🤖 AI Engine</div>
        </Html>
      )}

      {lanes.map((l, j) => {
        const col = l.unified ? PURPLE : "#a99bd6";
        return (
          <group key={l.name}>
            <Line points={l.curve.getPoints(2)} color={col} lineWidth={l.unified ? 2.4 : 1.6} transparent opacity={l.unified ? 0.7 : 0.5} toneMapped={false} />
            {awake && !reduced && <Packet curve={l.curve} color={col} speed={0.5} offset={j * 0.16} size={l.unified ? 0.075 : 0.06} />}
            {/* flat glowing route node (consistent with the dispatch network) */}
            <mesh position={[l.end[0], 0.135, l.end[2]]} rotation={[-Math.PI / 2, 0, 0]}><ringGeometry args={[0.34, 0.44, 28]} /><meshBasicMaterial color={col} transparent opacity={l.unified ? 0.6 : 0.4} side={THREE.DoubleSide} toneMapped={false} /></mesh>
            <GlowNode position={l.end} color={col} size={l.unified ? 0.15 : 0.11} />
            {focused && (
              <Html center distanceFactor={9} position={[l.end[0], l.end[1] + 0.62, l.end[2]]} zIndexRange={[20, 0]} pointerEvents="none">
                <div className={`dm-st3d-tag ${l.unified ? "is-u" : "is-muted"}`} style={{ ["--tc" as string]: l.unified ? PURPLE : "#94a3b8" }} ref={register}>{l.name}</div>
              </Html>
            )}
          </group>
        );
      })}
    </group>
  );
});

/* ===========================================================================
   DISTRICT 03 — ROUTE OPTIMIZATION (blue): a clean delivery map — a depot, glowing
   destination pins, the optimized route, and a van driving it. No grey cubes.
=========================================================================== */
const DEPOT3: [number, number] = [-2.9, 1.9];
const DEST: [number, number][] = [[-1.5, -2.6], [0.1, -1.0], [1.7, -2.3], [2.7, 0.3], [0.9, 1.5], [-0.6, 0.5]];
const VALIDATIONS = [
  { t: "Route Optimization", p: [-2.7, 2.5, -0.4] as [number, number, number] },
  { t: "Battery Validation", p: [2.6, 2.9, -1.2] as [number, number, number] },
  { t: "SLA Validation", p: [-2.3, 2.0, 1.6] as [number, number, number] },
  { t: "Feasibility Check", p: [2.5, 1.7, 1.8] as [number, number, number] },
];

// Node labels along the delivery route (Dispatch Hub → Route Nodes → Delivery).
const DEST_LABELS: (string | null)[] = ["Route Node A", null, "Route Node B", null, "Route Node C", "📦 Delivery"];

/** Expanding ring at the delivery node — fires only once the truck arrives (local t > 0.85). */
function DeliveryPulse({ pos, i, progress }: { pos: [number, number, number]; i: number; progress: React.RefObject<number> }) {
  const ring = useRef<THREE.Mesh>(null);
  const mat = useRef<THREE.MeshBasicMaterial>(null);
  useFrame((state) => {
    const idx = (progress.current ?? 0) * (N - 1);
    const t = THREE.MathUtils.clamp(idx - (i - 0.5), 0, 1);
    if (!ring.current || !mat.current) return;
    if (t > 0.85) {
      const ph = (state.clock.elapsedTime % 1.1) / 1.1;
      ring.current.scale.setScalar(1 + ph * 3);
      mat.current.opacity = (1 - ph) * 0.6;
    } else {
      mat.current.opacity = 0;
    }
  });
  return (
    <mesh ref={ring} position={pos} rotation={[-Math.PI / 2, 0, 0]}>
      <ringGeometry args={[0.2, 0.3, 32]} />
      <meshBasicMaterial ref={mat} color={BLUE} transparent opacity={0} side={THREE.DoubleSide} toneMapped={false} />
    </mesh>
  );
}

/** "✓ Delivered" completion badge — fades in when the truck reaches the destination. */
function DeliveryBadge({ pos, i, progress }: { pos: [number, number, number]; i: number; progress: React.RefObject<number> }) {
  const el = useRef<HTMLDivElement>(null);
  useFrame(() => {
    const idx = (progress.current ?? 0) * (N - 1);
    const t = THREE.MathUtils.clamp(idx - (i - 0.5), 0, 1);
    if (el.current) el.current.style.opacity = t > 0.9 ? "1" : "0";
  });
  return (
    <Html center distanceFactor={9} position={pos} zIndexRange={[20, 0]} pointerEvents="none">
      <div className="dm-st3d-tag is-win" style={{ ["--tc" as string]: BLUE, opacity: 0 }} ref={el}>✓ Delivered</div>
    </Html>
  );
}

const CityRouteMap = React.memo(function CityRouteMap({ i, progress, reduced, awake, focused }: { i: number; progress: React.RefObject<number>; reduced: boolean; awake: boolean; focused: boolean }) {
  const register = useLabelFade(i, progress, awake);
  // OPEN delivery path (Dispatch Hub → … → Delivery). Static "road"; only the truck moves.
  const route = useMemo(() => {
    const pts = [DEPOT3, ...DEST].map(([x, z]) => new THREE.Vector3(x, 0.12, z));
    return new THREE.CatmullRomCurve3(pts, false, "catmullrom", 0.2);
  }, []);
  const routePts = useMemo(() => route.getPoints(100), [route]);
  const delivery = DEST[DEST.length - 1];
  return (
    <group position={districtPosition(i)} visible={awake}>
      {/* STATIC delivery route (the road) — no flowing lines; the truck is the only mover */}
      <Line points={routePts} color={BLUE} lineWidth={2.6} transparent opacity={0.8} toneMapped={false} />
      <Suspense fallback={null}>
        <RouteTruck route={route} reduced={reduced} i={i} progress={progress} awake={awake} />
      </Suspense>

      {/* dispatch hub (static) */}
      <group position={[DEPOT3[0], 0, DEPOT3[1]]}>
        <mesh position={[0, 0.02, 0]} rotation={[-Math.PI / 2, 0, 0]}><ringGeometry args={[0.62, 0.74, 36]} /><meshBasicMaterial color={BLUE} transparent opacity={0.5} side={THREE.DoubleSide} toneMapped={false} /></mesh>
        <mesh position={[0, 0.13, 0]}><cylinderGeometry args={[0.5, 0.58, 0.26, 6]} /><meshStandardMaterial color="#0f2036" emissive={BLUE} emissiveIntensity={0.15} metalness={0.4} roughness={0.45} /></mesh>
        <GlowNode position={[0, 0.52, 0]} color={BLUE} size={0.14} />
      </group>

      {/* route nodes + delivery destination (static geometry; labels gated below) */}
      {DEST.map(([x, z], j) => {
        const isDelivery = j === DEST.length - 1;
        return (
          <group key={j} position={[x, 0, z]}>
            <mesh position={[0, 0.02, 0]} rotation={[-Math.PI / 2, 0, 0]}><ringGeometry args={[0.16, 0.23, 20]} /><meshBasicMaterial color={BLUE} transparent opacity={0.55} side={THREE.DoubleSide} toneMapped={false} /></mesh>
            <mesh position={[0, 0.3, 0]}><cylinderGeometry args={[0.03, 0.03, 0.6, 8]} /><meshStandardMaterial color={BLUE} emissive={BLUE} emissiveIntensity={0.4} /></mesh>
            <GlowNode position={[0, 0.66, 0]} color={BLUE} size={isDelivery ? 0.16 : 0.12} />
          </group>
        );
      })}

      {/* delivery arrival pulse (only mounted/animated while this district is active) */}
      {awake && <DeliveryPulse pos={[delivery[0], 0.02, delivery[1]]} i={i} progress={progress} />}

      {focused && (
        <>
          <Html center distanceFactor={9} position={[DEPOT3[0], 1.05, DEPOT3[1]]} zIndexRange={[20, 0]} pointerEvents="none">
            <div className="dm-st3d-tag" style={{ ["--tc" as string]: BLUE }} ref={register}>🏢 Dispatch Hub</div>
          </Html>
          {DEST.map(([x, z], j) => {
            const label = DEST_LABELS[j];
            return label ? (
              <Html key={j} center distanceFactor={9} position={[x, 1.06, z]} zIndexRange={[20, 0]} pointerEvents="none">
                <div className="dm-st3d-tag" style={{ ["--tc" as string]: BLUE }} ref={register}>{label}</div>
              </Html>
            ) : null;
          })}
          <DeliveryBadge pos={[delivery[0], 1.42, delivery[1]]} i={i} progress={progress} />
          {VALIDATIONS.map((v) => (
            <Html key={v.t} center distanceFactor={9} position={v.p} zIndexRange={[20, 0]} pointerEvents="none">
              <div className="dm-st3d-tag" style={{ ["--tc" as string]: BLUE }} ref={register}>✓ {v.t}</div>
            </Html>
          ))}
          <Html center distanceFactor={9} position={[0, 3.2, -0.4]} zIndexRange={[20, 0]} pointerEvents="none">
            <div className="dm-st3d-score" style={{ ["--tc" as string]: BLUE }} ref={register}>🗺️ Route optimized · <b>−18%</b> distance</div>
          </Html>
        </>
      )}
    </group>
  );
});

/* ===========================================================================
   DISTRICT 04 — COMMAND CENTER (orange): a control room — a curved bank of
   holographic KPI screens with live meters and a headline grade.
=========================================================================== */
const KPIS = [
  { n: "Fulfillment", v: 88, a: -0.66 },
  { n: "SLA Compliance", v: 95, a: -0.22 },
  { n: "Efficiency", v: 92, a: 0.22 },
  { n: "Battery", v: 100, a: 0.66 },
];

const CommandCenter = React.memo(function CommandCenter({ i, progress, awake, focused }: { i: number; progress: React.RefObject<number>; awake: boolean; focused: boolean }) {
  const register = useLabelFade(i, progress, awake);
  const screens = useMemo(
    () =>
      KPIS.map((k) => {
        const r = 3.6;
        const p: [number, number, number] = [Math.sin(k.a) * r, 1.5, -1.4 + (1 - Math.cos(k.a)) * r];
        return { ...k, p, rotY: -k.a };
      }),
    [],
  );
  return (
    <group position={districtPosition(i)} visible={awake}>
      <mesh position={[0, 0.45, 1.4]} rotation={[-0.12, 0, 0]}><boxGeometry args={[5.6, 0.18, 1.5]} /><meshStandardMaterial color="#dfe4f1" metalness={0.4} roughness={0.4} /></mesh>
      <mesh position={[0, 0.12, 0.9]}><boxGeometry args={[5.2, 0.24, 0.6]} /><meshStandardMaterial color="#cdd5e6" metalness={0.3} roughness={0.5} /></mesh>

      {screens.map((s) => (
        <group key={s.n} position={s.p} rotation={[0, s.rotY, 0]}>
          <RoundedBox args={[1.7, 1.15, 0.06]} radius={0.06} smoothness={2}>
            <meshStandardMaterial color="#ffffff" emissive={ORANGE} emissiveIntensity={0.18} metalness={0.1} roughness={0.4} transparent opacity={0.92} />
          </RoundedBox>
          {focused && (
            <Html center distanceFactor={8} position={[0, 0, 0.05]} zIndexRange={[20, 0]} pointerEvents="none">
              <div className="dm-st3d-kpi" style={{ ["--tc" as string]: ORANGE }} ref={register}>
                <span className="dm-st3d-kpi__n">{s.n}</span>
                <span className="dm-st3d-kpi__v">{s.v}<i>%</i></span>
                <span className="dm-st3d-kpi__bar"><i style={{ width: `${s.v}%` }} /></span>
              </div>
            </Html>
          )}
        </group>
      ))}

      {focused && (
        <Html center distanceFactor={9} position={[0, 3.1, -0.4]} zIndexRange={[20, 0]} pointerEvents="none">
          <div className="dm-st3d-score" style={{ ["--tc" as string]: ORANGE }} ref={register}>Performance Grade <b>A</b> · 4.5 / 5</div>
        </Html>
      )}
    </group>
  );
});

/* ===========================================================================
   DISTRICT 05 — WINNER PODIUM (red, hero): four strategy pillars at score
   heights; the winner is elevated under a spotlight with a trophy + confetti.
=========================================================================== */
const PODIUM = [
  { n: "EV Aware", v: 88, x: -2.4, win: true },
  { n: "Multi Trip", v: 79, x: -0.8, win: false },
  { n: "Balanced", v: 71, x: 0.8, win: false },
  { n: "Proximity", v: 64, x: 2.4, win: false },
];

function Pillar({ p, register, focused }: { p: typeof PODIUM[number]; register: (el: HTMLElement | null) => void; focused: boolean }) {
  const h = 0.6 + (p.v / 100) * 2.2;
  const col = p.win ? RED : "#94a3b8";
  return (
    <group position={[p.x, 0, -0.6]}>
      <mesh position={[0, h / 2 + 0.1, 0]}>
        <boxGeometry args={[1.0, h, 1.0]} />
        <meshStandardMaterial color={col} emissive={col} emissiveIntensity={p.win ? 0.7 : 0.15} metalness={0.2} roughness={0.4} transparent opacity={p.win ? 0.96 : 0.7} toneMapped={false} />
      </mesh>
      {focused && (
        <Html center distanceFactor={9} position={[0, h + 0.45, 0]} zIndexRange={[20, 0]} pointerEvents="none">
          <div className={`dm-st3d-tag ${p.win ? "is-win" : "is-muted"}`} style={{ ["--tc" as string]: col }} ref={register}><b>{p.v}%</b>&nbsp;{p.n}</div>
        </Html>
      )}
    </group>
  );
}

const WinnerPodium = React.memo(function WinnerPodium({ i, progress, reduced, isMobile, awake, focused }: { i: number; progress: React.RefObject<number>; reduced: boolean; isMobile: boolean; awake: boolean; focused: boolean }) {
  const register = useLabelFade(i, progress, awake);
  const trophy = useRef<THREE.Group>(null);
  useFrame((state) => {
    if (trophy.current && !reduced && awake) {
      const winH = 0.6 + (88 / 100) * 2.2;
      trophy.current.position.y = winH + 0.95 + Math.sin(state.clock.elapsedTime * 1.4) * 0.08;
      trophy.current.rotation.y += 0.01;
    }
  });
  return (
    <group position={districtPosition(i)} visible={awake}>
      <mesh position={[0, 0.05, -0.6]}><cylinderGeometry args={[4.3, 4.5, 0.12, 48]} /><meshStandardMaterial color="#1d1622" metalness={0.5} roughness={0.42} emissive={RED} emissiveIntensity={0.06} /></mesh>
      <mesh position={[0, 0.12, -0.6]} rotation={[-Math.PI / 2, 0, 0]}><ringGeometry args={[4.1, 4.32, 56]} /><meshBasicMaterial color={RED} transparent opacity={0.35} side={THREE.DoubleSide} toneMapped={false} /></mesh>
      <mesh position={[-2.4, 0.14, -0.6]}><cylinderGeometry args={[0.85, 0.9, 0.14, 28]} /><meshStandardMaterial color={RED} emissive={RED} emissiveIntensity={0.9} toneMapped={false} /></mesh>

      {PODIUM.map((p) => <Pillar key={p.n} p={p} register={register} focused={focused} />)}

      <mesh position={[-2.4, 3.0, -0.6]}><coneGeometry args={[1.0, 2.6, 28, 1, true]} /><meshBasicMaterial color={RED} transparent opacity={0.07} side={THREE.DoubleSide} toneMapped={false} /></mesh>
      <group ref={trophy} position={[-2.4, 3.5, -0.6]}>
        <mesh position={[0, 0.18, 0]}><sphereGeometry args={[0.26, 24, 12, 0, Math.PI * 2, 0, Math.PI / 1.7]} /><meshStandardMaterial color="#FFD45A" emissive="#FFB020" emissiveIntensity={1.2} metalness={0.6} roughness={0.25} toneMapped={false} /></mesh>
        <mesh position={[0, -0.06, 0]}><cylinderGeometry args={[0.05, 0.05, 0.24, 10]} /><meshStandardMaterial color="#FFD45A" emissive="#FFB020" emissiveIntensity={1} toneMapped={false} /></mesh>
        <mesh position={[0, -0.22, 0]}><cylinderGeometry args={[0.18, 0.22, 0.1, 16]} /><meshStandardMaterial color="#FFD45A" emissive="#FFB020" emissiveIntensity={1} toneMapped={false} /></mesh>
      </group>

      {awake && !reduced && <Sparkles count={isMobile ? 12 : 16} scale={[5, 4, 4]} position={[-2.4, 2.6, -0.6]} size={3.2} speed={0.5} opacity={0.9} color="#ff9aa9" />}

      {focused && (
        <Html center distanceFactor={9} position={[0.7, 3.0, -0.6]} zIndexRange={[20, 0]} pointerEvents="none">
          <div className="dm-st3d-winner3d" ref={register}>
            <span className="dm-st3d-winner3d__top">🏆 Best Strategy</span>
            <span className="dm-st3d-winner3d__name">EV Aware</span>
            <span className="dm-st3d-winner3d__row"><b>88%</b> Performance Score</span>
            <span className="dm-st3d-winner3d__row"><b>52/59</b> Orders Fulfilled</span>
          </div>
        </Html>
      )}
    </group>
  );
});

/* ===========================================================================
   The connected world: baked-texture floor + glowing "data road" through all
   five districts + the (proximity-mounted) districts + Bloom.
=========================================================================== */
function DataRoad() {
  const curve = useMemo(() => {
    const pts: THREE.Vector3[] = [];
    for (let i = 0; i < N; i++) {
      const [x, , z] = districtPosition(i);
      pts.push(new THREE.Vector3(x, 0.07, z));
    }
    return new THREE.CatmullRomCurve3(pts, false, "catmullrom", 0.3);
  }, []);
  const linePts = useMemo(() => curve.getPoints(110), [curve]);
  return (
    <group>
      <Line points={linePts} color="#9aa6c4" lineWidth={2} transparent opacity={0.5} toneMapped={false} />
      {[0, 0.33, 0.66].map((o) => <Packet key={o} curve={curve} color="#c7cee0" speed={0.06} offset={o} size={0.09} />)}
    </group>
  );
}

/** A baked grid texture (drawn once) — far cheaper than a per-frame procedural Grid shader. */
function gridCanvas() {
  const c = document.createElement("canvas");
  c.width = c.height = 256;
  const ctx = c.getContext("2d")!;
  ctx.fillStyle = FLOOR;
  ctx.fillRect(0, 0, 256, 256);
  ctx.strokeStyle = "#d3dae8";
  ctx.lineWidth = 1.5;
  for (let i = 0; i <= 8; i++) {
    const p = (i / 8) * 256 + 0.5;
    ctx.beginPath(); ctx.moveTo(p, 0); ctx.lineTo(p, 256); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(0, p); ctx.lineTo(256, p); ctx.stroke();
  }
  ctx.strokeStyle = "#c0cadd";
  ctx.lineWidth = 3;
  ctx.strokeRect(0, 0, 256, 256);
  return c;
}

function Floor() {
  const midZ = (-(N - 1) * 13) / 2;
  const tex = useMemo(() => {
    const t = new THREE.CanvasTexture(gridCanvas());
    t.wrapS = t.wrapT = THREE.RepeatWrapping;
    t.repeat.set(52, 52);
    t.colorSpace = THREE.SRGBColorSpace;
    t.anisotropy = 4;
    return t;
  }, []);
  return (
    <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.02, midZ]}>
      <planeGeometry args={[120, (N + 3) * 13]} />
      <meshStandardMaterial map={tex} color="#ffffff" metalness={0.05} roughness={0.92} />
    </mesh>
  );
}

function SceneReadySignal({ onReady }: { onReady?: () => void }) {
  const { active, progress, errors = [] } = useProgress();
  const gl = useThree((s) => s.gl);
  const scene = useThree((s) => s.scene);
  const camera = useThree((s) => s.camera);
  const fired = useRef(false);
  const compiling = useRef(false);
  const compiled = useRef(false);
  const meaningfulFrames = useRef(0);
  const totalFramesAfterCompile = useRef(0);

  // Track mount time to implement a fallback inside the hook
  const mountTime = useRef(0);
  const [bypassLoading, setBypassLoading] = useState(false);

  useEffect(() => {
    mountTime.current = performance.now();
    const checkTimeout = setInterval(() => {
      // If 3 seconds pass and we're still not fired, bypass loading and compile
      if (performance.now() - mountTime.current > 3000) {
        setBypassLoading(true);
        clearInterval(checkTimeout);
      }
    }, 200);
    return () => clearInterval(checkTimeout);
  }, []);

  const isLoaded = (!active && progress >= 100) || (errors && errors.length > 0);
  const isReadyToCompile = isLoaded || bypassLoading;

  useEffect(() => {
    if (fired.current || compiling.current || compiled.current || !isReadyToCompile) return;

    compiling.current = true;
    const renderer = gl as THREE.WebGLRenderer & {
      compileAsync?: (scene: THREE.Scene, camera: THREE.Camera) => Promise<void>;
    };

    try {
      const hasParallelExtension =
        typeof renderer.getContext === "function" &&
        !!renderer.getContext()?.getExtension?.("KHR_parallel_shader_compile");

      const compile = (typeof renderer.compileAsync === "function" && hasParallelExtension)
        ? renderer.compileAsync(scene, camera)
        : Promise.resolve().then(() => renderer.compile(scene, camera));

      compile
        .catch((err) => {
          console.warn("StrategyCanvas compileAsync rejected:", err);
        })
        .finally(() => {
          compiled.current = true;
        });
    } catch (e) {
      console.warn("StrategyCanvas compile sync error:", e);
      compiled.current = true;
    }
  }, [isReadyToCompile, camera, gl, scene]);

  useFrame(() => {
    if (fired.current || !compiled.current) return;

    totalFramesAfterCompile.current++;

    const renderInfo = gl.info.render;
    const hasMeaningfulScene = renderInfo.calls >= 12 && renderInfo.triangles >= 500;
    meaningfulFrames.current = hasMeaningfulScene ? meaningfulFrames.current + 1 : 0;

    if (meaningfulFrames.current >= 4 || totalFramesAfterCompile.current >= 30 || bypassLoading) {
      fired.current = true;
      requestAnimationFrame(() => onReady?.());
    }
  });

  return null;
}

function Scene({ progress, reduced, isMobile, stage, active, perf, onReady }: { progress: React.RefObject<number>; reduced: boolean; isMobile: boolean; stage: number; active: boolean; perf: boolean; onReady?: () => void }) {
  // Districts stay MOUNTED for the whole scroll (GLB cloned once, shaders compiled
  // once → no remount hitch, no blank frames). `near` only toggles per-district
  // visibility + per-frame work + <Html> mounting, so off-screen districts render
  // nothing and run zero per-frame CPU/DOM cost (only the active ±1 stays awake).
  const near = (i: number) => Math.abs(i - stage) <= 1;

  // Defer the expensive post-processing + ambient particles until the section is
  // actually active for the first time, then latch them on. This keeps Bloom's
  // render targets and the Sparkles buffers from being allocated during initial
  // page load / while WF3 is still below the fold — without re-allocating them
  // (and hitching) every time the user scrolls past and back.
  const [everActive, setEverActive] = useState(false);
  // eslint-disable-next-line react-hooks/set-state-in-effect
  useEffect(() => { if (active) setEverActive(true); }, [active]);
  const heavyFx = everActive && !reduced && !isMobile;
  return (
    <>
      <color attach="background" args={[BG]} />
      <fog attach="fog" args={[BG, 12, 40]} />
      <ambientLight intensity={0.55} />
      <hemisphereLight args={["#ffffff", "#dfe4ee", 0.5]} />
      <directionalLight position={[6, 12, 8]} intensity={0.7} />
      <directionalLight position={[-7, 5, 4]} intensity={0.22} color="#cdd6ff" />

      {/* Cheap studio environment for mild reflections — built from Lightformers
         (rendered once, no CDN HDRI fetch). Kept low so light PBR bodies don't blow out. */}
      <Environment resolution={64} frames={1}>
        <Lightformer intensity={0.5} position={[0, 6, -3]} scale={[12, 6, 1]} />
        <Lightformer intensity={0.3} position={[-6, 3, 4]} scale={[6, 6, 1]} color="#dfe6ff" />
        <Lightformer intensity={0.3} position={[6, 2, 2]} scale={[5, 5, 1]} color="#ffffff" />
      </Environment>

      <CameraRig progress={progress} reduced={reduced} isMobile={isMobile} />
      <Floor />
      <DataRoad />

      {/* `awake` (±1) keeps geometry/shaders/animation warm for a pop-free
          transition; `focused` (current district only) gates the expensive
          in-canvas <Html> labels so only ~one district's worth project + write
          to the DOM each frame instead of three. */}
      <IntakeHub i={0} progress={progress} reduced={reduced} awake={near(0)} focused={stage === 0} />
      <StrategyNetwork i={1} progress={progress} reduced={reduced} awake={near(1)} focused={stage === 1} />
      <CityRouteMap i={2} progress={progress} reduced={reduced} awake={near(2)} focused={stage === 2} />
      <CommandCenter i={3} progress={progress} awake={near(3)} focused={stage === 3} />
      <WinnerPodium i={4} progress={progress} reduced={reduced} isMobile={isMobile} awake={near(4)} focused={stage === 4} />

      {heavyFx && (
        <Sparkles count={16} scale={[18, 7, N * 13]} position={[0, 3, (-(N - 1) * 13) / 2]} size={2} speed={0.22} opacity={0.35} color="#9aa6c4" />
      )}

      {heavyFx && (
        <EffectComposer multisampling={0}>
          <Bloom mipmapBlur intensity={0.7} luminanceThreshold={0.74} luminanceSmoothing={0.06} radius={0.68} kernelSize={KernelSize.SMALL} />
        </EffectComposer>
      )}

      {/* Compile every material/texture upfront (districts are all mounted) so the
         first time a district becomes visible there's no shader-compile stall. */}
      <Preload all />
      <SceneReadySignal onReady={onReady} />
      {perf && <PerfHud />}
    </>
  );
}

export default function StrategyCanvas({ progress, reduced = false, isMobile = false, active = true, stage = 0, ready = false, onReady }: Props) {
  // Opt-in dev metrics overlay: append ?perf to the URL.
  const perf = typeof window !== "undefined" && new URLSearchParams(window.location.search).has("perf");
  return (
    <Canvas
      dpr={[1, isMobile ? 1 : 1.25]}
      camera={{ position: [0, 4, 8.2], fov: 50, near: 0.1, far: 120 }}
      gl={{ antialias: false, powerPreference: "high-performance", alpha: false }}
      frameloop={active || !ready ? "always" : "never"}
    >
      <Scene progress={progress} reduced={reduced} isMobile={isMobile} stage={stage} active={active} perf={perf} onReady={onReady} />
    </Canvas>
  );
}
