"use client";

import React, { useMemo, useRef } from "react";
import { useFrame } from "@react-three/fiber";
import { Html } from "@react-three/drei";
import * as THREE from "three";
import { C, P, ROUTE_COLORS, STRATEGIES, WINNER_INDEX } from "./theme";
import { clamp01, damp, lerp, seeded, smoothstep } from "./math";

/** Score per candidate, index-aligned to STRATEGIES. Multi-Trip(0) wins; EV-Aware(4) is rejected. */
const ROUTE_SCORES = [98, 76, 84, 68, 58, 90];
const REJECT_INDEX = 4; // EV-Aware — fails the battery/range constraint

const labelBase: React.CSSProperties = {
  display: "flex",
  alignItems: "center",
  gap: "6px",
  padding: "4px 11px",
  borderRadius: "999px",
  background: "rgba(7,11,22,0.72)",
  border: "1px solid rgba(255,255,255,0.16)",
  color: "#eaf2ff",
  fontSize: "11px",
  fontWeight: 600,
  letterSpacing: "0.04em",
  whiteSpace: "nowrap",
  backdropFilter: "blur(6px)",
  WebkitBackdropFilter: "blur(6px)",
  fontFamily: "var(--font-space-grotesk), system-ui, sans-serif",
  opacity: 0,
  pointerEvents: "none",
  willChange: "opacity",
};

type Props = {
  progress: React.RefObject<number>;
  reduced?: boolean;
  isMobile?: boolean;
};

const WINNER = WINNER_INDEX; // the surviving optimal route (Multi-Trip)
const EV_MID = (P.ev + P.network) / 2; // moment the battery fails / recalc fires

const green = new THREE.Color(C.green);
const amber = new THREE.Color(C.amber);
const red = new THREE.Color(C.red);
const tmp = new THREE.Color();

/**
 * Ground-hugging candidate route from the depot out to a delivery cluster. Each
 * route kinks through an intermediate stop (so it reads as a real plan visiting
 * locations, not a straight ray) but stays low on the map plane — the camera is
 * a near-static top-down view, so the routes ARE the story.
 */
function makeRouteCurve(i: number): THREE.CatmullRomCurve3 {
  const angle = (i / ROUTE_COLORS.length) * Math.PI * 2 + 0.4;
  const radius = 9 + seeded(i * 6.1) * 4;
  const end = new THREE.Vector3(Math.cos(angle) * radius, 0.16, Math.sin(angle) * radius);
  // intermediate stop, offset sideways so each candidate takes a visibly different path
  const side = (seeded(i * 3.7) - 0.5) * 5;
  const midA = new THREE.Vector3(
    Math.cos(angle) * radius * 0.4 + Math.cos(angle + Math.PI / 2) * side,
    0.18,
    Math.sin(angle) * radius * 0.4 + Math.sin(angle + Math.PI / 2) * side,
  );
  const start = new THREE.Vector3(0, 0.2, 0);
  return new THREE.CatmullRomCurve3([start, midA, end]);
}

/**
 * Six competing delivery strategies projected by the brain. They race packets
 * through the city; the losers fade out and the optimal route keeps glowing.
 * Then the EV beat: a scooter runs a ground route, its battery drains until the
 * route is invalid, the brain recalculates, a charging station rises and a new
 * green optimized route lights up.
 */
