import {
  HOCKEY_ARENA_MAX_X,
  HOCKEY_ARENA_MAX_Z,
  HOCKEY_ARENA_MIN_X,
  HOCKEY_ARENA_MIN_Z,
} from "./hockeyHealing";
import type { WorldPosition } from "./types";

export type AetherAssaultStatus = "inactive" | "live";
export type AetherShipKind = "standard" | "armored";
export type AetherShipPhase = "entering" | "formation" | "diving" | "returning";

export interface AetherShip {
  id: string;
  kind: AetherShipKind;
  hp: number;
  maxHp: number;
  position: WorldPosition;
  formationPosition: WorldPosition;
  phase: AetherShipPhase;
  phaseStartedAt: number;
  phaseEndsAt: number;
  startPosition: WorldPosition;
  targetPosition: WorldPosition;
  contactResolved: boolean;
}

export interface AetherProjectile {
  id: number;
  position: WorldPosition;
  velocity: WorldPosition;
}

export interface AetherAssaultState {
  status: AetherAssaultStatus;
  seed: number;
  randomState: number;
  wave: number;
  score: number;
  kills: number;
  killStreak: number;
  multiplier: number;
  ships: AetherShip[];
  playerShots: AetherProjectile[];
  enemyShots: AetherProjectile[];
  nextProjectileId: number;
  nextPlayerShotAt: number;
  nextEnemyShotAt: number;
  nextDiveAt: number;
  nextWaveAt: number | null;
  lastPlayerHitAt: number;
  lastKillAt: number;
  lastKillScore: number;
}

export interface AetherAssaultStep {
  delta: number;
  time: number;
  playerPosition: WorldPosition;
  canAutoFire: boolean;
}

export interface AetherAssaultAdvance {
  state: AetherAssaultState;
  playerDamage: number;
}

export const AETHER_MAX_SHIPS = 20;
export const AETHER_MAX_PLAYER_SHOTS = 32;
export const AETHER_MAX_ENEMY_SHOTS = 64;
export const AETHER_PLAYER_SHOT_DAMAGE = 1;
export const AETHER_ENEMY_SHOT_DAMAGE = 10;
export const AETHER_DIVE_DAMAGE = 24;
export const AETHER_HIT_GRACE_SECONDS = 0.6;
export const AETHER_PLAYER_SHOTS_PER_SECOND = 5;
export const AETHER_WAVE_CLEAR_DELAY = 1.5;
export const AETHER_STANDARD_SCORE = 100;
export const AETHER_ARMORED_SCORE = 250;

const PLAYER_SHOT_SPEED = 18;
const PLAYER_SHOT_RADIUS = 0.24;
const ENEMY_SHOT_RADIUS = 0.3;
const SHIP_RADIUS = 0.72;
const PLAYER_HIT_RADIUS = 0.62;
const DIVE_HIT_RADIUS = 1.05;
const ENTRY_DURATION = 1.45;
const RETURN_DURATION = 1.25;
const DIVE_DURATION = 2.15;

function normalizeSeed(seed: number) {
  const normalized = Math.floor(Number(seed)) >>> 0;
  return normalized || 0x9e3779b9;
}

function nextRandom(state: number) {
  let next = normalizeSeed(state);
  next ^= next << 13;
  next ^= next >>> 17;
  next ^= next << 5;
  return next >>> 0;
}

function randomUnit(state: number) {
  const next = nextRandom(state);
  return { state: next, value: next / 0x100000000 };
}

export function createAetherAssaultSeed(random: () => number = Math.random) {
  const sample = Number(random());
  const normalized = Number.isFinite(sample) ? Math.max(0, Math.min(0.999999999999, sample)) : 0;
  return normalizeSeed(Math.floor(normalized * 0x100000000));
}

export function aetherShipCount(wave: number) {
  return Math.min(AETHER_MAX_SHIPS, 8 + Math.max(0, Math.floor(wave) - 1) * 2);
}

export function aetherMultiplier(killStreak: number) {
  return Math.min(5, 1 + Math.floor(Math.max(0, killStreak) / 5) * 0.25);
}

export function aetherWaveClearBonus(wave: number) {
  return Math.max(1, Math.floor(wave)) * 750;
}

function formationPosition(index: number, count: number): WorldPosition {
  const columns = 5;
  const rows = Math.ceil(count / columns);
  const row = Math.floor(index / columns);
  const column = index % columns;
  const itemsInRow = row === rows - 1 && count % columns !== 0 ? count % columns : columns;
  const centeredColumn = column - (itemsInRow - 1) * 0.5;
  return [centeredColumn * 3.35, -10.7 + row * 2.25];
}

