import { AVAILABLE_BOSS_IDS } from "./bossCatalog";
import { hockeyReturnDirection } from "./hockeyHealing";
import type { BossId, BossMotionMode, PartyMember, WorldPosition } from "./types";

export type HockeyPvpRole = "cpu" | "host" | "guest";
export type HockeyPvpGoalSide = "local" | "opponent";

export interface HockeyPvpMatchConfig {
  matchId: string | null;
  seed: number;
  opponentName: string;
  role: HockeyPvpRole;
}

export interface HockeyPvpPuckState {
  puckPosition: WorldPosition;
  puckVelocity: WorldPosition;
  localReturns: number;
  opponentReturns: number;
  localGoalsConceded: number;
  opponentGoalsConceded: number;
  goalSequence: number;
  lastGoalSide: HockeyPvpGoalSide | null;
  serveIndex: number;
}

export interface HockeyPvpState extends HockeyPvpMatchConfig, HockeyPvpPuckState {
  status: "inactive" | "live" | "won" | "lost";
  aimDirection: WorldPosition;
  opponentBossKills: number;
  opponentPlayerPosition: WorldPosition;
  opponentAimDirection: WorldPosition;
  nextCpuHealAt: number;
  networkSequence: number;
  appliedGoalSequence: number;
}

export interface HockeyPvpRemoteSnapshot {
  sequence: number;
  time: number;
  party: PartyMember[];
  partyPositions: Record<PartyMember["id"], WorldPosition>;
  boss: { id: BossId; name: string; hp: number; maxHp: number };
  bossPosition: WorldPosition;
  bossMode: BossMotionMode;
  bossKills: number;
  playerPosition: WorldPosition;
  aimDirection: WorldPosition;
  puck?: HockeyPvpPuckState;
}

export const HOCKEY_PVP_SIDE_OFFSET_Z = 11;
export const HOCKEY_PVP_ARENA_MIN_X = -10;
export const HOCKEY_PVP_ARENA_MAX_X = 10;
export const HOCKEY_PVP_ARENA_MIN_Z = -25;
export const HOCKEY_PVP_ARENA_MAX_Z = 25;
export const HOCKEY_PVP_GOAL_Z = 23.5;
export const HOCKEY_PVP_GOAL_HALF_WIDTH = 8;
export const HOCKEY_PVP_PUCK_RADIUS = 0.42;
export const HOCKEY_PVP_INTERCEPT_RADIUS = 1.05;
export const HOCKEY_PVP_GOAL_DAMAGE = 45;
export const HOCKEY_PVP_BOSS_DAMAGE_MULTIPLIER = 1.5;
export const HOCKEY_PVP_DAMPENING_PER_BOSS_PERCENT = 5;
export const HOCKEY_PVP_QUEUE_TIMEOUT_MS = 5_000;

const STARTING_SPEED = 7.8;
const MAX_SPEED = 12.2;
const MAX_SUBSTEPS = 10;
const MAX_SUBSTEP_DISTANCE = 0.32;
const GUEST_RECONCILIATION_BLEND = 0.35;
const GUEST_RECONCILIATION_SNAP_DISTANCE = 3;
const SERVE_LANES = [0, -0.46, 0.58, -0.25, 0.34, -0.7, 0.74] as const;

export function hockeyPvpDampeningPercent(localBossKills: number, opponentBossKills: number): number {
  const totalBossKills = Math.max(0, Math.floor(localBossKills)) + Math.max(0, Math.floor(opponentBossKills));
  return Math.min(100, totalBossKills * HOCKEY_PVP_DAMPENING_PER_BOSS_PERCENT);
}

export function hockeyPvpHealingEffectiveness(localBossKills: number, opponentBossKills: number): number {
  return 1 - hockeyPvpDampeningPercent(localBossKills, opponentBossKills) / 100;
}

function normalizedSeed(seed: number) {
  return Math.max(1, Math.floor(Math.abs(Number(seed) || 1))) >>> 0;
}

