import * as THREE from "three";
import {
  applyCelestialDeltas as applyCelestialDeltaUpdates,
  applyClaimDeltas as applyClaimDeltaUpdates,
  applyConstructionSiteDeltas as applyConstructionSiteDeltaUpdates,
  applyNodeDeltas as applyNodeDeltaUpdates,
  applyShipDeltas as applyShipDeltaUpdates,
  applyStationDeltas as applyStationDeltaUpdates,
  rebuildSystems as rebuildSystemScene,
  syncCelestials as syncCelestialScene,
  syncClaims as syncClaimScene,
  syncConstructionSites as syncConstructionSiteScene,
  syncNodes as syncNodeScene,
  syncShips as syncShipScene,
  syncStations as syncStationScene,
} from "./viewerSceneSync";
import {
  deriveNodeOrbital,
  deriveOrbitalFromLocalPosition,
  resolveOrbitalAnchor,
  resolvePointPosition,
} from "./viewerWorldPresentation";
import {
  createCirclePoints,
  shipLength,
  shipPresentationColor,
  shipSize,
  celestialColor,
} from "./viewerSceneAppearance";
import type {
  CelestialDelta,
  CelestialSnapshot,
  ClaimDelta,
  ClaimSnapshot,
  ConstructionSiteDelta,
  ConstructionSiteSnapshot,
  ResourceNodeDelta,
  ResourceNodeSnapshot,
  ShipDelta,
  ShipSnapshot,
  StationDelta,
  StationSnapshot,
  SystemSnapshot,
} from "./contracts";
import type { OrbitalAnchor, Selectable } from "./viewerTypes";
import { rawObject } from "./viewerScenePrimitives";

export interface ViewerSceneDataContext {
  documentRef: Document;
  getWorldOrbitalTimeSeconds: () => number | undefined;
  getOrbitalSimulationSpeed: () => number;
  getWorldSeed: () => number;
  getWorldTimeSyncMs: () => number;
  getWorldPresentationContext: () => any;
  getActiveSystemId: () => string | undefined;
  galaxySystemGroup: THREE.Group;
  systemScene: THREE.Scene;
  celestialGroup: THREE.Group;
  nodeGroup: THREE.Group;
  stationGroup: THREE.Group;
  claimGroup: THREE.Group;
  constructionSiteGroup: THREE.Group;
  shipGroup: THREE.Group;
  galaxySelectableTargets: Map<THREE.Object3D, Selectable>;
  systemSelectableTargets: Map<THREE.Object3D, Selectable>;
  systemVisuals: Map<any, any>;
  planetVisuals: any[];
  celestialVisuals: Map<any, any>;
  nodeVisuals: Map<any, any>;
  stationVisuals: Map<any, any>;
  claimVisuals: Map<any, any>;
  constructionSiteVisuals: Map<any, any>;
  shipVisuals: Map<any, any>;
}

export class ViewerSceneDataController {
  private activeSystemRootInScene: THREE.Object3D | undefined;

  constructor(private readonly context: ViewerSceneDataContext) {}

  rebuildSystems(systems: SystemSnapshot[]) {
    this.activeSystemRootInScene = undefined;
    rebuildSystemScene(this.createSceneSyncContext(), systems);
    // Re-activate the current active system if any
    const activeId = this.context.getActiveSystemId();
    if (activeId) {
      this.activateSystemRoot(activeId);
    }
  }

  syncCelestials(celestials: CelestialSnapshot[]) {
    syncCelestialScene(this.createSceneSyncContext(), celestials, this.context.getActiveSystemId());
  }

  syncNodes(nodes: ResourceNodeSnapshot[]) {
    syncNodeScene(this.createSceneSyncContext(), nodes, this.context.getActiveSystemId());
  }

  syncStations(stations: StationSnapshot[]) {
    syncStationScene(this.createSceneSyncContext(), stations, this.context.getActiveSystemId());
  }