function armoredCount(wave: number, count: number) {
  if (wave % 3 !== 0) return 0;
  return Math.min(count, 1 + Math.floor(wave / 6));
}

function createWave(wave: number, startsAt: number, randomState: number) {
  const count = aetherShipCount(wave);
  const armored = armoredCount(wave, count);
  const ships: AetherShip[] = [];
  let nextState = randomState;
  for (let index = 0; index < count; index += 1) {
    const random = randomUnit(nextState);
    nextState = random.state;
    const formation = formationPosition(index, count);
    const entersFromLeft = index % 2 === 0;
    const start: WorldPosition = [
      entersFromLeft ? HOCKEY_ARENA_MIN_X - 4 - random.value * 3 : HOCKEY_ARENA_MAX_X + 4 + random.value * 3,
      HOCKEY_ARENA_MIN_Z - 2.5 - (index % 4) * 0.65,
    ];
    const kind: AetherShipKind = index >= count - armored ? "armored" : "standard";
    const spawnAt = startsAt + index * 0.11;
    ships.push({
      id: `${wave}:${index}`,
      kind,
      hp: kind === "armored" ? 3 : 1,
      maxHp: kind === "armored" ? 3 : 1,
      position: [...start],
      formationPosition: formation,
      phase: "entering",
      phaseStartedAt: spawnAt,
      phaseEndsAt: spawnAt + ENTRY_DURATION,
      startPosition: [...start],
      targetPosition: [...formation],
      contactResolved: false,
    });
  }
  return { ships, randomState: nextState };
}

export function createAetherAssaultState(active = false, requestedSeed = 1): AetherAssaultState {
  const seed = normalizeSeed(requestedSeed);
  const wave = createWave(1, 0, seed);
  return {
    status: active ? "live" : "inactive",
    seed,
    randomState: wave.randomState,
    wave: 1,
    score: 0,
    kills: 0,
    killStreak: 0,
    multiplier: 1,
    ships: active ? wave.ships : [],
    playerShots: [],
    enemyShots: [],
    nextProjectileId: 1,
    nextPlayerShotAt: 0,
    nextEnemyShotAt: 1.2,
    nextDiveAt: 4,
    nextWaveAt: null,
    lastPlayerHitAt: Number.NEGATIVE_INFINITY,
    lastKillAt: Number.NEGATIVE_INFINITY,
    lastKillScore: 0,
  };
}

function cloneShip(ship: AetherShip): AetherShip {
  return {
    ...ship,
    position: [...ship.position],
    formationPosition: [...ship.formationPosition],
    startPosition: [...ship.startPosition],
    targetPosition: [...ship.targetPosition],
  };
}

function easeOutCubic(value: number) {
  return 1 - (1 - value) ** 3;
}

function lerpPosition(start: WorldPosition, end: WorldPosition, progress: number): WorldPosition {
  return [start[0] + (end[0] - start[0]) * progress, start[1] + (end[1] - start[1]) * progress];
}

function divePosition(ship: AetherShip, progress: number): WorldPosition {
  const controlX = ship.targetPosition[0] + Math.sign(ship.targetPosition[0] - ship.startPosition[0] || 1) * 3.2;
  const controlZ = (ship.startPosition[1] + ship.targetPosition[1]) * 0.5 - 1.5;
  const inverse = 1 - progress;
  return [
    inverse * inverse * ship.startPosition[0] + 2 * inverse * progress * controlX + progress * progress * ship.targetPosition[0],
    inverse * inverse * ship.startPosition[1] + 2 * inverse * progress * controlZ + progress * progress * ship.targetPosition[1],
  ];
}

function segmentDistanceSquared(start: WorldPosition, end: WorldPosition, point: WorldPosition) {
  const dx = end[0] - start[0];
  const dz = end[1] - start[1];
  const lengthSquared = dx * dx + dz * dz;
  const projection = lengthSquared < 0.000001
    ? 0
    : Math.max(0, Math.min(1, ((point[0] - start[0]) * dx + (point[1] - start[1]) * dz) / lengthSquared));
  const nearestX = start[0] + dx * projection;
  const nearestZ = start[1] + dz * projection;
  return (point[0] - nearestX) ** 2 + (point[1] - nearestZ) ** 2;
}

function canDamagePlayer(state: AetherAssaultState, time: number) {
  return time - state.lastPlayerHitAt >= AETHER_HIT_GRACE_SECONDS;
}

