import * as THREE from "three";
import { ACTIVE_SYSTEM_CAPTURE_RADIUS, ACTIVE_SYSTEM_DETAIL_SCALE } from "./viewerConstants";
import { DISPLAY_UNITS_PER_KILOMETER, KILOMETERS_PER_AU, computePlanetLocalPosition, currentWorldTimeSeconds, scaleGalaxyVector, scaleLocalVector, toThreeVector } from "./viewerMath";
import { resolveSelectableSystemId } from "./viewerSelection";
import type {
  NodeVisual,
  PlanetVisual,
  Selectable,
  ShipVisual,
  WorldState,
  PovLevel,
} from "./viewerTypes";

interface ResolveSelectionPositionParams {
  world: WorldState | undefined;
  selection: Selectable;
  worldTimeSyncMs: number;
  nodeVisuals: Map<string, NodeVisual>;
  planetVisuals: PlanetVisual[];
  computeNodeLocalPosition: (visual: NodeVisual, timeSeconds: number) => THREE.Vector3;
  resolvePointPosition: (systemId: string, celestialId?: string | null) => THREE.Vector3;
}

interface FocusOnSelectionParams extends ResolveSelectionPositionParams {
  activeSystemId?: string;
  galaxyAnchor: THREE.Vector3;
  systemAnchor: THREE.Vector3;
}

interface DetermineActiveSystemParams {
  world: WorldState | undefined;
  cameraMode: "tactical" | "follow";
  cameraTargetShipId?: string;
  currentDistance: number;
  selectedItems: Selectable[];
  galaxyAnchor: THREE.Vector3;
}

interface SeedSystemFocusParams {
  world: WorldState | undefined;
  systemId: string;
  cameraMode: "tactical" | "follow";
  cameraTargetShipId?: string;
  selectedItems: Selectable[];
  systemAnchor: THREE.Vector3;
  worldTimeSyncMs: number;
  nodeVisuals: Map<string, NodeVisual>;
  planetVisuals: PlanetVisual[];
  computeNodeLocalPosition: (visual: NodeVisual, timeSeconds: number) => THREE.Vector3;
  resolvePointPosition: (systemId: string, celestialId?: string | null) => THREE.Vector3;
}

interface CameraFocusParams {
  galaxyAnchor: THREE.Vector3;
}

export function getSystemCameraFocus(systemAnchor: THREE.Vector3): THREE.Vector3 {
  return systemAnchor.clone().multiplyScalar(DISPLAY_UNITS_PER_KILOMETER * ACTIVE_SYSTEM_DETAIL_SCALE);
}

export function updatePanFromKeyboard(
  keyState: Set<string>,
  orbitYaw: number,
  currentDistance: number,
  povLevel: PovLevel,
  activeSystemId: string | undefined,
  systemAnchor: THREE.Vector3,
  galaxyAnchor: THREE.Vector3,
  delta: number,
  minimumDistance: number,
  maximumDistance: number,
) {
  const move = new THREE.Vector3();
  if (keyState.has("w")) {
    move.z -= 1;
  }
  if (keyState.has("s")) {
    move.z += 1;
  }
  if (keyState.has("a")) {
    move.x += 1;
  }
  if (keyState.has("d")) {
    move.x -= 1;
  }
  if (move.lengthSq() === 0) {
    return;
  }

  move.normalize();
  const forward = new THREE.Vector3(Math.cos(orbitYaw), 0, Math.sin(orbitYaw));
  const right = new THREE.Vector3(-forward.z, 0, forward.x);
  const pan = right.multiplyScalar(move.x).add(forward.multiplyScalar(move.z));
  if (activeSystemId) {
    const speedKilometers = povLevel === "system"
      ? THREE.MathUtils.mapLinear(currentDistance, 80, 4000, KILOMETERS_PER_AU * 0.002, KILOMETERS_PER_AU * 0.35)
      : THREE.MathUtils.mapLinear(currentDistance, minimumDistance, 120, 40, 180000);
    systemAnchor.addScaledVector(pan, speedKilometers * delta);
    return;
  }

  const speed = THREE.MathUtils.mapLinear(currentDistance, minimumDistance, maximumDistance, 320, 6800);
  galaxyAnchor.addScaledVector(pan, speed * delta);
}

