import * as THREE from "three";
import {
  ACTIVE_SYSTEM_DETAIL_SCALE,
  PLANET_RENDER_SCALE,
  STAR_RENDER_SCALE,
} from "./viewerConstants";
import { DISPLAY_UNITS_PER_KILOMETER } from "./viewerMath";
import type {
  CelestialVisual,
  ClaimVisual,
  ConstructionSiteVisual,
  NodeVisual,
  PlanetVisual,
  Selectable,
  ShipVisual,
  StructureVisual,
  SystemVisual,
} from "./viewerTypes";
import type {
  CelestialDelta,
  CelestialSnapshot,
  ClaimDelta,
  ClaimSnapshot,
  ConstructionSiteDelta,
  ConstructionSiteSnapshot,
  ResourceNodeDelta,
  ResourceNodeSnapshot,
  ShipDelta,
  ShipSnapshot,
  StationDelta,
  StationSnapshot,
  SystemSnapshot,
} from "./contracts";
import {
  celestialRenderRadius,
  computePlanetLocalPosition,
  scaleLocalScalar,
  scaleLocalVector,
  toDisplayGalaxyVector,
  toThreeVector,
} from "./viewerMath";
import { getAnimatedShipLocalPosition } from "./viewerPresentation";
import {
  createClaimMesh,
  createConstructionSiteMesh,
  createMoonVisuals,
  createNodeMesh,
  createPlanetOrbit,
  createPlanetRing,
  createPlanetTexture,
  createShellReticle,
  createShipMesh,
  createShipTacticalIcon,
  createCelestialMesh,
  createStarCluster,
  createStarDot,
  createStationMesh,
  createTacticalIcon,
} from "./viewerSceneFactory";
import {
  createSceneNode,
  rawObject,
  registerSelectableDescendants,
  registerSelectableTarget,
} from "./viewerScenePrimitives";
import type { SceneNode } from "./viewerScenePrimitives";

/** Scale a local km position to system-scene display coordinates. */
function toSystemPos(localPosition: THREE.Vector3): THREE.Vector3 {
  return localPosition.clone().multiplyScalar(DISPLAY_UNITS_PER_KILOMETER * ACTIVE_SYSTEM_DETAIL_SCALE);
}

interface SceneSyncContext {
  documentRef: Document;
  worldOrbitalTimeSeconds?: number;
  orbitalSimulationSpeed: number;
  worldSeed: number;
  worldTimeSyncMs: number;
  galaxySystemGroup: THREE.Group;
  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<string, SystemVisual>;
  planetVisuals: PlanetVisual[];
  celestialVisuals: Map<string, CelestialVisual>;
  nodeVisuals: Map<string, NodeVisual>;
  stationVisuals: Map<string, StructureVisual>;
  claimVisuals: Map<string, ClaimVisual>;
  constructionSiteVisuals: Map<string, ConstructionSiteVisual>;
  shipVisuals: Map<string, ShipVisual>;
  shipSize: (ship: ShipSnapshot) => number;
  shipLength: (ship: ShipSnapshot) => number;
  shipPresentationColor: (ship: ShipSnapshot) => string;
  celestialColor: (kind: string) => string;
  createCirclePoints: (radius: number, segments: number) => THREE.Vector3[];
  resolvePointPosition: (systemId: string, celestialId?: string | null) => THREE.Vector3;
  resolveOrbitalAnchor: (systemId: string, localPosition: THREE.Vector3) => NodeVisual["anchor"];
  deriveNodeOrbital: (node: ResourceNodeSnapshot | ResourceNodeDelta, anchor: NodeVisual["anchor"]) => {
    radius: number;
    phase: number;
    inclination: number;
  };
  deriveOrbitalFromLocalPosition: (localPosition: THREE.Vector3, systemId: string, anchor: StructureVisual["anchor"]) => {
    radius: number;
    phase: number;
    inclination: number;
  };
}