function registerPlayerHit(state: AetherAssaultState, time: number, damage: number) {
  if (!canDamagePlayer(state, time)) return 0;
  state.lastPlayerHitAt = time;
  state.killStreak = 0;
  state.multiplier = 1;
  return damage;
}

function awardKill(state: AetherAssaultState, ship: AetherShip, time: number) {
  state.kills += 1;
  state.killStreak += 1;
  state.multiplier = aetherMultiplier(state.killStreak);
  const base = ship.kind === "armored" ? AETHER_ARMORED_SCORE : AETHER_STANDARD_SCORE;
  const award = Math.round(base * state.multiplier);
  state.score += award;
  state.lastKillAt = time;
  state.lastKillScore = award;
}

function chooseShip(state: AetherAssaultState, candidates: readonly AetherShip[]) {
  if (!candidates.length) return undefined;
  const random = randomUnit(state.randomState);
  state.randomState = random.state;
  return candidates[Math.min(candidates.length - 1, Math.floor(random.value * candidates.length))];
}

function enemyFireInterval(wave: number) {
  return Math.max(0.52, 1.85 - Math.max(0, wave - 1) * 0.055);
}

function diveInterval(wave: number) {
  return Math.max(1.7, 4.4 - Math.max(0, wave - 1) * 0.11);
}

