import { ContactShadows, Sparkles, useTexture } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { useLayoutEffect, useMemo, useRef } from "react";
import * as THREE from "three";
import { makeRockGeometry, scatterRocks } from "@/lib/rock-geometry";
import {
  rocksFromSilhouette,
  strataWord,
  ternBird,
  watcherFace,
  type AnamorphicRock,
} from "@/lib/silhouettes";
import { MESAS, VISTAS, type Vista } from "@/lib/world-layout";
import { useWorldUi } from "@/lib/world-input";
import { LibraryCairns, StoreTable } from "./cairns";
import { LenticularStones } from "./lenticular";
import { Player } from "./player";

function Ground() {
  const tex = useTexture("/art/tex-ground.jpg");
  tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
  tex.repeat.set(18, 18);
  tex.anisotropy = 8;
  tex.colorSpace = THREE.SRGBColorSpace;
  return (
    <mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
      <planeGeometry args={[140, 140, 1, 1]} />
      <meshStandardMaterial map={tex} roughness={0.95} metalness={0} color="#cbb79a" />
    </mesh>
  );
}

function InstancedRocks({
  rocks,
  map,
  seed,
}: {
  rocks: AnamorphicRock[];
  map: THREE.Texture;
  seed: number;
}) {
  const mesh = useRef<THREE.InstancedMesh>(null);
  const geo = useMemo(() => makeRockGeometry(seed, 1), [seed]);
  useLayoutEffect(() => {
    const inst = mesh.current;
    if (!inst) return;
    const dummy = new THREE.Object3D();
    const color = new THREE.Color();
    rocks.forEach((rock, i) => {
      dummy.position.set(...rock.position);
      dummy.scale.set(...rock.scale);
      dummy.rotation.set(rock.position[0] * 0.07, rock.position[2] * 0.13, rock.position[1] * 0.04);
      dummy.updateMatrix();
      inst.setMatrixAt(i, dummy.matrix);
      const shade = 0.38 + (i % 9) * 0.035;
      color.setRGB(shade, shade * 0.84, shade * 0.68);
      inst.setColorAt(i, color);
    });
    inst.instanceMatrix.needsUpdate = true;
    if (inst.instanceColor) inst.instanceColor.needsUpdate = true;
  }, [rocks]);
  if (!rocks.length) return null;
  return (
    <instancedMesh ref={mesh} args={[geo, undefined, rocks.length]} frustumCulled={false}>
      <meshStandardMaterial map={map} roughness={0.92} metalness={0.03} />
    </instancedMesh>
  );
}

function GhostPlane({ vista, grid }: { vista: Vista; grid: boolean[][] }) {
  const mesh = useRef<THREE.Mesh>(null);
  const tex = useMemo(() => {
    const h = grid.length;
    const w = grid[0]?.length ?? 1;
    const canvas = document.createElement("canvas");
    canvas.width = w;
    canvas.height = h;
    const ctx = canvas.getContext("2d");
    if (!ctx) return null;
    const img = ctx.createImageData(w, h);
    for (let y = 0; y < h; y++) {
      for (let x = 0; x < w; x++) {
        const i = (y * w + x) * 4;
        const on = grid[y][x];
        img.data[i] = 18;
        img.data[i + 1] = 14;
        img.data[i + 2] = 12;
        img.data[i + 3] = on ? 255 : 0;
      }
    }
    ctx.putImageData(img, 0, 0);
    const t = new THREE.CanvasTexture(canvas);
    t.needsUpdate = true;
    t.minFilter = THREE.NearestFilter;
    t.magFilter = THREE.NearestFilter;
    return t;
  }, [grid]);

  useLayoutEffect(() => {
    if (mesh.current) {
      mesh.current.lookAt(vista.viewpoint[0], vista.viewpoint[1], vista.viewpoint[2]);
    }
  }, [vista]);

  useFrame(() => {
    const mat = mesh.current?.material;
    if (!mat || Array.isArray(mat) || !("opacity" in mat)) return;
    const aligned = useWorldUi.getState().alignedVista === vista.id;
    const near = useWorldUi.getState().nearestVista === vista.id;
    const target = aligned ? 0.92 : near ? 0.28 : 0.04;
    mat.opacity += (target - mat.opacity) * 0.12;
  });

  if (!tex) return null;
  return (
    <mesh ref={mesh} position={vista.target}>
      <planeGeometry args={[vista.planeW, vista.planeH]} />
      <meshBasicMaterial map={tex} transparent opacity={0.12} depthWrite={false} />
    </mesh>
  );
}

