"use client";

import { useEffect, useRef } from "react";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { COL3D, computeH, type Panel, type Spec } from "@/lib/disenador/engine";

interface Props {
  panels: Panel[];
  spec: Spec;
  showDoors?: boolean;
  showWall?: boolean;
  // Recibe una función para capturar el canvas como PNG dataURL (o null al desmontar).
  registerCapture?: (fn: (() => string | null) | null) => void;
}

interface Ctx {
  scene: THREE.Scene;
  camera: THREE.PerspectiveCamera;
  controls: OrbitControls;
  group: THREE.Group;
  radius: number; // tamaño del modelo actual, para encuadrar la toma de captura
}

export default function Viewer3D({ panels, spec, showDoors = true, showWall = true, registerCapture }: Props) {
  const mountRef = useRef<HTMLDivElement>(null);
  const ctxRef = useRef<Ctx | null>(null);
  const lastDims = useRef<string>("");

  // Setup once.
  useEffect(() => {
    const mount = mountRef.current;
    if (!mount) return;

    const renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.domElement.style.display = "block";
    mount.appendChild(renderer.domElement);

    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x0c0e12);
    const camera = new THREE.PerspectiveCamera(35, 1, 10, 40000);
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    scene.add(new THREE.HemisphereLight(0xffffff, 0x404654, 1.05));
    const d1 = new THREE.DirectionalLight(0xffffff, 1.1); d1.position.set(1, 2, 1.5); scene.add(d1);
    const d2 = new THREE.DirectionalLight(0xffffff, 0.35); d2.position.set(-1, 0.5, -1); scene.add(d2);
    const group = new THREE.Group(); scene.add(group);

    ctxRef.current = { scene, camera, controls, group, radius: 0 };

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

    let raf = 0;
    const loop = () => { raf = requestAnimationFrame(loop); controls.update(); renderer.render(scene, camera); };
    loop();

    // Captura CANÓNICA para el render: cámara isométrica fija + tamaño fijo 4:3 a 1x,
    // independiente de cómo el usuario haya rotado/redimensionado el visor.
    registerCapture?.(() => {
      const c = ctxRef.current;
      if (!c) return null;
      const CW = 1024, CH = 768; // 4:3, resolución moderada
      const prev = new THREE.Vector2();
      renderer.getSize(prev);
      const prevRatio = renderer.getPixelRatio();

      const r = c.radius || 100;
      const capCam = new THREE.PerspectiveCamera(35, CW / CH, 10, 40000);
      capCam.position.set(r * 1.05, r * 0.72, r * 1.3);
      capCam.lookAt(0, 0, 0);
      capCam.updateProjectionMatrix();

      renderer.setPixelRatio(1);
      renderer.setSize(CW, CH, false); // no toca el CSS → sin flicker
      renderer.render(scene, capCam);
      const url = renderer.domElement.toDataURL("image/jpeg", 0.9);

      // restaurar el visor
      renderer.setPixelRatio(prevRatio);
      renderer.setSize(prev.x, prev.y, true);
      renderer.render(scene, camera);
      return url;
    });

    return () => {
      cancelAnimationFrame(raf);
      ro.disconnect();
      controls.dispose();
      renderer.dispose();
      if (renderer.domElement.parentNode === mount) mount.removeChild(renderer.domElement);
      registerCapture?.(null);
      ctxRef.current = null;
      lastDims.current = "";
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Rebuild geometry when data changes.
  useEffect(() => {
    const c = ctxRef.current;
    if (!c) return;
    const H = computeH(spec);

    c.scene.remove(c.group);
    c.group.traverse((o) => {
      const m = o as THREE.Mesh;
      if (m.geometry) m.geometry.dispose();
      const mat = m.material;
      if (Array.isArray(mat)) mat.forEach((x) => x.dispose());
      else if (mat) (mat as THREE.Material).dispose();
    });

    const group = new THREE.Group();
    const cx = spec.W / 2, cy = H / 2, cz = spec.D / 2;
    for (const p of panels) {
      if (p.role === "puerta" && !showDoors) continue;
      const geo = new THREE.BoxGeometry(p.w, p.h, p.d);
      const mesh = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({ color: COL3D[p.role], roughness: 0.78, metalness: 0.04 }));
      mesh.position.set(p.x + p.w / 2 - cx, p.y + p.h / 2 - cy, p.z + p.d / 2 - cz);
      group.add(mesh);
      const edges = new THREE.LineSegments(new THREE.EdgesGeometry(geo), new THREE.LineBasicMaterial({ color: 0x2b2b2b }));
      edges.position.copy(mesh.position);
      group.add(edges);
    }
    if (showWall) {
      const wall = new THREE.Mesh(
        new THREE.PlaneGeometry(spec.W * 2, H * 2.4),
        new THREE.MeshStandardMaterial({ color: 0x171b22, roughness: 1 }),
      );
      wall.position.set(0, 0, -cz - 6);
      group.add(wall);
    }
    c.scene.add(group);
    c.group = group;
    c.radius = Math.max(spec.W, H, spec.D);

    // Reencuadrar la cámara sólo cuando cambian las dimensiones (no al togglear puertas/pared).
    const dimsKey = `${spec.W}|${H}|${spec.D}`;
    if (lastDims.current !== dimsKey) {
      const r = Math.max(spec.W, H, spec.D);
      c.camera.position.set(r * 1.05, r * 0.72, r * 1.3);
      c.controls.target.set(0, 0, 0);
      c.controls.update();
      lastDims.current = dimsKey;
    }
  }, [panels, spec, showDoors, showWall]);

  return <div ref={mountRef} className="w-full h-full" />;
}