export function advanceAetherAssault(source: AetherAssaultState, step: AetherAssaultStep): AetherAssaultAdvance {
  if (source.status !== "live" || step.delta <= 0) return { state: source, playerDamage: 0 };

  const state: AetherAssaultState = {
    ...source,
    ships: source.ships.map(cloneShip),
    playerShots: source.playerShots.map((shot) => ({ ...shot, position: [...shot.position], velocity: [...shot.velocity] })),
    enemyShots: source.enemyShots.map((shot) => ({ ...shot, position: [...shot.position], velocity: [...shot.velocity] })),
  };
  let playerDamage = 0;

  if (step.canAutoFire) {
    while (state.nextPlayerShotAt <= step.time && state.playerShots.length < AETHER_MAX_PLAYER_SHOTS) {
      state.playerShots.push({
        id: state.nextProjectileId,
        position: [step.playerPosition[0], step.playerPosition[1] - 0.7],
        velocity: [0, -PLAYER_SHOT_SPEED],
      });
      state.nextProjectileId += 1;
      state.nextPlayerShotAt += 1 / AETHER_PLAYER_SHOTS_PER_SECOND;
    }
  } else if (state.nextPlayerShotAt < step.time) {
    state.nextPlayerShotAt = step.time;
  }

  for (const ship of state.ships) {
    if (step.time < ship.phaseStartedAt) continue;
    const previous = [...ship.position] as WorldPosition;
    if (ship.phase === "entering") {
      const progress = Math.max(0, Math.min(1, (step.time - ship.phaseStartedAt) / Math.max(0.001, ship.phaseEndsAt - ship.phaseStartedAt)));
      ship.position = lerpPosition(ship.startPosition, ship.formationPosition, easeOutCubic(progress));
      if (progress >= 1) {
        ship.phase = "formation";
        ship.position = [...ship.formationPosition];
      }
    } else if (ship.phase === "diving") {
      const progress = Math.max(0, Math.min(1, (step.time - ship.phaseStartedAt) / DIVE_DURATION));
      ship.position = divePosition(ship, progress);
      if (!ship.contactResolved
        && segmentDistanceSquared(previous, ship.position, step.playerPosition) <= DIVE_HIT_RADIUS ** 2) {
        ship.contactResolved = true;
        playerDamage += registerPlayerHit(state, step.time, AETHER_DIVE_DAMAGE);
      }
      if (progress >= 1) {
        ship.phase = "returning";
        ship.phaseStartedAt = step.time;
        ship.phaseEndsAt = step.time + RETURN_DURATION;
        ship.startPosition = [...ship.position];
        ship.targetPosition = [...ship.formationPosition];
      }
    } else if (ship.phase === "returning") {
      const progress = Math.max(0, Math.min(1, (step.time - ship.phaseStartedAt) / RETURN_DURATION));
      ship.position = lerpPosition(ship.startPosition, ship.formationPosition, easeOutCubic(progress));
      if (progress >= 1) {
        ship.phase = "formation";
        ship.position = [...ship.formationPosition];
        ship.contactResolved = false;
      }
    }
  }

  if (step.time >= state.nextDiveAt) {
    const diver = chooseShip(state, state.ships.filter((ship) => ship.phase === "formation"));
    if (diver) {
      diver.phase = "diving";
      diver.phaseStartedAt = step.time;
      diver.phaseEndsAt = step.time + DIVE_DURATION;
      diver.startPosition = [...diver.position];
      diver.targetPosition = [step.playerPosition[0], HOCKEY_ARENA_MAX_Z + 1.8];
      diver.contactResolved = false;
      state.nextDiveAt = step.time + diveInterval(state.wave);
    } else {
      state.nextDiveAt = step.time + 0.25;
    }
  }

  if (step.time >= state.nextEnemyShotAt && state.enemyShots.length < AETHER_MAX_ENEMY_SHOTS) {
    const shooter = chooseShip(state, state.ships.filter((ship) => ship.phase === "formation" || ship.phase === "diving"));
    if (shooter) {
      const dx = step.playerPosition[0] - shooter.position[0];
      const dz = step.playerPosition[1] - shooter.position[1];
      const length = Math.max(0.001, Math.hypot(dx, dz));
      const speed = Math.min(8.5, 5.6 + state.wave * 0.08);
      state.enemyShots.push({
        id: state.nextProjectileId,
        position: [...shooter.position],
        velocity: [dx / length * speed, dz / length * speed],
      });
      state.nextProjectileId += 1;
      state.nextEnemyShotAt = step.time + enemyFireInterval(state.wave);
    } else {
      state.nextEnemyShotAt = step.time + 0.25;
    }
  }

  const survivingPlayerShots: AetherProjectile[] = [];
  for (const shot of state.playerShots) {
    const start = [...shot.position] as WorldPosition;
    const end: WorldPosition = [start[0] + shot.velocity[0] * step.delta, start[1] + shot.velocity[1] * step.delta];
    let hit: AetherShip | undefined;
    let hitDistance = Number.POSITIVE_INFINITY;
    for (const ship of state.ships) {
      if (step.time < ship.phaseStartedAt) continue;
      if (segmentDistanceSquared(start, end, ship.position) > (SHIP_RADIUS + PLAYER_SHOT_RADIUS) ** 2) continue;
      const distance = Math.hypot(ship.position[0] - start[0], ship.position[1] - start[1]);
      if (distance < hitDistance) {
        hit = ship;
        hitDistance = distance;
      }
    }
    if (hit) {
      hit.hp -= AETHER_PLAYER_SHOT_DAMAGE;
      if (hit.hp <= 0) awardKill(state, hit, step.time);
      continue;
    }
    shot.position = end;
    if (end[1] >= HOCKEY_ARENA_MIN_Z - 3 && Math.abs(end[0]) <= HOCKEY_ARENA_MAX_X + 4) survivingPlayerShots.push(shot);
  }
  state.playerShots = survivingPlayerShots;
  state.ships = state.ships.filter((ship) => ship.hp > 0);

  const survivingEnemyShots: AetherProjectile[] = [];
  for (const shot of state.enemyShots) {
    const start = [...shot.position] as WorldPosition;
    const end: WorldPosition = [start[0] + shot.velocity[0] * step.delta, start[1] + shot.velocity[1] * step.delta];
    if (segmentDistanceSquared(start, end, step.playerPosition) <= (PLAYER_HIT_RADIUS + ENEMY_SHOT_RADIUS) ** 2) {
      playerDamage += registerPlayerHit(state, step.time, AETHER_ENEMY_SHOT_DAMAGE);
      continue;
    }
    shot.position = end;
    if (end[1] <= HOCKEY_ARENA_MAX_Z + 3
      && end[1] >= HOCKEY_ARENA_MIN_Z - 3
      && Math.abs(end[0]) <= HOCKEY_ARENA_MAX_X + 4) {
      survivingEnemyShots.push(shot);
    }
  }
  state.enemyShots = survivingEnemyShots;

  if (state.ships.length === 0 && state.nextWaveAt === null) {
    state.score += aetherWaveClearBonus(state.wave);
    state.nextWaveAt = step.time + AETHER_WAVE_CLEAR_DELAY;
  }
  if (state.nextWaveAt !== null && step.time >= state.nextWaveAt) {
    state.wave += 1;
    const wave = createWave(state.wave, step.time, state.randomState);
    state.ships = wave.ships;
    state.randomState = wave.randomState;
    state.nextWaveAt = null;
    state.nextEnemyShotAt = step.time + Math.min(1.2, enemyFireInterval(state.wave));
    state.nextDiveAt = step.time + Math.min(3, diveInterval(state.wave));
  }

  return { state, playerDamage };
}
