import { Canvas, useThree, useFrame } from '@react-three/fiber';
import { Suspense, useEffect, useRef, useState, Component } from 'react';
import * as THREE from 'three';
import NativeOrbitControls from './NativeOrbitControls';
import { setViewport } from './viewportLabelStore';
import { BUILD_LABEL } from './viewportBuild';

// STABLE VIEWPORT CORE — DO NOT REPLACE OR ADD SHADER HELPERS
// WITHOUT RUNNING THE VIEWPORT REGRESSION TESTS (see VIEWPORT_CORE_LOCK.md).
//
// Responsibilities (and ONLY these): Canvas creation, renderer, camera, lights,
// native grid, native OrbitControls, scene root, WebGL lifecycle, context-loss
// handling, a permanent procedural fallback, resize, basic view controls.
// No business forms, API calls or database loading here.

const TARGET = [0, -60, 0];
const D = 780;
const VIEW_POSITIONS = {
  perspective: [TARGET[0] + 509, TARGET[1] + 297, TARGET[2] + 509],
  front: [0, TARGET[1], D],
  rear: [0, TARGET[1], -D],
  lateral: [D, TARGET[1], 0],
  medial: [-D, TARGET[1], 0],
  top: [0.01, D, 0.01],
  bottom: [0.01, -D - 120, 0.01],
  fit: [TARGET[0] + 509, TARGET[1] + 297, TARGET[2] + 509],
  reset: [TARGET[0] + 509, TARGET[1] + 297, TARGET[2] + 509],
};

function WorkshopLights() {
  return (
    <>
      <ambientLight intensity={0.62} color="#aeb8c6" />
      <hemisphereLight args={['#cdd7e6', '#0a0e12', 0.55]} />
      <directionalLight position={[300, 500, 300]} intensity={1.15} color="#ffffff" />
      <directionalLight position={[-300, 220, -220]} intensity={0.5} color="#9fb4d4" />
      <directionalLight position={[0, 160, -420]} intensity={0.32} color="#22d3ee" />
    </>
  );
}

// Emergency fallback: meshBasicMaterial (no lighting needed) so the prosthetic
// stays visible even if every light failed. Non-black, bright cyan.
function CoreFallbackModel() {
  return (
    <group position={[0, -60, 0]}>
      <mesh position={[0, 90, 0]}>
        <cylinderGeometry args={[52, 64, 220, 24]} />
        <meshBasicMaterial color="#0e7490" />
      </mesh>
      <mesh position={[0, -70, 0]}>
        <cylinderGeometry args={[13, 13, 180, 16]} />
        <meshBasicMaterial color="#22d3ee" />
      </mesh>
      <mesh position={[0, -185, 40]}>
        <boxGeometry args={[60, 26, 110]} />
        <meshBasicMaterial color="#155e75" />
      </mesh>
    </group>
  );
}

// Isolates the scene content (children) so a failure there never replaces the
// core Canvas. On error it hides the content and shows the emergency fallback.
class SceneChildBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error) {
    setViewport({ error: 'Scene content failed: ' + (error?.message || String(error)) });
    this.props.onError?.();
  }
  render() {
    if (this.state.error) return null;
    return this.props.children;
  }
}