export function rebuildSystems(context: SceneSyncContext, systems: SystemSnapshot[]) {
  const worldTimeSeconds = context.worldOrbitalTimeSeconds !== undefined
    ? context.worldOrbitalTimeSeconds + ((performance.now() - context.worldTimeSyncMs) / 1000 * context.orbitalSimulationSpeed)
    : 0;

  context.galaxySystemGroup.clear();
  context.galaxySelectableTargets.clear();
  context.systemSelectableTargets.clear();
  context.planetVisuals.length = 0;
  context.systemVisuals.clear();

  for (const system of systems) {
    // Galaxy root: star dot + shell reticle — lives in galaxyScene
    const galaxyRoot = createSceneNode(new THREE.Group());
    galaxyRoot.setPosition(toDisplayGalaxyVector(system.galaxyPosition));

    const systemIcon = createStarDot(context.documentRef, system.stars[0]?.color ?? "#ffffff");
    const shellReticle = createShellReticle(context.documentRef, "#ff3b30", 400);
    galaxyRoot.add(systemIcon, shellReticle);

    registerSelectableTarget(context.galaxySelectableTargets, systemIcon, { kind: "system", id: system.id });
    registerSelectableTarget(context.galaxySelectableTargets, shellReticle, { kind: "system", id: system.id });

    // System root: star cluster + planet detail group — added to systemScene only when this system is active
    const systemRoot = createSceneNode(new THREE.Group());
    const detailGroup = createSceneNode(new THREE.Group());
    const starCluster = createStarCluster(system);
    systemRoot.add(starCluster, detailGroup);

    registerSelectableDescendants(
      context.systemSelectableTargets,
      starCluster,
      { kind: "system", id: system.id },
      (child) => child instanceof THREE.Mesh,
    );

    for (const [planetIndex, planet] of system.planets.entries()) {
      const renderedPlanetRadius = celestialRenderRadius(planet.size, 0.00012, 0.03, 0.62);
      const planetTexture = createPlanetTexture(planet.color, planetIndex * 17 + system.id.length * 31, context.documentRef);
      const planetMesh = createSceneNode(new THREE.Mesh(
        new THREE.SphereGeometry(renderedPlanetRadius, 24, 24),
        new THREE.MeshStandardMaterial({
          map: planetTexture,
          roughness: 0.88,
          metalness: 0.04,
        }),
      ));
      const initialPos = toSystemPos(computePlanetLocalPosition(planet, worldTimeSeconds));
      planetMesh.setPosition(initialPos);
      const iconBaseScale = Math.max(120, renderedPlanetRadius * 10);
      const planetHsl = { h: 0, s: 0, l: 0 };
      new THREE.Color(planet.color).getHSL(planetHsl);
      const planetIconColor = new THREE.Color().setHSL(planetHsl.h, Math.max(planetHsl.s, 0.5), 0.72).getStyle();
      const planetIcon = createTacticalIcon(context.documentRef, planetIconColor, iconBaseScale);
      planetIcon.setPosition(initialPos);
      planetIcon.setVisible(true);
      const orbit = createPlanetOrbit(planet);
      const ring = planet.hasRing ? createPlanetRing(planet) : undefined;
      if (ring) {
        ring.setPosition(initialPos);
      }
      const moons = createMoonVisuals(planet, context.documentRef);
      detailGroup.add(orbit, planetMesh, planetIcon);
      if (ring) {
        detailGroup.add(ring);
      }
      for (const [moonIdx, moon] of moons.entries()) {
        moon.systemId = system.id;
        moon.planetIndex = planetIndex;
        moon.mesh.setPosition(initialPos);
        moon.icon.setPosition(initialPos);
        detailGroup.add(moon.mesh, moon.icon, moon.orbit);
        registerSelectableTarget(context.systemSelectableTargets, moon.mesh, { kind: "moon", systemId: system.id, planetIndex, moonIndex: moonIdx });
        registerSelectableTarget(context.systemSelectableTargets, moon.icon, { kind: "moon", systemId: system.id, planetIndex, moonIndex: moonIdx });
      }
      context.planetVisuals.push({ systemId: system.id, planet, orbit, mesh: planetMesh, icon: planetIcon, iconBaseScale, ring, moons });
      registerSelectableTarget(context.systemSelectableTargets, planetMesh, { kind: "planet", systemId: system.id, planetIndex });
      registerSelectableTarget(context.systemSelectableTargets, planetIcon, { kind: "planet", systemId: system.id, planetIndex });
    }

    context.systemVisuals.set(system.id, {
      galaxyRoot,
      systemRoot,
      starCluster,
      icon: systemIcon,
      shellReticle,
      shellReticleBaseScale: 400,
      detailGroup,
      galaxyPosition: toDisplayGalaxyVector(system.galaxyPosition),
    });

    context.galaxySystemGroup.add(rawObject(galaxyRoot));
  }
}

