import type { WorldPosition } from "./types";

export type HockeyHealingStatus = "inactive" | "live" | "lost";

export interface HockeyHealingState {
  status: HockeyHealingStatus;
  puckPosition: WorldPosition;
  puckVelocity: WorldPosition;
  aimDirection: WorldPosition;
  returns: number;
  paddleReturns: number;
  paddleX: number;
  paddleHitAt: number;
  npcGoals: number;
  serveIndex: number;
  lastReturnAt: number;
  lostAt: number | null;
}

export interface HockeyHealingStep {
  delta: number;
  time: number;
  playerPosition: WorldPosition;
}

export const HOCKEY_ARENA_MIN_X = -10;
export const HOCKEY_ARENA_MAX_X = 10;
export const HOCKEY_ARENA_MIN_Z = -15;
export const HOCKEY_ARENA_MAX_Z = 13;
export const HOCKEY_ARENA_CENTER_Z = (HOCKEY_ARENA_MIN_Z + HOCKEY_ARENA_MAX_Z) * 0.5;
export const HOCKEY_ARENA_WIDTH = HOCKEY_ARENA_MAX_X - HOCKEY_ARENA_MIN_X;
export const HOCKEY_ARENA_LENGTH = HOCKEY_ARENA_MAX_Z - HOCKEY_ARENA_MIN_Z;
export const HOCKEY_MIDLINE_Z = -1;
export const HOCKEY_GOAL_HALF_WIDTH = 8;
export const HOCKEY_HEALER_GOAL_Z = 12.5;
export const HOCKEY_NPC_GOAL_Z = -14.5;
export const HOCKEY_NPC_PADDLE_Z = -13.35;
export const HOCKEY_NPC_PADDLE_WIDTH = 4.5;
export const HOCKEY_NPC_PADDLE_HALF_WIDTH = HOCKEY_NPC_PADDLE_WIDTH * 0.5;
export const HOCKEY_PUCK_RADIUS = 0.42;
export const HOCKEY_PLAYER_INTERCEPT_RADIUS = 1.05;
export const HOCKEY_AIM_PREVIEW_SECONDS = 1.5;

const STARTING_SPEED = 6.2;
const MAX_SPEED = 10.7;
const MAX_SUBSTEPS = 8;
const MAX_SUBSTEP_DISTANCE = 0.34;
const AIM_DEAD_ZONE = 0.12;
const MIN_FORWARD_COMPONENT = 0.3;
const NPC_PADDLE_SPEED = 12;
const NPC_PADDLE_MAX_REBOUND_X = 0.76;
const INCOMING_LANES = [0, -0.62, 0.7, -0.28, 0.38, -0.82, 0.86, -0.48, 0.18] as const;
const PADDLE_CONTACT_OFFSETS = [0, -0.48, 0.56, -0.26, 0.34, -0.62, 0.64, -0.38, 0.2] as const;

const HOCKEY_NPC_PADDLE_HIT_Z = HOCKEY_NPC_PADDLE_Z + HOCKEY_PUCK_RADIUS + 0.28;

export const HOCKEY_DEFAULT_PLAYER_POSITION: WorldPosition = [0, 8.5];

export function hockeyPuckSpeed(returns: number) {
  return Math.min(MAX_SPEED, STARTING_SPEED + Math.max(0, returns) * 0.18);
}

function velocityToward(start: WorldPosition, target: WorldPosition, speed: number): WorldPosition {
  const dx = target[0] - start[0];
  const dz = target[1] - start[1];
  const length = Math.max(0.0001, Math.hypot(dx, dz));
  return [dx / length * speed, dz / length * speed];
}

function incomingVelocity(position: WorldPosition, serveIndex: number, returns: number) {
  const lane = INCOMING_LANES[serveIndex % INCOMING_LANES.length];
  return velocityToward(
    position,
    [lane * HOCKEY_GOAL_HALF_WIDTH, HOCKEY_HEALER_GOAL_Z],
    hockeyPuckSpeed(returns),
  );
}

