/* global React, THREE, ANATOMY */
const { useEffect, useRef, useState, useCallback, useMemo } = React;
const { COLORS, PARTS, buildStaticScene, buildDoorPanelsGroup } = ANATOMY;

// Camera presets
const CAMERA_PRESETS = {
  'three-quarter': { pos: [4.5, 2.6, 5.0], look: [0, 1.8, 0.3] },
  'front': { pos: [0, 2.0, 6.2], look: [0, 1.8, 0] },
  'side': { pos: [6.0, 2.6, 1.5], look: [0, 2.0, 0.8] },
  'top': { pos: [0.5, 5.8, 3.2], look: [0, 2.5, 0.8] },
};

function lerp(a, b, t) { return a + (b - a) * t; }
function easeInOutQuart(t) {
  return t < 0.5 ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2;
}

function DoorScene({
  selectedId,
  hoveredId,
  onHover,
  onSelect,
  exploded,
  showLabels,
  cameraPreset,
  theme,
}) {
  const mountRef = useRef(null);
  const stateRef = useRef(null);
  const [labels, setLabels] = useState([]); // [{id, x, y, label, visible}]
  const animRef = useRef({ explodeT: 0, targetExplode: 0, cameraT: 1, camFrom: null, camTo: null });
  const propsRef = useRef({});

  propsRef.current = { selectedId, hoveredId, exploded, showLabels, cameraPreset, theme };

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

    const scene = new THREE.Scene();
    scene.background = null; // transparent — body bg shows through

    const camera = new THREE.PerspectiveCamera(
      38,
      mount.clientWidth / mount.clientHeight,
      0.1,
      100
    );
    const preset = CAMERA_PRESETS[cameraPreset] || CAMERA_PRESETS['three-quarter'];
    camera.position.set(...preset.pos);
    camera.lookAt(new THREE.Vector3(...preset.look));

    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setPixelRatio(window.devicePixelRatio || 1);
    renderer.setSize(mount.clientWidth, mount.clientHeight);
    mount.appendChild(renderer.domElement);

    // Lights
    const ambient = new THREE.AmbientLight(0xE8EAF0, 0.55);
    scene.add(ambient);
    const key = new THREE.DirectionalLight(0xFFF8F0, 1.1);
    key.position.set(-4, 5, 3);
    scene.add(key);
    const fill = new THREE.DirectionalLight(0xC8D4E0, 0.3);
    fill.position.set(4, 2, 2);
    scene.add(fill);

    // Static scene (walls, header, shaft, hinges, right side)
    const staticGroup = buildStaticScene();
    scene.add(staticGroup);

    // Door panels (non-clickable, but visible)
    const panelsGroup = buildDoorPanelsGroup();
    panelsGroup.position.set(0, 0, 0);
    scene.add(panelsGroup);

    // Clickable parts — build a map
    const partObjects = {}; // id -> {root: Group, meshes: Mesh[], restingPos, explodedPos}
    const raycastTargets = []; // flat list of meshes with userData.partId

    for (const part of PARTS) {
      const root = new THREE.Group();
      root.position.set(...part.resting);
      if (part.rotation) root.rotation.set(...part.rotation);

      const materials = [];

      if (part.instances) {
        // Multi-instance (e.g. rollers)
        for (const [ix, iy, iz] of part.instances) {
          const instanceGroup = new THREE.Group();
          const built = part.build();
          if (built.isBufferGeometry) {
            const mat = new THREE.MeshLambertMaterial({ color: part.color });
            const m = new THREE.Mesh(built, mat);
            instanceGroup.add(m);
            materials.push({ mat, baseColor: part.color });
            m.userData.partId = part.id;
            raycastTargets.push(m);
          } else {
            // Group
            built.position.set(0, 0, 0);
            built.traverse((child) => {
              if (child.isMesh) {
                const baseColor = '#' + child.material.color.getHexString();
                materials.push({ mat: child.material, baseColor });
                child.userData.partId = part.id;
                raycastTargets.push(child);
              }
            });
            instanceGroup.add(built);
          }
          // Override instance position relative to root
          instanceGroup.position.set(ix - part.resting[0], iy - part.resting[1], iz - part.resting[2]);
          root.add(instanceGroup);
        }
      } else {
        const built = part.build();
        if (built.isBufferGeometry) {
          const mat = new THREE.MeshLambertMaterial({ color: part.color });
          const m = new THREE.Mesh(built, mat);
          m.userData.partId = part.id;
          root.add(m);
          materials.push({ mat, baseColor: part.color });
          raycastTargets.push(m);
        } else {
          built.traverse((child) => {
            if (child.isMesh) {
              const baseColor = '#' + child.material.color.getHexString();
              materials.push({ mat: child.material, baseColor });
              child.userData.partId = part.id;
              raycastTargets.push(child);
            }
          });
          root.add(built);
        }
      }

      scene.add(root);

      // Outline (for selected): a scaled backside clone
      const outlineGroup = new THREE.Group();
      outlineGroup.position.copy(root.position);
      outlineGroup.visible = false;
      partObjects[part.id] = {
        root,
        materials,
        restingPos: [...part.resting],
        explodedPos: [...part.exploded],
        part,
      };
    }

    // Raycaster
    const raycaster = new THREE.Raycaster();
    const pointer = new THREE.Vector2();
    let lastHoverId = null;

    const onPointerMove = (ev) => {
      const rect = renderer.domElement.getBoundingClientRect();
      pointer.x = ((ev.clientX - rect.left) / rect.width) * 2 - 1;
      pointer.y = -((ev.clientY - rect.top) / rect.height) * 2 + 1;
      raycaster.setFromCamera(pointer, camera);
      const hits = raycaster.intersectObjects(raycastTargets, false);
      const id = hits.length ? hits[0].object.userData.partId : null;
      if (id !== lastHoverId) {
        lastHoverId = id;
        onHover(id);
        renderer.domElement.style.cursor = id ? 'pointer' : '';
      }
    };
    const onClick = (ev) => {
      const rect = renderer.domElement.getBoundingClientRect();
      pointer.x = ((ev.clientX - rect.left) / rect.width) * 2 - 1;
      pointer.y = -((ev.clientY - rect.top) / rect.height) * 2 + 1;
      raycaster.setFromCamera(pointer, camera);
      const hits = raycaster.intersectObjects(raycastTargets, false);
      if (hits.length) {
        onSelect(hits[0].object.userData.partId);
      }
    };
    renderer.domElement.addEventListener('pointermove', onPointerMove);
    renderer.domElement.addEventListener('click', onClick);
    renderer.domElement.addEventListener('pointerleave', () => {
      if (lastHoverId !== null) { lastHoverId = null; onHover(null); }
      renderer.domElement.style.cursor = '';
    });

    const onResize = () => {
      const w = mount.clientWidth;
      const h = mount.clientHeight;
      camera.aspect = w / h;
      camera.updateProjectionMatrix();
      renderer.setSize(w, h);
    };
    window.addEventListener('resize', onResize);

    // Animation loop
    let rafId;
    const clock = new THREE.Clock();
    const projectToScreen = (vec3) => {
      const v = vec3.clone().project(camera);
      return {
        x: (v.x * 0.5 + 0.5) * mount.clientWidth,
        y: (-v.y * 0.5 + 0.5) * mount.clientHeight,
      };
    };

    const tick = () => {
      const dt = Math.min(clock.getDelta(), 0.1);
      const props = propsRef.current;
      const anim = animRef.current;

      // Explode animation
      anim.targetExplode = props.exploded ? 1 : 0;
      anim.explodeT = lerp(anim.explodeT, anim.targetExplode, 1 - Math.pow(0.001, dt));
      const eased = easeInOutQuart(anim.explodeT);
      for (const p of PARTS) {
        const o = partObjects[p.id];
        if (!o) continue;
        o.root.position.x = lerp(o.restingPos[0], o.explodedPos[0], eased);
        o.root.position.y = lerp(o.restingPos[1], o.explodedPos[1], eased);
        o.root.position.z = lerp(o.restingPos[2], o.explodedPos[2], eased);
      }

      // Material colors (hover/selected)
      for (const p of PARTS) {
        const o = partObjects[p.id];
        if (!o) continue;
        const isHover = p.id === props.hoveredId;
        const isSelected = p.id === props.selectedId;
        const targetColor = (isHover || isSelected) ? COLORS.hover : null;
        for (const { mat, baseColor } of o.materials) {
          const from = mat.color;
          const toHex = targetColor || baseColor;
          const to = new THREE.Color(toHex);
          from.lerp(to, 1 - Math.pow(0.0001, dt));
        }
      }

      // Camera preset tween
      if (anim.camTo && anim.cameraT < 1) {
        anim.cameraT = Math.min(1, anim.cameraT + dt * 1.3);
        const e = easeInOutQuart(anim.cameraT);
        camera.position.set(
          lerp(anim.camFrom.pos[0], anim.camTo.pos[0], e),
          lerp(anim.camFrom.pos[1], anim.camTo.pos[1], e),
          lerp(anim.camFrom.pos[2], anim.camTo.pos[2], e)
        );
        const look = new THREE.Vector3(
          lerp(anim.camFrom.look[0], anim.camTo.look[0], e),
          lerp(anim.camFrom.look[1], anim.camTo.look[1], e),
          lerp(anim.camFrom.look[2], anim.camTo.look[2], e)
        );
        camera.lookAt(look);
      }

      renderer.render(scene, camera);

      // Project label anchors to 2D
      if (props.showLabels !== 'off') {
        const newLabels = [];
        for (const p of PARTS) {
          const o = partObjects[p.id];
          if (!o) continue;
          const isHover = p.id === props.hoveredId;
          const isSelected = p.id === props.selectedId;
          let visible = false;
          if (props.showLabels === 'all') visible = true;
          else if (props.showLabels === 'hover') visible = isHover || isSelected;
          if (!visible) continue;
          const worldPos = new THREE.Vector3();
          o.root.getWorldPosition(worldPos);
          // Add slight offset for readability
          worldPos.y += 0.1;
          const { x, y } = projectToScreen(worldPos);
          newLabels.push({
            id: p.id,
            label: p.label,
            x, y,
            active: isHover || isSelected,
          });
        }
        // Simple anti-overlap: offset vertically
        newLabels.sort((a, b) => a.y - b.y);
        setLabels(newLabels);
      } else if (labels.length > 0) {
        setLabels([]);
      }

      rafId = requestAnimationFrame(tick);
    };
    tick();

    stateRef.current = {
      scene, camera, renderer, partObjects, animRef, projectToScreen,
    };

    return () => {
      cancelAnimationFrame(rafId);
      window.removeEventListener('resize', onResize);
      renderer.domElement.removeEventListener('pointermove', onPointerMove);
      renderer.domElement.removeEventListener('click', onClick);
      renderer.dispose();
      if (mount.contains(renderer.domElement)) mount.removeChild(renderer.domElement);
    };
  }, []); // mount once

  // Camera preset change
  useEffect(() => {
    if (!stateRef.current) return;
    const { camera } = stateRef.current;
    const to = CAMERA_PRESETS[cameraPreset] || CAMERA_PRESETS['three-quarter'];
    const anim = animRef.current;
    // Get current look target
    const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
    const currentLook = camera.position.clone().add(forward.multiplyScalar(5));
    anim.camFrom = {
      pos: [camera.position.x, camera.position.y, camera.position.z],
      look: [currentLook.x, currentLook.y, currentLook.z],
    };
    anim.camTo = to;
    anim.cameraT = 0;
  }, [cameraPreset]);

  return (
    <div className="scene-wrap">
      <div ref={mountRef} className="scene-canvas" />
      <div className="scene-labels">
        {labels.map((l) => (
          <Label3DOverlay key={l.id} {...l} />
        ))}
      </div>
    </div>
  );
}

function Label3DOverlay({ label, x, y, active }) {
  return (
    <div
      className={`scene-label${active ? ' is-active' : ''}`}
      style={{ left: x, top: y }}
    >
      <div className="scene-label__leader" />
      <div className="scene-label__chip">{label}</div>
    </div>
  );
}

window.DoorScene = DoorScene;