function CameraController({ ortho, viewPreset }) {
  const { set, camera, size } = useThree();
  const targetPos = useRef(new THREE.Vector3(...VIEW_POSITIONS.perspective));
  const targetLook = useRef(new THREE.Vector3(...TARGET));
  const [animating, setAnimating] = useState(true);

  // (Re)create the camera when the projection mode changes. Single active camera.
  useEffect(() => {
    let cam;
    if (ortho) {
      const half = 360;
      cam = new THREE.OrthographicCamera(-half, half, half, -half, -3000, 5000);
      cam.zoom = 0.9;
    } else {
      cam = new THREE.PerspectiveCamera(42, 1, 1, 5000);
    }
    cam.position.set(...VIEW_POSITIONS.perspective);
    cam.lookAt(TARGET[0], TARGET[1], TARGET[2]);
    set({ camera: cam });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [ortho]);

  // Keep aspect in sync with the viewport size.
  useEffect(() => {
    if (!camera || !size.width || !size.height) return;
    if (camera.isPerspectiveCamera) {
      const a = size.width / size.height;
      if (Math.abs(camera.aspect - a) > 0.0001) {
        camera.aspect = a;
        camera.updateProjectionMatrix();
      }
    } else if (camera.isOrthographicCamera) {
      camera.updateProjectionMatrix();
    }
  }, [camera, size.width, size.height]);

  useEffect(() => {
    targetPos.current.set(...(VIEW_POSITIONS[viewPreset] || VIEW_POSITIONS.perspective));
    setAnimating(true);
  }, [viewPreset]);

  useFrame(() => {
    if (!camera || !animating) return;
    camera.position.lerp(targetPos.current, 0.12);
    camera.lookAt(targetLook.current);
    if (camera.position.distanceTo(targetPos.current) < 1.5) setAnimating(false);
  });

  return null;
}

// Publishes camera/gl/size/info to the shared store each frame for the DOM
// overlay (labels) and the diagnostics drawer. No re-renders triggered.
function ViewportSync() {
  const { camera, gl, size } = useThree();
  useFrame(() => {
    if (camera && size.width && size.height) {
      setViewport({
        camera,
        gl,
        width: size.width,
        height: size.height,
        ready: true,
        info: {
          calls: gl.info.render.calls,
          triangles: gl.info.render.triangles,
          geometries: gl.info.memory.geometries,
          textures: gl.info.memory.textures,
        },
      });
    }
  });
  return null;
}

function ContextGuard() {
  const gl = useThree();
  useEffect(() => {
    const el = gl.domElement;
    const onLost = (e) => {
      e.preventDefault();
      setViewport({ contextLost: true, error: 'WebGL context lost — recovering' });
    };
    const onRestored = () => {
      setViewport({ contextLost: false, error: null });
    };
    el.addEventListener('webglcontextlost', onLost);
    el.addEventListener('webglcontextrestored', onRestored);
    return () => {
      el.removeEventListener('webglcontextlost', onLost);
      el.removeEventListener('webglcontextrestored', onRestored);
    };
  }, [gl]);
  return null;
}

// Stable key: a constant. Never keyed by route/project/customer/selection.
export default function StableViewportCore({
  viewPreset = 'perspective',
  ortho = false,
  onBackgroundClick,
  children,
  onCoreFailure,
}) {
  const [coreFailed, setCoreFailed] = useState(false);

  return (
    <Canvas
      key="stable-viewport-core"
      shadows={false}
      dpr={[1, 2]}
      gl={{ antialias: true, alpha: false, powerPreference: 'high-performance' }}
      camera={{ position: VIEW_POSITIONS.perspective, fov: 42, near: 1, far: 5000 }}
      onCreated={({ gl }) => gl.setClearColor('#0b0f13')}
      onPointerMissed={() => onBackgroundClick?.()}
      style={{ width: '100%', height: '100%', display: 'block', touchAction: 'none' }}
    >
      <CameraController ortho={ortho} viewPreset={viewPreset} />
      <ViewportSync />
      <ContextGuard />
      <WorkshopLights />
      <gridHelper args={[1600, 32, '#22d3ee', '#1a2530']} position={[0, -312, 0]} />
      <axesHelper args={[60]} position={[-180, -310, -180]} />
      <NativeOrbitControls target={TARGET} minDistance={120} maxDistance={1600} />

      {/* Permanent procedural fallback — always present, basic material */}
      {coreFailed && <CoreFallbackModel />}

      <Suspense fallback={null}>
        <SceneChildBoundary
          onError={() => { setCoreFailed(true); onCoreFailure?.(); }}
        >
          {children}
        </SceneChildBoundary>
      </Suspense>
    </Canvas>
  );
}

export { VIEW_POSITIONS, TARGET, BUILD_LABEL };