import { useMemo, useRef } from 'react';
import { useFrame, useThree } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
import type { Point } from '@house-plan-maker/shared';
import { boundingBox } from '../utils/geometry';
import styles from './camera-view-cube.module.css';

export interface CameraOrientation {
  /** Rotation around the world Y axis, in degrees. 0 = looking south (toward +Z). */
  readonly yawDeg: number;
  /** Rotation around the world X axis, in degrees. Negative = looking down. */
  readonly pitchDeg: number;
}

/**
 * CameraOrientationTracker reports the live camera direction (yaw + pitch)
 * to the parent so the view-cube widget can mirror it. Uses useFrame so we
 * pick up OrbitControls damping without subscribing to its onChange event.
 */
export function CameraOrientationTracker({
  onChange,
}: {
  readonly onChange: (orientation: CameraOrientation) => void;
}) {
  const { camera, controls } = useThree() as unknown as {
    camera: import('three').Camera;
    controls?: { target?: { x: number; y: number; z: number } };
  };
  const lastReportRef = useRef<CameraOrientation>({ yawDeg: NaN, pitchDeg: NaN });

  useFrame(() => {
    const target = controls?.target;
    const tx = target?.x ?? 0;
    const ty = target?.y ?? 0;
    const tz = target?.z ?? 0;
    const dx = tx - camera.position.x;
    const dy = ty - camera.position.y;
    const dz = tz - camera.position.z;
    const len = Math.hypot(dx, dy, dz);
    if (len < 1e-6) return;

    const nx = dx / len;
    const ny = dy / len;
    const nz = dz / len;

    // Yaw: rotation around world Y. atan2(x, z) gives the heading angle.
    const yawDeg = (Math.atan2(nx, nz) * 180) / Math.PI;
    // Pitch: arcsin of vertical component. Negative when looking down.
    const pitchDeg = (Math.asin(Math.max(-1, Math.min(1, ny))) * 180) / Math.PI;

    const last = lastReportRef.current;
    if (
      Number.isNaN(last.yawDeg) ||
      Math.abs(yawDeg - last.yawDeg) > 0.5 ||
      Math.abs(pitchDeg - last.pitchDeg) > 0.5
    ) {
      lastReportRef.current = { yawDeg, pitchDeg };
      onChange({ yawDeg, pitchDeg });
    }
  });

  return null;
}

interface CameraControlsProps {
  readonly shape: readonly Point[];
  readonly wallHeight: number;
}

export type CameraPreset =
  | 'birds-eye'
  | 'birds-eye-export'
  | 'eye-level'
  | 'corner-ne'
  | 'corner-nw'
  | 'corner-se'
  | 'corner-sw';

interface PresetConfig {
  readonly position: [number, number, number];
  readonly target: [number, number, number];
}

function computePresets(
  shape: readonly Point[],
  wallHeight: number,
): Record<CameraPreset, PresetConfig> {
  const bb = boundingBox(shape);
  const centerX = (bb.minX + bb.maxX) / 2;
  const centerZ = (bb.minY + bb.maxY) / 2;
  const centerY = wallHeight / 2;

  const sizeX = bb.maxX - bb.minX;
  const sizeZ = bb.maxY - bb.minY;
  const maxSize = Math.max(sizeX, sizeZ, 2);
  const dist = maxSize * 1.5;

  const target: [number, number, number] = [centerX, centerY, centerZ];
  const floorTarget: [number, number, number] = [centerX, 0, centerZ];

  // Compute the camera height for the export-only top-down preset. Three.js
  // PerspectiveCamera.fov is VERTICAL FOV, so we derive the horizontal half-
  // angle from (tanV · aspect). Walls extend up from the floor toward the
  // camera, so the frustum must fit the room at the WALL-TOP plane (closer
  // to the camera) — fitting at floor level lets wall edges overflow into
  // the frame. Canvas is rendered at 4:3 for the off-screen export pass.
  const exportAspect = 4 / 3;
  const tanV = Math.tan((50 / 2) * (Math.PI / 180));
  const tanH = tanV * exportAspect;
  const dTop = Math.max(sizeX / (2 * tanH), sizeZ / (2 * tanV));
  const exportDist = Math.max(wallHeight + dTop * 1.15, wallHeight + 1);

  return {
    'birds-eye': {
      position: [centerX, dist * 1.5, centerZ + 0.01],
      target: floorTarget,
    },
    'birds-eye-export': {
      position: [centerX, exportDist, centerZ + 0.01],
      target: floorTarget,
    },
    'eye-level': {
      position: [centerX - dist, 1.6, centerZ],
      target: [centerX, 1.6, centerZ],
    },
    'corner-ne': {
      position: [bb.maxX + dist * 0.6, wallHeight + dist * 0.4, bb.minY - dist * 0.6],
      target,
    },
    'corner-nw': {
      position: [bb.minX - dist * 0.6, wallHeight + dist * 0.4, bb.minY - dist * 0.6],
      target,
    },
    'corner-se': {
      position: [bb.maxX + dist * 0.6, wallHeight + dist * 0.4, bb.maxY + dist * 0.6],
      target,
    },
    'corner-sw': {
      position: [bb.minX - dist * 0.6, wallHeight + dist * 0.4, bb.maxY + dist * 0.6],
      target,
    },
  };
}

