import * as THREE from "three";
import { MAX_CAMERA_DISTANCE, MIN_CAMERA_DISTANCE, NAV_DISTANCE } from "./viewerConstants";
import { scaleGalaxyVector, toThreeVector } from "./viewerMath";
import { rawObject } from "./viewerScenePrimitives";
import { resolveShipWorldPosition } from "./viewerWorldPresentation";
import type { StatsOverlayMode } from "./viewerHudState";
import type {
  CameraMode,
  Selectable,
  ShipVisual,
  SystemVisual,
  WorldState,
} from "./viewerTypes";

export function syncFollowStateFromSelection(
  selectedItems: Selectable[],
  cameraMode: CameraMode,
  cameraTargetShipId?: string,
) {
  if (selectedItems.length === 1 && selectedItems[0].kind === "ship") {
    return {
      cameraMode,
      cameraTargetShipId: selectedItems[0].id,
    };
  }

  return {
    cameraMode: cameraMode === "follow" ? "tactical" : cameraMode,
    cameraTargetShipId: undefined,
  };
}

export function toggleCameraMode(params: {
  cameraMode: CameraMode;
  cameraTargetShipId?: string;
  selectedItems: Selectable[];
  desiredDistance: number;
  followCameraPosition: THREE.Vector3;
  followCameraFocus: THREE.Vector3;
  forceMode?: CameraMode;
}) {
  const {
    cameraMode,
    cameraTargetShipId,
    selectedItems,
    desiredDistance,
    followCameraPosition,
    followCameraFocus,
    forceMode,
  } = params;

  const nextMode = forceMode ?? (cameraMode === "follow" ? "tactical" : "follow");
  if (nextMode === "tactical") {
    return {
      cameraMode: "tactical" as const,
      cameraTargetShipId,
      desiredDistance,
    };
  }

  const nextTargetShipId = cameraTargetShipId
    ?? (selectedItems.length === 1 && selectedItems[0].kind === "ship" ? selectedItems[0].id : undefined);
  if (!nextTargetShipId) {
    return {
      cameraMode,
      cameraTargetShipId,
      desiredDistance,
    };
  }

  followCameraPosition.set(0, 0, 0);
  followCameraFocus.set(0, 0, 0);
  return {
    cameraMode: "follow" as const,
    cameraTargetShipId: nextTargetShipId,
    desiredDistance: Math.min(desiredDistance, 0.0012),
  };
}

