"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, ROUTE_COLORS } from "./theme";
import { damp, lerp, seeded, smoothstep } from "./math";

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

const dummy = new THREE.Object3D();
const tmpColor = new THREE.Color();

type Building = {
  x: number;
  z: number;
  w: number;
  d: number;
  h: number; // total height
  bodyH: number;
  rot: number;
  ci: number;
  tier: boolean;
  tall: boolean;
};

const WIN_VERT = `
  attribute vec3 aSize;
  attribute float aSeed;
  varying vec2 vWinUv;
  varying vec3 vWinNrm;
  varying vec3 vWinSize;
  varying float vWinSeed;
`;
const WIN_VERT_BODY = `
  vWinUv = uv;
  vWinNrm = normal;
  vWinSize = aSize;
  vWinSeed = aSeed;
`;
const WIN_FRAG = `
  uniform float uTime;
  varying vec2 vWinUv;
  varying vec3 vWinNrm;
  varying vec3 vWinSize;
  varying float vWinSeed;
  float h11(float p){ p = fract(p*0.1031); p *= p+33.33; p *= p+p; return fract(p); }
  float h21(vec2 p){ vec3 p3 = fract(vec3(p.xyx)*0.1031); p3 += dot(p3, p3.yzx+33.33); return fract((p3.x+p3.y)*p3.z); }
`;
const WIN_FRAG_BODY = `
  // facade tint varies per building — dark neutral charcoal (brand: near-black)
  vec3 baseA = vec3(0.040, 0.040, 0.048);
  vec3 baseB = vec3(0.065, 0.058, 0.066);
  diffuseColor.rgb = mix(baseA, baseB, h11(vWinSeed*1.7));

  // windows only on the four vertical faces (skip roof/floor)
  float isVert = 1.0 - step(0.5, abs(vWinNrm.y));
  vec2 faceDim = abs(vWinNrm.x) > 0.5 ? vec2(vWinSize.z, vWinSize.y) : vec2(vWinSize.x, vWinSize.y);
  vec2 cells = max(vec2(1.0), floor(faceDim / 0.52));
  vec2 g = vWinUv * cells;
  vec2 id = floor(g);
  vec2 f = fract(g);
  float m = 0.16;                       // mullion margin
  float pane = step(m, f.x) * step(f.x, 1.0 - m) * step(m, f.y) * step(f.y, 1.0 - m);

  float rnd = h21(id + vWinSeed * 37.0);
  float lit = step(0.68, rnd);          // ~32% of windows lit (dimmer skyline backdrop)
  float toggle = step(0.97, h21(id * 1.31 + vWinSeed * 5.0 + floor(uTime * 0.5)));
  lit = clamp(lit + toggle, 0.0, 1.0);  // a few flick on/off over time
  float flick = 0.9 + 0.1 * sin(uTime * 2.0 + rnd * 30.0);

  // Brand-tinted windows: mostly warm white with occasional brand-red panes.
  vec3 warmWhite = vec3(1.0, 0.88, 0.80);
  vec3 brandRed  = vec3(0.82, 0.18, 0.24);
  vec3 wcol = mix(warmWhite, brandRed, step(0.7, h21(id * 0.7 + vWinSeed)));

  float glow = isVert * pane * lit * flick;
  totalEmissiveRadiance += wcol * glow * 0.85;

  // dark mullion grid + dimmer unlit glass
  diffuseColor.rgb *= (0.42 + 0.58 * pane);
`;

/**
 * The futuristic logistics city. Buildings are instanced boxes, but a custom
 * shader paints procedural apartment windows on their facades (lit/unlit panes,
 * mullion grid, per-building variation, gentle night flicker) so they read as
 * real architecture rather than blank boxes. Taller towers get a setback tier
 * and a rooftop antenna with an aviation light. The whole skyline rises out of
 * the ground during the birth beat.
 */
