import { Canvas, useThree } from "@react-three/fiber";
import { OrbitControls, Stage, useGLTF } from "@react-three/drei";
import React, { useEffect, Suspense } from "react";
import { BACKGROUND_COLOR } from "../../assets/Constants";
import * as THREE from "three";
import type { OrbitControls as OrbitControlsImpl } from "three-stdlib";

export interface ThreeViewHandle {
  zoomIn: () => void;
  zoomOut: () => void;
}

interface ThreeViewProps {
  fileUrl: string;
  width?: string;
  height?: string;
  onLoad?: () => void;
  onError?: (error: string) => void;
  onAspectRatioCalculated?: (ratio: number) => void;
  controlRef?: React.MutableRefObject<ThreeViewHandle | null>;
}

const ZOOM_FACTOR = 1.2;
const MIN_DISTANCE = 1;
const MAX_DISTANCE = 100;

const TouchController = () => {
  const { camera, controls, gl } = useThree();

  useEffect(() => {
    if (!controls) return;

    const orbit = controls as unknown as OrbitControlsImpl;

    // Отключаем встроенную обработку 2-пальцевых жестов — делаем вручную
    orbit.touches.TWO = undefined as unknown as number;

    const domElement = gl.domElement;
    let panStart: { x: number; y: number } | null = null;
    let pinchStartDist: number | null = null;
    let pinchStartCamDist: number | null = null;

    const getPinchDist = (touches: TouchList) => {
      const dx = touches[0].clientX - touches[1].clientX;
      const dy = touches[0].clientY - touches[1].clientY;
      return Math.sqrt(dx * dx + dy * dy);
    };

    const getCenter3 = (touches: TouchList) => ({
      x: (touches[0].clientX + touches[1].clientX + touches[2].clientX) / 3,
      y: (touches[0].clientY + touches[1].clientY + touches[2].clientY) / 3,
    });

    const handleTouchStart = (e: TouchEvent) => {
      if (e.touches.length === 2) {
        e.preventDefault();
        orbit.enabled = false;
        pinchStartDist = getPinchDist(e.touches);
        pinchStartCamDist = camera.position.distanceTo(orbit.target);
      } else if (e.touches.length === 3) {
        e.preventDefault();
        orbit.enabled = false;
        panStart = getCenter3(e.touches);
      }
    };

    const handleTouchMove = (e: TouchEvent) => {
      if (e.touches.length === 2 && pinchStartDist !== null && pinchStartCamDist !== null) {
        e.preventDefault();
        const currentDist = getPinchDist(e.touches);
        const scale = pinchStartDist / currentDist;
        const newDist = Math.min(MAX_DISTANCE, Math.max(MIN_DISTANCE, pinchStartCamDist * scale));
        const dir = new THREE.Vector3()
          .subVectors(camera.position, orbit.target)
          .normalize();
        camera.position.copy(orbit.target).addScaledVector(dir, newDist);
        camera.updateProjectionMatrix();
        orbit.update();
      } else if (e.touches.length === 3 && panStart) {
        e.preventDefault();
        const center = getCenter3(e.touches);
        const deltaX = center.x - panStart.x;
        const deltaY = center.y - panStart.y;
        panStart = center;

        const perspCamera = camera as THREE.PerspectiveCamera;
        const targetDistance = camera.position.distanceTo(orbit.target);
        const height =
          2 *
          Math.tan(THREE.MathUtils.degToRad(perspCamera.fov / 2)) *
          targetDistance;
        const worldPerPixel = height / domElement.clientHeight;

        const panOffset = new THREE.Vector3(
          -deltaX * worldPerPixel,
          deltaY * worldPerPixel,
          0
        );
        panOffset.applyQuaternion(camera.quaternion);

        camera.position.add(panOffset);
        orbit.target.add(panOffset);
        orbit.update();
      }
    };

    const handleTouchEnd = (e: TouchEvent) => {
      if (e.touches.length < 2) {
        pinchStartDist = null;
        pinchStartCamDist = null;
      }
      if (e.touches.length < 3) {
        panStart = null;
      }
      if (e.touches.length <= 1) {
        orbit.enabled = true;
      }
    };

    domElement.addEventListener("touchstart", handleTouchStart, {
      passive: false,
    });
    domElement.addEventListener("touchmove", handleTouchMove, {
      passive: false,
    });
    domElement.addEventListener("touchend", handleTouchEnd);

    return () => {
      domElement.removeEventListener("touchstart", handleTouchStart);
      domElement.removeEventListener("touchmove", handleTouchMove);
      domElement.removeEventListener("touchend", handleTouchEnd);
    };
  }, [camera, controls, gl]);

  return null;
};

