import { useFrame, useThree } from "@react-three/fiber";
import { useEffect, useRef } from "react";
import { libraryBySlug } from "@/lib/libraries";
import { COLLIDERS, LIBRARY_PLACEMENTS, STORE_POSITION, VISTAS } from "@/lib/world-layout";
import { heldKeys, input, installControlsTest, useWorldUi } from "@/lib/world-input";

function blocked(x: number, z: number) {
  for (const c of COLLIDERS) {
    if (Math.hypot(x - c.x, z - c.z) < c.r) return true;
  }
  return false;
}

export function Player() {
  const { camera, gl } = useThree();
  const lastUi = useRef(0);

  useEffect(() => {
    installControlsTest();
    camera.rotation.order = "YXZ";

    const onKeyDown = (e: KeyboardEvent) => {
      if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Space"].includes(e.code)) {
        e.preventDefault();
      }
      input.keys.add(e.code);
    };
    const onKeyUp = (e: KeyboardEvent) => {
      input.keys.delete(e.code);
    };
    const clear = () => input.keys.clear();

    window.addEventListener("keydown", onKeyDown);
    window.addEventListener("keyup", onKeyUp);
    window.addEventListener("blur", clear);
    document.addEventListener("visibilitychange", () => {
      if (document.hidden) clear();
    });

    const el = gl.domElement;
    let dragging = false;
    let lastX = 0;
    let lastY = 0;

    const onDown = (e: PointerEvent) => {
      if (e.pointerType === "touch" && e.clientX < Math.min(180, window.innerWidth * 0.38)) return;
      dragging = true;
      lastX = e.clientX;
      lastY = e.clientY;
      el.setPointerCapture(e.pointerId);
    };
    const onMove = (e: PointerEvent) => {
      if (!dragging) return;
      const dx = e.clientX - lastX;
      const dy = e.clientY - lastY;
      lastX = e.clientX;
      lastY = e.clientY;
      input.yaw -= dx * 0.0048;
      input.pitch = Math.max(-1.15, Math.min(1.15, input.pitch - dy * 0.004));
    };
    const onUp = () => {
      dragging = false;
    };

    el.addEventListener("pointerdown", onDown);
    el.addEventListener("pointermove", onMove);
    el.addEventListener("pointerup", onUp);
    el.addEventListener("pointercancel", onUp);

    return () => {
      window.removeEventListener("keydown", onKeyDown);
      window.removeEventListener("keyup", onKeyUp);
      window.removeEventListener("blur", clear);
      el.removeEventListener("pointerdown", onDown);
      el.removeEventListener("pointermove", onMove);
      el.removeEventListener("pointerup", onUp);
      el.removeEventListener("pointercancel", onUp);
    };
  }, [camera, gl]);

  useFrame((_, delta) => {
    const dt = Math.min(delta, 0.1);
    const keys = heldKeys();
    let ax = 0;
    let az = 0;
    if (keys.has("KeyW") || keys.has("ArrowUp")) az += 1;
    if (keys.has("KeyS") || keys.has("ArrowDown")) az -= 1;
    if (keys.has("KeyD") || keys.has("ArrowRight")) ax += 1;
    if (keys.has("KeyA") || keys.has("ArrowLeft")) ax -= 1;
    ax += input.joyX;
    az += input.joyY;
    const mag = Math.hypot(ax, az);
    if (mag > 1) {
      ax /= mag;
      az /= mag;
    }

    const sprint = keys.has("ShiftLeft") || keys.has("ShiftRight") ? 1.65 : 1;
    const maxSpeed = 5.4 * sprint;
    const fwdX = -Math.sin(input.yaw);
    const fwdZ = -Math.cos(input.yaw);
    const rightX = Math.cos(input.yaw);
    const rightZ = -Math.sin(input.yaw);
    const nx = input.x + (fwdX * az + rightX * ax) * maxSpeed * dt;
    const nz = input.z + (fwdZ * az + rightZ * ax) * maxSpeed * dt;
    const cx = Math.max(-48, Math.min(48, nx));
    const cz = Math.max(-44, Math.min(48, nz));
    if (!blocked(cx, cz)) {
      input.x = cx;
      input.z = cz;
    } else {
      if (!blocked(cx, input.z)) input.x = Math.max(-48, Math.min(48, cx));
      if (!blocked(input.x, cz)) input.z = Math.max(-44, Math.min(48, cz));
    }

    input.speed = mag > 0.04 ? maxSpeed * Math.min(1, mag) : 0;
    input.y = Math.sin(input.x * 0.04) * 0.22 + Math.cos(input.z * 0.035) * 0.18;
    camera.position.set(input.x, input.y + 1.62, input.z);
    camera.rotation.set(input.pitch, input.yaw, 0);

    lastUi.current += dt;
    if (lastUi.current < 0.08) return;
    lastUi.current = 0;

    let nearestLibrary: (typeof LIBRARY_PLACEMENTS)[number]["slug"] | null = null;
    let libDist = 4.2;
    for (const p of LIBRARY_PLACEMENTS) {
      const d = Math.hypot(input.x - p.position[0], input.z - p.position[2]);
      if (d < libDist) {
        libDist = d;
        nearestLibrary = p.slug;
      }
    }
    const storeDist = Math.hypot(input.x - STORE_POSITION[0], input.z - STORE_POSITION[2]);
    const enterStore = storeDist < 4.2 && (storeDist <= libDist || !nearestLibrary);

    let nearestVista: (typeof VISTAS)[number]["id"] | null = null;
    let alignedVista: (typeof VISTAS)[number]["id"] | null = null;
    let hint: string | null = null;
    let bestView = 5;
    for (const v of VISTAS) {
      const d = Math.hypot(input.x - v.viewpoint[0], input.z - v.viewpoint[2]);
      if (d < bestView) {
        bestView = d;
        nearestVista = v.id;
        hint = v.hint;
      }
      const dx = v.target[0] - input.x;
      const dz = v.target[2] - input.z;
      const tl = Math.hypot(dx, dz) || 1;
      const look = (fwdX * dx + fwdZ * dz) / tl;
      if (d < 3.6 && look > 0.82) alignedVista = v.id;
    }

    if (enterStore) {
      nearestLibrary = null;
      hint = "The table of works. Enter the store.";
    } else if (nearestLibrary) {
      const lib = libraryBySlug(nearestLibrary);
      hint = lib ? `${lib.name} — press E or tap the cairn.` : hint;
    }

    const prev = useWorldUi.getState();
    if (
      prev.nearestLibrary !== nearestLibrary ||
      prev.nearestVista !== nearestVista ||
      prev.alignedVista !== alignedVista ||
      prev.hint !== hint ||
      prev.enterStore !== enterStore
    ) {
      useWorldUi.setState({
        nearestLibrary,
        nearestVista,
        alignedVista,
        hint,
        enterStore,
      });
    }
  });

  return null;
}