export function CameraPresetsUI({
  shape: _shape,
  wallHeight: _wallHeight,
  orientation,
  onPreset,
}: {
  readonly shape: readonly Point[];
  readonly wallHeight: number;
  readonly orientation?: CameraOrientation;
  readonly onPreset: (preset: CameraPreset) => void;
}) {
  // A small CSS-3D "view cube" widget. Top face = bird's eye, front face =
  // eye level, and the four corner pins map to the corner presets.
  // The cube rotates live with the Three.js camera so it doubles as an
  // orientation indicator.
  //
  // Mapping: a camera looking down at the scene gives pitch ≈ -90° and we
  // want the cube to show its top. The cube's `rotateX` flips the top face
  // toward the viewer when positive, so we use -pitch. Yaw maps directly so
  // turning the camera right rotates the cube right.
  const cubeRotateX = orientation ? -orientation.pitchDeg : -26;
  const cubeRotateY = orientation ? orientation.yawDeg : -32;
  const cubeStyle = {
    transform: `rotateX(${cubeRotateX}deg) rotateY(${cubeRotateY}deg)`,
  } as const;

  return (
    <div className={styles.wrapper} aria-label="Camera view selector">
      <div className={styles.scene}>
        <div className={styles.cube} style={cubeStyle}>
          <button
            type="button"
            className={`${styles.face} ${styles.faceTop}`}
            onClick={() => onPreset('birds-eye')}
            title="Bird's Eye"
          >
            Top
          </button>
          <button
            type="button"
            className={`${styles.face} ${styles.faceFront}`}
            onClick={() => onPreset('eye-level')}
            title="Eye Level"
          >
            Eye
          </button>
          <div className={`${styles.face} ${styles.faceBack}`} />
          <div className={`${styles.face} ${styles.faceRight}`} />
          <div className={`${styles.face} ${styles.faceLeft}`} />
          <div className={`${styles.face} ${styles.faceBottom}`} />
        </div>
      </div>

      {/* Corner pins live outside .scene so they're never sucked into the
          cube's 3D perspective transform. */}
      <div className={styles.pins}>
        <button
          type="button"
          className={`${styles.corner} ${styles.cornerNW}`}
          onClick={() => onPreset('corner-nw')}
          title="NW Corner"
          aria-label="NW Corner"
        >
          NW
        </button>
        <button
          type="button"
          className={`${styles.corner} ${styles.cornerNE}`}
          onClick={() => onPreset('corner-ne')}
          title="NE Corner"
          aria-label="NE Corner"
        >
          NE
        </button>
        <button
          type="button"
          className={`${styles.corner} ${styles.cornerSW}`}
          onClick={() => onPreset('corner-sw')}
          title="SW Corner"
          aria-label="SW Corner"
        >
          SW
        </button>
        <button
          type="button"
          className={`${styles.corner} ${styles.cornerSE}`}
          onClick={() => onPreset('corner-se')}
          title="SE Corner"
          aria-label="SE Corner"
        >
          SE
        </button>
      </div>
      <div className={styles.label}>View</div>
    </div>
  );
}

export function SceneCamera({ shape, wallHeight, activePreset }: CameraControlsProps & { readonly activePreset: CameraPreset | null }) {
  const { camera } = useThree();
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const controlsRef = useRef<any>(null);

  const presets = useMemo(
    () => computePresets(shape, wallHeight),
    [shape, wallHeight],
  );

  // Apply preset when it changes
  const lastApplied = useRef<CameraPreset | null>(null);

  if (activePreset && activePreset !== lastApplied.current) {
    lastApplied.current = activePreset;
    const preset = presets[activePreset];
    if (preset) {
      camera.position.set(...preset.position);
      camera.lookAt(...preset.target);
      if (controlsRef.current) {
        controlsRef.current.target.set(...preset.target);
        controlsRef.current.update();
      }
    }
  }

  // Set camera to bird's eye on every mount
  const initialized = useRef(false);
  if (!initialized.current && !activePreset) {
    initialized.current = true;
    const defaultPreset = presets['birds-eye'];
    camera.position.set(...defaultPreset.position);
    camera.lookAt(...defaultPreset.target);
    camera.updateProjectionMatrix();
  }

  return (
    <OrbitControls
      ref={controlsRef}
      makeDefault
      enableDamping
      dampingFactor={0.1}
      minDistance={0.5}
      maxDistance={50}
      maxPolarAngle={Math.PI * 0.9}
      target={presets['birds-eye'].target}
    />
  );
}