  syncClaims(claims: ClaimSnapshot[]) {
    syncClaimScene(this.createSceneSyncContext(), claims, this.context.getActiveSystemId());
  }

  syncConstructionSites(sites: ConstructionSiteSnapshot[]) {
    syncConstructionSiteScene(this.createSceneSyncContext(), sites, this.context.getActiveSystemId());
  }

  syncShips(ships: ShipSnapshot[], tickIntervalMs: number) {
    syncShipScene(this.createSceneSyncContext(), ships, tickIntervalMs, this.context.getActiveSystemId());
  }

  applyCelestialDeltas(celestials: CelestialDelta[]) {
    applyCelestialDeltaUpdates(this.createSceneSyncContext(), celestials, this.context.getActiveSystemId());
  }

  applyNodeDeltas(nodes: ResourceNodeDelta[]) {
    applyNodeDeltaUpdates(this.createSceneSyncContext(), nodes, this.context.getActiveSystemId());
  }

  applyStationDeltas(stations: StationDelta[]) {
    applyStationDeltaUpdates(this.createSceneSyncContext(), stations, this.context.getActiveSystemId());
  }

  applyClaimDeltas(claims: ClaimDelta[]) {
    applyClaimDeltaUpdates(this.createSceneSyncContext(), claims, this.context.getActiveSystemId());
  }

  applyConstructionSiteDeltas(sites: ConstructionSiteDelta[]) {
    applyConstructionSiteDeltaUpdates(this.createSceneSyncContext(), sites, this.context.getActiveSystemId());
  }

  applyShipDeltas(ships: ShipDelta[], tickIntervalMs: number) {
    applyShipDeltaUpdates(this.createSceneSyncContext(), ships, tickIntervalMs, this.context.getActiveSystemId());
  }

  /**
   * Called when the active system changes. Swaps which system's root is in systemScene
   * and updates visibility of all system-filtered objects.
   */
  onActiveSystemChanged(oldSystemId: string | undefined, newSystemId: string | undefined) {
    // Remove old system's root from systemScene
    if (this.activeSystemRootInScene) {
      this.context.systemScene.remove(this.activeSystemRootInScene);
      this.activeSystemRootInScene = undefined;
    }

    // Add new system's root to systemScene
    if (newSystemId) {
      this.activateSystemRoot(newSystemId);
    }

    // Update visibility of all system-filtered objects
    this.updateSystemObjectVisibility(newSystemId);
  }

  private activateSystemRoot(systemId: string) {
    const visual = this.context.systemVisuals.get(systemId);
    if (!visual) {
      return;
    }
    const threeObj = rawObject(visual.systemRoot);
    this.context.systemScene.add(threeObj);
    this.activeSystemRootInScene = threeObj;
  }

  private updateSystemObjectVisibility(activeSystemId: string | undefined) {
    for (const visual of this.context.celestialVisuals.values()) {
      const isActive = visual.systemId === activeSystemId;
      visual.mesh.setVisible(isActive);
      visual.icon.setVisible(isActive);
    }
    for (const visual of this.context.nodeVisuals.values()) {
      const isActive = visual.systemId === activeSystemId;
      visual.mesh.setVisible(isActive);
      visual.icon.setVisible(isActive);
    }
    for (const visual of this.context.stationVisuals.values()) {
      const isActive = visual.systemId === activeSystemId;
      visual.mesh.setVisible(isActive);
      visual.icon.setVisible(isActive);
    }
    for (const visual of this.context.claimVisuals.values()) {
      const isActive = visual.systemId === activeSystemId;
      visual.mesh.setVisible(isActive);
      visual.icon.setVisible(isActive);
    }
    for (const visual of this.context.constructionSiteVisuals.values()) {
      const isActive = visual.systemId === activeSystemId;
      visual.mesh.setVisible(isActive);
      visual.icon.setVisible(isActive);
    }
    for (const visual of this.context.shipVisuals.values()) {
      const isActive = visual.systemId === activeSystemId;
      visual.mesh.setVisible(isActive);
      visual.icon.setVisible(isActive);
    }
  }

