// vhs3d.jsx — Three.js-powered VHS boxes inside the shelf.
// Replaces the CSS spine rows with real 3D boxes, per-face colored + textured.

const { useEffect, useRef, useState } = React;

/**
 * Build a procedural "spine label" texture:
 * - Dark strip at top (VHS + number)
 * - Vertical TITLE down the middle
 * - Colored stripe at bottom (TROOPER)
 */
function makeSpineTexture(THREE, ch) {
  const { bg, stripe, text } = ch.spine;
  const W = 256;
  const H = 1024;
  const c = document.createElement("canvas");
  c.width = W; c.height = H;
  const g = c.getContext("2d");

  // Base plastic gradient (left→right highlight)
  const grad = g.createLinearGradient(0, 0, W, 0);
  grad.addColorStop(0, shadeHex(bg, -22));
  grad.addColorStop(0.15, shadeHex(bg, 6));
  grad.addColorStop(0.5, bg);
  grad.addColorStop(0.85, shadeHex(bg, -6));
  grad.addColorStop(1, shadeHex(bg, -28));
  g.fillStyle = grad;
  g.fillRect(0, 0, W, H);

  // Subtle noise / wear
  g.globalAlpha = 0.08;
  for (let i = 0; i < 900; i++) {
    g.fillStyle = Math.random() > 0.5 ? "#000" : "#fff";
    g.fillRect(Math.random() * W, Math.random() * H, 1, 1);
  }
  g.globalAlpha = 1;

  // Top label band
  g.fillStyle = "rgba(0,0,0,0.55)";
  g.fillRect(0, 60, W, 170);
  g.fillStyle = stripe;
  g.font = "900 italic 48px 'Montserrat', sans-serif";
  g.textAlign = "center";
  g.textBaseline = "middle";
  g.fillText("VHS", W / 2, 110);
  g.fillStyle = text;
  g.font = "900 italic 90px 'Montserrat', sans-serif";
  g.fillText(ch.num, W / 2, 185);

  // Vertical title (rotated)
  g.save();
  g.translate(W / 2, H / 2 + 40);
  g.rotate(-Math.PI / 2);
  g.fillStyle = text;
  g.font = "900 italic 62px 'Montserrat', sans-serif";
  g.textAlign = "center";
  g.textBaseline = "middle";
  // shrink if too long
  let title = ch.title;
  let size = 62;
  while (g.measureText(title).width > 560 && size > 30) {
    size -= 4;
    g.font = `900 italic ${size}px 'Montserrat', sans-serif`;
  }
  g.fillText(title, 0, 0);
  g.restore();

  // Bottom stripe — TROOPER brand
  g.fillStyle = stripe;
  g.fillRect(0, H - 180, W, 140);
  g.fillStyle = bg;
  g.font = "900 italic 36px 'Montserrat', sans-serif";
  g.textAlign = "center";
  g.textBaseline = "middle";
  g.fillText("TROOPER", W / 2, H - 110);

  // Edge shadows (left/right)
  const edgeL = g.createLinearGradient(0, 0, 30, 0);
  edgeL.addColorStop(0, "rgba(0,0,0,0.65)");
  edgeL.addColorStop(1, "rgba(0,0,0,0)");
  g.fillStyle = edgeL;
  g.fillRect(0, 0, 30, H);
  const edgeR = g.createLinearGradient(W - 30, 0, W, 0);
  edgeR.addColorStop(0, "rgba(0,0,0,0)");
  edgeR.addColorStop(1, "rgba(0,0,0,0.65)");
  g.fillStyle = edgeR;
  g.fillRect(W - 30, 0, 30, H);

  const tex = new THREE.CanvasTexture(c);
  tex.colorSpace = THREE.SRGBColorSpace;
  tex.anisotropy = 4;
  return tex;
}

/** Solid dark plastic texture for the other faces (top/bottom/back/front) */
function makeSolidTexture(THREE, bg) {
  const c = document.createElement("canvas");
  c.width = 64; c.height = 256;
  const g = c.getContext("2d");
  const grad = g.createLinearGradient(0, 0, 0, 256);
  grad.addColorStop(0, shadeHex(bg, -28));
  grad.addColorStop(0.5, shadeHex(bg, -18));
  grad.addColorStop(1, shadeHex(bg, -38));
  g.fillStyle = grad;
  g.fillRect(0, 0, 64, 256);
  // faint noise
  g.globalAlpha = 0.08;
  for (let i = 0; i < 160; i++) {
    g.fillStyle = Math.random() > 0.5 ? "#000" : "#fff";
    g.fillRect(Math.random() * 64, Math.random() * 256, 1, 1);
  }
  const tex = new THREE.CanvasTexture(c);
  tex.colorSpace = THREE.SRGBColorSpace;
  return tex;
}

