"use client";

import { motion } from "framer-motion";
import { useEffect, useRef } from "react";
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import type { Messages } from "@/messages";

const MODEL_URL = "/models/ray-ban-meta-smart-glasses/Untitled.glb";
const ANCHORS = [
  new THREE.Vector3(-0.78, 0.12, 0.2),
  new THREE.Vector3(0.92, 0.08, 0.22),
  new THREE.Vector3(0, -0.24, 0.26)
];

export default function GlassesModelSection({ t }: { t: Messages }) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const wrapRef = useRef<HTMLDivElement | null>(null);
  const annotationRefs = useRef<Array<HTMLDivElement | null>>([]);
  const anchorDotRefs = useRef<Array<HTMLSpanElement | null>>([]);
  const lineRefs = useRef<Array<SVGLineElement | null>>([]);
  const pointerRef = useRef({ x: 0, y: 0 });

  useEffect(() => {
    const canvas = canvasRef.current;
    const wrap = wrapRef.current;
    if (!canvas || !wrap) return;

    let frame = 0;
    let disposed = false;
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100);
    camera.position.set(0, 0.15, 7.9);

    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: "high-performance" });
    renderer.setClearColor(0x000000, 0);
    renderer.outputColorSpace = THREE.SRGBColorSpace;
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.08;

    const group = new THREE.Group();
    const anchorObjects = ANCHORS.map((position) => {
      const anchor = new THREE.Object3D();
      anchor.position.copy(position);
      group.add(anchor);
      return anchor;
    });
    const fallback = createFallbackGlassesModel();
    group.add(fallback);
    group.position.y = -0.34;
    scene.add(group);
    scene.add(new THREE.HemisphereLight(0xffffff, 0xaeb9c6, 1.8));

    const key = new THREE.DirectionalLight(0xffffff, 3.8);
    key.position.set(3.4, 4.6, 5.2);
    scene.add(key);

    const rim = new THREE.DirectionalLight(0x9ec7ff, 1.7);
    rim.position.set(-4, 1.8, -2);
    scene.add(rim);

    const shadow = new THREE.Mesh(
      new THREE.CircleGeometry(2.8, 96),
      new THREE.MeshBasicMaterial({ color: 0x315f8f, transparent: true, opacity: 0.08, depthWrite: false })
    );
    shadow.position.set(0, -1.12, -0.92);
    shadow.scale.y = 0.18;
    group.add(shadow);

    const loader = new GLTFLoader();
    loader.load(
      MODEL_URL,
      (gltf) => {
        if (disposed) return;
        fallback.visible = false;
        const model = gltf.scene;
        normalizeModel(model);
        group.add(model);
      },
      undefined,
      () => {
        fallback.visible = true;
      }
    );

    const resize = () => {
      const { width, height } = wrap.getBoundingClientRect();
      const pixelRatio = Math.min(window.devicePixelRatio || 1, 1.7);
      const scale = width < 640 ? 0.5 : width < 1024 ? 0.68 : 0.9;
      renderer.setPixelRatio(pixelRatio);
      renderer.setSize(width, height, false);
      group.scale.setScalar(scale);
      group.position.y = width < 640 ? -0.44 : -0.34;
      camera.aspect = width / Math.max(height, 1);
      camera.updateProjectionMatrix();
    };

    const updateAnnotations = () => {
      const bounds = wrap.getBoundingClientRect();
      const projected = new THREE.Vector3();
      const world = new THREE.Vector3();

      anchorObjects.forEach((anchor, index) => {
        anchor.getWorldPosition(world);
        projected.copy(world).project(camera);
        const x = (projected.x * 0.5 + 0.5) * bounds.width;
        const y = (-projected.y * 0.5 + 0.5) * bounds.height;
        const visible = projected.z > -1 && projected.z < 1;
        const dot = anchorDotRefs.current[index];
        const line = lineRefs.current[index];
        const annotation = annotationRefs.current[index];

        if (dot) {
          dot.style.opacity = visible ? "1" : "0";
          dot.style.transform = `translate3d(${x}px, ${y}px, 0) translate(-50%, -50%)`;
        }

        if (line && annotation) {
          const labelBounds = annotation.getBoundingClientRect();
          const labelX = labelBounds.left - bounds.left + (index === 1 ? labelBounds.width - 8 : 8);
          const labelY = labelBounds.top - bounds.top + 12;
          line.setAttribute("x1", String(x));
          line.setAttribute("y1", String(y));
          line.setAttribute("x2", String(labelX));
          line.setAttribute("y2", String(labelY));
          line.style.opacity = visible ? "0.62" : "0";
        }
      });
    };

    const observer = new ResizeObserver(resize);
    observer.observe(wrap);
    resize();

    const handlePointerMove = (event: PointerEvent) => {
      const bounds = wrap.getBoundingClientRect();
      pointerRef.current.x = ((event.clientX - bounds.left) / bounds.width - 0.5) * 2;
      pointerRef.current.y = ((event.clientY - bounds.top) / bounds.height - 0.5) * 2;
    };

    const handlePointerLeave = () => {
      pointerRef.current.x = 0;
      pointerRef.current.y = 0;
    };

    wrap.addEventListener("pointermove", handlePointerMove);
    wrap.addEventListener("pointerleave", handlePointerLeave);

    const animate = () => {
      if (disposed) return;
      const targetY = pointerRef.current.x * 0.28;
      const targetX = pointerRef.current.y * 0.16;
      group.rotation.y += (targetY + Math.sin(performance.now() * 0.00045) * 0.22 - group.rotation.y) * 0.055;
      group.rotation.x += (-0.08 - targetX - group.rotation.x) * 0.055;
      group.rotation.z = Math.sin(performance.now() * 0.00062) * 0.025;

      renderer.render(scene, camera);
      updateAnnotations();
      frame = window.requestAnimationFrame(animate);
    };

    animate();

    return () => {
      disposed = true;
      window.cancelAnimationFrame(frame);
      observer.disconnect();
      wrap.removeEventListener("pointermove", handlePointerMove);
      wrap.removeEventListener("pointerleave", handlePointerLeave);
      scene.traverse((object) => {
        if (!(object instanceof THREE.Mesh)) return;
        object.geometry.dispose();
        const material = object.material;
        if (Array.isArray(material)) material.forEach((item) => item.dispose());
        else material.dispose();
      });
      renderer.dispose();
    };
  }, []);

  return (
    <section id="model" className="relative overflow-hidden py-20 lg:py-28">
      <div ref={wrapRef} className="relative left-1/2 min-h-[42rem] w-screen -translate-x-1/2 overflow-hidden lg:min-h-[46rem]" data-testid="glasses-3d-wrap">
        <canvas ref={canvasRef} className="absolute inset-0 h-full w-full touch-pan-y" aria-label={t.model.canvasLabel} data-testid="glasses-3d-canvas" />
        <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_52%_46%,rgba(255,255,255,0.58),transparent_34%),radial-gradient(circle_at_52%_68%,rgba(49,95,143,0.15),transparent_30%)]" />
        <svg className="pointer-events-none absolute inset-0 z-[8] h-full w-full overflow-visible" aria-hidden>
          {t.model.annotations.map((item, index) => (
            <line key={item.label} ref={(node) => { lineRefs.current[index] = node; }} className="transition-opacity duration-200" stroke="rgba(49,95,143,0.55)" strokeWidth="1.5" strokeDasharray="5 7" />
          ))}
        </svg>
        {t.model.annotations.map((item, index) => (
          <span key={item.label} ref={(node) => { anchorDotRefs.current[index] = node; }} className="pointer-events-none absolute left-0 top-0 z-[9] grid size-4 place-items-center rounded-full bg-optical shadow-[0_0_0_8px_rgba(49,95,143,0.12)] transition-opacity duration-200" aria-hidden>
            <span className="size-1.5 rounded-full bg-white" />
          </span>
        ))}

        <div className="relative z-10 mx-auto max-w-7xl px-4 sm:px-6">
          <p className="mb-4 text-xs font-semibold uppercase tracking-[0.28em] text-optical/75">{t.model.eyebrow}</p>
          <h2 className="text-4xl font-semibold tracking-[-0.055em] text-ink sm:text-6xl">{t.model.title}</h2>
          <p className="mt-5 max-w-2xl text-base leading-8 text-ink/62 sm:text-lg">{t.model.body}</p>
        </div>

        <Annotation refCallback={(node) => { annotationRefs.current[0] = node; }} className="left-4 top-[17rem] sm:left-10 lg:left-16 lg:top-[24rem]" align="left" label={t.model.annotations[0].label} body={t.model.annotations[0].body} />
        <Annotation refCallback={(node) => { annotationRefs.current[1] = node; }} className="right-4 top-[33rem] sm:right-10 lg:right-20 lg:top-[24rem]" align="right" label={t.model.annotations[1].label} body={t.model.annotations[1].body} />
        <Annotation refCallback={(node) => { annotationRefs.current[2] = node; }} className="bottom-8 left-6 sm:left-16 lg:bottom-16 lg:left-[42%]" align="left" label={t.model.annotations[2].label} body={t.model.annotations[2].body} />
      </div>
    </section>
  );
}

