import { fetchWorldSnapshot, openWorldStream } from "./api";
import type { ViewerHudState } from "./viewerHudState";
import { buildOpsStripState } from "./viewerOpsStrip";
import { useGmStore } from "./ui/stores/gmStore";
import { usePlayerFactionStore } from "./ui/stores/playerFactionStore";
import { viewerPinia } from "./ui/stores/pinia";
import { buildDetailPanelState } from "./viewerPanels";
import { applyDeltaToWorld, cloneFactions, createWorldState, recordDeltaStats } from "./viewerState";
import type {
  CelestialDelta,
  CelestialSnapshot,
  ClaimDelta,
  ClaimSnapshot,
  ConstructionSiteDelta,
  ConstructionSiteSnapshot,
  FactionSnapshot,
  ResourceNodeDelta,
  ResourceNodeSnapshot,
  ShipDelta,
  ShipSnapshot,
  StationDelta,
  StationSnapshot,
  SystemSnapshot,
  WorldDelta,
  WorldSnapshot,
} from "./contracts";
import type {
  CameraMode,
  NetworkStats,
  Selectable,
  WorldState,
  PovLevel,
} from "./viewerTypes";

export interface ViewerWorldLifecycleContext {
  getWorld: () => WorldState | undefined;
  setWorld: (world: WorldState | undefined) => void;
  getWorldTimeSyncMs: () => number;
  setWorldTimeSyncMs: (value: number) => void;
  getWorldSignature: () => string;
  setWorldSignature: (value: string) => void;
  getStream: () => EventSource | undefined;
  setStream: (stream: EventSource | undefined) => void;
  getCurrentStreamScopeKey: () => string;
  setCurrentStreamScopeKey: (value: string) => void;
  getPovLevel: () => PovLevel;
  getActiveSystemId: () => string | undefined;
  getSelectedItems: () => Selectable[];
  getCameraMode: () => CameraMode;
  getCameraTargetShipId: () => string | undefined;
  getNetworkStats: () => NetworkStats;
  getSystemSummaryVisuals: () => Map<string, unknown>;
  hudState: ViewerHudState;
  worldLabel: () => string;
  rebuildSystems: (systems: SystemSnapshot[]) => void;
  syncCelestials: (celestials: CelestialSnapshot[]) => void;
  syncNodes: (nodes: ResourceNodeSnapshot[]) => void;
  syncStations: (stations: StationSnapshot[]) => void;
  syncClaims: (claims: ClaimSnapshot[]) => void;
  syncConstructionSites: (sites: ConstructionSiteSnapshot[]) => void;
  syncShips: (ships: ShipSnapshot[], tickIntervalMs: number) => void;
  applyCelestialDeltas: (celestials: CelestialDelta[]) => void;
  applyNodeDeltas: (nodes: ResourceNodeDelta[]) => void;
  applyStationDeltas: (stations: StationDelta[]) => void;
  applyClaimDeltas: (claims: ClaimDelta[]) => void;
  applyConstructionSiteDeltas: (sites: ConstructionSiteDelta[]) => void;
  applyShipDeltas: (ships: ShipDelta[], tickIntervalMs: number) => void;
  refreshHistoryWindows: () => void;
  resolveFocusedCelestialId: () => string | undefined;
  updateSystemSummaries: () => void;
  applyZoomPresentation: () => void;
  updateNetworkPanel: () => void;
  updateSystemPanel: () => void;
  updateGamePanel: (mode: string) => void;
  describeSelectionParent: (selection: Selectable) => string;
}

export class ViewerWorldLifecycle {
  constructor(private readonly context: ViewerWorldLifecycleContext) {}

  async bootstrapWorld() {
    try {
      const snapshot = await fetchWorldSnapshot();
      this.context.setWorld(createWorldState(snapshot));
      this.context.getNetworkStats().snapshotBytes = new Blob([JSON.stringify(snapshot)]).size;
      this.context.updateGamePanel("Bootstrapped");
      this.context.hudState.error.hidden = true;
      this.context.hudState.error.message = "";
      this.applySnapshot(snapshot);
      this.openDeltaStream(snapshot.sequence);
      this.updatePanels();
    } catch (error) {
      this.context.updateGamePanel("Backend offline");
      this.context.hudState.error.hidden = false;
      this.context.hudState.error.message = error instanceof Error ? error.message : "Unable to bootstrap the backend snapshot.";
    }
  }

  openDeltaStream(afterSequence: number) {
    this.context.getStream()?.close();
    const scope = this.getPreferredStreamScope();
    this.context.setCurrentStreamScopeKey(JSON.stringify(scope));
    this.context.setStream(openWorldStream(afterSequence, {
      onOpen: () => {
        const networkStats = this.context.getNetworkStats();
        networkStats.streamConnected = true;
        networkStats.streamOpenedAtMs = performance.now();
        this.context.updateGamePanel("Stream live");
        this.context.updateNetworkPanel();
      },
      onError: () => {
        this.context.getNetworkStats().streamConnected = false;
        this.context.updateGamePanel("Stream reconnecting");
        this.context.updateNetworkPanel();
      },
      onDelta: (delta, rawBytes) => {
        void this.handleDelta(delta, rawBytes);
      },
    }, scope));
  }