function shadeHex(hex, p) {
  let c = hex.replace("#", "");
  if (c.length === 3) c = c.split("").map(x => x + x).join("");
  const r = parseInt(c.substr(0, 2), 16);
  const g = parseInt(c.substr(2, 2), 16);
  const b = parseInt(c.substr(4, 2), 16);
  const amt = Math.round(2.55 * p);
  const nr = Math.min(255, Math.max(0, r + amt));
  const ng = Math.min(255, Math.max(0, g + amt));
  const nb = Math.min(255, Math.max(0, b + amt));
  return "#" + [nr, ng, nb].map(v => v.toString(16).padStart(2, "0")).join("");
}

function VHS3D({ chapters, onOpenGallery }) {
  const mountRef = useRef(null);
  const stateRef = useRef({});

  useEffect(() => {
    if (!window.THREE) return;
    const THREE = window.THREE;
    const mount = mountRef.current;
    if (!mount) return;

    let cancelled = false;
    // Wait for Montserrat to be loaded before drawing canvas textures.
    const fontReady = document.fonts
      ? document.fonts.load("900 italic 62px 'Montserrat'").then(() => document.fonts.ready)
      : Promise.resolve();

    fontReady.then(() => {
      if (cancelled) return;
      initScene();
    });

    function initScene() {
    const width = mount.clientWidth;
    const height = mount.clientHeight;

    // Scene
    const scene = new THREE.Scene();

    // Camera — frame the boxes at the BOTTOM of the canvas so there's room above
    // for the hover lift animation (the canvas extends above the shelf ceiling).
    const camera = new THREE.PerspectiveCamera(28, width / height, 0.1, 100);
    camera.position.set(0, 1.5, 9.5);
    camera.lookAt(0, 0, 0);

    // Renderer
    const renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true,
      powerPreference: "high-performance",
    });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.setSize(width, height);
    renderer.setClearColor(0x000000, 0);
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    mount.appendChild(renderer.domElement);

    // Lights
    // Ambient dim
    scene.add(new THREE.AmbientLight(0xffffff, 0.35));

    // Warm key from upper right (shelf lit softly by room?)
    const key = new THREE.DirectionalLight(0xffe5c0, 0.55);
    key.position.set(3, 5, 6);
    scene.add(key);

    // TV cyan cast from front-top
    const tvCast = new THREE.SpotLight(0x5fc8e0, 1.4, 18, Math.PI / 3, 0.7, 1.0);
    tvCast.position.set(0, 3.8, 6);
    tvCast.target.position.set(0, 0, 0);
    scene.add(tvCast);
    scene.add(tvCast.target);

    // Fill from below (warm, bounced)
    const fill = new THREE.DirectionalLight(0x6a4428, 0.2);
    fill.position.set(0, -3, 5);
    scene.add(fill);

    // Layout: VHS boxes side by side.
    // Anchor the bottom of each box to a fixed "floor" Y. Shrinking VHS_H
    // will keep the floor anchored and only reduce the top.
    const VHS_H = 2.9;   // was 3.4, -15%
    const VHS_D = 2.0;
    const VHS_W = 0.72;  // was 0.85, -15%
    const FLOOR_Y = -2.14; // bottom of the cubby in world space
    const baseY = FLOOR_Y + VHS_H / 2;

    const n = chapters.length;
    const gap = 0.04;
    const totalW = n * VHS_W + (n - 1) * gap;
    const startX = -totalW / 2 + VHS_W / 2;

    const group = new THREE.Group();
    scene.add(group);

    const boxes = [];

    chapters.forEach((ch, i) => {
      const spineTex = makeSpineTexture(THREE, ch);
      const plasticTex = makeSolidTexture(THREE, ch.spine.bg);

      // Six materials in BoxGeometry order: +X, -X, +Y, -Y, +Z (front), -Z (back)
      // The label on a real VHS is on the spine = the narrow side facing OUT.
      // Here the camera looks at +Z, so the "visible" spine is the +Z face.
      // Other faces get dark plastic.
      const matSpine = new THREE.MeshStandardMaterial({
        map: spineTex,
        roughness: 0.55,
        metalness: 0.05,
      });
      const matPlastic = new THREE.MeshStandardMaterial({
        map: plasticTex,
        roughness: 0.7,
        metalness: 0.02,
      });
      const matTop = new THREE.MeshStandardMaterial({
        color: new THREE.Color(shadeHex(ch.spine.bg, -35)),
        roughness: 0.65,
        metalness: 0.05,
      });

      const geo = new THREE.BoxGeometry(VHS_W, VHS_H, VHS_D);
      const mesh = new THREE.Mesh(geo, [
        matPlastic, // +X right edge
        matPlastic, // -X left edge
        matTop,     // +Y top
        matTop,     // -Y bottom
        matSpine,   // +Z front (spine label — this is what we see)
        matPlastic, // -Z back
      ]);
      mesh.position.x = startX + i * (VHS_W + gap);
      mesh.position.y = baseY;
      mesh.position.z = 0;
      mesh.userData.chapterId = ch.id;
      mesh.userData.baseY = baseY;
      mesh.userData.baseZ = 0;
      mesh.userData.index = i;
      group.add(mesh);
      boxes.push(mesh);
    });

    // Raycaster for hover + click
    const raycaster = new THREE.Raycaster();
    const pointer = new THREE.Vector2(-10, -10);
    let hovered = null;

    function onMove(e) {
      const rect = renderer.domElement.getBoundingClientRect();
      pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
      pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
    }
    function onLeave() {
      pointer.x = -10; pointer.y = -10;
    }
    function onClick() {
      if (hovered) {
        onOpenGallery(hovered.userData.chapterId);
      }
    }
    renderer.domElement.addEventListener("pointermove", onMove);
    renderer.domElement.addEventListener("pointerleave", onLeave);
    renderer.domElement.addEventListener("click", onClick);

    // Animation loop
    let rafId;
    const clock = new THREE.Clock();
    function loop() {
      const t = clock.getElapsedTime();

      // hover detection
      raycaster.setFromCamera(pointer, camera);
      const hits = raycaster.intersectObjects(boxes, false);
      const next = hits[0] ? hits[0].object : null;
      if (next !== hovered) {
        hovered = next;
        mount.style.cursor = hovered ? "pointer" : "default";
      }

      // lerp each box toward its target state
      boxes.forEach((m) => {
        const isHover = m === hovered;
        const targetY = isHover ? m.userData.baseY + 0.25 : m.userData.baseY;
        const targetZ = isHover ? 0.35 : m.userData.baseZ;
        const targetRx = isHover ? -0.08 : 0;
        m.position.y += (targetY - m.position.y) * 0.18;
        m.position.z += (targetZ - m.position.z) * 0.18;
        m.rotation.x += (targetRx - m.rotation.x) * 0.18;
      });

      renderer.render(scene, camera);
      rafId = requestAnimationFrame(loop);
    }
    loop();

    // Resize
    function onResize() {
      const w = mount.clientWidth;
      const h = mount.clientHeight;
      if (!w || !h) return;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    }
    const ro = new ResizeObserver(onResize);
    ro.observe(mount);

    stateRef.current = { scene, camera, renderer, boxes, cleanup: () => {
      cancelAnimationFrame(rafId);
      ro.disconnect();
      renderer.domElement.removeEventListener("pointermove", onMove);
      renderer.domElement.removeEventListener("pointerleave", onLeave);
      renderer.domElement.removeEventListener("click", onClick);
      boxes.forEach(m => {
        m.geometry.dispose();
        (Array.isArray(m.material) ? m.material : [m.material]).forEach(mat => {
          if (mat.map) mat.map.dispose();
          mat.dispose();
        });
      });
      renderer.dispose();
      if (renderer.domElement.parentNode) {
        renderer.domElement.parentNode.removeChild(renderer.domElement);
      }
    }};

    return () => stateRef.current.cleanup && stateRef.current.cleanup();
    } // end initScene

    return () => {
      cancelled = true;
      if (stateRef.current.cleanup) stateRef.current.cleanup();
    };
  }, [chapters, onOpenGallery]);

  return <div ref={mountRef} className="vhs3d-mount" />;
}

window.VHS3D = VHS3D;