export function syncCelestials(context: SceneSyncContext, celestials: CelestialSnapshot[], activeSystemId?: string) {
  context.celestialGroup.clear();
  context.celestialVisuals.clear();

  for (const celestial of celestials) {
    // Stars, planets, and moons are already rendered by rebuildSystems via SystemSnapshot.
    // Only create visual objects for kinds not covered by the system builder.
    if (celestial.kind === "star" || celestial.kind === "planet" || celestial.kind === "moon") {
      continue;
    }

    const mesh = createCelestialMesh(celestial, context.celestialColor);
    const celestialIconBaseScale = 90;
    const icon = createTacticalIcon(context.documentRef, context.celestialColor(celestial.kind), celestialIconBaseScale);
    const orbitalAnchor = toSystemPos(toThreeVector(celestial.orbitalAnchor));
    mesh.setPosition(orbitalAnchor);
    icon.setPosition(orbitalAnchor);
    const isActive = celestial.systemId === activeSystemId;
    mesh.setVisible(isActive);
    icon.setVisible(isActive);
    context.celestialVisuals.set(celestial.id, {
      id: celestial.id,
      systemId: celestial.systemId,
      mesh,
      icon,
      iconBaseScale: celestialIconBaseScale,
      kind: celestial.kind,
      orbitalAnchor,
    });
    context.celestialGroup.add(rawObject(mesh), rawObject(icon));
    registerSelectableTarget(context.systemSelectableTargets, mesh, { kind: "celestial", id: celestial.id });
    registerSelectableTarget(context.systemSelectableTargets, icon, { kind: "celestial", id: celestial.id });
  }
}

export function syncNodes(context: SceneSyncContext, nodes: ResourceNodeSnapshot[], activeSystemId?: string) {
  context.nodeGroup.clear();
  context.nodeVisuals.clear();

  for (const node of nodes) {
    const mesh = createNodeMesh(node);
    const icon = createTacticalIcon(context.documentRef, node.sourceKind === "gas-cloud" ? "#7fd6ff" : "#d2b07a", 100);
    const localPosition = toThreeVector(node.localPosition);
    const displayPos = toSystemPos(localPosition);
    mesh.setPosition(displayPos);
    icon.setPosition(displayPos);
    const isActive = node.systemId === activeSystemId;
    mesh.setVisible(isActive);
    icon.setVisible(isActive);
    const anchor = context.resolveOrbitalAnchor(node.systemId, localPosition);
    const orbital = context.deriveNodeOrbital(node, anchor);
    context.nodeVisuals.set(node.id, {
      systemId: node.systemId,
      mesh,
      icon,
      sourceKind: node.sourceKind,
      anchor,
      localPosition,
      orbitRadius: orbital.radius,
      orbitPhase: orbital.phase,
      orbitInclination: orbital.inclination,
    });
    context.nodeGroup.add(rawObject(mesh), rawObject(icon));
    registerSelectableTarget(context.systemSelectableTargets, mesh, { kind: "node", id: node.id });
    registerSelectableTarget(context.systemSelectableTargets, icon, { kind: "node", id: node.id });
  }
}

export function syncStations(context: SceneSyncContext, stations: StationSnapshot[], activeSystemId?: string) {
  context.stationGroup.clear();
  context.stationVisuals.clear();

  for (const station of stations) {
    const mesh = createStationMesh(station);
    const icon = createTacticalIcon(context.documentRef, station.color, 130);
    const localPosition = toThreeVector(station.localPosition);
    const displayPos = toSystemPos(localPosition);
    mesh.setPosition(displayPos);
    icon.setPosition(displayPos);
    const isActive = station.systemId === activeSystemId;
    mesh.setVisible(isActive);
    icon.setVisible(isActive);
    const anchor = context.resolveOrbitalAnchor(station.systemId, localPosition);
    const orbital = context.deriveOrbitalFromLocalPosition(localPosition, station.systemId, anchor);
    context.stationVisuals.set(station.id, {
      id: station.id,
      systemId: station.systemId,
      mesh,
      icon,
      anchor,
      orbitRadius: orbital.radius,
      orbitPhase: orbital.phase,
      orbitInclination: orbital.inclination,
      localPosition,
    });
    context.stationGroup.add(rawObject(mesh), rawObject(icon));
    registerSelectableTarget(context.systemSelectableTargets, mesh, { kind: "station", id: station.id });
    registerSelectableTarget(context.systemSelectableTargets, icon, { kind: "station", id: station.id });
  }
}

export function syncClaims(context: SceneSyncContext, claims: ClaimSnapshot[], activeSystemId?: string) {
  context.claimGroup.clear();
  context.claimVisuals.clear();

  for (const claim of claims) {
    const localPosition = context.resolvePointPosition(claim.systemId, claim.celestialId);
    const displayPos = toSystemPos(localPosition);
    const mesh = createClaimMesh(claim);
    const icon = createTacticalIcon(context.documentRef, "#ff5b5b", 90);
    mesh.setPosition(displayPos);
    icon.setPosition(displayPos);
    const isActive = claim.systemId === activeSystemId;
    mesh.setVisible(isActive);
    icon.setVisible(isActive);
    context.claimVisuals.set(claim.id, {
      id: claim.id,
      celestialId: claim.celestialId,
      systemId: claim.systemId,
      mesh,
      icon,
      localPosition,
    });
    context.claimGroup.add(rawObject(mesh), rawObject(icon));
    registerSelectableTarget(context.systemSelectableTargets, mesh, { kind: "claim", id: claim.id });
    registerSelectableTarget(context.systemSelectableTargets, icon, { kind: "claim", id: claim.id });
  }
}

