import * as THREE from "three";
import {
  MAX_CAMERA_DISTANCE,
  MIN_CAMERA_DISTANCE,
  NAV_DISTANCE,
} from "./viewerConstants";
import { updatePanFromKeyboard } from "./viewerCamera";
import { setShellReticleOpacity } from "./viewerControls";
import { renderFrame, resizeViewer, stepCamera } from "./viewerRenderLoop";
import { updateSystemStarPresentation } from "./viewerPresentation";
import { resolveFocusedCelestialId } from "./viewerSelection";
import { describeSelectionParent } from "./viewerPanels";
import {
  createInitialNetworkStats,
  createInitialPerformanceStats,
} from "./viewerState";
import { ViewerWorldLifecycle } from "./viewerWorldLifecycle";
import { ViewerInteractionController } from "./viewerInteractionController";
import { ViewerNavigationController } from "./viewerNavigationController";
import { ViewerSceneDataController } from "./viewerSceneDataController";
import { ViewerPresentationController } from "./viewerPresentationController";
import { createViewerControllers, wireViewerEvents } from "./viewerControllerFactory";
import { createViewerRenderer } from "./runtime/rendering/createViewerRenderer";
import { disposeSceneResources } from "./runtime/rendering/disposeThreeResources";
import { ViewerRenderSurface } from "./runtime/rendering/ViewerRenderSurface";
import { toDisplayLocalPosition, getSystemCameraFocus } from "./viewerCamera";
import { UniverseLayer } from "./viewerUniverseLayer";
import { GalaxyLayer } from "./viewerGalaxyLayer";
import { SystemLayer } from "./viewerSystemLayer";
import { LocalLayer } from "./viewerLocalLayer";
import type { HistoryWindowState, ViewerHudBindings, ViewerHudState } from "./viewerHudState";
import { describeSelectable } from "./viewerSelection";
import { entityIdToSelectable, selectionToEntityId, type ViewerSelectionStore } from "./ui/stores/viewerSelection";
import { useViewerSceneStore } from "./ui/stores/viewerScene";
import { useViewerOrderContextMenuStore } from "./ui/stores/viewerOrderContextMenu";
import { viewerPinia } from "./ui/stores/pinia";
import type { FactionSnapshot } from "./contracts";
import type {
  CameraMode,
  DragMode,
  NetworkStats,
  PerformanceStats,
  Selectable,
  SystemVisual,
  WorldState,
  PovLevel,
} from "./viewerTypes";

export class ViewerAppController {
  private readonly container: HTMLElement;
  private readonly renderer = createViewerRenderer();
  private readonly renderSurface: ViewerRenderSurface;

  // ── Three independent rendering layers ───────────────────────────────────
  readonly universeLayer = new UniverseLayer();
  readonly galaxyLayer = new GalaxyLayer();
  readonly systemLayer = new SystemLayer();
  readonly localLayer = new LocalLayer();

  private readonly clock = new THREE.Clock();
  private readonly raycaster = new THREE.Raycaster();
  private readonly mouse = new THREE.Vector2();

  // ── Galaxy-space anchor ───────────────────────────────────────────────────
  private readonly galaxyAnchor = new THREE.Vector3(2200, 0, 300);
  // ── System-space anchor ───────────────────────────────────────────────────
  private readonly systemAnchor = new THREE.Vector3();

  private readonly cameraOffset = new THREE.Vector3();
  private readonly keyState = new Set<string>();

  readonly hudState: ViewerHudState;
  readonly selectionStore: ViewerSelectionStore;
  private readonly sceneStore = useViewerSceneStore(viewerPinia);
  private readonly orderContextMenuStore = useViewerOrderContextMenuStore(viewerPinia);
  private readonly historyLayerEl: HTMLDivElement;
  private readonly marqueeEl: HTMLDivElement;
  private readonly hoverLabelEl: HTMLDivElement;
  private readonly hoverConnectorLineEl: SVGLineElement;

  private world?: WorldState;
  private worldTimeSyncMs = performance.now();
  private stream?: EventSource;
  private currentStreamScopeKey = "";
  private readonly networkStats: NetworkStats = createInitialNetworkStats();
  private readonly performanceStats: PerformanceStats = createInitialPerformanceStats();

