"use client";

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

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

const dummy = new THREE.Object3D();
const cBlue = new THREE.Color(C.blue);
const cGreen = new THREE.Color(C.green);
const tmpC = new THREE.Color();
const tmpV = new THREE.Vector3();

type Seg = { a: THREE.Vector3; b: THREE.Vector3 };

/**
 * The live logistics network revealed at the eagle-eye pass: warehouses, a field
 * of delivery nodes, the connection lattice between them, glow particles flowing
 * through the routes (the algorithm computing), and an autonomous fleet that
 * leaves the warehouses and lights up delivery markers as it completes runs.
 */
function Network({ progress, isMobile = false }: Props) {
  const eased = useRef(0);

  const warehouses = useRef<THREE.Group>(null);
  const whMats = useRef<(THREE.MeshStandardMaterial | null)[]>([]);
  const linksMat = useRef<THREE.LineBasicMaterial>(null);
  const flow = useRef<THREE.Points>(null);
  const fleet = useRef<THREE.InstancedMesh>(null);
  const markers = useRef<THREE.InstancedMesh>(null);

  const nodeCount = isMobile ? 40 : 70;
  const flowCount = isMobile ? 70 : 150;
  const fleetCount = isMobile ? 12 : 24;

  // Warehouse anchors on a mid ring.
  const whPos = useMemo(
    () =>
      Array.from({ length: 5 }, (_, i) => {
        const a = (i / 5) * Math.PI * 2 + 0.6;
        const r = 6.5;
        return new THREE.Vector3(Math.cos(a) * r, 0, Math.sin(a) * r);
      }),
    [],
  );

  // Scattered delivery nodes.
  const nodes = useMemo(
    () =>
      Array.from({ length: nodeCount }, (_, i) => {
        const a = seeded(i * 3.3 + 1) * Math.PI * 2;
        const r = 3 + seeded(i * 1.9 + 5) * (CITY_RADIUS - 2);
        return new THREE.Vector3(Math.cos(a) * r, 0.12, Math.sin(a) * r);
      }),
    [nodeCount],
  );

  // Connection segments: each node to its nearest warehouse.
  const segs = useMemo<Seg[]>(() => {
    return nodes.map((n) => {
      let best = whPos[0];
      let bestD = Infinity;
      for (const w of whPos) {
        const d = n.distanceToSquared(w);
        if (d < bestD) { bestD = d; best = w; }
      }
      return { a: new THREE.Vector3(best.x, 0.2, best.z), b: n.clone() };
    });
  }, [nodes, whPos]);

  // Link geometry (one line per segment).
  const linkGeom = useMemo(() => {
    const arr: number[] = [];
    for (const s of segs) arr.push(s.a.x, s.a.y, s.a.z, s.b.x, s.b.y, s.b.z);
    const g = new THREE.BufferGeometry();
    g.setAttribute("position", new THREE.Float32BufferAttribute(arr, 3));
    return g;
  }, [segs]);

  // Node point cloud geometry.
  const nodeGeom = useMemo(() => {
    const arr = new Float32Array(nodes.length * 3);
    nodes.forEach((n, i) => { arr[i * 3] = n.x; arr[i * 3 + 1] = n.y; arr[i * 3 + 2] = n.z; });
    const g = new THREE.BufferGeometry();
    g.setAttribute("position", new THREE.BufferAttribute(arr, 3));
    return g;
  }, [nodes]);

  // Flow particles: each rides a random segment.
  const flowState = useMemo(
    () =>
      Array.from({ length: flowCount }, (_, i) => ({
        seg: Math.floor(seeded(i * 4.7) * segs.length),
        speed: 0.12 + seeded(i * 2.1) * 0.25,
        phase: seeded(i * 8.3),
      })),
    [flowCount, segs.length],
  );
  const flowGeom = useMemo(() => {
    const g = new THREE.BufferGeometry();
    g.setAttribute("position", new THREE.BufferAttribute(new Float32Array(flowCount * 3), 3));
    return g;
  }, [flowCount]);

  // Fleet: each vehicle rides a segment outward and loops.
  const fleetState = useMemo(
    () =>
      Array.from({ length: fleetCount }, (_, i) => ({
        seg: Math.floor(seeded(i * 5.9 + 3) * segs.length),
        speed: 0.06 + seeded(i * 3.7) * 0.08,
        phase: seeded(i * 6.6),
        kind: Math.floor(seeded(i * 9.2) * 3), // 0 bike, 1 auto, 2 truck
      })),
    [fleetCount, segs.length],
  );

  useLayoutEffect(() => {
    // Static delivery-marker pillars at each node.
    if (markers.current) {
      nodes.forEach((n, i) => {
        dummy.position.set(n.x, 0.18, n.z);
        dummy.scale.set(0.06, 0.36, 0.06);
        dummy.rotation.set(0, 0, 0);
        dummy.updateMatrix();
        markers.current!.setMatrixAt(i, dummy.matrix);
        markers.current!.setColorAt(i, cBlue);
      });
      markers.current.instanceMatrix.needsUpdate = true;
      if (markers.current.instanceColor) markers.current.instanceColor.needsUpdate = true;
    }
    // Fleet base colour by kind.
    if (fleet.current) {
      fleetState.forEach((v, i) => {
        dummy.position.set(0, -50, 0);
        dummy.updateMatrix();
        fleet.current!.setMatrixAt(i, dummy.matrix);
        const col = v.kind === 0 ? C.cyan : v.kind === 1 ? C.violet : C.sky;
        fleet.current!.setColorAt(i, tmpC.set(col));
      });
      fleet.current.instanceMatrix.needsUpdate = true;
      if (fleet.current.instanceColor) fleet.current.instanceColor.needsUpdate = true;
    }
  }, [nodes, fleetState]);

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

    // Delivery locations appear early (step 1) so the map is populated before the
    // candidate routes are drawn; the lattice/markers strengthen through the run.
    const reveal = smoothstep(P.routes - 0.02, P.routes + 0.06, e);
    const eco = smoothstep(P.ecosystem - 0.03, P.ecosystem + 0.06, e);
    const finaleGlow = smoothstep(P.finale, 1, e);

    // Warehouses activate during the ecosystem beat.
    whMats.current.forEach((m, i) => {
      if (m) m.emissiveIntensity = lerp(0.3, 1.8, eco) * (0.85 + Math.sin(t * 2 + i) * 0.15);
    });
    if (warehouses.current) warehouses.current.visible = reveal > 0.02;

    // Connection lattice.
    if (linksMat.current) {
      linksMat.current.opacity = (0.05 + 0.16 * reveal + 0.1 * finaleGlow) * (0.8 + Math.sin(t * 1.5) * 0.2);
    }

    // Flow particles streaming along the lattice.
    if (flow.current) {
      const attr = flow.current.geometry.getAttribute("position") as THREE.BufferAttribute;
      for (let i = 0; i < flowState.length; i++) {
        const f = flowState[i];
        const s = segs[f.seg];
        const frac = ((t * f.speed + f.phase) % 1 + 1) % 1;
        tmpV.copy(s.a).lerp(s.b, frac);
        attr.setXYZ(i, tmpV.x, tmpV.y + 0.05, tmpV.z);
      }
      attr.needsUpdate = true;
      (flow.current.material as THREE.PointsMaterial).opacity = clamp01(0.2 + reveal * 0.8);
    }

    // Fleet rolls out during the ecosystem beat; markers light green as runs complete.
    if (fleet.current) {
      for (let i = 0; i < fleetState.length; i++) {
        const v = fleetState[i];
        const s = segs[v.seg];
        const frac = ((t * v.speed + v.phase) % 1 + 1) % 1;
        tmpV.copy(s.a).lerp(s.b, frac);
        const scaleK = eco * (v.kind === 2 ? 1.4 : v.kind === 1 ? 1.1 : 0.85);
        dummy.position.set(tmpV.x, 0.16, tmpV.z);
        // orient along the segment
        const dir = Math.atan2(s.b.z - s.a.z, s.b.x - s.a.x);
        dummy.rotation.set(0, -dir, 0);
        dummy.scale.set(0.34 * scaleK + 0.0001, 0.16 * scaleK + 0.0001, 0.2 * scaleK + 0.0001);
        dummy.updateMatrix();
        fleet.current.setMatrixAt(i, dummy.matrix);
      }
      fleet.current.instanceMatrix.needsUpdate = true;
      fleet.current.visible = eco > 0.02;
    }

    // Delivery markers illuminate progressively (blue → green).
    if (markers.current) {
      for (let i = 0; i < nodes.length; i++) {
        const thr = (i / nodes.length) * 0.9;
        const lit = smoothstep(thr, thr + 0.08, eco) * (0.6 + Math.sin(t * 3 + i) * 0.4);
        tmpC.copy(cBlue).lerp(cGreen, smoothstep(thr, thr + 0.08, eco));
        markers.current.setColorAt(i, tmpC.multiplyScalar(0.3 + lit));
      }
      if (markers.current.instanceColor) markers.current.instanceColor.needsUpdate = true;
      (markers.current.material as THREE.MeshBasicMaterial).opacity = clamp01(0.15 + reveal * 0.5 + eco * 0.4);
    }
  });

  return (
    <group>
      {/* Connection lattice */}
      <lineSegments geometry={linkGeom}>
        <lineBasicMaterial ref={linksMat} color={C.blue} transparent opacity={0} blending={THREE.AdditiveBlending} depthWrite={false} />
      </lineSegments>

      {/* Delivery nodes */}
      <points geometry={nodeGeom}>
        <pointsMaterial size={isMobile ? 0.12 : 0.1} color={C.sky} transparent opacity={0.6} sizeAttenuation blending={THREE.AdditiveBlending} depthWrite={false} />
      </points>

      {/* Flow particles (the algorithm computing) */}
      <points ref={flow} geometry={flowGeom}>
        <pointsMaterial size={isMobile ? 0.11 : 0.09} color={C.cyan} transparent opacity={0} sizeAttenuation blending={THREE.AdditiveBlending} depthWrite={false} />
      </points>

      {/* Delivery markers */}
      <instancedMesh ref={markers} args={[undefined, undefined, nodeCount]}>
        <boxGeometry args={[1, 1, 1]} />
        <meshBasicMaterial toneMapped={false} transparent opacity={0} blending={THREE.AdditiveBlending} depthWrite={false} />
      </instancedMesh>

      {/* Autonomous fleet */}
      <instancedMesh ref={fleet} args={[undefined, undefined, fleetCount]} visible={false}>
        <boxGeometry args={[1, 1, 1]} />
        <meshBasicMaterial toneMapped={false} blending={THREE.AdditiveBlending} />
      </instancedMesh>

      {/* Warehouses */}
      <group ref={warehouses} visible={false}>
        {whPos.map((w, i) => (
          <group key={i} position={[w.x, 0, w.z]}>
            <mesh position={[0, 0.5, 0]}>
              <boxGeometry args={[1.6, 1, 1.3]} />
              <meshStandardMaterial
                ref={(el) => { whMats.current[i] = el; }}
                color="#0b1426"
                emissive={C.blue}
                emissiveIntensity={0.3}
                metalness={0.4}
                roughness={0.5}
              />
            </mesh>
            <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.03, 0]}>
              <ringGeometry args={[1.2, 1.4, 36]} />
              <meshBasicMaterial color={C.cyan} toneMapped={false} transparent opacity={0.4} side={THREE.DoubleSide} blending={THREE.AdditiveBlending} depthWrite={false} />
            </mesh>
          </group>
        ))}
      </group>
    </group>
  );
}

export default React.memo(Network);