function City({ progress, isMobile = false }: Props) {
  const cityGroup = useRef<THREE.Group>(null);
  const bodies = useRef<THREE.InstancedMesh>(null);
  const tiers = useRef<THREE.InstancedMesh>(null);
  const caps = useRef<THREE.InstancedMesh>(null);
  const masts = useRef<THREE.InstancedMesh>(null);
  const lights = useRef<THREE.InstancedMesh>(null);
  const roadsMat = useRef<THREE.LineBasicMaterial>(null);
  const ring1 = useRef<THREE.Mesh>(null);
  const ring2 = useRef<THREE.Mesh>(null);
  const shaderRef = useRef<{ uniforms: { uTime: { value: number } } } | null>(null);
  const eased = useRef(0);

  const count = isMobile ? 20 : 34;

  const data = useMemo(() => {
    const buildings: Building[] = [];
    // Inner clear radius — buildings form a dim perimeter ring only, so the whole
    // centre of the map (depot, delivery nodes, candidate routes, vehicles) stays
    // open and reads as the routing engine, not a city the camera flies through.
    const INNER = 12.5;
    for (let i = 0; i < count; i++) {
      const angle = seeded(i * 3.1 + 0.5) * Math.PI * 2;
      const radius = INNER + seeded(i * 1.7 + 2.2) * (CITY_RADIUS - INNER);
      const x = Math.cos(angle) * radius;
      const z = Math.sin(angle) * radius;
      const w = 0.8 + seeded(i * 5.3) * 1.7;
      const d = 0.8 + seeded(i * 2.9 + 9) * 1.7;
      const h = 1.6 + seeded(i * 7.7 + 4) * 3.2;
      const ci = Math.floor(seeded(i * 11.1) * ROUTE_COLORS.length);
      const tier = h > 3.4 && seeded(i * 13.3) > 0.42;
      const bodyH = tier ? h * 0.62 : h;
      const rot = (Math.floor(seeded(i * 17.7) * 4) * Math.PI) / 8 + seeded(i * 4.1) * 0.12;
      const tall = h > 5;
      buildings.push({ x, z, w, d, h, bodyH, rot, ci, tier, tall });
    }

    const tierList = buildings.filter((b) => b.tier);
    const tallList = buildings.filter((b) => b.tall);

    // Per-instance shader attributes (world dimensions + a stable seed).
    const bodyGeom = new THREE.BoxGeometry(1, 1, 1);
    const bSize = new Float32Array(count * 3);
    const bSeed = new Float32Array(count);
    buildings.forEach((b, i) => {
      bSize[i * 3] = b.w; bSize[i * 3 + 1] = b.bodyH; bSize[i * 3 + 2] = b.d;
      bSeed[i] = seeded(i * 23.1) * 10;
    });
    bodyGeom.setAttribute("aSize", new THREE.InstancedBufferAttribute(bSize, 3));
    bodyGeom.setAttribute("aSeed", new THREE.InstancedBufferAttribute(bSeed, 1));

    const tierGeom = new THREE.BoxGeometry(1, 1, 1);
    const tSize = new Float32Array(Math.max(1, tierList.length) * 3);
    const tSeed = new Float32Array(Math.max(1, tierList.length));
    tierList.forEach((b, i) => {
      const tw = b.w * 0.64, td = b.d * 0.64, th = b.h - b.bodyH;
      tSize[i * 3] = tw; tSize[i * 3 + 1] = th; tSize[i * 3 + 2] = td;
      tSeed[i] = seeded(i * 29.7) * 10;
    });
    tierGeom.setAttribute("aSize", new THREE.InstancedBufferAttribute(tSize, 3));
    tierGeom.setAttribute("aSeed", new THREE.InstancedBufferAttribute(tSeed, 1));

    return { buildings, tierList, tallList, bodyGeom, tierGeom };
  }, [count]);

  // Shared facade material with the procedural window shader.
  const facadeMat = useMemo(() => {
    const mat = new THREE.MeshStandardMaterial({
      color: "#0c1226",
      emissive: "#000000",
      metalness: 0.35,
      roughness: 0.62,
    });
    mat.onBeforeCompile = (shader) => {
      shader.uniforms.uTime = { value: 0 };
      shaderRef.current = shader as unknown as { uniforms: { uTime: { value: number } } };
      shader.vertexShader = shader.vertexShader
        .replace("#include <common>", `#include <common>\n${WIN_VERT}`)
        .replace("#include <begin_vertex>", `#include <begin_vertex>\n${WIN_VERT_BODY}`);
      shader.fragmentShader = shader.fragmentShader
        .replace("#include <common>", `#include <common>\n${WIN_FRAG}`)
        .replace("#include <emissivemap_fragment>", `#include <emissivemap_fragment>\n${WIN_FRAG_BODY}`);
    };
    return mat;
  }, []);

  const roadGeom = useMemo(() => {
    const pts: number[] = [];
    const y = 0.04;
    // Radial avenues out from the centre.
    const spokes = 12;
    for (let i = 0; i < spokes; i++) {
      const a = (i / spokes) * Math.PI * 2;
      const r0 = 1.0;
      const r1 = CITY_RADIUS * (0.72 + seeded(i * 4.4) * 0.26);
      pts.push(Math.cos(a) * r0, y, Math.sin(a) * r0);
      pts.push(Math.cos(a) * r1, y, Math.sin(a) * r1);
    }
    // Concentric ring roads so the ground reads as a real street grid.
    const rings = [3.5, 7, 11, 15];
    const seg = 64;
    for (const r of rings) {
      for (let i = 0; i < seg; i++) {
        const a0 = (i / seg) * Math.PI * 2;
        const a1 = ((i + 1) / seg) * Math.PI * 2;
        pts.push(Math.cos(a0) * r, y, Math.sin(a0) * r);
        pts.push(Math.cos(a1) * r, y, Math.sin(a1) * r);
      }
    }
    const g = new THREE.BufferGeometry();
    g.setAttribute("position", new THREE.Float32BufferAttribute(pts, 3));
    return g;
  }, []);

  useLayoutEffect(() => {
    const { buildings, tierList, tallList } = data;

    if (bodies.current) {
      buildings.forEach((b, i) => {
        dummy.position.set(b.x, b.bodyH / 2, b.z);
        dummy.rotation.set(0, b.rot, 0);
        dummy.scale.set(b.w, b.bodyH, b.d);
        dummy.updateMatrix();
        bodies.current!.setMatrixAt(i, dummy.matrix);
      });
      bodies.current.instanceMatrix.needsUpdate = true;
    }

    if (tiers.current) {
      tierList.forEach((b, i) => {
        const th = b.h - b.bodyH;
        dummy.position.set(b.x, b.bodyH + th / 2, b.z);
        dummy.rotation.set(0, b.rot, 0);
        dummy.scale.set(b.w * 0.64, th, b.d * 0.64);
        dummy.updateMatrix();
        tiers.current!.setMatrixAt(i, dummy.matrix);
      });
      tiers.current.instanceMatrix.needsUpdate = true;
    }

    if (caps.current) {
      buildings.forEach((b, i) => {
        const top = b.h;
        const cw = b.tier ? b.w * 0.64 : b.w;
        const cd = b.tier ? b.d * 0.64 : b.d;
        dummy.position.set(b.x, top + 0.03, b.z);
        dummy.rotation.set(0, b.rot, 0);
        dummy.scale.set(cw * 1.02, 0.07, cd * 1.02);
        dummy.updateMatrix();
        caps.current!.setMatrixAt(i, dummy.matrix);
        caps.current!.setColorAt(i, tmpColor.set(ROUTE_COLORS[b.ci]));
      });
      caps.current.instanceMatrix.needsUpdate = true;
      if (caps.current.instanceColor) caps.current.instanceColor.needsUpdate = true;
    }

    if (masts.current && lights.current) {
      tallList.forEach((b, i) => {
        dummy.position.set(b.x, b.h + 0.34, b.z);
        dummy.rotation.set(0, 0, 0);
        dummy.scale.set(0.05, 0.68, 0.05);
        dummy.updateMatrix();
        masts.current!.setMatrixAt(i, dummy.matrix);

        dummy.position.set(b.x, b.h + 0.7, b.z);
        dummy.scale.set(1, 1, 1);
        dummy.updateMatrix();
        lights.current!.setMatrixAt(i, dummy.matrix);
      });
      masts.current.instanceMatrix.needsUpdate = true;
      lights.current.instanceMatrix.needsUpdate = true;
    }
  }, [data]);

  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;

    if (shaderRef.current) shaderRef.current.uniforms.uTime.value = t;

    // City rises from the floor during the birth beat.
    const rise = smoothstep(0.01, P.routes + 0.02, e);
    if (cityGroup.current) cityGroup.current.scale.y = lerp(0.001, 1, rise);

    // Aviation lights — gentle glow pulse (kept soft to avoid an unnatural
    // perfectly-synchronized hard blink across all masts).
    if (lights.current) {
      const m = lights.current.material as THREE.MeshBasicMaterial;
      m.opacity = (0.5 + 0.3 * Math.sin(t * 2.2)) * rise;
    }

    if (roadsMat.current) {
      roadsMat.current.opacity = lerp(0, 0.6, smoothstep(0.02, P.routes, e)) * (0.78 + Math.sin(t * 2.2) * 0.22);
    }

    const ringPulse = smoothstep(0.0, P.routes, e);
    if (ring1.current) {
      const s = 1 + ((t * 0.35) % 1) * 6;
      ring1.current.scale.set(s, s, s);
      (ring1.current.material as THREE.MeshBasicMaterial).opacity = (1 - ((t * 0.35) % 1)) * 0.28 * ringPulse;
    }
    if (ring2.current) {
      const s = 1 + ((t * 0.35 + 0.5) % 1) * 6;
      ring2.current.scale.set(s, s, s);
      (ring2.current.material as THREE.MeshBasicMaterial).opacity = (1 - ((t * 0.35 + 0.5) % 1)) * 0.28 * ringPulse;
    }
  });

  return (
    <group>
      {/* Energy grid floor */}
      <gridHelper args={[CITY_RADIUS * 2.6, 60, C.blue, C.blue]} position={[0, 0, 0]}>
        <lineBasicMaterial attach="material" color={C.blue} transparent opacity={0.11} depthWrite={false} />
      </gridHelper>

      {/* Radial energy roads */}
      <lineSegments geometry={roadGeom}>
        <lineBasicMaterial ref={roadsMat} color={C.cyan} transparent opacity={0} blending={THREE.AdditiveBlending} depthWrite={false} />
      </lineSegments>

      {/* Foundation pulse rings beneath the brain */}
      <mesh ref={ring1} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.03, 0]}>
        <ringGeometry args={[1.0, 1.12, 48]} />
        <meshBasicMaterial color={C.cyan} transparent opacity={0} blending={THREE.AdditiveBlending} depthWrite={false} />
      </mesh>
      <mesh ref={ring2} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.03, 0]}>
        <ringGeometry args={[1.0, 1.12, 48]} />
        <meshBasicMaterial color={C.violet} transparent opacity={0} blending={THREE.AdditiveBlending} depthWrite={false} />
      </mesh>

      {/* Skyline (rises from the floor) */}
      <group ref={cityGroup}>
        {/* Building bodies — windowed facade shader */}
        <instancedMesh ref={bodies} args={[data.bodyGeom, facadeMat, data.buildings.length]} />
        {/* Setback tiers — same facade shader */}
        {data.tierList.length > 0 && (
          <instancedMesh ref={tiers} args={[data.tierGeom, facadeMat, data.tierList.length]} />
        )}
        {/* Glowing rooftop caps */}
        <instancedMesh ref={caps} args={[undefined, undefined, data.buildings.length]}>
          <boxGeometry args={[1, 1, 1]} />
          <meshBasicMaterial toneMapped={false} transparent opacity={0.9} blending={THREE.AdditiveBlending} />
        </instancedMesh>
        {/* Rooftop antenna masts */}
        {data.tallList.length > 0 && (
          <instancedMesh ref={masts} args={[undefined, undefined, data.tallList.length]}>
            <boxGeometry args={[1, 1, 1]} />
            <meshStandardMaterial color="#11182c" metalness={0.7} roughness={0.4} />
          </instancedMesh>
        )}
        {/* Aviation lights on the masts */}
        {data.tallList.length > 0 && (
          <instancedMesh ref={lights} args={[undefined, undefined, data.tallList.length]}>
            <sphereGeometry args={[0.07, 8, 8]} />
            <meshBasicMaterial color={C.red} toneMapped={false} transparent opacity={0.8} blending={THREE.AdditiveBlending} depthWrite={false} />
          </instancedMesh>
        )}
      </group>
    </group>
  );
}

export default React.memo(City);