export function applyPanFromScreenDelta(
  delta: THREE.Vector2,
  orbitYaw: number,
  currentDistance: number,
  povLevel: PovLevel,
  activeSystemId: string | undefined,
  systemAnchor: THREE.Vector3,
  galaxyAnchor: THREE.Vector3,
  viewportWidth: number,
  viewportHeight: number,
  minimumDistance: number,
  maximumDistance: number,
) {
  const safeWidth = Math.max(viewportWidth, 1);
  const safeHeight = Math.max(viewportHeight, 1);
  const normalized = new THREE.Vector2(delta.x / safeWidth, delta.y / safeHeight);
  if (normalized.lengthSq() === 0) {
    return;
  }

  const forward = new THREE.Vector3(Math.cos(orbitYaw), 0, Math.sin(orbitYaw));
  const right = new THREE.Vector3(-forward.z, 0, forward.x);
  const pan = right.multiplyScalar(-normalized.x).add(forward.multiplyScalar(-normalized.y));

  if (activeSystemId) {
    const scale = povLevel === "system"
      ? THREE.MathUtils.mapLinear(currentDistance, 80, 4000, KILOMETERS_PER_AU * 0.35, KILOMETERS_PER_AU * 6.5)
      : THREE.MathUtils.mapLinear(currentDistance, minimumDistance, 120, 1200, 180000);
    systemAnchor.addScaledVector(pan, scale);
    return;
  }

  const galaxyScale = THREE.MathUtils.mapLinear(currentDistance, minimumDistance, maximumDistance, 1800, 22000);
  galaxyAnchor.addScaledVector(pan, galaxyScale);
}

export function determineActiveSystemId(params: DetermineActiveSystemParams): string | undefined {
  const {
    world,
    cameraMode,
    cameraTargetShipId,
    currentDistance,
    selectedItems,
    galaxyAnchor,
  } = params;

  if (!world) {
    return undefined;
  }

  if (cameraMode === "follow" && cameraTargetShipId) {
    const followedShip = world.ships.get(cameraTargetShipId);
    if (!followedShip) {
      return undefined;
    }

    return followedShip.spatialState.movementRegime === "ftl-transit"
      ? undefined
      : followedShip.systemId;
  }

  if (currentDistance >= 12000) {
    return undefined;
  }

  const selected = selectedItems[0];
  if (selected && selectedItems.length === 1) {
    if (selected.kind === "system") {
      return selected.id;
    }
    if (selected.kind === "planet") {
      return selected.systemId;
    }

    const selectedSystemId = resolveSelectableSystemId(world, selected);
    if (selectedSystemId) {
      return selectedSystemId;
    }
  }

  let nearestSystemId: string | undefined;
  let nearestDistance = Number.POSITIVE_INFINITY;
  for (const system of world.systems.values()) {
    const center = scaleGalaxyVector(toThreeVector(system.galaxyPosition));
    const distance = center.distanceTo(galaxyAnchor);
    if (distance < nearestDistance) {
      nearestDistance = distance;
      nearestSystemId = system.id;
    }
  }

  return nearestDistance <= Math.max(ACTIVE_SYSTEM_CAPTURE_RADIUS, currentDistance * 2.2)
    ? nearestSystemId
    : undefined;
}

