import {
  ARENA_CENTER,
  ARENA_RADIUS,
  ARENA_WALL_RADIUS,
  clampToArena,
} from "../arena";
import type { WorldPosition } from "../types";

/** North follows Three.js convention toward negative Z; south points toward positive Z. */
export type BossArenaPortalSide = "north" | "south";

export type BossArenaPortalOpenState = Readonly<Record<BossArenaPortalSide, boolean>>;

export const CLOSED_BOSS_ARENA_PORTALS: BossArenaPortalOpenState = {
  north: false,
  south: false,
};

export const NORTH_OPEN_BOSS_ARENA_PORTALS: BossArenaPortalOpenState = {
  north: true,
  south: false,
};

/** Half-width of the walkable opening between each doorway frame. */
export const BOSS_ARENA_PORTAL_HALF_WIDTH = 1.45;
/** Overlap keeps each rectangular throat connected to the circular arena. */
export const BOSS_ARENA_PORTAL_THROAT_OVERLAP = 0.75;
/** Walkable space beyond the visible wall before the room transition fires. */
export const BOSS_ARENA_PORTAL_OUTER_DEPTH = 1.5;
/** Exit plane sits just beyond the doorway instead of at the arena's combat edge. */
export const BOSS_ARENA_PORTAL_EXIT_DEPTH = 0.55;

interface Bounds {
  minX: number;
  maxX: number;
  minZ: number;
  maxZ: number;
}

function normalizedPadding(padding: number) {
  return Number.isFinite(padding) ? Math.max(0, padding) : 0;
}

function portalBounds(side: BossArenaPortalSide, padding: number): Bounds | null {
  const safePadding = normalizedPadding(padding);
  const halfWidth = Math.max(0, BOSS_ARENA_PORTAL_HALF_WIDTH - safePadding);
  if (halfWidth <= 0) return null;

  const arenaDistance = Math.max(0, ARENA_RADIUS - safePadding);
  const innerDistance = Math.max(0, arenaDistance - BOSS_ARENA_PORTAL_THROAT_OVERLAP);
  const outerDistance = Math.max(
    innerDistance,
    ARENA_WALL_RADIUS + BOSS_ARENA_PORTAL_OUTER_DEPTH - safePadding,
  );
  const centerZ = ARENA_CENTER[1];

  return side === "north"
    ? {
        minX: ARENA_CENTER[0] - halfWidth,
        maxX: ARENA_CENTER[0] + halfWidth,
        minZ: centerZ - outerDistance,
        maxZ: centerZ - innerDistance,
      }
    : {
        minX: ARENA_CENTER[0] - halfWidth,
        maxX: ARENA_CENTER[0] + halfWidth,
        minZ: centerZ + innerDistance,
        maxZ: centerZ + outerDistance,
      };
}

function contains(bounds: Bounds, position: WorldPosition) {
  return position[0] >= bounds.minX
    && position[0] <= bounds.maxX
    && position[1] >= bounds.minZ
    && position[1] <= bounds.maxZ;
}

function clampToBounds(bounds: Bounds, position: WorldPosition): WorldPosition {
  return [
    Math.max(bounds.minX, Math.min(bounds.maxX, position[0])),
    Math.max(bounds.minZ, Math.min(bounds.maxZ, position[1])),
  ];
}

function distanceSquared(left: WorldPosition, right: WorldPosition) {
  return (left[0] - right[0]) ** 2 + (left[1] - right[1]) ** 2;
}

/**
 * Clamps movement to the circular boss arena plus any currently open doorway
 * throats. With both portals closed this is exactly the existing arena clamp.
 */
export function clampToBossArenaWithPortals(
  position: WorldPosition,
  portals: BossArenaPortalOpenState = CLOSED_BOSS_ARENA_PORTALS,
  padding = 0,
): WorldPosition {
  if (!portals.north && !portals.south) return clampToArena(position, padding);

  const safePadding = normalizedPadding(padding);
  const radius = Math.max(0, ARENA_RADIUS - safePadding);
  const offsetX = position[0] - ARENA_CENTER[0];
  const offsetZ = position[1] - ARENA_CENTER[1];
  if (Math.hypot(offsetX, offsetZ) <= radius) return [position[0], position[1]];

  const openBounds = (["north", "south"] as const)
    .filter((side) => portals[side])
    .map((side) => portalBounds(side, safePadding))
    .filter((bounds): bounds is Bounds => bounds !== null);

  if (openBounds.some((bounds) => contains(bounds, position))) {
    return [position[0], position[1]];
  }

  let nearest = clampToArena(position, safePadding);
  let nearestDistance = distanceSquared(position, nearest);
  for (const bounds of openBounds) {
    const candidate = clampToBounds(bounds, position);
    const candidateDistance = distanceSquared(position, candidate);
    if (candidateDistance < nearestDistance) {
      nearest = candidate;
      nearestDistance = candidateDistance;
    }
  }
  return nearest;
}

/** Returns the open portal crossed past its visible wall plane, if any. */
export function detectBossArenaExit(
  position: WorldPosition,
  portals: BossArenaPortalOpenState = CLOSED_BOSS_ARENA_PORTALS,
  padding = 0,
): BossArenaPortalSide | null {
  const halfWidth = Math.max(0, BOSS_ARENA_PORTAL_HALF_WIDTH - normalizedPadding(padding));
  if (Math.abs(position[0] - ARENA_CENTER[0]) > halfWidth) return null;

  const exitDistance = ARENA_WALL_RADIUS + BOSS_ARENA_PORTAL_EXIT_DEPTH;
  const offsetZ = position[1] - ARENA_CENTER[1];
  if (portals.north && offsetZ <= -exitDistance) return "north";
  if (portals.south && offsetZ >= exitDistance) return "south";
  return null;
}