  private selectedItems: Selectable[] = [];
  private worldSignature = "";
  private povLevel: PovLevel = "system";
  private currentDistance = NAV_DISTANCE.system;
  private desiredDistance = NAV_DISTANCE.system;
  private orbitYaw = -2.3;
  private orbitPitch = 1.08;
  private cameraMode: CameraMode = "tactical";
  private dragMode?: DragMode;
  private dragPointerId?: number;
  private dragStart = new THREE.Vector2();
  private dragLast = new THREE.Vector2();
  private marqueeActive = false;
  private suppressClickSelection = false;
  private activeSystemId?: string;
  private cameraTargetShipId?: string;
  private readonly followCameraPosition = new THREE.Vector3();
  private readonly followCameraFocus = new THREE.Vector3();
  private readonly followCameraDirection = new THREE.Vector3(0, 0.16, 1);
  private readonly followCameraDesiredDirection = new THREE.Vector3(0, 0.16, 1);
  private readonly followCameraOffset = new THREE.Vector3();
  private followOrbitYaw = 0;
  private followOrbitPitch = 0.2;
  private readonly historyWindows: HistoryWindowState[] = [];
  private historyWindowCounter = 0;
  private historyWindowZCounter = 10;
  private historyWindowDragId?: string;
  private historyWindowDragPointerId?: number;
  private historyWindowDragOffset = new THREE.Vector2();
  private readonly worldLifecycle: ViewerWorldLifecycle;
  private readonly interactionController: ViewerInteractionController;
  private readonly navigationController: ViewerNavigationController;
  private readonly sceneDataController: ViewerSceneDataController;
  private readonly presentationController: ViewerPresentationController;
  private readonly disposeEventBindings: () => void;
  private readonly unsubscribeSelectionStore: () => void;

  constructor(container: HTMLElement, hud: ViewerHudBindings) {
    this.container = container;
    this.hudState = hud.state;
    this.selectionStore = hud.selectionStore;
    this.historyLayerEl = hud.historyLayerEl;
    this.marqueeEl = hud.marqueeEl;
    this.hoverLabelEl = hud.hoverLabelEl;
    this.hoverConnectorLineEl = hud.hoverConnectorLineEl;
    ({
      sceneDataController: this.sceneDataController,
      navigationController: this.navigationController,
      presentationController: this.presentationController,
      worldLifecycle: this.worldLifecycle,
      interactionController: this.interactionController,
    } = createViewerControllers(this));
    this.presentationController.initializeAmbience();
    this.unsubscribeSelectionStore = this.selectionStore.$subscribe((_mutation, state) => {
      this.syncSelectionFromStore(state.selectedEntityKind, state.selectedEntityId);
    });
    this.renderSurface = new ViewerRenderSurface({
      container: this.container,
      renderer: this.renderer,
      onFrame: () => this.render(),
      onResize: (width, height) => this.onResize(width, height),
    });
    this.disposeEventBindings = wireViewerEvents(this);
    this.updateCamera(0);
  }

  async start() {
    this.selectionStore.clearSelection();
    await this.worldLifecycle.bootstrapWorld();
    this.renderSurface.start();
  }

  dispose() {
    this.disposeEventBindings();
    this.unsubscribeSelectionStore();
    this.stream?.close();
    this.sceneStore.reset();
    this.orderContextMenuStore.close();
    this.renderSurface.dispose();
    disposeSceneResources(this.universeLayer.scene);
    disposeSceneResources(this.galaxyLayer.scene);
    disposeSceneResources(this.systemLayer.scene);
    disposeSceneResources(this.localLayer.scene);
  }

  focusSelection(selection: Selectable, cameraMode?: CameraMode) {
    this.applySelectedItems([selection], "ui");
    this.navigationController.focusOnSelection(selection);
    if (cameraMode) {
      this.interactionController.toggleCameraMode(cameraMode);
      if (selection.kind === "ship" && cameraMode === "follow") {
        this.desiredDistance = 0.00018;
      }
    }
    this.updatePanels();
    this.updateGamePanel("Live");
  }

  openHistoryWindow(selection: Selectable) {
    this.interactionController.openHistoryWindow(selection);
  }

  private refreshStreamScopeIfNeeded() {
    this.worldLifecycle.refreshStreamScopeIfNeeded();
  }

  private createWorldPresentationContext() {
    return this.sceneDataController.createWorldPresentationContext({
      world: this.world,
      activeSystemId: this.activeSystemId,
      cameraMode: this.cameraMode,
      povLevel: this.povLevel,
      orbitYaw: this.orbitYaw,
      systemCamera: this.systemLayer.camera,
      systemAnchor: this.systemAnchor,
      toDisplayLocalPosition: (localPosition) => toDisplayLocalPosition(localPosition),
      setShellReticleOpacity: (sprite, opacity) => this.setShellReticleOpacity(sprite, opacity),
    });
  }

  private rebuildFactions(_factions: FactionSnapshot[]) {
    this.worldLifecycle.rebuildFactions(_factions);
  }

  private updatePanels() {
    this.worldLifecycle.updatePanels();
  }

  private applySelectedItems(items: Selectable[], source: "viewer" | "ui") {
    this.orderContextMenuStore.close();
    this.selectedItems = items;
    if (items.length === 1) {
      const selection = items[0];
      this.selectionStore.selectSelection({
        id: selectionToEntityId(selection),
        kind: selection.kind,
        label: describeSelectable(this.world, selection),
      }, source);
      return;
    }

    this.selectionStore.clearSelection(source);
  }

  private syncSelectionFromStore(
    kind: Selectable["kind"] | null,
    entityId: string | null,
  ) {
    this.orderContextMenuStore.close();
    const selection = entityIdToSelectable(kind, entityId);
    this.selectedItems = selection ? [selection] : [];
    this.navigationController.syncFollowStateFromSelection();
    this.updatePanels();
    this.updateGamePanel("Live");
  }

