"use client";

import React, { useMemo, useRef } from "react";
import { useFrame } from "@react-three/fiber";
import * as THREE from "three";
import { COLORS } from "./constants";
import { buildRoutes } from "./routes";
import { smoothstep, seeded } from "./math";

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

const RADIAL = 6;
const TUBULAR = 120;

type TubeHandle = { geo: THREE.TubeGeometry; mat: THREE.MeshBasicMaterial };

type PulsingRingProps = {
  position: THREE.Vector3;
  color: string;
  maxScale?: number;
  pulseSpeed?: number;
};

/**
 * Clean floating radar ring that pulses outwards and fades.
 */
function PulsingRing({ position, color, maxScale = 2.2, pulseSpeed = 2.4 }: PulsingRingProps) {
  const ref = useRef<THREE.Mesh>(null);
  const matRef = useRef<THREE.MeshBasicMaterial>(null);

  useFrame((state) => {
    if (!ref.current || !matRef.current) return;
    const t = state.clock.elapsedTime;
    const cycle = (t * (pulseSpeed / 4)) % 1; // 0 -> 1 loop
    const s = 0.4 + cycle * (maxScale - 0.4);
    ref.current.scale.setScalar(s);
    matRef.current.opacity = Math.sin((1 - cycle) * Math.PI) * 0.72;
  });

  return (
    <mesh ref={ref} position={[position.x, 0.03, position.z]} rotation={[-Math.PI / 2, 0, 0]}>
      <ringGeometry args={[0.22, 0.28, 24]} />
      <meshBasicMaterial
        ref={matRef}
        color={color}
        transparent
        opacity={0}
        depthWrite={false}
        blending={THREE.AdditiveBlending}
      />
    </mesh>
  );
}

const PulsingRingMemo = React.memo(PulsingRing);

/* ── Chaotic Route Flow Particles & Chevrons ── */
const CHAOS_TMP_POS = new THREE.Vector3();
const CHAOS_TMP_TAN = new THREE.Vector3();

function ChaoticFlow({ progress, chaoticCurves }: {
  progress: React.RefObject<number>;
  chaoticCurves: THREE.CatmullRomCurve3[];
}) {
  const particlesPerCurve = 4;
  const count = chaoticCurves.length * particlesPerCurve;
  const particleRefs = useRef<(THREE.Mesh | null)[]>([]);
  const chevronRefs = useRef<(THREE.Group | null)[]>([]);

  const particleOffsets = useMemo(
    () => Array.from({ length: count }, (_, i) => seeded(i * 13 + 3)),
    [count]
  );
  const chevronOffsets = useMemo(
    () => Array.from({ length: chaoticCurves.length * 2 }, (_, i) => (i % 2) / 2),
    [chaoticCurves.length]
  );

  useFrame((state) => {
    const p = progress.current ?? 0;
    const vis = 1 - smoothstep(0.48, 0.60, p);
    
    const isVisible = vis > 0.01;
    particleRefs.current.forEach(m => { if (m) m.visible = isVisible; });
    chevronRefs.current.forEach(g => { if (g) g.visible = isVisible; });
    if (!isVisible) return;

    const t = state.clock.elapsedTime;

    // Animate tiny red flow particles
    for (let i = 0; i < count; i++) {
      const mesh = particleRefs.current[i];
      if (!mesh) continue;
      const curveIdx = Math.floor(i / particlesPerCurve);
      const curve = chaoticCurves[curveIdx];
      
      const speed = 0.05 + seeded(i * 9) * 0.03;
      const u = (particleOffsets[i] + t * speed) % 1;
      
      curve.getPointAt(u, CHAOS_TMP_POS);
      mesh.position.copy(CHAOS_TMP_POS);
      
      const mat = mesh.material as THREE.MeshBasicMaterial;
      mat.opacity = vis * 0.6 * Math.sin(u * Math.PI);
    }

    // Animate red chevrons (erratic flow)
    for (let i = 0; i < chaoticCurves.length * 2; i++) {
      const group = chevronRefs.current[i];
      if (!group) continue;
      const curveIdx = Math.floor(i / 2);
      const curve = chaoticCurves[curveIdx];
      
      const u = (chevronOffsets[i] + t * 0.04) % 1;
      curve.getPointAt(u, CHAOS_TMP_POS);
      curve.getTangentAt(u, CHAOS_TMP_TAN);
      
      group.position.copy(CHAOS_TMP_POS);
      group.rotation.y = Math.atan2(CHAOS_TMP_TAN.x, CHAOS_TMP_TAN.z);
      
      group.traverse((o) => {
        const m = (o as THREE.Mesh).material as THREE.MeshBasicMaterial | undefined;
        if (m && "opacity" in m) {
          m.opacity = vis * 0.5 * Math.sin(u * Math.PI);
        }
      });
    }
  });

  return (
    <group>
      {/* Chaotic Red Flow Particles */}
      {Array.from({ length: count }).map((_, i) => (
        <mesh
          key={`cp-${i}`}
          ref={(el) => { particleRefs.current[i] = el; }}
          visible={false}
        >
          <sphereGeometry args={[0.06, 6, 6]} />
          <meshBasicMaterial
            color={COLORS.red}
            transparent
            opacity={0}
            depthWrite={false}
            blending={THREE.AdditiveBlending}
          />
        </mesh>
      ))}

      {/* Chaotic Red Chevrons */}
      {Array.from({ length: chaoticCurves.length * 2 }).map((_, i) => (
        <group
          key={`cc-${i}`}
          ref={(el) => { chevronRefs.current[i] = el; }}
          visible={false}
        >
          <mesh rotation={[Math.PI / 2, 0, 0]}>
            <coneGeometry args={[0.06, 0.16, 4]} />
            <meshBasicMaterial
              color={COLORS.red}
              transparent
              opacity={0}
              depthWrite={false}
              blending={THREE.AdditiveBlending}
            />
          </mesh>
        </group>
      ))}
    </group>
  );
}