  createWorldPresentationContext(overrides: {
    world: any;
    activeSystemId?: string;
    cameraMode: any;
    povLevel: any;
    orbitYaw: number;
    systemCamera: THREE.PerspectiveCamera;
    systemAnchor: THREE.Vector3;
    toDisplayLocalPosition: (localPosition: THREE.Vector3) => THREE.Vector3;
    setShellReticleOpacity: (sprite: any, opacity: number) => void;
  }) {
    return {
      world: overrides.world,
      worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
      worldSeed: this.context.getWorldSeed(),
      activeSystemId: overrides.activeSystemId,
      cameraMode: overrides.cameraMode,
      povLevel: overrides.povLevel,
      orbitYaw: overrides.orbitYaw,
      camera: overrides.systemCamera,
      systemAnchor: overrides.systemAnchor,
      shipVisuals: this.context.shipVisuals,
      nodeVisuals: this.context.nodeVisuals,
      celestialVisuals: this.context.celestialVisuals,
      stationVisuals: this.context.stationVisuals,
      claimVisuals: this.context.claimVisuals,
      constructionSiteVisuals: this.context.constructionSiteVisuals,
      systemVisuals: this.context.systemVisuals,
      systemSummaryVisuals: new Map(),
      toDisplayLocalPosition: overrides.toDisplayLocalPosition,
      updateSystemDetailVisibility: () => {},
      setShellReticleOpacity: overrides.setShellReticleOpacity,
    };
  }

  private createSceneSyncContext() {
    return {
      documentRef: this.context.documentRef,
      worldOrbitalTimeSeconds: this.context.getWorldOrbitalTimeSeconds(),
      orbitalSimulationSpeed: this.context.getOrbitalSimulationSpeed(),
      worldSeed: this.context.getWorldSeed(),
      worldTimeSyncMs: this.context.getWorldTimeSyncMs(),
      galaxySystemGroup: this.context.galaxySystemGroup,
      celestialGroup: this.context.celestialGroup,
      nodeGroup: this.context.nodeGroup,
      stationGroup: this.context.stationGroup,
      claimGroup: this.context.claimGroup,
      constructionSiteGroup: this.context.constructionSiteGroup,
      shipGroup: this.context.shipGroup,
      galaxySelectableTargets: this.context.galaxySelectableTargets,
      systemSelectableTargets: this.context.systemSelectableTargets,
      systemVisuals: this.context.systemVisuals,
      planetVisuals: this.context.planetVisuals,
      celestialVisuals: this.context.celestialVisuals,
      nodeVisuals: this.context.nodeVisuals,
      stationVisuals: this.context.stationVisuals,
      claimVisuals: this.context.claimVisuals,
      constructionSiteVisuals: this.context.constructionSiteVisuals,
      shipVisuals: this.context.shipVisuals,
      shipSize,
      shipLength,
      shipPresentationColor,
      celestialColor,
      createCirclePoints,
      resolvePointPosition: (systemId: string, celestialId?: string | null) => resolvePointPosition(this.context.getWorldPresentationContext(), systemId, celestialId),
      resolveOrbitalAnchor: (systemId: string, localPosition: THREE.Vector3) => resolveOrbitalAnchor(this.context.getWorldPresentationContext(), systemId, localPosition),
      deriveNodeOrbital: (node: ResourceNodeSnapshot | ResourceNodeDelta, anchor: OrbitalAnchor) => deriveNodeOrbital(this.context.getWorldPresentationContext(), node, anchor),
      deriveOrbitalFromLocalPosition: (localPosition: THREE.Vector3, systemId: string, anchor: OrbitalAnchor) => deriveOrbitalFromLocalPosition(this.context.getWorldPresentationContext(), localPosition, systemId, anchor),
    };
  }
}