export function hockeyPvpBossAt(seed: number, index: number): BossId {
  let value = (normalizedSeed(seed) + Math.max(0, Math.floor(index)) * 0x9e3779b9) >>> 0;
  value ^= value >>> 16;
  value = Math.imul(value, 0x21f0aaad) >>> 0;
  value ^= value >>> 15;
  value = Math.imul(value, 0x735a2d97) >>> 0;
  value ^= value >>> 15;
  return AVAILABLE_BOSS_IDS[(value >>> 0) % AVAILABLE_BOSS_IDS.length];
}

export function hockeyPvpPuckSpeed(totalReturns: number) {
  return Math.min(MAX_SPEED, STARTING_SPEED + Math.max(0, totalReturns) * 0.16);
}

function serveVelocity(side: HockeyPvpGoalSide, serveIndex: number, totalReturns: number): WorldPosition {
  const x = SERVE_LANES[serveIndex % SERVE_LANES.length] * HOCKEY_PVP_GOAL_HALF_WIDTH;
  const z = side === "local" ? HOCKEY_PVP_GOAL_Z : -HOCKEY_PVP_GOAL_Z;
  const length = Math.max(0.001, Math.hypot(x, z));
  const speed = hockeyPvpPuckSpeed(totalReturns);
  return [x / length * speed, z / length * speed];
}

export function createHockeyPvpState(config?: HockeyPvpMatchConfig): HockeyPvpState {
  const match = config ?? { matchId: null, seed: 1, opponentName: "CPU Willow", role: "cpu" as const };
  return {
    ...match,
    status: config ? "live" : "inactive",
    puckPosition: [0, 0],
    puckVelocity: config ? serveVelocity("local", 0, 0) : [0, 0],
    localReturns: 0,
    opponentReturns: 0,
    localGoalsConceded: 0,
    opponentGoalsConceded: 0,
    goalSequence: 0,
    lastGoalSide: null,
    serveIndex: 0,
    aimDirection: [0, -1],
    opponentBossKills: 0,
    opponentPlayerPosition: [0, 8.5],
    opponentAimDirection: [0, -1],
    nextCpuHealAt: 1,
    networkSequence: 0,
    appliedGoalSequence: 0,
  };
}

export function hockeyPvpLocalToWorld(position: WorldPosition): WorldPosition {
  return [position[0], position[1] + HOCKEY_PVP_SIDE_OFFSET_Z];
}

export function hockeyPvpOpponentToWorld(position: WorldPosition): WorldPosition {
  return [-position[0], -position[1] - HOCKEY_PVP_SIDE_OFFSET_Z];
}

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 mirrorDirection(direction: WorldPosition): WorldPosition {
  return [-direction[0], -direction[1]];
}

function resetAfterGoal(state: HockeyPvpPuckState, side: HockeyPvpGoalSide) {
  state.goalSequence += 1;
  state.lastGoalSide = side;
  if (side === "local") state.localGoalsConceded += 1;
  else state.opponentGoalsConceded += 1;
  state.serveIndex += 1;
  state.puckPosition = [0, 0];
  state.puckVelocity = serveVelocity(side, state.serveIndex, state.localReturns + state.opponentReturns);
}

function predictGuestPuck(source: HockeyPvpState, delta: number): HockeyPvpState {
  const state: HockeyPvpState = {
    ...source,
    puckPosition: [...source.puckPosition],
    puckVelocity: [...source.puckVelocity],
  };
  const speed = Math.hypot(state.puckVelocity[0], state.puckVelocity[1]);
  const substeps = Math.max(1, Math.min(MAX_SUBSTEPS, Math.ceil(speed * delta / MAX_SUBSTEP_DISTANCE)));
  const subDelta = delta / substeps;
  const minX = HOCKEY_PVP_ARENA_MIN_X + HOCKEY_PVP_PUCK_RADIUS;
  const maxX = HOCKEY_PVP_ARENA_MAX_X - HOCKEY_PVP_PUCK_RADIUS;

  for (let index = 0; index < substeps; index += 1) {
    const end: WorldPosition = [
      state.puckPosition[0] + state.puckVelocity[0] * subDelta,
      state.puckPosition[1] + state.puckVelocity[1] * subDelta,
    ];
    if (end[0] < minX || end[0] > maxX) {
      end[0] = Math.max(minX, Math.min(maxX, end[0]));
      state.puckVelocity[0] *= -1;
    }
    if (end[1] >= HOCKEY_PVP_GOAL_Z) {
      end[1] = HOCKEY_PVP_GOAL_Z;
      if (Math.abs(end[0]) > HOCKEY_PVP_GOAL_HALF_WIDTH) state.puckVelocity[1] = -Math.abs(state.puckVelocity[1]);
    } else if (end[1] <= -HOCKEY_PVP_GOAL_Z) {
      end[1] = -HOCKEY_PVP_GOAL_Z;
      if (Math.abs(end[0]) > HOCKEY_PVP_GOAL_HALF_WIDTH) state.puckVelocity[1] = Math.abs(state.puckVelocity[1]);
    }
    state.puckPosition = end;
  }
  return state;
}

