import { useMemo } from "react";
import { Line } from "@react-three/drei";
import { SpaceCanvas } from "../shared/SpaceCanvas";
import { SpaceEnvironment } from "../shared/SpaceEnvironment";
import { CameraRig } from "../shared/CameraRig";
import { RouteLine } from "../shared/RouteLine";
import { StarSystemContents } from "../shared/StarSystemContents";
import { systemToGalaxyWorld } from "../shared/galaxyMap";
import type { GalaxyConnection, GalaxySystem, SystemPlanet, SystemPointOfInterest, Vec3 } from "../shared/types";

export type GeneratedGalaxySystem = GalaxySystem;
export type GeneratedGalaxyConnection = GalaxyConnection;

function createRng(seed: number) {
  let state = seed >>> 0 || 1;
  return () => {
    state = (state * 1664525 + 1013904223) >>> 0;
    return state / 4294967296;
  };
}

const planetTypes = ["Rocky", "Terrestrial", "Ice", "Oceanic", "Barren", "Gas Giant", "Lava"] as const;
const poiTypes = [
  { type: "station", label: "Trade Station", description: "Docking, repair, market, and agent services." },
  { type: "asteroid_belt", label: "Asteroid Belt", description: "Mineable belt seeded with local ore classes." },
  { type: "anomaly", label: "Sensor Anomaly", description: "Scan signature with time-limited exploration rewards." },
  { type: "site", label: "Relic Site", description: "Salvageable ruins and hacking containers." },
] as const;

const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));

function generateSystemContents(systemId: string, systemName: string, security: number, rand: () => number) {
  const planetCount = 1 + Math.floor(rand() * 8);
  const planets: SystemPlanet[] = Array.from({ length: planetCount }, (_, index) => {
    const type = planetTypes[Math.floor(rand() * planetTypes.length)];
    return {
      id: `${systemId}-planet-${index}`,
      name: `${systemName} ${index + 1}`,
      type,
      orbit: Math.round((5 + index * (4 + rand() * 5)) * 10) / 10,
      moons: type === "Gas Giant" ? 1 + Math.floor(rand() * 8) : Math.floor(rand() * 3),
    };
  });

  const poiCount = 1 + Math.floor(rand() * 4) + (security < 0.25 ? 1 : 0);
  const pointsOfInterest: SystemPointOfInterest[] = Array.from({ length: poiCount }, (_, index) => {
    const template = index === 0 && security > 0.45 ? poiTypes[0] : poiTypes[Math.floor(rand() * poiTypes.length)];
    return {
      id: `${systemId}-poi-${index}`,
      name: `${systemName} ${template.label}`,
      type: template.type,
      description: template.description,
    };
  });

  return { planets, pointsOfInterest };
}