function Annotation({ label, body, align, className, refCallback }: { label: string; body: string; align: "left" | "right"; className: string; refCallback: (node: HTMLDivElement | null) => void }) {
  return (
    <motion.div
      ref={refCallback}
      className={`pointer-events-none absolute z-10 max-w-[14rem] ${className} ${align === "right" ? "text-right" : "text-left"}`}
      initial={{ opacity: 0, y: 14 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, amount: 0.6 }}
      transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
    >
      <div className={`mb-3 flex items-center gap-3 ${align === "right" ? "justify-end" : ""}`}>
        {align === "right" ? <span className="h-px w-16 bg-ink/18" /> : null}
        <span className="grid size-3 place-items-center rounded-full bg-optical shadow-[0_0_0_7px_rgba(49,95,143,0.12)]" />
        {align === "left" ? <span className="h-px w-16 bg-ink/18" /> : null}
      </div>
      <p className="text-xs font-semibold uppercase tracking-[0.22em] text-optical/80">{label}</p>
      <p className="mt-2 text-sm leading-6 text-ink/60">{body}</p>
    </motion.div>
  );
}

function normalizeModel(model: THREE.Object3D) {
  const box = new THREE.Box3().setFromObject(model);
  const center = box.getCenter(new THREE.Vector3());
  const size = box.getSize(new THREE.Vector3());
  const maxDimension = Math.max(size.x, size.y, size.z) || 1;
  model.position.sub(center);
  model.scale.setScalar(3.25 / maxDimension);
  model.rotation.set(0, Math.PI, 0);
  model.traverse((object) => {
    if (!(object instanceof THREE.Mesh)) return;
    object.castShadow = false;
    object.receiveShadow = false;
    const material = object.material;
    const materials = Array.isArray(material) ? material : [material];
    materials.forEach((item) => {
      if ("roughness" in item) item.roughness = Math.max(0.26, item.roughness ?? 0.26);
      if ("metalness" in item) item.metalness = Math.min(0.65, item.metalness ?? 0.35);
    });
  });
}