export function reconcileHockeyPvpPuck(
  predicted: HockeyPvpPuckState,
  authoritative: HockeyPvpPuckState,
): HockeyPvpPuckState {
  const errorX = authoritative.puckPosition[0] - predicted.puckPosition[0];
  const errorZ = authoritative.puckPosition[1] - predicted.puckPosition[1];
  const shouldSnap = authoritative.goalSequence !== predicted.goalSequence
    || Math.hypot(errorX, errorZ) >= GUEST_RECONCILIATION_SNAP_DISTANCE;
  return {
    ...authoritative,
    puckPosition: shouldSnap
      ? [...authoritative.puckPosition]
      : [
          predicted.puckPosition[0] + errorX * GUEST_RECONCILIATION_BLEND,
          predicted.puckPosition[1] + errorZ * GUEST_RECONCILIATION_BLEND,
        ],
    puckVelocity: [...authoritative.puckVelocity],
  };
}

export function advanceHockeyPvpPuck(
  source: HockeyPvpState,
  step: {
    delta: number;
    localPlayerPosition: WorldPosition;
    localAimDirection: WorldPosition;
    opponentPlayerPosition: WorldPosition;
    opponentAimDirection: WorldPosition;
  },
): HockeyPvpState {
  if (source.status !== "live" || step.delta <= 0) return source;
  // Guest predicts travel only. Host snapshots remain authoritative for contacts,
  // goals, damage, and rally counters.
  if (source.role === "guest") return predictGuestPuck(source, step.delta);
  const state: HockeyPvpState = {
    ...source,
    puckPosition: [...source.puckPosition],
    puckVelocity: [...source.puckVelocity],
  };
  const localPlayer = hockeyPvpLocalToWorld(step.localPlayerPosition);
  const opponentPlayer = hockeyPvpOpponentToWorld(step.opponentPlayerPosition);
  const speed = Math.hypot(state.puckVelocity[0], state.puckVelocity[1]);
  const substeps = Math.max(1, Math.min(MAX_SUBSTEPS, Math.ceil(speed * step.delta / MAX_SUBSTEP_DISTANCE)));
  const subDelta = step.delta / substeps;
  const interceptRadiusSquared = (HOCKEY_PVP_INTERCEPT_RADIUS + HOCKEY_PVP_PUCK_RADIUS) ** 2;

  for (let index = 0; index < substeps; index += 1) {
    const start: WorldPosition = [...state.puckPosition];
    const end: WorldPosition = [
      start[0] + state.puckVelocity[0] * subDelta,
      start[1] + state.puckVelocity[1] * subDelta,
    ];
    const minX = HOCKEY_PVP_ARENA_MIN_X + HOCKEY_PVP_PUCK_RADIUS;
    const maxX = HOCKEY_PVP_ARENA_MAX_X - HOCKEY_PVP_PUCK_RADIUS;
    if (end[0] < minX || end[0] > maxX) {
      end[0] = Math.max(minX, Math.min(maxX, end[0]));
      state.puckVelocity[0] *= -1;
    }

    if (state.puckVelocity[1] > 0 && segmentDistanceSquared(start, end, localPlayer) <= interceptRadiusSquared) {
      const direction = hockeyReturnDirection(step.localAimDirection);
      state.localReturns += 1;
      const returnSpeed = hockeyPvpPuckSpeed(state.localReturns + state.opponentReturns);
      state.puckPosition = [end[0], Math.min(end[1], localPlayer[1])];
      state.puckVelocity = [direction[0] * returnSpeed, direction[1] * returnSpeed];
      continue;
    }

    if (state.puckVelocity[1] < 0 && segmentDistanceSquared(start, end, opponentPlayer) <= interceptRadiusSquared) {
      const direction = mirrorDirection(hockeyReturnDirection(step.opponentAimDirection));
      state.opponentReturns += 1;
      const returnSpeed = hockeyPvpPuckSpeed(state.localReturns + state.opponentReturns);
      state.puckPosition = [end[0], Math.max(end[1], opponentPlayer[1])];
      state.puckVelocity = [direction[0] * returnSpeed, direction[1] * returnSpeed];
      continue;
    }

    if (state.puckVelocity[1] > 0 && end[1] >= HOCKEY_PVP_GOAL_Z) {
      if (Math.abs(end[0]) <= HOCKEY_PVP_GOAL_HALF_WIDTH) resetAfterGoal(state, "local");
      else {
        state.puckPosition = [end[0], HOCKEY_PVP_GOAL_Z];
        state.puckVelocity[1] = -Math.abs(state.puckVelocity[1]);
      }
      continue;
    }

    if (state.puckVelocity[1] < 0 && end[1] <= -HOCKEY_PVP_GOAL_Z) {
      if (Math.abs(end[0]) <= HOCKEY_PVP_GOAL_HALF_WIDTH) resetAfterGoal(state, "opponent");
      else {
        state.puckPosition = [end[0], -HOCKEY_PVP_GOAL_Z];
        state.puckVelocity[1] = Math.abs(state.puckVelocity[1]);
      }
      continue;
    }

    state.puckPosition = end;
  }
  return state;
}