  private render() {
    renderFrame({
      clock: this.clock,
      renderer: this.renderer,
      universeLayer: this.universeLayer,
      galaxyLayer: this.galaxyLayer,
      systemLayer: this.systemLayer,
      localLayer: this.localLayer,
      getPovLevel: () => this.povLevel,
      updateCamera: (delta) => this.updateCamera(delta),
      updateAmbience: (delta) => this.presentationController.updateAmbience(delta),
      updatePlanetPresentation: () => this.presentationController.updatePlanetPresentation(),
      updateShipPresentation: () => this.presentationController.updateShipPresentation(),
      updateNetworkPanel: () => this.presentationController.updateNetworkPanel(),
      applyZoomPresentation: () => this.presentationController.applyZoomPresentation(),
      recordPerformanceStats: (frameMs) => this.presentationController.recordPerformanceStats(frameMs),
      updatePerformancePanel: () => this.presentationController.updatePerformancePanel(),
    });
  }

  private computeOrbitOffset(): THREE.Vector3 {
    const horizontalDistance = this.currentDistance * Math.cos(this.orbitPitch);
    return new THREE.Vector3(
      Math.cos(this.orbitYaw) * horizontalDistance,
      this.currentDistance * Math.sin(this.orbitPitch),
      Math.sin(this.orbitYaw) * horizontalDistance,
    );
  }

  private updateCamera(delta: number) {
    const nextState = stepCamera({
      currentDistance: this.currentDistance,
      desiredDistance: this.desiredDistance,
      orbitPitch: this.orbitPitch,
      delta,
    });
    this.currentDistance = nextState.currentDistance;
    this.povLevel = nextState.povLevel;
    this.orbitPitch = nextState.orbitPitch;
    if (this.sceneStore.povLevel !== this.povLevel) {
      this.sceneStore.setViewContext(this.activeSystemId ?? null, this.povLevel);
    }
    this.navigationController.updateActiveSystem();

    if (this.cameraMode === "follow" && this.navigationController.updateFollowCamera(delta)) {
      // Follow camera directly controls systemLayer.camera in updateFollowCamera.
      // Still update galaxy camera independently.
      const orbitOffset = this.computeOrbitOffset();
      this.galaxyLayer.updateCamera(this.galaxyAnchor, orbitOffset);
      return;
    }

    this.updatePanFromKeyboard(delta);
    this.orbitPitch = THREE.MathUtils.clamp(this.orbitPitch, 0.92, 1.32);

    const orbitOffset = this.computeOrbitOffset();

    this.galaxyLayer.updateCamera(this.galaxyAnchor, orbitOffset);

    if (this.activeSystemId) {
      this.systemLayer.updateCamera(getSystemCameraFocus(this.systemAnchor), orbitOffset);
    }

    this.localLayer.updateCamera(orbitOffset);

    // Update star dot scales in galaxy scene
    updateSystemStarPresentation(
      this.galaxyLayer.systemVisuals,
      this.activeSystemId,
      this.galaxyLayer.camera,
      (sprite, opacity) => this.setShellReticleOpacity(sprite, opacity),
    );
  }

  private updatePanFromKeyboard(delta: number) {
    updatePanFromKeyboard(
      this.keyState,
      this.orbitYaw,
      this.currentDistance,
      this.povLevel,
      this.activeSystemId,
      this.systemAnchor,
      this.galaxyAnchor,
      delta,
      MIN_CAMERA_DISTANCE,
      MAX_CAMERA_DISTANCE,
    );
  }

  private updateSystemSummaries() {
    this.presentationController.updateSystemSummaries();
  }

  private renderRecentEvents(entityKind: string, entityId: string) {
    return this.presentationController.renderRecentEvents(entityKind, entityId);
  }

  private updateGamePanel(mode: string) {
    this.presentationController.updateGamePanel(mode);
  }

  private screenPointFromClient(clientX: number, clientY: number) {
    return this.presentationController.screenPointFromClient(clientX, clientY);
  }

  private refreshHistoryWindows() {
    this.interactionController.refreshHistoryWindows();
  }

  private resolveFocusedCelestialId() {
    return resolveFocusedCelestialId(this.world, this.selectedItems);
  }

  private onResize(width: number, height: number) {
    resizeViewer({
      galaxyLayer: this.galaxyLayer,
      systemLayer: this.systemLayer,
      localLayer: this.localLayer,
      width,
      height,
    });
  }

  private setShellReticleOpacity(sprite: SystemVisual["shellReticle"], opacity: number) {
    setShellReticleOpacity(sprite, opacity);
  }

  private describeSelectionParent(selection: Selectable) {
    return describeSelectionParent(this.world, selection, this.systemLayer.stationVisuals, this.systemLayer.nodeVisuals);
  }

  private toDisplayLocalPosition(localPosition: THREE.Vector3, systemId?: string) {
    return toDisplayLocalPosition(localPosition);
  }

  private updateSystemPanel() {
    this.presentationController.updateSystemPanel();
  }
}
