import * as THREE from "three";
import {
  determineActiveSystemId,
  focusOnSelection,
  getCameraFocusWorldPosition,
  getSystemCameraFocus,
  resolveSelectionPosition,
  seedSystemFocusLocal,
  toDisplayLocalPosition,
} from "./viewerCamera";
import {
  syncFollowStateFromSelection,
  updateFollowCamera,
} from "./viewerControls";
import { computeNodeLocalPosition, resolvePointPosition } from "./viewerWorldPresentation";
import { getAnimatedShipLocalPosition, resolveShipHeading } from "./viewerPresentation";
import type {
  CameraMode,
  NodeVisual,
  PlanetVisual,
  Selectable,
  ShipVisual,
  SystemVisual,
  WorldState,
  PovLevel,
} from "./viewerTypes";

export interface ViewerNavigationContext {
  getWorld: () => WorldState | undefined;
  getWorldTimeSyncMs: () => number;
  getActiveSystemId: () => string | undefined;
  setActiveSystemId: (value: string | undefined) => void;
  onActiveSystemChanged: (oldId: string | undefined, newId: string | undefined) => void;
  getCameraMode: () => CameraMode;
  setCameraMode: (value: CameraMode) => void;
  getCameraTargetShipId: () => string | undefined;
  setCameraTargetShipId: (value: string | undefined) => void;
  getCurrentDistance: () => number;
  getPovLevel: () => PovLevel;
  getSelectedItems: () => Selectable[];
  getOrbitYaw: () => number;
  getFollowOrbitYaw: () => number;
  getFollowOrbitPitch: () => number;
  galaxyAnchor: THREE.Vector3;
  systemAnchor: THREE.Vector3;
  galaxyCamera: THREE.PerspectiveCamera;
  systemCamera: THREE.PerspectiveCamera;
  shipVisuals: Map<string, ShipVisual>;
  nodeVisuals: Map<string, NodeVisual>;
  planetVisuals: PlanetVisual[];
  systemVisuals: Map<string, SystemVisual>;
  followCameraPosition: THREE.Vector3;
  followCameraFocus: THREE.Vector3;
  followCameraDirection: THREE.Vector3;
  followCameraDesiredDirection: THREE.Vector3;
  followCameraOffset: THREE.Vector3;
  createWorldPresentationContext: () => any;
  updatePanels: () => void;
  updateGamePanel: (mode: string) => void;
}

export class ViewerNavigationController {
  constructor(private readonly context: ViewerNavigationContext) {}

  focusOnSelection(selection: Selectable) {
    focusOnSelection({
      world: this.context.getWorld(),
      selection,
      worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
      nodeVisuals: this.context.nodeVisuals,
      planetVisuals: this.context.planetVisuals,
      computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
      resolvePointPosition: (systemId, celestialId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, celestialId),
      activeSystemId: this.context.getActiveSystemId(),
      galaxyAnchor: this.context.galaxyAnchor,
      systemAnchor: this.context.systemAnchor,
    });
  }

  resolveSelectionPosition(selection: Selectable) {
    return resolveSelectionPosition({
      world: this.context.getWorld(),
      selection,
      worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
      nodeVisuals: this.context.nodeVisuals,
      planetVisuals: this.context.planetVisuals,
      computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
      resolvePointPosition: (systemId, celestialId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemId, celestialId),
    });
  }

  updateActiveSystem() {
    const nextActiveSystemId = determineActiveSystemId({
      world: this.context.getWorld(),
      cameraMode: this.context.getCameraMode(),
      cameraTargetShipId: this.context.getCameraTargetShipId(),
      currentDistance: this.context.getCurrentDistance(),
      selectedItems: this.context.getSelectedItems(),
      galaxyAnchor: this.context.galaxyAnchor,
    });
    const previousSystemId = this.context.getActiveSystemId();
    if (nextActiveSystemId === previousSystemId) {
      return;
    }

    if (nextActiveSystemId) {
      this.seedSystemFocusLocal(nextActiveSystemId);
    }

    this.context.setActiveSystemId(nextActiveSystemId);
    this.context.onActiveSystemChanged(previousSystemId, nextActiveSystemId);
    this.context.updatePanels();
    this.context.updateGamePanel("Live");
  }

  updateFollowCamera(delta: number) {
    const nextState = updateFollowCamera({
      world: this.context.getWorld(),
      cameraMode: this.context.getCameraMode(),
      cameraTargetShipId: this.context.getCameraTargetShipId(),
      shipVisuals: this.context.shipVisuals,
      currentDistance: this.context.getCurrentDistance(),
      camera: this.context.systemCamera,
      followCameraPosition: this.context.followCameraPosition,
      followCameraFocus: this.context.followCameraFocus,
      followCameraDirection: this.context.followCameraDirection,
      followCameraDesiredDirection: this.context.followCameraDesiredDirection,
      followCameraOffset: this.context.followCameraOffset,
      systemAnchor: this.context.systemAnchor,
      delta,
      followOrbitYaw: this.context.getFollowOrbitYaw(),
      followOrbitPitch: this.context.getFollowOrbitPitch(),
      getAnimatedShipLocalPosition,
      toDisplayLocalPosition: (localPosition) => toDisplayLocalPosition(localPosition),
      resolveShipHeading: (visual, worldPosition) => resolveShipHeading(visual, worldPosition, this.context.getOrbitYaw()),
    });
    this.context.setCameraMode(nextState.cameraMode);
    this.context.setCameraTargetShipId(nextState.cameraTargetShipId);
    return nextState.handled;
  }

  syncFollowStateFromSelection() {
    const nextState = syncFollowStateFromSelection(
      this.context.getSelectedItems(),
      this.context.getCameraMode(),
      this.context.getCameraTargetShipId(),
    );
    this.context.setCameraMode(nextState.cameraMode);
    this.context.setCameraTargetShipId(nextState.cameraTargetShipId);
  }

  getCameraFocusWorldPosition() {
    return getCameraFocusWorldPosition({
      galaxyAnchor: this.context.galaxyAnchor,
    });
  }

  getSystemCameraFocus() {
    return getSystemCameraFocus(this.context.systemAnchor);
  }

  seedSystemFocusLocal(systemId: string) {
    seedSystemFocusLocal({
      world: this.context.getWorld(),
      systemId,
      cameraMode: this.context.getCameraMode(),
      cameraTargetShipId: this.context.getCameraTargetShipId(),
      selectedItems: this.context.getSelectedItems(),
      systemAnchor: this.context.systemAnchor,
      worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
      nodeVisuals: this.context.nodeVisuals,
      planetVisuals: this.context.planetVisuals,
      computeNodeLocalPosition: (node, timeSeconds) => computeNodeLocalPosition(this.context.createWorldPresentationContext(), node, timeSeconds),
      resolvePointPosition: (systemIdValue, celestialId) => resolvePointPosition(this.context.createWorldPresentationContext(), systemIdValue, celestialId),
    });
  }

  toDisplayLocalPosition(localPosition: THREE.Vector3) {
    return toDisplayLocalPosition(localPosition);
  }

  /** Returns a display position for the system camera, derived from a raw local position in km. */
  toSystemDisplayPosition(localPosition: THREE.Vector3) {
    return toDisplayLocalPosition(localPosition);
  }
}
