"use client";

import { useEffect, useState } from "react";
import { Canvas } from "@react-three/fiber";
import { Bounds, OrbitControls } from "@react-three/drei";
import { Loader2, RotateCcw } from "lucide-react";
import * as THREE from "three";
import { STLLoader } from "three/addons/loaders/STLLoader.js";
import { PLYLoader } from "three/addons/loaders/PLYLoader.js";
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";

import { Button } from "@/components/ui/button";

type SupportedFormat = "stl" | "ply" | "obj";

function detectFormat(filename: string): SupportedFormat | null {
  const lower = filename.toLowerCase();
  if (lower.endsWith(".stl")) return "stl";
  if (lower.endsWith(".ply")) return "ply";
  if (lower.endsWith(".obj")) return "obj";
  return null;
}

/**
 * Loads a 3D scan from the given URL into a BufferGeometry. OBJ files come
 * back from the loader as a THREE.Group, so we merge their child meshes'
 * geometries into a single BufferGeometry for a uniform render path.
 */
async function loadGeometry(
  url: string,
  format: SupportedFormat,
): Promise<THREE.BufferGeometry> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`İndirilemedi (HTTP ${res.status})`);
  const buffer = await res.arrayBuffer();

  if (format === "stl") {
    const g = new STLLoader().parse(buffer);
    g.computeVertexNormals();
    return g;
  }
  if (format === "ply") {
    const g = new PLYLoader().parse(buffer);
    g.computeVertexNormals();
    return g;
  }
  // OBJ — text format, parse needs a string and yields a Group of meshes.
  const text = new TextDecoder().decode(buffer);
  const group = new OBJLoader().parse(text);
  const geometries: THREE.BufferGeometry[] = [];
  group.traverse((obj) => {
    if ((obj as THREE.Mesh).isMesh) {
      const g = (obj as THREE.Mesh).geometry as THREE.BufferGeometry;
      if (g) geometries.push(g);
    }
  });
  if (geometries.length === 0) throw new Error("OBJ içinde mesh bulunamadı.");
  // For a single mesh we can avoid the merge dependency entirely.
  if (geometries.length === 1) {
    const g = geometries[0].clone();
    g.computeVertexNormals();
    return g;
  }
  // Cheap concat: positions only. Good enough for previewing scans.
  const positions: number[] = [];
  for (const g of geometries) {
    const pos = g.getAttribute("position");
    if (!pos) continue;
    for (let i = 0; i < pos.count * 3; i++) {
      positions.push((pos.array as ArrayLike<number>)[i]);
    }
  }
  const merged = new THREE.BufferGeometry();
  merged.setAttribute(
    "position",
    new THREE.Float32BufferAttribute(positions, 3),
  );
  merged.computeVertexNormals();
  return merged;
}

export function STLViewer({
  url,
  filename,
}: {
  url: string;
  filename: string;
}) {
  const [geometry, setGeometry] = useState<THREE.BufferGeometry | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [resetKey, setResetKey] = useState(0);

  const format = detectFormat(filename);

  useEffect(() => {
    if (!format) {
      setError("Bu dosya türü görüntüleyici tarafından desteklenmiyor.");
      return;
    }
    let cancelled = false;
    setGeometry(null);
    setError(null);
    loadGeometry(url, format)
      .then((g) => {
        if (cancelled) {
          g.dispose();
          return;
        }
        setGeometry(g);
      })
      .catch((e: unknown) => {
        if (cancelled) return;
        setError(e instanceof Error ? e.message : "Yüklenemedi.");
      });
    return () => {
      cancelled = true;
    };
  }, [url, format]);

  // Dispose old geometry when it gets replaced or the component unmounts.
  useEffect(() => {
    return () => {
      geometry?.dispose();
    };
  }, [geometry]);

  if (error) {
    return (
      <div className="bg-muted/40 text-muted-foreground flex h-full items-center justify-center p-6 text-sm">
        {error}
      </div>
    );
  }
  if (!geometry) {
    return (
      <div className="bg-muted/40 text-muted-foreground flex h-full items-center justify-center gap-2 p-6 text-sm">
        <Loader2 className="size-4 animate-spin" />
        Tarama yükleniyor...
      </div>
    );
  }

  return (
    <div className="relative h-full w-full">
      <Canvas camera={{ position: [0, 0, 100], fov: 45, near: 0.1, far: 5000 }} dpr={[1, 2]}>
        <color attach="background" args={["#0b1220"]} />
        <ambientLight intensity={0.45} />
        <directionalLight position={[10, 10, 10]} intensity={0.7} />
        <directionalLight position={[-10, -10, -10]} intensity={0.35} />
        <Bounds key={resetKey} fit clip observe margin={1.25}>
          <mesh geometry={geometry}>
            <meshStandardMaterial
              color="#e2e8f0"
              roughness={0.55}
              metalness={0.05}
            />
          </mesh>
        </Bounds>
        <OrbitControls makeDefault enableDamping dampingFactor={0.1} />
      </Canvas>
      <Button
        type="button"
        size="sm"
        variant="outline"
        onClick={() => setResetKey((k) => k + 1)}
        className="absolute right-3 top-3 bg-background/80 backdrop-blur"
      >
        <RotateCcw className="size-4" />
        Sığdır
      </Button>
    </div>
  );
}

export default STLViewer;