export function syncConstructionSites(context: SceneSyncContext, sites: ConstructionSiteSnapshot[], activeSystemId?: string) {
  context.constructionSiteGroup.clear();
  context.constructionSiteVisuals.clear();

  for (const site of sites) {
    const localPosition = context.resolvePointPosition(site.systemId, site.celestialId);
    const displayPos = toSystemPos(localPosition);
    const mesh = createConstructionSiteMesh(site);
    const icon = createTacticalIcon(context.documentRef, "#9df29c", 90);
    mesh.setPosition(displayPos);
    icon.setPosition(displayPos);
    const isActive = site.systemId === activeSystemId;
    mesh.setVisible(isActive);
    icon.setVisible(isActive);
    context.constructionSiteVisuals.set(site.id, {
      id: site.id,
      celestialId: site.celestialId,
      systemId: site.systemId,
      mesh,
      icon,
      localPosition,
    });
    context.constructionSiteGroup.add(rawObject(mesh), rawObject(icon));
    registerSelectableTarget(context.systemSelectableTargets, mesh, { kind: "construction-site", id: site.id });
    registerSelectableTarget(context.systemSelectableTargets, icon, { kind: "construction-site", id: site.id });
  }
}

export function syncShips(context: SceneSyncContext, ships: ShipSnapshot[], tickIntervalMs: number, activeSystemId?: string) {
  context.shipGroup.clear();
  context.shipVisuals.clear();

  for (const ship of ships) {
    const mesh = createShipMesh(ship, context.shipSize(ship), context.shipLength(ship), context.shipPresentationColor(ship));
    const shipColor = context.shipPresentationColor(ship);
    const iconBaseScale = 78;
    const icon = createShipTacticalIcon(context.documentRef, shipColor, iconBaseScale);
    const localPosition = toThreeVector(ship.localPosition);
    const displayPos = toSystemPos(localPosition);
    mesh.setPosition(displayPos);
    icon.setPosition(displayPos);
    icon.setColor(shipColor);
    const isActive = ship.systemId === activeSystemId;
    mesh.setVisible(isActive);
    icon.setVisible(isActive);
    context.shipGroup.add(rawObject(mesh), rawObject(icon));
    registerSelectableTarget(context.systemSelectableTargets, mesh, { kind: "ship", id: ship.id });
    registerSelectableTarget(context.systemSelectableTargets, icon, { kind: "ship", id: ship.id });
    context.shipVisuals.set(ship.id, {
      systemId: ship.systemId,
      mesh,
      icon,
      iconBaseScale,
      startPosition: localPosition.clone(),
      authoritativePosition: localPosition.clone(),
      targetPosition: toThreeVector(ship.targetLocalPosition),
      velocity: toThreeVector(ship.localVelocity),
      receivedAtMs: performance.now(),
      blendDurationMs: Math.max(tickIntervalMs, 80),
    });
  }
}

export function applyCelestialDeltas(context: SceneSyncContext, celestials: CelestialDelta[], activeSystemId?: string) {
  for (const celestial of celestials) {
    if (celestial.kind === "star" || celestial.kind === "planet" || celestial.kind === "moon") {
      continue;
    }

    const visual = context.celestialVisuals.get(celestial.id);
    if (!visual) {
      continue;
    }

    visual.systemId = celestial.systemId;
    visual.kind = celestial.kind;
    visual.orbitalAnchor.copy(toSystemPos(toThreeVector(celestial.orbitalAnchor)));
    visual.mesh.setPosition(visual.orbitalAnchor);
    visual.icon.setPosition(visual.orbitalAnchor);
    visual.mesh.setColor(context.celestialColor(celestial.kind));
    const isActive = visual.systemId === activeSystemId;
    visual.mesh.setVisible(isActive);
    visual.icon.setVisible(isActive);
  }
}