export function advanceHockeyPvpCpuGoalie(
  position: WorldPosition,
  puckPosition: WorldPosition,
  delta: number,
): { position: WorldPosition; aimDirection: WorldPosition } {
  const opponentLocalPuckX = -puckPosition[0];
  const targetX = Math.max(-8.6, Math.min(8.6, opponentLocalPuckX));
  const maxMovement = 7.2 * Math.max(0, delta);
  const nextX = Math.abs(targetX - position[0]) <= maxMovement
    ? targetX
    : position[0] + Math.sign(targetX - position[0]) * maxMovement;
  const aimX = Math.max(-0.85, Math.min(0.85, -opponentLocalPuckX / HOCKEY_PVP_GOAL_HALF_WIDTH));
  return { position: [nextX, 8.5], aimDirection: [aimX, -1] };
}

export function mirrorHockeyPvpPuck(source: HockeyPvpPuckState): HockeyPvpPuckState {
  return {
    puckPosition: [-source.puckPosition[0], -source.puckPosition[1]],
    puckVelocity: [-source.puckVelocity[0], -source.puckVelocity[1]],
    localReturns: source.opponentReturns,
    opponentReturns: source.localReturns,
    localGoalsConceded: source.opponentGoalsConceded,
    opponentGoalsConceded: source.localGoalsConceded,
    goalSequence: source.goalSequence,
    lastGoalSide: source.lastGoalSide === "local" ? "opponent" : source.lastGoalSide === "opponent" ? "local" : null,
    serveIndex: source.serveIndex,
  };
}

export const HOCKEY_PVP_CPU_NAMES = [
  "Willow Warden",
  "Mender Nova",
  "Pulsekeeper",
  "Sage Rook",
  "Mercy Vale",
  "Aster Ward",
] as const;

export function randomHockeyPvpCpuName(random: () => number = Math.random) {
  return HOCKEY_PVP_CPU_NAMES[Math.floor(random() * HOCKEY_PVP_CPU_NAMES.length)] ?? HOCKEY_PVP_CPU_NAMES[0];
}