const ChaoticFlowMemo = React.memo(ChaoticFlow);

/* ── Optimized Route Flow Particles, Chevrons & Delivery Pulses ── */
const OPT_TMP_POS = new THREE.Vector3();
const OPT_TMP_TAN = new THREE.Vector3();

function OptimizedFlow({ progress, optimizedCurves }: {
  progress: React.RefObject<number>;
  optimizedCurves: THREE.CatmullRomCurve3[];
}) {
  const particlesPerCurve = 6;
  const count = optimizedCurves.length * particlesPerCurve;
  const particleRefs = useRef<(THREE.Mesh | null)[]>([]);
  const chevronRefs = useRef<(THREE.Group | null)[]>([]);
  const pulseRefs = useRef<(THREE.Mesh | null)[]>([]);

  const particleOffsets = useMemo(
    () => Array.from({ length: count }, (_, i) => seeded(i * 17 + 4)),
    [count]
  );
  const chevronOffsets = useMemo(
    () => Array.from({ length: optimizedCurves.length * 3 }, (_, i) => (i % 3) / 3),
    [optimizedCurves.length]
  );

  useFrame((state) => {
    const p = progress.current ?? 0;
    const vis = smoothstep(0.52, 0.70, p);
    
    const isVisible = vis > 0.01;
    particleRefs.current.forEach(m => { if (m) m.visible = isVisible; });
    chevronRefs.current.forEach(g => { if (g) g.visible = isVisible; });
    pulseRefs.current.forEach(m => { if (m) m.visible = isVisible; });
    if (!isVisible) return;

    const t = state.clock.elapsedTime;

    // 1. Animate smooth green/cyan flow particles
    for (let i = 0; i < count; i++) {
      const mesh = particleRefs.current[i];
      if (!mesh) continue;
      const curveIdx = Math.floor(i / particlesPerCurve);
      const curve = optimizedCurves[curveIdx];
      
      const speed = 0.08 + seeded(i * 11) * 0.04;
      const u = (particleOffsets[i] + t * speed) % 1;
      
      curve.getPointAt(u, OPT_TMP_POS);
      mesh.position.copy(OPT_TMP_POS);
      
      const mat = mesh.material as THREE.MeshBasicMaterial;
      mat.opacity = vis * 0.75 * Math.sin(u * Math.PI);
      const s = 0.04 + Math.sin(t * 6 + i) * 0.012;
      mesh.scale.setScalar(s / 0.05);
    }

    // 2. Animate direction chevrons (fast, steady green chevrons)
    for (let i = 0; i < optimizedCurves.length * 3; i++) {
      const group = chevronRefs.current[i];
      if (!group) continue;
      const curveIdx = Math.floor(i / 3);
      const curve = optimizedCurves[curveIdx];
      
      const u = (chevronOffsets[i] + t * 0.16) % 1;
      curve.getPointAt(u, OPT_TMP_POS);
      curve.getTangentAt(u, OPT_TMP_TAN);
      
      group.position.copy(OPT_TMP_POS);
      group.rotation.y = Math.atan2(OPT_TMP_TAN.x, OPT_TMP_TAN.z);
      group.rotation.x = Math.atan2(OPT_TMP_TAN.y, Math.sqrt(OPT_TMP_TAN.x * OPT_TMP_TAN.x + OPT_TMP_TAN.z * OPT_TMP_TAN.z)) - Math.PI / 2;

      group.traverse((o) => {
        const m = (o as THREE.Mesh).material as THREE.MeshBasicMaterial | undefined;
        if (m && "opacity" in m) {
          m.opacity = vis * 0.85 * Math.sin(u * Math.PI);
        }
      });
    }

    // 3. Animate expanding delivery pulses (shooting data pulses)
    for (let i = 0; i < optimizedCurves.length; i++) {
      const mesh = pulseRefs.current[i];
      if (!mesh) continue;
      const curve = optimizedCurves[i];
      
      const u = (t * 0.55 + i * 0.2) % 1;
      curve.getPointAt(u, OPT_TMP_POS);
      mesh.position.copy(OPT_TMP_POS);
      
      const mat = mesh.material as THREE.MeshBasicMaterial;
      mat.opacity = vis * 0.9 * Math.sin(u * Math.PI);
      const s = 0.08 + Math.sin(t * 12 + i) * 0.02;
      mesh.scale.setScalar(s / 0.07);
    }
  });

  return (
    <group>
      {/* Tiny Cyan/Green Flow Particles */}
      {Array.from({ length: count }).map((_, i) => {
        const color = (Math.floor(i / particlesPerCurve) % 2 === 0) ? COLORS.cyan : COLORS.green;
        return (
          <mesh
            key={`op-${i}`}
            ref={(el) => { particleRefs.current[i] = el; }}
            visible={false}
          >
            <sphereGeometry args={[0.05, 8, 8]} />
            <meshBasicMaterial
              color={color}
              transparent
              opacity={0}
              depthWrite={false}
              blending={THREE.AdditiveBlending}
            />
          </mesh>
        );
      })}

      {/* Glowing Chevrons */}
      {Array.from({ length: optimizedCurves.length * 3 }).map((_, i) => {
        const color = (Math.floor(i / 3) % 2 === 0) ? COLORS.cyan : COLORS.green;
        return (
          <group
            key={`oc-${i}`}
            ref={(el) => { chevronRefs.current[i] = el; }}
            visible={false}
          >
            <mesh rotation={[Math.PI / 2, 0, 0]}>
              <coneGeometry args={[0.07, 0.20, 4]} />
              <meshBasicMaterial
                color={color}
                transparent
                opacity={0}
                depthWrite={false}
                blending={THREE.AdditiveBlending}
              />
            </mesh>
          </group>
        );
      })}

      {/* Large Delivery Pulses */}
      {optimizedCurves.map((_, i) => {
        const color = (i % 2 === 0) ? COLORS.cyan : COLORS.green;
        return (
          <mesh
            key={`pulse-${i}`}
            ref={(el) => { pulseRefs.current[i] = el; }}
            visible={false}
          >
            <sphereGeometry args={[0.07, 12, 12]} />
            <meshBasicMaterial
              color={color}
              transparent
              opacity={0}
              depthWrite={false}
              blending={THREE.AdditiveBlending}
            />
          </mesh>
        );
      })}
    </group>
  );
}