export function applyNodeDeltas(context: SceneSyncContext, nodes: ResourceNodeDelta[], activeSystemId?: string) {
  for (const node of nodes) {
    const visual = context.nodeVisuals.get(node.id);
    if (!visual) {
      continue;
    }

    visual.systemId = node.systemId;
    visual.sourceKind = node.sourceKind;
    visual.localPosition.copy(toThreeVector(node.localPosition));
    visual.anchor = context.resolveOrbitalAnchor(node.systemId, visual.localPosition);
    const orbital = context.deriveNodeOrbital(node, visual.anchor);
    visual.orbitRadius = orbital.radius;
    visual.orbitPhase = orbital.phase;
    visual.orbitInclination = orbital.inclination;
    visual.mesh.setScaleScalar(0.8 + (node.oreRemaining / Math.max(node.maxOre, 1)) * 0.6);
    const isActive = visual.systemId === activeSystemId;
    visual.mesh.setVisible(isActive);
    visual.icon.setVisible(isActive);
  }
}

export function applyStationDeltas(context: SceneSyncContext, stations: StationDelta[], activeSystemId?: string) {
  for (const station of stations) {
    const visual = context.stationVisuals.get(station.id);
    if (!visual) {
      continue;
    }

    visual.systemId = station.systemId;
    visual.localPosition.copy(toThreeVector(station.localPosition));
    visual.anchor = context.resolveOrbitalAnchor(station.systemId, visual.localPosition);
    const orbital = context.deriveOrbitalFromLocalPosition(visual.localPosition, station.systemId, visual.anchor);
    visual.orbitRadius = orbital.radius;
    visual.orbitPhase = orbital.phase;
    visual.orbitInclination = orbital.inclination;
    visual.mesh.setColor(station.color);
    visual.mesh.setEmissive(station.color, 0.1);
    const isActive = visual.systemId === activeSystemId;
    visual.mesh.setVisible(isActive);
    visual.icon.setVisible(isActive);
  }
}

export function applyClaimDeltas(context: SceneSyncContext, claims: ClaimDelta[], activeSystemId?: string) {
  for (const claim of claims) {
    const visual = context.claimVisuals.get(claim.id);
    if (!visual) {
      continue;
    }

    visual.systemId = claim.systemId;
    visual.localPosition.copy(context.resolvePointPosition(claim.systemId, claim.celestialId));
    const displayPos = toSystemPos(visual.localPosition);
    visual.mesh.setPosition(displayPos);
    visual.icon.setPosition(displayPos);
    visual.mesh.setColor(claim.state === "active" ? "#ff7f50" : "#ff5b5b");
    visual.mesh.setEmissive(claim.state === "active" ? "#ffb27d" : "#7a2020");
    const isActive = visual.systemId === activeSystemId;
    visual.mesh.setVisible(isActive);
    visual.icon.setVisible(isActive);
  }
}

export function applyConstructionSiteDeltas(context: SceneSyncContext, sites: ConstructionSiteDelta[], activeSystemId?: string) {
  for (const site of sites) {
    const visual = context.constructionSiteVisuals.get(site.id);
    if (!visual) {
      continue;
    }

    visual.systemId = site.systemId;
    visual.localPosition.copy(context.resolvePointPosition(site.systemId, site.celestialId));
    const displayPos = toSystemPos(visual.localPosition);
    visual.mesh.setPosition(displayPos);
    visual.icon.setPosition(displayPos);
    visual.mesh.setColor(site.state === "completed" ? "#46d37f" : "#9df29c");
    visual.mesh.setScaleScalar(0.75 + site.progress * 0.35);
    const isActive = visual.systemId === activeSystemId;
    visual.mesh.setVisible(isActive);
    visual.icon.setVisible(isActive);
  }
}

export function applyShipDeltas(context: SceneSyncContext, ships: ShipDelta[], tickIntervalMs: number, activeSystemId?: string) {
  for (const ship of ships) {
    const visual = context.shipVisuals.get(ship.id);
    if (!visual) {
      continue;
    }

    visual.systemId = ship.systemId;
    visual.startPosition.copy(getAnimatedShipLocalPosition(visual));
    visual.authoritativePosition.copy(toThreeVector(ship.localPosition));
    visual.targetPosition.copy(toThreeVector(ship.targetLocalPosition));
    visual.velocity.copy(toThreeVector(ship.localVelocity));
    visual.receivedAtMs = performance.now();
    visual.blendDurationMs = Math.max(tickIntervalMs * 1.35, 100);
    const shipColor = context.shipPresentationColor(ship);
    visual.mesh.setColor(shipColor);
    visual.mesh.setEmissive(shipColor, 0.18);
    visual.icon.setColor(shipColor);
    const isActive = visual.systemId === activeSystemId;
    visual.mesh.setVisible(isActive);
    visual.icon.setVisible(isActive);
  }
}