export function createHockeyHealingState(active = false): HockeyHealingState {
  const puckPosition: WorldPosition = [0, HOCKEY_NPC_PADDLE_HIT_Z + 0.08];
  return {
    status: active ? "live" : "inactive",
    puckPosition,
    puckVelocity: active ? incomingVelocity(puckPosition, 0, 0) : [0, 0],
    aimDirection: [0, -1],
    returns: 0,
    paddleReturns: 0,
    paddleX: 0,
    paddleHitAt: Number.NEGATIVE_INFINITY,
    npcGoals: 0,
    serveIndex: 0,
    lastReturnAt: 0,
    lostAt: null,
  };
}

export function setHockeyAim(state: HockeyHealingState, aim: WorldPosition): HockeyHealingState {
  const x = Number.isFinite(aim[0]) ? aim[0] : 0;
  const z = Number.isFinite(aim[1]) ? aim[1] : 0;
  if (state.aimDirection[0] === x && state.aimDirection[1] === z) return state;
  return { ...state, aimDirection: [x, z] };
}

/** Maps world-space left-stick movement into opponent-facing puck travel. */
export function hockeyReturnDirection(aim: WorldPosition): WorldPosition {
  const magnitude = Math.hypot(aim[0], aim[1]);
  if (magnitude < AIM_DEAD_ZONE) return [0, -1];
  const x = aim[0] / magnitude;
  const forward = Math.max(MIN_FORWARD_COMPONENT, -aim[1] / magnitude);
  const length = Math.hypot(x, forward);
  return [x / length, -forward / length];
}

/** Pong-style rebound: edge hits travel wider while every return faces the healer goal. */
export function hockeyPaddleReturnDirection(contactOffset: number): WorldPosition {
  const normalizedOffset = Math.max(-1, Math.min(1, contactOffset / HOCKEY_NPC_PADDLE_HALF_WIDTH));
  const x = normalizedOffset * NPC_PADDLE_MAX_REBOUND_X;
  return [x, Math.sqrt(Math.max(0.001, 1 - x * x))];
}

function moveToward(current: number, target: number, distance: number) {
  if (Math.abs(target - current) <= distance) return target;
  return current + Math.sign(target - current) * distance;
}

function paddleTargetX(puckX: number, serveIndex: number, returningToPaddle: boolean) {
  const contactOffset = returningToPaddle
    ? PADDLE_CONTACT_OFFSETS[serveIndex % PADDLE_CONTACT_OFFSETS.length] * HOCKEY_NPC_PADDLE_HALF_WIDTH
    : 0;
  return Math.max(
    HOCKEY_ARENA_MIN_X + HOCKEY_NPC_PADDLE_HALF_WIDTH,
    Math.min(HOCKEY_ARENA_MAX_X - HOCKEY_NPC_PADDLE_HALF_WIDTH, puckX - contactOffset),
  );
}

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

export function hockeyAimPreviewVisible(state: HockeyHealingState, playerPosition: WorldPosition) {
  if (state.status !== "live" || state.puckVelocity[1] <= 0) return false;
  const distance = Math.hypot(
    playerPosition[0] - state.puckPosition[0],
    playerPosition[1] - state.puckPosition[1],
  );
  return distance / Math.max(0.001, Math.hypot(state.puckVelocity[0], state.puckVelocity[1])) <= HOCKEY_AIM_PREVIEW_SECONDS;
}