const OptimizedFlowMemo = React.memo(OptimizedFlow);

/**
 * Glowing route network. Chaotic red routes are pre-drawn then "erased" via
 * geometry draw-range during the dissolve phase; optimized cyan/green routes
 * draw themselves in during the optimize phase. Delivery nodes flip red→green.
 */
function RouteSystem({ progress, reduced = false, isMobile = false }: Props) {
  const { chaotic, optimized, chaosNodes, optimizedNodes } = useMemo(
    () => buildRoutes(),
    [],
  );

  const chaosRefs = useRef<TubeHandle[]>([]);
  const optRefs = useRef<TubeHandle[]>([]);
  const chaosNodeGroup = useRef<THREE.Group>(null);
  const optNodeGroup = useRef<THREE.Group>(null);

  const registerChaos = (i: number) => (geo: THREE.TubeGeometry | null) => {
    if (geo) chaosRefs.current[i] = { ...(chaosRefs.current[i] ?? {}), geo } as TubeHandle;
  };
  const registerChaosMat = (i: number) => (mat: THREE.MeshBasicMaterial | null) => {
    if (mat) chaosRefs.current[i] = { ...(chaosRefs.current[i] ?? {}), mat } as TubeHandle;
  };
  const registerOpt = (i: number) => (geo: THREE.TubeGeometry | null) => {
    if (geo) optRefs.current[i] = { ...(optRefs.current[i] ?? {}), geo } as TubeHandle;
  };
  const registerOptMat = (i: number) => (mat: THREE.MeshBasicMaterial | null) => {
    if (mat) optRefs.current[i] = { ...(optRefs.current[i] ?? {}), mat } as TubeHandle;
  };

  const setDraw = (geo: THREE.TubeGeometry, frac: number) => {
    if (!geo.index) return;
    const total = geo.index.count;
    const step = RADIAL * 6;
    const count = Math.max(0, Math.floor((total * frac) / step) * step);
    geo.setDrawRange(0, count);
  };

  useFrame((state) => {
    const p = progress.current ?? 0;
    const t = state.clock.elapsedTime;

    // Chaotic routes: full → erased during dissolve, with warning flicker.
    const chaosDraw = 1 - smoothstep(0.4, 0.56, p);
    const chaosBase = (1 - smoothstep(0.38, 0.55, p)) * 0.85;
    const flicker = 0.7 + Math.sin(t * 7) * 0.18;
    for (const h of chaosRefs.current) {
      if (!h?.geo) continue;
      setDraw(h.geo, chaosDraw);
      if (h.mat) h.mat.opacity = chaosBase * flicker;
    }

    // Optimized routes: draw-in during optimize phase + flowing glow.
    const optDraw = smoothstep(0.55, 0.74, p);
    const optBase = smoothstep(0.55, 0.66, p);
    for (let i = 0; i < optRefs.current.length; i++) {
      const h = optRefs.current[i];
      if (!h?.geo) continue;
      setDraw(h.geo, optDraw);
      if (h.mat) {
        // Keep the tube a crisp, saturated energy line rather than a
        // bloom-blown white slab — the flow energy comes from the pulses + trails.
        const flow = 0.52 + Math.sin(t * 3 - i * 0.7) * 0.12;
        h.mat.opacity = optBase * flow;
      }
    }

    // Delivery nodes
    if (chaosNodeGroup.current) {
      const s = 0.0001 + (1 - smoothstep(0.4, 0.55, p));
      chaosNodeGroup.current.scale.setScalar(s);
    }
    if (optNodeGroup.current) {
      const appear = smoothstep(0.6, 0.82, p);
      const pop = appear * (1 + Math.sin(t * 4) * 0.06 * appear);
      optNodeGroup.current.scale.setScalar(0.0001 + pop);
    }
  });

  return (
    <group>
      {/* Chaotic routes (red) */}
      {chaotic.map((r, i) => (
        <mesh key={`c${i}`} frustumCulled={false}>
          <tubeGeometry
            ref={registerChaos(i)}
            args={[r.curve, TUBULAR, 0.08, RADIAL, false]}
          />
          <meshBasicMaterial
            ref={registerChaosMat(i)}
            color={COLORS.red}
            transparent
            opacity={0.85}
            depthWrite={false}
            blending={reduced ? THREE.NormalBlending : THREE.AdditiveBlending}
          />
        </mesh>
      ))}

      {/* Optimized routes (cyan → green) */}
      {optimized.map((r, i) => (
        <mesh key={`o${i}`} frustumCulled={false}>
          <tubeGeometry
            ref={registerOpt(i)}
            args={[r.curve, TUBULAR, 0.10, RADIAL, false]}
          />
          <meshBasicMaterial
            ref={registerOptMat(i)}
            color={i % 2 === 0 ? COLORS.cyan : COLORS.green}
            transparent
            opacity={0}
            depthWrite={false}
            blending={reduced ? THREE.NormalBlending : THREE.AdditiveBlending}
          />
        </mesh>
      ))}

      {/* Chaos delivery nodes (red, delayed) with pulsing warning alerts */}
      <group ref={chaosNodeGroup}>
        {chaosNodes.map((n, i) => (
          <group key={`cn-group-${i}`}>
            <mesh position={n}>
              <sphereGeometry args={[0.18, 12, 12]} />
              <meshBasicMaterial
                color={COLORS.red}
                transparent
                opacity={0.9}
                blending={THREE.AdditiveBlending}
              />
            </mesh>
            <PulsingRingMemo position={n} color={COLORS.red} />
          </group>
        ))}
      </group>

      {/* Optimized delivery nodes (green, fulfilled) with pulsing success alerts */}
      <group ref={optNodeGroup} scale={0.0001}>
        {optimizedNodes.map((n, i) => (
          <group key={`on-group-${i}`}>
            <mesh position={n}>
              <sphereGeometry args={[0.15, 14, 14]} />
              <meshBasicMaterial
                color={COLORS.green}
                transparent
                opacity={0.95}
                blending={THREE.AdditiveBlending}
              />
            </mesh>
            <PulsingRingMemo position={n} color={COLORS.green} maxScale={1.8} pulseSpeed={3.0} />
          </group>
        ))}
      </group>

      {/* Flowing delivery particles along routes */}
      {!isMobile && (
        <>
          <ChaoticFlowMemo
            progress={progress}
            chaoticCurves={chaotic.map(r => r.curve)}
          />
          <OptimizedFlowMemo
            progress={progress}
            optimizedCurves={optimized.map(r => r.curve)}
          />
        </>
      )}
    </group>
  );
}

export default React.memo(RouteSystem);