export function generateGalaxy(seed: number, params: { count: number; arms: number; verticalArms: number; size: number; twist: number; verticalTwist: number }): GeneratedGalaxySystem[] {
  const rand = createRng(seed);
  const systems: GeneratedGalaxySystem[] = [];
  const baseSpacing = clamp((params.size / Math.sqrt(params.count)) * 1.25, 9, 24);
  const horizontalArms = Math.max(1, params.arms);
  const verticalArms = Math.max(0, params.verticalArms);
  const totalArmSlots = horizontalArms + verticalArms;
  const factions = [
    ["Concord", "#22d3ee"],
    ["Amarr", "#f59e0b"],
    ["Minmatar", "#ef4444"],
    ["Gallente", "#a855f7"],
    ["Caldari", "#38bdf8"],
  ] as const;
  for (let index = 0; index < params.count; index += 1) {
    const core = index < 7;
    const armSlot = index % totalArmSlots;
    const vertical = !core && armSlot >= horizontalArms;
    const arm = vertical ? armSlot - horizontalArms : armSlot;
    const [faction, color] = core ? factions[0] : factions[(armSlot % (factions.length - 1)) + 1];
    let r = 0;
    let angle = 0;
    let x = 0;
    let y = 0;
    let z = 0;

    for (let attempt = 0; attempt < 180; attempt += 1) {
      r = core ? 8 + rand() * 30 : Math.pow(rand(), 0.62) * params.size;
      const armCount = vertical ? verticalArms : horizontalArms;
      const armTwist = vertical ? params.verticalTwist : params.twist;
      angle = core ? rand() * Math.PI * 2 : (Math.PI * 2 * arm) / armCount + (r / params.size) * armTwist + (rand() - 0.5) * 0.72;
      if (vertical) {
        const planeAngle = (Math.PI * 2 * arm) / Math.max(1, verticalArms);
        const armSweep = Math.cos(angle) * r;
        x = Math.cos(planeAngle) * armSweep;
        z = Math.sin(planeAngle) * armSweep;
        y = Math.sin(angle) * r * 0.72 + (rand() - 0.5) * 12;
      } else {
        x = Math.cos(angle) * r;
        z = Math.sin(angle) * r;
        y = (rand() - 0.5) * 20;
      }
      const relaxedSpacing = baseSpacing * (attempt > 120 ? 0.68 : attempt > 60 ? 0.82 : 1);
      const localSpacing = core ? relaxedSpacing * 0.9 : relaxedSpacing;
      const clear = systems.every((system) => Math.hypot(system.x - x, system.y - y, system.z - z) >= localSpacing);
      if (clear) break;
    }

    const security = Math.round((1 - (r / params.size) * 2) * 100) / 100;
    const name = core ? `COR-${100 + index}` : `${faction.slice(0, 3).toUpperCase()}-${100 + index}`;
    const contents = generateSystemContents(`g-${index}`, name, security, rand);
    systems.push({
      id: `g-${index}`,
      name,
      x,
      z,
      y,
      mapProjection: "world3d",
      security,
      faction,
      type: faction,
      color,
      planetCount: contents.planets.length,
      planets: contents.planets,
      stations: contents.pointsOfInterest.filter((poi) => poi.type === "station").map((poi) => poi.name),
      pointsOfInterest: contents.pointsOfInterest,
    });
  }

  return systems;
}

export function buildGalaxyConnections(systems: GeneratedGalaxySystem[]): GeneratedGalaxyConnection[] {
  return systems.flatMap((system) => {
    const [sx, sy, sz] = systemToGalaxyWorld(system);
    const nearest = systems
      .filter((candidate) => candidate.id !== system.id)
      .map((candidate) => {
        const [cx, cy, cz] = systemToGalaxyWorld(candidate);
        return { id: candidate.id, d: Math.hypot(sx - cx, sy - cy, sz - cz) };
      })
      .sort((a, b) => a.d - b.d)
      .slice(0, 2);
    return nearest.map((candidate) => [system.id, candidate.id] as GeneratedGalaxyConnection);
  }).filter(([a, b], index, all) => all.findIndex(([x, y]) => (x === a && y === b) || (x === b && y === a)) === index);
}

export function findGalaxyRoute(originId: string, destinationId: string, connections: GeneratedGalaxyConnection[]) {
  if (originId === destinationId) return [originId];
  const graph = new Map<string, string[]>();
  connections.forEach(([a, b]) => {
    graph.set(a, [...(graph.get(a) ?? []), b]);
    graph.set(b, [...(graph.get(b) ?? []), a]);
  });
  const queue = [originId];
  const prev = new Map<string, string | null>([[originId, null]]);
  for (let i = 0; i < queue.length; i += 1) {
    const id = queue[i];
    for (const next of graph.get(id) ?? []) {
      if (prev.has(next)) continue;
      prev.set(next, id);
      queue.push(next);
    }
  }
  if (!prev.has(destinationId)) return [];
  const route: string[] = [];
  let cursor: string | null = destinationId;
  while (cursor) {
    route.unshift(cursor);
    cursor = prev.get(cursor) ?? null;
  }
  return route;
}