function Routes({ progress, isMobile = false }: Props) {
  const eased = useRef(0);

  const tubeMats = useRef<(THREE.MeshBasicMaterial | null)[]>([]);
  const packets = useRef<(THREE.Mesh | null)[]>([]);

  const scooter = useRef<THREE.Group>(null);
  const battFill = useRef<THREE.Mesh>(null);
  const battMat = useRef<THREE.MeshBasicMaterial>(null);
  const station = useRef<THREE.Group>(null);
  const evRedMat = useRef<THREE.MeshBasicMaterial>(null);
  const evGreenMat = useRef<THREE.MeshBasicMaterial>(null);

  const curves = useMemo(() => ROUTE_COLORS.map((_, i) => makeRouteCurve(i)), []);
  const tubes = useMemo(
    () => curves.map((c) => new THREE.TubeGeometry(c, 60, 0.05, 8, false)),
    [curves],
  );

  // EV ground path + its red (failed) and green (optimized) variants.
  const evCurve = useMemo(
    () =>
      new THREE.CatmullRomCurve3([
        new THREE.Vector3(-11, 0.3, 7),
        new THREE.Vector3(-4, 0.3, 3),
        new THREE.Vector3(2.6, 0.3, -1.2),
        new THREE.Vector3(9.5, 0.3, -6.5),
      ]),
    [],
  );
  const evTube = useMemo(() => new THREE.TubeGeometry(evCurve, 70, 0.07, 8, false), [evCurve]);
  const evGreenTube = useMemo(() => {
    const c = new THREE.CatmullRomCurve3([
      new THREE.Vector3(-11, 0.3, 7),
      new THREE.Vector3(-3.5, 0.3, 1.5),
      new THREE.Vector3(2.6, 0.6, -1.2), // routes via the charging station
      new THREE.Vector3(6.5, 0.3, -3),
      new THREE.Vector3(9.5, 0.3, -6.5),
    ]);
    return new THREE.TubeGeometry(c, 80, 0.08, 8, false);
  }, []);
  const stationPos = useMemo(() => new THREE.Vector3(2.6, 0, -1.2), []);

  const scratch = useMemo(() => new THREE.Vector3(), []);

  // Per-candidate base colours (winner red, others grey) so we can tint the
  // rejected candidate toward red each frame as it's flagged invalid.
  const baseColors = useMemo(() => ROUTE_COLORS.map((c) => new THREE.Color(c)), []);
  const rejectRed = useMemo(() => new THREE.Color(C.red), []);

  // Vehicles dispatched along the WINNING route at the dispatch beat.
  const dispatch = useRef<(THREE.Mesh | null)[]>([]);

  // 3D label anchors near each route's far end + the recharge hub.
  const labelRefs = useRef<(HTMLDivElement | null)[]>([]);
  const stationLabelRef = useRef<HTMLDivElement>(null);
  const labelPos = useMemo(
    () =>
      curves.map((c) => {
        const v = c.getPointAt(0.82).clone();
        v.y += 1.0;
        return v;
      }),
    [curves],
  );

  useFrame((state, dt) => {
    const p = progress.current ?? 0;
    eased.current = damp(eased.current, p, 3.2, dt);
    const e = eased.current;
    const t = state.clock.elapsedTime;

    // Beat gates — drive the whole "why this route was chosen" story.
    const appear = smoothstep(P.routes - 0.03, P.routes + 0.05, e);   // candidates draw in
    const racing = smoothstep(P.routes, P.routes + 0.05, e);
    const reject = smoothstep(P.ev, P.ev + 0.05, e);                  // invalid candidate flagged
    const losersGone = smoothstep(P.sla, P.ecosystem, e);             // lower-scoring routes eliminated
    const winnerBoost = smoothstep(P.ecosystem, P.ecosystem + 0.06, e); // winner glows stronger
    const dispatchT = clamp01((e - P.ecosystem) / (1 - P.ecosystem)); // vehicles roll out along winner
    const endFade = smoothstep(0.97, 1.0, e);

    // Candidate routes: all draw in → one flagged invalid → losers eliminated → winner persists & glows.
    for (let i = 0; i < curves.length; i++) {
      const isWinner = i === WINNER;
      const isReject = i === REJECT_INDEX;
      const loserFade = isReject ? Math.max(losersGone, reject * 0.55) : losersGone;

      const mat = tubeMats.current[i];
      if (mat) {
        if (isReject) mat.color.copy(baseColors[i]).lerp(rejectRed, reject);
        let o = 0.55 * appear;
        if (isWinner) o *= (0.72 + Math.sin(t * 3) * 0.28) * (1 + winnerBoost * 0.9);
        else o *= 1 - loserFade;
        mat.opacity = clamp01(o) * (1 - endFade);
      }
      const pk = packets.current[i];
      if (pk) {
        const frac = ((t * (0.16 + i * 0.02) + seeded(i * 9)) % 1 + 1) % 1;
        curves[i].getPointAt(frac, scratch);
        pk.position.copy(scratch);
        const vis = (isWinner ? 1 : 1 - loserFade) * racing * (1 - endFade);
        pk.scale.setScalar(0.0001 + vis * (isWinner ? 0.18 : 0.1));
      }
    }

    // Floating score labels — appear at the SCORE beat; the rejected one shows its ✕
    // earlier (constraints beat); winner keeps its label to the end.
    for (let i = 0; i < labelRefs.current.length; i++) {
      const el = labelRefs.current[i];
      if (!el) continue;
      const isW = i === WINNER;
      const isReject = i === REJECT_INDEX;
      const show = isReject
        ? smoothstep(P.ev, P.ev + 0.04, e)
        : smoothstep(P.network, P.network + 0.04, e);
      const fade = isW ? endFade : (isReject ? Math.max(losersGone, 0) : losersGone);
      const o = show * (1 - fade);
      el.style.opacity = o.toFixed(3);
      el.style.display = o < 0.02 ? "none" : "flex";
    }

    // Dispatch: vehicles travel the winning route once it's selected.
    if (dispatch.current.length) {
      const wc = curves[WINNER];
      for (let k = 0; k < dispatch.current.length; k++) {
        const m = dispatch.current[k];
        if (!m) continue;
        const on = dispatchT > 0.02;
        m.visible = on;
        if (!on) continue;
        const frac = ((t * 0.14 + k * 0.34) % 1 + 1) % 1;
        wc.getPointAt(frac, scratch);
        m.position.set(scratch.x, 0.3, scratch.z);
        wc.getPointAt(Math.min(0.999, frac + 0.02), scratch);
        m.lookAt(scratch.x, 0.3, scratch.z);
        m.scale.setScalar(0.0001 + smoothstep(0, 0.12, dispatchT) * (1 - endFade));
      }
    }

    // ---- EV beat ----
    const evIn = smoothstep(P.ev - 0.02, P.ev + 0.03, e);
    const evOut = 1 - smoothstep(P.network + 0.02, P.network + 0.1, e);
    const recalc = smoothstep(EV_MID - 0.015, EV_MID + 0.03, e);
    const evActive = evIn * evOut;

    // Scooter travels the full ground path across the beat.
    const drive = clamp01((e - P.ev) / (P.network - P.ev));
    if (scooter.current) {
      // After recalc the scooter follows the optimized green path.
      const curve = recalc > 0.5 ? evGreenTube.parameters.path : evCurve;
      curve.getPointAt(clamp01(drive), scratch);
      scooter.current.position.set(scratch.x, 0.32, scratch.z);
      // face direction of travel
      curve.getPointAt(clamp01(drive + 0.01), scratch);
      scooter.current.lookAt(scratch.x, 0.32, scratch.z);
      scooter.current.visible = evActive > 0.02;
      scooter.current.scale.setScalar(0.0001 + evActive);
    }

    // Battery: drains to near-empty, then refills after recalc/charging.
    const drain = clamp01((e - P.ev) / (EV_MID - P.ev));
    const level = recalc > 0 ? lerp(0.14, 1, recalc) : clamp01(1 - drain * 0.92);
    if (battFill.current) {
      battFill.current.scale.x = 0.02 + level * 0.98;
      battFill.current.position.x = -0.18 * (1 - level); // shrink from the right
    }
    if (battMat.current) {
      if (level > 0.5) tmp.copy(amber).lerp(green, (level - 0.5) * 2);
      else tmp.copy(red).lerp(amber, level * 2);
      battMat.current.color.copy(tmp);
    }

    // Failed (red) route fades in as battery dies, then dissolves after recalc.
    if (evRedMat.current) {
      const fail = smoothstep(EV_MID - 0.06, EV_MID, e);
      evRedMat.current.opacity = evActive * (0.55 * Math.max(evIn * 0.4, fail)) * (1 - recalc) * (0.7 + Math.sin(t * 5) * 0.3);
    }
    // Optimized green route lights up after recalc.
    if (evGreenMat.current) {
      evGreenMat.current.opacity = evActive * recalc * (0.65 + Math.sin(t * 3) * 0.25);
    }

    // Charging station rises from the ground at recalc.
    if (station.current) {
      station.current.scale.y = 0.0001 + recalc;
      station.current.visible = recalc > 0.01 && evOut > 0.02;
    }
    if (stationLabelRef.current) {
      const o = recalc * evOut;
      stationLabelRef.current.style.opacity = o.toFixed(3);
      stationLabelRef.current.style.display = o < 0.02 ? "none" : "flex";
    }
  });

  return (
    <group>
      {/* Six competing route simulations */}
      {tubes.map((geom, i) => (
        <group key={i}>
          <mesh geometry={geom}>
            <meshBasicMaterial
              ref={(el) => { tubeMats.current[i] = el; }}
              color={ROUTE_COLORS[i]}
              toneMapped={false}
              transparent
              opacity={0}
              blending={THREE.AdditiveBlending}
              depthWrite={false}
            />
          </mesh>
          <mesh ref={(el) => { packets.current[i] = el; }}>
            <sphereGeometry args={[1, 12, 12]} />
            <meshBasicMaterial color={C.white} toneMapped={false} transparent opacity={0.95} blending={THREE.AdditiveBlending} depthWrite={false} />
          </mesh>
        </group>
      ))}

      {/* Floating score labels above each candidate route — winner / rejected called out */}
      {labelPos.map((pos, i) => {
        const isWinner = i === WINNER;
        const isReject = i === REJECT_INDEX;
        // Mobile clarity: hide ALL floating strategy labels (Multi-Trip, EV-Aware,
        // Balanced, Best, Time-Aware …) so the small canvas shows only the routing
        // nodes/network — these screen-space labels overlap badly at 320–390px.
        if (isMobile) return null;
        const dotColor = isWinner || isReject ? C.red : ROUTE_COLORS[i];
        return (
          <Html key={`lbl${i}`} position={[pos.x, pos.y, pos.z]} center zIndexRange={[30, 0]} style={{ pointerEvents: "none" }}>
            <div
              ref={(el) => { labelRefs.current[i] = el; }}
              style={{
                ...labelBase,
                border: isWinner ? "1px solid rgba(226,53,66,0.85)" : isReject ? "1px solid rgba(239,68,68,0.7)" : labelBase.border,
                background: isWinner ? "rgba(28,8,11,0.85)" : labelBase.background,
                boxShadow: isWinner ? "0 0 22px rgba(192,18,39,0.6)" : "none",
              }}
            >
              <span style={{ width: 7, height: 7, borderRadius: "50%", background: dotColor, boxShadow: `0 0 8px ${dotColor}` }} />
              {STRATEGIES[i]}
              <span style={{ fontWeight: 800, color: isReject ? "#fca5a5" : "#fff", marginLeft: 2 }}>{ROUTE_SCORES[i]}</span>
              {isWinner ? <span style={{ color: "#4ade80", fontWeight: 700 }}>&nbsp;✓ Best</span> : null}
              {isReject ? <span style={{ color: "#fca5a5", fontWeight: 700 }}>&nbsp;✕ Over range</span> : null}
            </div>
          </Html>
        );
      })}

      {/* EV failed route (red) */}
      <mesh geometry={evTube}>
        <meshBasicMaterial ref={evRedMat} color={C.red} toneMapped={false} transparent opacity={0} blending={THREE.AdditiveBlending} depthWrite={false} />
      </mesh>
      {/* EV optimized route (green) */}
      <mesh geometry={evGreenTube}>
        <meshBasicMaterial ref={evGreenMat} color={C.green} toneMapped={false} transparent opacity={0} blending={THREE.AdditiveBlending} depthWrite={false} />
      </mesh>

      {/* Charging station */}
      <group ref={station} position={stationPos} visible={false}>
        <mesh position={[0, 0.55, 0]}>
          <boxGeometry args={[0.32, 1.1, 0.32]} />
          <meshStandardMaterial color="#0e1b14" emissive={C.green} emissiveIntensity={0.6} metalness={0.4} roughness={0.5} />
        </mesh>
        <mesh position={[0, 1.18, 0]}>
          <boxGeometry args={[0.5, 0.18, 0.42]} />
          <meshBasicMaterial color={C.green} toneMapped={false} transparent opacity={0.9} blending={THREE.AdditiveBlending} />
        </mesh>
        <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.03, 0]}>
          <ringGeometry args={[0.5, 0.62, 40]} />
          <meshBasicMaterial color={C.green} toneMapped={false} transparent opacity={0.7} side={THREE.DoubleSide} blending={THREE.AdditiveBlending} depthWrite={false} />
        </mesh>
      </group>

      {/* Recharge hub label (the "Kitchen / Recharge" in the EV paradox).
          Hidden on mobile to keep the small canvas free of overlapping overlays. */}
      {!isMobile && (
        <Html position={[stationPos.x, 1.7, stationPos.z]} center zIndexRange={[30, 0]} style={{ pointerEvents: "none" }}>
          <div ref={stationLabelRef} style={{ ...labelBase, border: "1px solid rgba(34,197,94,0.65)", boxShadow: "0 0 18px rgba(34,197,94,0.45)" }}>
            <span style={{ width: 7, height: 7, borderRadius: "50%", background: C.green, boxShadow: `0 0 8px ${C.green}` }} />
            Recharge Hub
          </div>
        </Html>
      )}

      {/* EV scooter */}
      <group ref={scooter} visible={false}>
        <mesh position={[0, 0.12, 0]}>
          <boxGeometry args={[0.5, 0.16, 0.22]} />
          <meshStandardMaterial color="#11203a" emissive={C.cyan} emissiveIntensity={0.5} metalness={0.5} roughness={0.4} />
        </mesh>
        <mesh position={[0.18, 0.26, 0]}>
          <boxGeometry args={[0.06, 0.28, 0.12]} />
          <meshStandardMaterial color="#0d1830" emissive={C.cyan} emissiveIntensity={0.4} metalness={0.5} roughness={0.4} />
        </mesh>
        <mesh position={[0.3, 0.14, 0]}>
          <sphereGeometry args={[0.06, 10, 10]} />
          <meshBasicMaterial color={C.white} toneMapped={false} />
        </mesh>
        {[-0.16, 0.16].map((x, i) => (
          <mesh key={i} position={[x, 0.05, 0]} rotation={[Math.PI / 2, 0, 0]}>
            <cylinderGeometry args={[0.09, 0.09, 0.05, 14]} />
            <meshStandardMaterial color="#05070d" metalness={0.6} roughness={0.5} />
          </mesh>
        ))}
        {/* Battery indicator floating above */}
        <group position={[0, 0.62, 0]}>
          <mesh>
            <boxGeometry args={[0.42, 0.12, 0.02]} />
            <meshBasicMaterial color="#0a0f1c" toneMapped={false} transparent opacity={0.85} />
          </mesh>
          <mesh ref={battFill} position={[0, 0, 0.012]}>
            <boxGeometry args={[0.38, 0.08, 0.02]} />
            <meshBasicMaterial ref={battMat} color={C.green} toneMapped={false} blending={THREE.AdditiveBlending} />
          </mesh>
        </group>
      </group>

      {/* Dispatch fleet — bike, auto, truck rolling along the WINNING route */}
      {[
        { w: 0.34, h: 0.16, d: 0.2, col: C.sky },   // bike
        { w: 0.46, h: 0.22, d: 0.26, col: C.white }, // auto
        { w: 0.6, h: 0.3, d: 0.32, col: C.red },     // truck
      ].map((v, k) => (
        <mesh key={`disp${k}`} ref={(el) => { dispatch.current[k] = el; }} visible={false}>
          <boxGeometry args={[v.w, v.h, v.d]} />
          <meshStandardMaterial color="#12060a" emissive={v.col} emissiveIntensity={0.7} metalness={0.5} roughness={0.4} />
        </mesh>
      ))}
    </group>
  );
}

export default React.memo(Routes);