function ViewingGate({
  position,
  lookAt,
}: {
  position: [number, number, number];
  lookAt: [number, number, number];
}) {
  const yaw = Math.atan2(lookAt[0] - position[0], lookAt[2] - position[2]);
  return (
    <group position={[position[0], 0, position[2]]} rotation={[0, yaw, 0]}>
      <mesh position={[-1.55, 1.15, 0]}>
        <boxGeometry args={[0.32, 2.3, 0.32]} />
        <meshStandardMaterial color="#3f3a34" roughness={0.9} />
      </mesh>
      <mesh position={[1.55, 1.15, 0]}>
        <boxGeometry args={[0.32, 2.3, 0.32]} />
        <meshStandardMaterial color="#3f3a34" roughness={0.9} />
      </mesh>
      <mesh position={[0, 2.35, 0]}>
        <boxGeometry args={[3.5, 0.22, 0.28]} />
        <meshStandardMaterial color="#4a433c" roughness={0.88} />
      </mesh>
      <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.03, 0]}>
        <circleGeometry args={[1.15, 20]} />
        <meshStandardMaterial color="#d9cbb4" roughness={1} />
      </mesh>
    </group>
  );
}

function Mesas() {
  const geo = useMemo(() => makeRockGeometry(4, 2), []);
  return (
    <>
      {MESAS.map((m) => (
        <mesh
          key={m.color + m.position.join(",")}
          geometry={geo}
          position={m.position}
          scale={m.scale}
          castShadow
        >
          <meshStandardMaterial color={m.color} roughness={0.94} />
        </mesh>
      ))}
    </>
  );
}

function AmbientScatter({ map }: { map: THREE.Texture }) {
  const rocks = useMemo(() => {
    return scatterRocks(110, 91).map((r) => ({ position: r.position, scale: r.scale }));
  }, []);
  return <InstancedRocks rocks={rocks} map={map} seed={8} />;
}

function AnamorphicRidge({ sand, basalt }: { sand: THREE.Texture; basalt: THREE.Texture }) {
  const mobile = typeof window !== "undefined" && window.innerWidth < 640;
  const stride = mobile ? 2 : 1;

  const fields = useMemo(() => {
    const word = strataWord();
    const face = watcherFace();
    const bird = ternBird();
    const strata = VISTAS[0]!;
    const watcher = VISTAS[1]!;
    const tern = VISTAS[2]!;
    return {
      word,
      face,
      bird,
      strataRocks: rocksFromSilhouette(
        word,
        strata.viewpoint,
        strata.target,
        strata.planeW,
        strata.planeH,
        strata.depth,
        strata.seed,
        stride,
      ),
      watcherRocks: rocksFromSilhouette(
        face,
        watcher.viewpoint,
        watcher.target,
        watcher.planeW,
        watcher.planeH,
        watcher.depth,
        watcher.seed,
        stride,
      ),
      ternRocks: rocksFromSilhouette(
        bird,
        tern.viewpoint,
        tern.target,
        tern.planeW,
        tern.planeH,
        tern.depth,
        tern.seed,
        stride,
      ),
    };
  }, [stride]);

  return (
    <>
      <InstancedRocks rocks={fields.strataRocks} map={sand} seed={1} />
      <InstancedRocks rocks={fields.watcherRocks} map={sand} seed={3} />
      <InstancedRocks rocks={fields.ternRocks} map={basalt} seed={5} />
      <GhostPlane vista={VISTAS[0]!} grid={fields.word} />
      <GhostPlane vista={VISTAS[1]!} grid={fields.face} />
      <GhostPlane vista={VISTAS[2]!} grid={fields.bird} />
    </>
  );
}

export function WorldScene() {
  const sand = useTexture("/art/tex-sandstone.jpg");
  const basalt = useTexture("/art/tex-basalt.jpg");
  sand.colorSpace = THREE.SRGBColorSpace;
  basalt.colorSpace = THREE.SRGBColorSpace;
  sand.wrapS = sand.wrapT = THREE.RepeatWrapping;
  basalt.wrapS = basalt.wrapT = THREE.RepeatWrapping;

  return (
    <>
      <color attach="background" args={["#b9a894"]} />
      <fog attach="fog" args={["#b9a894", 26, 88]} />
      <hemisphereLight args={["#f0e6d6", "#4a3c32", 0.72]} />
      <directionalLight
        position={[-28, 22, 10]}
        intensity={1.55}
        color="#ffd6a8"
        castShadow
        shadow-mapSize-width={1024}
        shadow-mapSize-height={1024}
        shadow-camera-far={80}
        shadow-camera-left={-30}
        shadow-camera-right={30}
        shadow-camera-top={30}
        shadow-camera-bottom={-30}
      />
      <ambientLight intensity={0.18} />
      <Player />
      <Ground />
      <Mesas />
      <AmbientScatter map={sand} />
      <AnamorphicRidge sand={sand} basalt={basalt} />
      {VISTAS.map((v) => (
        <ViewingGate key={v.id} position={v.viewpoint} lookAt={v.target} />
      ))}
      <LibraryCairns />
      <StoreTable />
      <LenticularStones />
      <ContactShadows position={[0, 0.02, 0]} opacity={0.35} scale={70} blur={2.2} far={9} />
      <Sparkles count={28} scale={[50, 6, 50]} size={2.4} speed={0.25} color="#efe6d6" opacity={0.35} />
    </>
  );
}