function createFallbackGlassesModel() {
  const group = new THREE.Group();
  group.scale.setScalar(0.9);

  const frameMaterial = new THREE.MeshPhysicalMaterial({
    color: 0x161a20,
    roughness: 0.28,
    metalness: 0.55,
    clearcoat: 0.85,
    clearcoatRoughness: 0.18
  });
  const lensMaterial = new THREE.MeshPhysicalMaterial({
    color: 0xbfd7e3,
    roughness: 0.12,
    metalness: 0,
    transmission: 0.48,
    transparent: true,
    opacity: 0.28,
    thickness: 0.12,
    side: THREE.DoubleSide
  });
  const padMaterial = new THREE.MeshPhysicalMaterial({
    color: 0xf8fbff,
    roughness: 0.18,
    transparent: true,
    opacity: 0.52
  });
  const accentMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.42 });

  const ringGeometry = new THREE.TorusGeometry(1.04, 0.065, 18, 112);
  const lensGeometry = new THREE.CircleGeometry(0.93, 72);
  const hingeGeometry = new THREE.BoxGeometry(0.22, 0.22, 0.22);
  const padGeometry = new THREE.SphereGeometry(0.16, 24, 16);

  [-1.18, 1.18].forEach((xPosition) => {
    const ring = new THREE.Mesh(ringGeometry, frameMaterial);
    ring.position.x = xPosition;
    ring.scale.y = 0.68;
    group.add(ring);

    const lens = new THREE.Mesh(lensGeometry, lensMaterial);
    lens.position.set(xPosition, 0, -0.03);
    lens.scale.set(1, 0.66, 1);
    group.add(lens);

    const highlight = new THREE.Mesh(new THREE.PlaneGeometry(0.58, 0.045), accentMaterial);
    highlight.position.set(xPosition - 0.2, 0.34, 0.02);
    highlight.rotation.z = -0.22;
    group.add(highlight);

    const hinge = new THREE.Mesh(hingeGeometry, frameMaterial);
    hinge.position.set(xPosition > 0 ? 2.18 : -2.18, 0.03, -0.02);
    hinge.scale.set(0.55, 0.8, 0.62);
    group.add(hinge);
  });

  const bridgeCurve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-0.3, 0.08, 0),
    new THREE.Vector3(-0.12, 0.25, 0.04),
    new THREE.Vector3(0, 0.3, 0.05),
    new THREE.Vector3(0.12, 0.25, 0.04),
    new THREE.Vector3(0.3, 0.08, 0)
  ]);
  group.add(new THREE.Mesh(new THREE.TubeGeometry(bridgeCurve, 38, 0.052, 14, false), frameMaterial));

  [-0.28, 0.28].forEach((xPosition) => {
    const pad = new THREE.Mesh(padGeometry, padMaterial);
    pad.position.set(xPosition, -0.22, 0.14);
    pad.scale.set(0.55, 0.9, 0.28);
    pad.rotation.z = xPosition > 0 ? -0.22 : 0.22;
    group.add(pad);
  });

  const leftTemple = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-2.18, 0.03, -0.03),
    new THREE.Vector3(-2.75, 0.03, -0.72),
    new THREE.Vector3(-3.1, -0.04, -1.75),
    new THREE.Vector3(-2.84, -0.28, -2.42)
  ]);
  const rightTemple = new THREE.CatmullRomCurve3([
    new THREE.Vector3(2.18, 0.03, -0.03),
    new THREE.Vector3(2.75, 0.03, -0.72),
    new THREE.Vector3(3.1, -0.04, -1.75),
    new THREE.Vector3(2.84, -0.28, -2.42)
  ]);

  group.add(new THREE.Mesh(new THREE.TubeGeometry(leftTemple, 54, 0.055, 14, false), frameMaterial));
  group.add(new THREE.Mesh(new THREE.TubeGeometry(rightTemple, 54, 0.055, 14, false), frameMaterial));

  group.rotation.set(-0.08, -0.2, 0);
  return group;
}