type GalaxySceneProps = {
  systems: GeneratedGalaxySystem[];
  connections: GeneratedGalaxyConnection[];
  selectedId: string | null;
  selectedPoiId?: string | null;
  routeIds: string[];
  onSelect: (system: GeneratedGalaxySystem) => void;
  onPoiSelect: (system: GeneratedGalaxySystem, poi: SystemPointOfInterest) => void;
  onRoutePick: (system: GeneratedGalaxySystem) => void;
};

export function GalaxyScene({ systems, connections, selectedId, selectedPoiId, routeIds, onSelect, onPoiSelect, onRoutePick }: GalaxySceneProps) {
  const byId = useMemo(() => new Map(systems.map((system) => [system.id, system])), [systems]);
  const routePoints = routeIds.map((id) => byId.get(id)).filter(Boolean).map((system) => systemToGalaxyWorld(system!) as Vec3);
  const nearestSystem = (point: { x: number; z: number }) => {
    return systems.reduce((best, system) => {
      const [x, , z] = systemToGalaxyWorld(system);
      const distance = Math.hypot(x - point.x, z - point.z);
      if (!best || distance < best.distance) return { system, distance };
      return best;
    }, null as null | { system: GeneratedGalaxySystem; distance: number })?.system;
  };

  return (
    <SpaceCanvas camera={{ position: [0, 260, 360], fov: 52, near: 0.1, far: 5000 }}>
      <SpaceEnvironment density={5200} spread={2200} fog />
      <CameraRig orbit distance={420} />
      <mesh
        rotation={[-Math.PI / 2, 0, 0]}
        onContextMenu={(event) => {
          event.stopPropagation();
          const system = nearestSystem({ x: event.point.x, z: event.point.z });
          if (system) onRoutePick(system);
        }}
      >
        <planeGeometry args={[900, 900]} />
        <meshBasicMaterial transparent opacity={0} depthWrite={false} />
      </mesh>
      <points>
        <bufferGeometry>
          <bufferAttribute
            attach="attributes-position"
            args={[new Float32Array(systems.flatMap((system) => {
              const [x, y, z] = systemToGalaxyWorld(system);
              return [x, y - 2, z];
            })), 3]}
          />
        </bufferGeometry>
        <pointsMaterial color="#64748b" size={2.2} transparent opacity={0.22} />
      </points>
      {connections.map(([a, b]) => {
        const from = byId.get(a);
        const to = byId.get(b);
        if (!from || !to) return null;
        const onRoute = routeIds.includes(a) && routeIds.includes(b);
        return <Line key={`${a}-${b}`} points={[systemToGalaxyWorld(from), systemToGalaxyWorld(to)]} color={onRoute ? "#22d3ee" : "#1c2a3f"} lineWidth={onRoute ? 2 : 1} transparent opacity={onRoute ? 0.75 : 0.28} />;
      })}
      <RouteLine points={routePoints} color="#22d3ee" width={3} />
      {systems.map((system) => {
        const selected = system.id === selectedId;
        const color = routeIds.includes(system.id) ? "#22d3ee" : selected ? "#f0a030" : system.color;

        return (
        <group
          key={system.id}
          position={systemToGalaxyWorld(system)}
        >
          <StarSystemContents
            systemId={system.id}
            systemName={system.name}
            starColor={color}
            planets={system.planets}
            pointsOfInterest={system.pointsOfInterest}
            selectedPoiId={selectedPoiId}
            expanded={selected}
            scale={selected ? 0.95 : 0.36}
            onPoiSelect={(poi, event) => {
              event.stopPropagation();
              onPoiSelect(system, poi);
            }}
          />
          <mesh
            onClick={(event) => {
              event.stopPropagation();
              onSelect(system);
            }}
            onContextMenu={(event) => {
              event.stopPropagation();
              onRoutePick(system);
            }}
          >
            <sphereGeometry args={[selected ? 3.6 : 2.2, 16, 16]} />
            <meshBasicMaterial color={color} />
          </mesh>
        </group>
        );
      })}
    </SpaceCanvas>
  );
}