export function advanceHockeyHealing(source: HockeyHealingState, step: HockeyHealingStep): HockeyHealingState {
  if (source.status !== "live" || step.delta <= 0) return source;

  const state: HockeyHealingState = {
    ...source,
    puckPosition: [...source.puckPosition],
    puckVelocity: [...source.puckVelocity],
    aimDirection: [...source.aimDirection],
  };
  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_PLAYER_INTERCEPT_RADIUS + HOCKEY_PUCK_RADIUS) ** 2;

  for (let substep = 0; substep < substeps && state.status === "live"; substep += 1) {
    const startX = state.puckPosition[0];
    const startZ = state.puckPosition[1];
    let endX = startX + state.puckVelocity[0] * subDelta;
    let endZ = startZ + state.puckVelocity[1] * subDelta;

    const minPuckX = HOCKEY_ARENA_MIN_X + HOCKEY_PUCK_RADIUS;
    const maxPuckX = HOCKEY_ARENA_MAX_X - HOCKEY_PUCK_RADIUS;
    if (endX < minPuckX || endX > maxPuckX) {
      endX = Math.max(minPuckX, Math.min(maxPuckX, endX));
      state.puckVelocity[0] *= -1;
    }

    const returningToPaddle = state.puckVelocity[1] < 0;
    const paddleTarget = paddleTargetX(endX, state.serveIndex, returningToPaddle);
    state.paddleX = moveToward(state.paddleX, paddleTarget, NPC_PADDLE_SPEED * subDelta);

    if (state.puckVelocity[1] > 0 && segmentDistanceSquared(startX, startZ, endX, endZ, step.playerPosition) <= interceptRadiusSquared) {
      const direction = hockeyReturnDirection(state.aimDirection);
      const nextReturns = state.returns + 1;
      const returnSpeed = hockeyPuckSpeed(nextReturns);
      state.puckPosition[0] = endX;
      state.puckPosition[1] = Math.min(endZ, step.playerPosition[1]);
      state.puckVelocity[0] = direction[0] * returnSpeed;
      state.puckVelocity[1] = direction[1] * returnSpeed;
      state.returns = nextReturns;
      state.lastReturnAt = step.time;
      continue;
    }

    if (state.puckVelocity[1] > 0 && endZ >= HOCKEY_HEALER_GOAL_Z) {
      state.puckPosition[0] = endX;
      state.puckPosition[1] = HOCKEY_HEALER_GOAL_Z;
      if (Math.abs(endX) <= HOCKEY_GOAL_HALF_WIDTH) {
        state.status = "lost";
        state.puckVelocity = [0, 0];
        state.lostAt = step.time;
      } else {
        state.puckVelocity[1] = -Math.abs(state.puckVelocity[1]);
      }
      continue;
    }

    if (state.puckVelocity[1] < 0 && startZ >= HOCKEY_NPC_PADDLE_HIT_Z && endZ <= HOCKEY_NPC_PADDLE_HIT_Z) {
      const travelRatio = Math.max(0, Math.min(1, (HOCKEY_NPC_PADDLE_HIT_Z - startZ) / Math.min(-0.0001, endZ - startZ)));
      const hitX = startX + (endX - startX) * travelRatio;
      const contactOffset = hitX - state.paddleX;
      if (Math.abs(contactOffset) <= HOCKEY_NPC_PADDLE_HALF_WIDTH + HOCKEY_PUCK_RADIUS) {
        const direction = hockeyPaddleReturnDirection(contactOffset);
        const returnSpeed = hockeyPuckSpeed(state.returns);
        state.puckPosition = [hitX, HOCKEY_NPC_PADDLE_HIT_Z];
        state.puckVelocity = [direction[0] * returnSpeed, direction[1] * returnSpeed];
        state.paddleReturns += 1;
        state.paddleHitAt = step.time;
        state.serveIndex += 1;
        continue;
      }
    }

    if (state.puckVelocity[1] < 0 && endZ <= HOCKEY_NPC_GOAL_Z) {
      state.npcGoals += 1;
      state.serveIndex += 1;
      state.paddleX = paddleTargetX(endX, state.serveIndex, false);
      state.puckPosition = [state.paddleX, HOCKEY_NPC_PADDLE_HIT_Z];
      state.puckVelocity = incomingVelocity(state.puckPosition, state.serveIndex, state.returns);
      state.paddleReturns += 1;
      state.paddleHitAt = step.time;
      continue;
    }

    state.puckPosition[0] = endX;
    state.puckPosition[1] = endZ;
  }

  return state;
}