export function resolveSelectionPosition(params: ResolveSelectionPositionParams): THREE.Vector3 | undefined {
  const {
    world,
    selection,
    worldTimeSyncMs,
    nodeVisuals,
    planetVisuals,
    computeNodeLocalPosition,
    resolvePointPosition,
  } = params;

  if (!world) {
    return undefined;
  }

  if (selection.kind === "ship") {
    const ship = world.ships.get(selection.id);
    return ship ? toThreeVector(ship.localPosition) : undefined;
  }
  if (selection.kind === "station") {
    const station = world.stations.get(selection.id);
    return station ? toThreeVector(station.localPosition) : undefined;
  }
  if (selection.kind === "node") {
    const node = world.nodes.get(selection.id);
    const visual = node ? nodeVisuals.get(node.id) : undefined;
    return visual
      ? computeNodeLocalPosition(visual, currentWorldTimeSeconds(world, worldTimeSyncMs))
      : (node ? toThreeVector(node.localPosition) : undefined);
  }
  if (selection.kind === "celestial") {
    const celestial = world.celestials.get(selection.id);
    return celestial ? toThreeVector(celestial.orbitalAnchor) : undefined;
  }
  if (selection.kind === "claim") {
    const claim = world.claims.get(selection.id);
    return claim ? resolvePointPosition(claim.systemId, claim.celestialId) : undefined;
  }
  if (selection.kind === "construction-site") {
    const site = world.constructionSites.get(selection.id);
    return site ? resolvePointPosition(site.systemId, site.celestialId) : undefined;
  }
  if (selection.kind === "planet") {
    const system = world.systems.get(selection.systemId);
    const planet = system?.planets[selection.planetIndex];
    if (!system || !planet) {
      return undefined;
    }

    return computePlanetLocalPosition(planet, currentWorldTimeSeconds(world, worldTimeSyncMs));
  }

  if (selection.kind === "moon") {
    const system = world.systems.get(selection.systemId);
    const planet = system?.planets[selection.planetIndex];
    return planet ? computePlanetLocalPosition(planet, currentWorldTimeSeconds(world, worldTimeSyncMs)) : undefined;
  }

  const system = world.systems.get(selection.id);
  return system ? scaleGalaxyVector(toThreeVector(system.galaxyPosition)) : undefined;
}

export function focusOnSelection(params: FocusOnSelectionParams) {
  const {
    world,
    selection,
    activeSystemId,
    galaxyAnchor,
    systemAnchor,
  } = params;

  const nextFocus = resolveSelectionPosition(params);
  if (!nextFocus) {
    return;
  }

  if (selection.kind === "system") {
    galaxyAnchor.copy(nextFocus);
    systemAnchor.set(0, 0, 0);
    return;
  }

  const selectionSystemId = resolveSelectableSystemId(world, selection);
  if (selectionSystemId && world) {
    const system = world.systems.get(selectionSystemId);
    if (system) {
      galaxyAnchor.copy(scaleGalaxyVector(toThreeVector(system.galaxyPosition)));
      systemAnchor.copy(nextFocus);
      return;
    }
  }

  if (activeSystemId && resolveSelectableSystemId(world, selection) === activeSystemId) {
    systemAnchor.copy(nextFocus);
    return;
  }

  galaxyAnchor.copy(nextFocus);
}

export function seedSystemFocusLocal(params: SeedSystemFocusParams) {
  const {
    world,
    systemId,
    cameraMode,
    cameraTargetShipId,
    selectedItems,
    systemAnchor,
  } = params;

  if (!world) {
    return;
  }

  if (cameraMode === "follow" && cameraTargetShipId) {
    const followedShip = world.ships.get(cameraTargetShipId);
    if (followedShip?.systemId === systemId) {
      systemAnchor.copy(toThreeVector(followedShip.localPosition));
      return;
    }
  }

  const selected = selectedItems[0];
  if (selected && resolveSelectableSystemId(world, selected) === systemId) {
    if (selected.kind === "system") {
      systemAnchor.set(0, 0, 0);
      return;
    }

    const selectedPosition = resolveSelectionPosition({
      world,
      selection: selected,
      worldTimeSyncMs: params.worldTimeSyncMs,
      nodeVisuals: params.nodeVisuals,
      planetVisuals: params.planetVisuals,
      computeNodeLocalPosition: params.computeNodeLocalPosition,
      resolvePointPosition: params.resolvePointPosition,
    });
    if (selectedPosition) {
      systemAnchor.copy(selectedPosition);
      return;
    }
  }

  systemAnchor.set(0, 0, 0);
}

export function getCameraFocusWorldPosition(params: CameraFocusParams): THREE.Vector3 {
  return params.galaxyAnchor;
}

/**
 * Convert a local km position to system-scene display coordinates.
 * System scene coordinate system: star at origin, all positions scaled by
 * DISPLAY_UNITS_PER_KILOMETER * ACTIVE_SYSTEM_DETAIL_SCALE.
 */
export function toDisplayLocalPosition(localPosition: THREE.Vector3): THREE.Vector3 {
  return localPosition.clone().multiplyScalar(DISPLAY_UNITS_PER_KILOMETER * ACTIVE_SYSTEM_DETAIL_SCALE);
}