  async handleDelta(delta: WorldDelta, rawBytes: number) {
    if (!this.context.getWorld()) {
      return;
    }

    if (delta.requiresSnapshotRefresh) {
      await this.bootstrapWorld();
      return;
    }

    this.applyDelta(delta);
    recordDeltaStats(this.context.getNetworkStats(), delta, rawBytes);
    this.context.updateGamePanel("Live");
    this.updatePanels();
    this.context.updateNetworkPanel();
    this.refreshStreamScopeIfNeeded();
  }

  refreshStreamScopeIfNeeded() {
    const world = this.context.getWorld();
    if (!world) {
      return;
    }

    const nextScopeKey = JSON.stringify(this.getPreferredStreamScope());
    if (nextScopeKey === this.context.getCurrentStreamScopeKey()) {
      return;
    }

    this.openDeltaStream(world.sequence);
  }

  applySnapshot(snapshot: WorldSnapshot) {
    this.context.setWorldTimeSyncMs(performance.now());
    const signature = `${snapshot.seed}|${snapshot.systems.length}`;
    if (signature !== this.context.getWorldSignature()) {
      this.context.setWorldSignature(signature);
      this.context.rebuildSystems(snapshot.systems);
    }

    this.context.syncCelestials(snapshot.celestials);
    this.context.syncNodes(snapshot.nodes);
    this.context.syncStations(snapshot.stations);
    this.context.syncClaims(snapshot.claims);
    this.context.syncConstructionSites(snapshot.constructionSites);
    this.context.syncShips(snapshot.ships, snapshot.tickIntervalMs);
    this.rebuildFactions(snapshot.factions);
    this.context.updateSystemSummaries();
    this.context.applyZoomPresentation();
    this.context.updateNetworkPanel();
  }

  applyDelta(delta: WorldDelta) {
    const world = this.context.getWorld();
    if (!world) {
      return;
    }

    this.context.setWorldTimeSyncMs(performance.now());
    applyDeltaToWorld(world, delta);
    this.context.applyCelestialDeltas(delta.celestials);
    this.context.applyNodeDeltas(delta.nodes);
    this.context.applyStationDeltas(delta.stations);
    this.context.applyClaimDeltas(delta.claims);
    this.context.applyConstructionSiteDeltas(delta.constructionSites);
    this.context.applyShipDeltas(delta.ships, delta.tickIntervalMs);
    this.rebuildFactions(cloneFactions(world));
    this.context.updateSystemSummaries();
  }

  rebuildFactions(_factions: FactionSnapshot[]) {
    this.context.hudState.opsStrip = buildOpsStripState(
      this.context.getWorld(),
      this.context.getSelectedItems(),
      this.context.getCameraMode(),
      this.context.getCameraTargetShipId(),
      this.context.getPovLevel(),
      this.context.getActiveSystemId(),
    );

    const world = this.context.getWorld();
    if (world) {
      useGmStore(viewerPinia).updateWorld(
        [...world.systems.values()],
        [...world.ships.values()],
        [...world.stations.values()],
        [...world.factions.values()],
        [...world.marketOrders.values()],
        world.geopolitics,
      );
    }
  }

  updatePanels() {
    const world = this.context.getWorld();
    if (!world) {
      return;
    }

    this.context.refreshHistoryWindows();
    this.context.updateSystemPanel();
    this.refreshStreamScopeIfNeeded();
    const detailState = buildDetailPanelState({
      world,
      selectedItems: this.context.getSelectedItems(),
      povLevel: this.context.getPovLevel(),
      cameraMode: this.context.getCameraMode(),
      cameraTargetShipId: this.context.getCameraTargetShipId(),
      worldLabel: this.context.worldLabel(),
      describeSelectionParent: this.context.describeSelectionParent,
    });
    this.context.hudState.detailPanel.hidden = false;
    this.context.hudState.detailPanel.title = detailState.title;
    this.context.hudState.detailPanel.bodyHtml = detailState.bodyHtml;
  }

  private getPreferredStreamScope() {
    const activeSystemId = this.context.getActiveSystemId();
    if (this.context.getPovLevel() === "galaxy" || !activeSystemId) {
      return { scopeKind: "universe" as const };
    }

    const celestialId = this.context.resolveFocusedCelestialId();
    if (this.context.getPovLevel() === "local" && celestialId) {
      return {
        scopeKind: "local-celestial" as const,
        systemId: activeSystemId,
        celestialId,
      };
    }

    return {
      scopeKind: "system" as const,
      systemId: activeSystemId,
    };
  }
}