const ZoomController = ({
  controlRef,
}: {
  controlRef?: React.MutableRefObject<ThreeViewHandle | null>;
}) => {
  const { camera, controls } = useThree();

  useEffect(() => {
    if (!controlRef || !controls) return;
    const orbit = controls as unknown as {
      target: THREE.Vector3;
      getDistance: () => number;
      setDistance?: (d: number) => void;
    };
    const zoomIn = () => {
      const target = orbit.target;
      const dir = new THREE.Vector3()
        .subVectors(camera.position, target)
        .normalize();
      const dist = camera.position.distanceTo(target);
      const newDist = Math.max(MIN_DISTANCE, dist / ZOOM_FACTOR);
      camera.position.copy(target).addScaledVector(dir, newDist);
      camera.updateProjectionMatrix();
    };
    const zoomOut = () => {
      const target = orbit.target;
      const dir = new THREE.Vector3()
        .subVectors(camera.position, target)
        .normalize();
      const dist = camera.position.distanceTo(target);
      const newDist = Math.min(MAX_DISTANCE, dist * ZOOM_FACTOR);
      camera.position.copy(target).addScaledVector(dir, newDist);
      camera.updateProjectionMatrix();
    };
    controlRef.current = { zoomIn, zoomOut };
    return () => {
      controlRef.current = null;
    };
  }, [camera, controls, controlRef]);

  return null;
};

const AutoResize = () => {
  const { gl, camera } = useThree();
  useEffect(() => {
    const handleResize = () => {
      const parent = gl.domElement.parentElement;
      if (!parent) return;
      gl.setSize(parent.clientWidth, parent.clientHeight);
      if (camera instanceof THREE.PerspectiveCamera) {
        camera.aspect = parent.clientWidth / parent.clientHeight;
        camera.updateProjectionMatrix();
      }
    };
    handleResize();
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, [gl, camera]);
  return null;
};

const Model = ({
  fileUrl,
  onLoad,
}: {
  fileUrl: string;
  onLoad?: () => void;
}) => {
  const { scene } = useGLTF(fileUrl);

  useEffect(() => {
    if (scene) {
      scene.traverse((child) => {
        if ((child as THREE.Mesh).isMesh) {
          const m = (child as THREE.Mesh)
            .material as THREE.MeshStandardMaterial;
          if (m) {
            m.envMapIntensity = 1.5;
            m.needsUpdate = true;
          }
        }
      });
      onLoad?.();
    }
  }, [scene, onLoad]);

  return <primitive object={scene} />;
};

export const ThreeView: React.FC<ThreeViewProps> = ({
  fileUrl,
  width = "100%",
  height = "100%",
  onLoad,
  onError,
  controlRef,
}) => {
  return (
    <div style={{ width, height, position: "relative", overflow: "hidden" }}>
      <Canvas
        gl={{
          antialias: true,
          toneMappingExposure: 1.5,
          outputColorSpace: THREE.SRGBColorSpace,
        }}
        camera={{ position: [0, 0, 5], fov: 40 }}
        style={{ width: "100%", height: "100%" }}
        onError={(e: any) => onError?.(e.message)}
      >
        <AutoResize />
        <TouchController />
        {controlRef && <ZoomController controlRef={controlRef} />}
        <color attach="background" args={[BACKGROUND_COLOR]} />
        <ambientLight intensity={0.8} />
        <directionalLight position={[30, 30, 30]} intensity={1.2} />
        <directionalLight position={[-30, 30, 30]} intensity={1.2} />
        <directionalLight position={[30, -30, 30]} intensity={1} />
        <directionalLight position={[-30, -30, 30]} intensity={1} />
        <directionalLight position={[0, 0, 50]} intensity={1} />
        <directionalLight position={[0, 0, -50]} intensity={0.8} />
        <directionalLight position={[50, 0, 0]} intensity={0.8} />
        <directionalLight position={[-50, 0, 0]} intensity={0.8} />
        <directionalLight position={[0, 50, 0]} intensity={0.8} />
        <directionalLight position={[0, -50, 0]} intensity={0.6} />
        <pointLight position={[30, 30, 30]} intensity={1.2} />
        <pointLight position={[-30, -30, -30]} intensity={1.2} />
        <pointLight position={[0, 30, 0]} intensity={0.6} />

        <Suspense fallback={null}>
          <Stage
            environment={null}
            intensity={1}
            castShadow={false}
            shadows={false}
            adjustCamera={true}
            center={{ precise: true }}
          >
            <Model fileUrl={fileUrl} onLoad={onLoad} />
          </Stage>
        </Suspense>

        <OrbitControls
          makeDefault
          enableZoom={true}
          enablePan={true}
          target={[50, 50, 50]}
          minDistance={1}
          maxDistance={100}
          enableDamping={true}
        />
      </Canvas>
    </div>
  );
};