export function updateFollowCamera(params: {
  world: WorldState | undefined;
  cameraMode: CameraMode;
  cameraTargetShipId?: string;
  shipVisuals: Map<string, ShipVisual>;
  currentDistance: number;
  camera: THREE.PerspectiveCamera;
  followCameraPosition: THREE.Vector3;
  followCameraFocus: THREE.Vector3;
  followCameraDirection: THREE.Vector3;
  followCameraDesiredDirection: THREE.Vector3;
  followCameraOffset: THREE.Vector3;
  systemAnchor: THREE.Vector3;
  delta: number;
  followOrbitYaw: number;
  followOrbitPitch: number;
  getAnimatedShipLocalPosition: (visual: ShipVisual) => THREE.Vector3;
  toDisplayLocalPosition: (localPosition: THREE.Vector3, systemId?: string) => THREE.Vector3;
  resolveShipHeading: (visual: ShipVisual, worldPosition: THREE.Vector3) => THREE.Vector3;
}) {
  const {
    world,
    cameraTargetShipId,
    shipVisuals,
    currentDistance,
    camera,
    followCameraPosition,
    followCameraFocus,
    followCameraDirection,
    followCameraDesiredDirection,
    followCameraOffset,
    systemAnchor,
    delta,
    followOrbitYaw,
    followOrbitPitch,
    getAnimatedShipLocalPosition,
    toDisplayLocalPosition,
    resolveShipHeading,
  } = params;

  if (!cameraTargetShipId || !world) {
    return {
      handled: false,
      cameraMode: "tactical" as const,
      cameraTargetShipId,
    };
  }

  const ship = world.ships.get(cameraTargetShipId);
  const visual = shipVisuals.get(cameraTargetShipId);
  if (!ship || !visual) {
    return {
      handled: false,
      cameraMode: "tactical" as const,
      cameraTargetShipId: undefined,
    };
  }

  const shipLocalPosition = getAnimatedShipLocalPosition(visual);
  const shipWorldPosition = resolveShipWorldPosition(
    {
      world,
      toDisplayLocalPosition,
    },
    ship,
    visual,
    shipLocalPosition,
  );

  if (ship.spatialState.movementRegime === "ftl-transit") {
    systemAnchor.set(0, 0, 0);
    const destinationNodeId = ship.spatialState.transit?.destinationNodeId;
    const destinationCelestial = destinationNodeId ? world.celestials.get(destinationNodeId) : undefined;
    const destinationSystem = destinationCelestial ? world.systems.get(destinationCelestial.systemId) : undefined;
    const originSystem = world.systems.get(ship.systemId);
    if (originSystem && destinationSystem) {
      followCameraDesiredDirection
        .copy(scaleGalaxyVector(toThreeVector(destinationSystem.galaxyPosition)).sub(scaleGalaxyVector(toThreeVector(originSystem.galaxyPosition))))
        .normalize();
    }
  } else {
    systemAnchor.lerp(shipLocalPosition, 1 - Math.exp(-delta * 8));
    followCameraDesiredDirection.copy(resolveShipHeading(visual, shipLocalPosition)).normalize();
  }

  followCameraDirection.lerp(followCameraDesiredDirection, 1 - Math.exp(-delta * 5));
  followCameraDirection.normalize();

  const distance = THREE.MathUtils.clamp(currentDistance * 0.72, 0.00018, 0.012);
  const height = THREE.MathUtils.clamp(distance * 0.14, 0.00002, 0.0012);
  const lookAhead = THREE.MathUtils.clamp(distance * 2.6, 0.0006, 0.028);

  // Orbit the camera around the ship using followOrbitYaw/Pitch.
  // Base direction is "behind ship" (negate heading). Yaw rotates left/right, pitch elevates.
  const baseBack = followCameraDirection.clone().negate();
  const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), followOrbitYaw);
  const orbitBack = baseBack.clone().applyQuaternion(yawQuat);
  const cosP = Math.cos(followOrbitPitch), sinP = Math.sin(followOrbitPitch);
  followCameraOffset.set(orbitBack.x * cosP, sinP, orbitBack.z * cosP).normalize().multiplyScalar(distance);
  followCameraOffset.y += height;

  const desiredPosition = shipWorldPosition.clone().add(followCameraOffset);
  // Blend look-ahead based on how far off-axis the orbit is (full ahead when behind, ship center when in front)
  const lookBlend = Math.max(0, Math.cos(followOrbitYaw));
  const desiredFocus = shipWorldPosition.clone().addScaledVector(followCameraDirection, lookAhead * lookBlend);
  desiredFocus.y += height * 0.28;

  const positionLerp = 1 - Math.exp(-delta * 6);
  const focusLerp = 1 - Math.exp(-delta * 8);
  if (followCameraPosition.lengthSq() === 0) {
    followCameraPosition.copy(desiredPosition);
    followCameraFocus.copy(desiredFocus);
  } else {
    followCameraPosition.lerp(desiredPosition, positionLerp);
    followCameraFocus.lerp(desiredFocus, focusLerp);
  }

  camera.position.copy(followCameraPosition);
  camera.lookAt(followCameraFocus);
  return {
    handled: true,
    cameraMode: "follow" as const,
    cameraTargetShipId,
  };
}

export function setShellReticleOpacity(sprite: SystemVisual["shellReticle"], opacity: number) {
  sprite.setVisible(opacity > 0.02);
  const material = (rawObject(sprite) as THREE.Sprite).material;
  material.opacity = opacity;
  material.needsUpdate = true;
}

export function navigateFromWheel(desiredDistance: number, deltaY: number) {
  const clampedDelta = THREE.MathUtils.clamp(deltaY, -180, 180);
  const zoomFactor = Math.exp(clampedDelta * 0.00135);
  return THREE.MathUtils.clamp(desiredDistance * zoomFactor, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
}

export function applyKeyboardControl(params: {
  keyState: Set<string>;
  cameraMode: CameraMode;
  desiredDistance: number;
  key: string;
}) {
  const { keyState, key } = params;
  keyState.add(key);

  let cameraMode = params.cameraMode;
  let desiredDistance = params.desiredDistance;

  if (["w", "a", "s", "d"].includes(key)) {
    cameraMode = "tactical";
  }
  if (key === "1") {
    desiredDistance = NAV_DISTANCE.local;
  } else if (key === "2") {
    desiredDistance = NAV_DISTANCE.system;
  } else if (key === "3") {
    desiredDistance = NAV_DISTANCE.galaxy;
  } else if (key === "=") {
    desiredDistance = desiredDistance <= NAV_DISTANCE.system
      ? NAV_DISTANCE.local
      : NAV_DISTANCE.system;
  } else if (key === "-") {
    desiredDistance = desiredDistance >= NAV_DISTANCE.system
      ? NAV_DISTANCE.galaxy
      : NAV_DISTANCE.system;
  } else if (key === "/") {
    desiredDistance = NAV_DISTANCE.system;
  }

  return { cameraMode, desiredDistance };
}

export function cycleStatsOverlayMode(current: StatsOverlayMode): StatsOverlayMode {
  switch (current) {
    case "hidden":
      return "compact";
    case "compact":
      return "status";
    case "status":
      return "network";
    case "network":
      return "performance";
    case "performance":
      return "full";
    default:
      return "hidden";
  }
}
