// Player movement. Pure maths — the scene owns the sprite, this owns the weight.
// You are a big unit: slow to start, slower to stop, and you slide off walls
// rather than sticking to them.

import { isWalkableWorld } from './venueMap';
import type { VenueMap } from './venueMap';

export interface PlayerInput { up: boolean; down: boolean; left: boolean; right: boolean }

export interface PlayerState {
  x: number; y: number;       // world px
  vx: number; vy: number;     // px/sec
  facing: number;             // radians, persists when stationary
}

export const PLAYER_ACCEL = 620;   // px/s^2
export const PLAYER_MAX_SPEED = 116;
export const PLAYER_FRICTION = 7;  // per second, exponential damping
export const PLAYER_RADIUS = 5;

// A stalled tab hands back one enormous delta; cap it so nobody blinks through a
// wall. At the cap a full-speed step is ~6px, well under a tile.
const MAX_STEP_MS = 50;

// Below this the heading is noise, so the torch keeps pointing where it was.
const FACING_EPS = 4;

export function freshPlayer(x: number, y: number): PlayerState {
  return { x, y, vx: 0, vy: 0, facing: 0 };
}

/** The 4 cardinal edge points of the body circle must all be on walkable floor. */
function canStand(map: VenueMap, x: number, y: number): boolean {
  return isWalkableWorld(map, x + PLAYER_RADIUS, y)
    && isWalkableWorld(map, x - PLAYER_RADIUS, y)
    && isWalkableWorld(map, x, y + PLAYER_RADIUS)
    && isWalkableWorld(map, x, y - PLAYER_RADIUS);
}

/** Mutates nothing; returns the next state. Slides along walls (axis-separated collision). */
export function stepPlayer(s: PlayerState, input: PlayerInput, deltaMs: number, map: VenueMap): PlayerState {
  const dt = Math.max(0, Math.min(deltaMs, MAX_STEP_MS)) / 1000;

  let ix = (input.right ? 1 : 0) - (input.left ? 1 : 0);
  let iy = (input.down ? 1 : 0) - (input.up ? 1 : 0);
  const mag = Math.hypot(ix, iy);
  if (mag > 0) { ix /= mag; iy /= mag; }

  let vx = s.vx + ix * PLAYER_ACCEL * dt;
  let vy = s.vy + iy * PLAYER_ACCEL * dt;

  // Friction only bites on an axis you aren't driving, so held input still tops
  // out at PLAYER_MAX_SPEED instead of settling at accel/friction.
  const damp = Math.exp(-PLAYER_FRICTION * dt);
  if (ix === 0) vx *= damp;
  if (iy === 0) vy *= damp;

  const speed = Math.hypot(vx, vy);
  if (speed > PLAYER_MAX_SPEED) {
    const k = PLAYER_MAX_SPEED / speed;
    vx *= k;
    vy *= k;
  }

  let x = s.x;
  let y = s.y;
  const nx = x + vx * dt;
  if (canStand(map, nx, y)) x = nx; else vx = 0;
  const ny = y + vy * dt;
  if (canStand(map, x, ny)) y = ny; else vy = 0;

  const moved = Math.hypot(vx, vy);
  const facing = moved > FACING_EPS ? Math.atan2(vy, vx) : s.facing;

  return { x, y, vx, vy, facing };
}
