// Arena maps, shared by server sim and client renderer. Height is a pure
// single-valued field: every point has one surface height, every slope is
// rideable, so gameplay collision stays 2D and only the visuals climb.
// Obstacles are solid axis-aligned walls: lethal 2D rectangles the sim
// collides against and the client extrudes into neon towers / barriers.
// FLAT GRID stays the untouched classic; Highlands and Night City carry the
// extras (tunnels, arch gates, boost pads). CIRCUIT exists only for race mode.

export const MAPS = {
  empty: { arena: 400, spawnRadius: 140 },
  ridge: { arena: 560, spawnRadius: 200, obstacles: ridgeGates(), padCount: 5 },
  // spawnRings sit inside the obstacle-free band (see cityBlocks)
  city: {
    arena: 560, spawnRadius: 150, spawnRings: [115, 185],
    obstacles: cityBlocks(), tunnels: cityTunnels(), padCount: 5,
  },
  circuit: {
    arena: 720, spawnRadius: 200, raceOnly: true,
    obstacles: circuitWalls(),
    gates: circuitGates(),
    waypoints: circuitWaypoints(),
    spawnGrid: { x0: 66, y0: 395, dx: 100 / 11, dy: 0, cols: 12, heading: -Math.PI / 2 },
    padSpots: [{ x: 400, y: 100 }, { x: 620, y: 400 }, { x: 320, y: 620 }, { x: 100, y: 320 }],
    pickupSpots: circuitGates().map((g) => ({ x: g.mx, y: g.my })),
  },
};

export const MODES = {
  classic: {},
  royale: { shrinkAt: 15, shrinkDuration: 75 },
  // races never shrink; short trails keep the circuit passable lap after lap
  race: { race: true, shrinkAt: 1e9, roundHardCap: 300, trailMaxArc: 220, raceLaps: 3 },
};

function finishRect(b) {
  b.minX = b.x - b.hw; b.maxX = b.x + b.hw;
  b.minY = b.y - b.hh; b.maxY = b.y + b.hh;
  b.segs = [
    [b.minX, b.minY, b.maxX, b.minY],
    [b.maxX, b.minY, b.maxX, b.maxY],
    [b.maxX, b.maxY, b.minX, b.maxY],
    [b.minX, b.maxY, b.minX, b.minY],
  ];
  return b;
}
function rect(x, y, hw, hh) { return finishRect({ x, y, hw, hh }); }

// Night City: rectangles as {x, y, hw, hh} (center + half extents, meters).
// The spawn circle (r=150 around center 280,280) must stay clear: downtown
// towers keep every corner inside r<110, districts start outside r>195, so
// riders always launch onto an open ring road.
function cityBlocks() {
  return [
    // downtown core, inside the spawn ring
    rect(280, 280, 16, 16),
    rect(236, 236, 17, 17),
    rect(324, 236, 17, 17),
    rect(236, 324, 17, 17),
    rect(324, 324, 17, 17),
    // the four avenue slabs are tunnels now: two thin side walls each, open at
    // both ends, roofed by the client — same lethal footprint edges as before
    rect(280, 47, 46, 3), rect(280, 77, 46, 3),
    rect(280, 483, 46, 3), rect(280, 513, 46, 3),
    rect(47, 280, 3, 46), rect(77, 280, 3, 46),
    rect(483, 280, 3, 46), rect(513, 280, 3, 46),
    // corner districts
    rect(100, 100, 22, 22),
    rect(460, 100, 22, 22),
    rect(100, 460, 22, 22),
    rect(460, 460, 22, 22),
    // mid-ring towers between the slabs and the corners
    rect(172, 78, 13, 19),
    rect(388, 78, 13, 19),
    rect(172, 482, 13, 19),
    rect(388, 482, 13, 19),
    rect(78, 172, 19, 13),
    rect(482, 172, 19, 13),
    rect(78, 388, 19, 13),
    rect(482, 388, 19, 13),
  ];
}

// tunnel shells for the renderer: roof + entrance arches over the corridors
function cityTunnels() {
  return [
    { x: 280, y: 62, hw: 46, hh: 18, dir: 'h' },
    { x: 280, y: 498, hw: 46, hh: 18, dir: 'h' },
    { x: 62, y: 280, hw: 18, hh: 46, dir: 'v' },
    { x: 498, y: 280, hw: 18, hh: 46, dir: 'v' },
  ];
}

// Highlands: two neon arch gates on the corner plateaus (pillar pairs the sim
// treats as lethal walls, the client tops with a beam). Both sit well off the
// 136/236 spawn rings around (280,280).
function ridgeGates() {
  return [
    rect(44, 60, 3.5, 3.5), rect(76, 60, 3.5, 3.5),
    rect(484, 500, 3.5, 3.5), rect(516, 500, 3.5, 3.5),
  ];
}

// Race circuit (arena 720): a clockwise ring corridor between the outer walls
// (inset 60) and the inner island (170..550), pinched by alternating chicane
// blocks on every straight. Corridor 108 wide, chicane gaps ~50.
function circuitWalls() {
  return [
    // outer ring
    rect(360, 60, 300, 2), rect(360, 660, 300, 2),
    rect(60, 360, 2, 300), rect(660, 360, 2, 300),
    // inner island
    rect(360, 360, 190, 190),
    // chicanes: outer-wall block then inner-island block per straight
    rect(250, 90, 12, 30), rect(470, 140, 12, 30),      // north
    rect(630, 250, 30, 12), rect(580, 470, 30, 12),     // east
    rect(470, 630, 12, 30), rect(250, 580, 12, 30),     // south
    rect(90, 470, 30, 12), rect(140, 250, 30, 12),      // west
  ];
}

// Ordered gates for lap scoring, clockwise from the NW corner; the last entry
// is the start/finish line on the west straight. Corner gates run from the
// outer corner to the inner-island corner so no line can be skipped.
function circuitGates() {
  const g = (x1, y1, x2, y2) => ({ x1, y1, x2, y2, mx: (x1 + x2) / 2, my: (y1 + y2) / 2 });
  return [
    g(62, 62, 170, 170),    // NW corner
    g(360, 62, 360, 170),   // north straight
    g(658, 62, 550, 170),   // NE corner
    g(550, 360, 658, 360),  // east straight
    g(658, 658, 550, 550),  // SE corner
    g(360, 550, 360, 658),  // south straight
    g(62, 658, 170, 550),   // SW corner
    g(62, 360, 170, 360),   // start / finish
  ];
}

// The racing line: a dense clockwise centerline loop that threads every
// chicane gap. Bots steer waypoint-to-waypoint and the sim aims respawned
// riders back along it. Starts at the finish line heading north (-y).
function circuitWaypoints() {
  return [
    [116, 360], [116, 320], [96, 280], [86, 250], [96, 215], [116, 180],
    [116, 116],
    [180, 116], [215, 132], [250, 145], [285, 132], [320, 116], [360, 116],
    [400, 116], [435, 100], [470, 86], [505, 100], [540, 116],
    [604, 116],
    [604, 180], [588, 215], [575, 250], [588, 285], [604, 320], [604, 360],
    [604, 400], [620, 435], [634, 470], [620, 505], [604, 540],
    [604, 604],
    [540, 604], [505, 588], [470, 575], [435, 588], [400, 604], [360, 604],
    [320, 604], [285, 620], [250, 634], [215, 620], [180, 604],
    [116, 604],
    [116, 540], [132, 505], [145, 470], [132, 435], [116, 400],
  ].map(([x, y]) => ({ x, y }));
}

export function insideObstacle(obstacles, x, y, pad = 0) {
  if (!obstacles) return false;
  for (const b of obstacles) {
    if (x >= b.minX - pad && x <= b.maxX + pad && y >= b.minY - pad && y <= b.maxY + pad) return true;
  }
  return false;
}

function smoothstep(e0, e1, x) {
  const t = Math.max(0, Math.min(1, (x - e0) / (e1 - e0)));
  return t * t * (3 - 2 * t);
}

// round-cornered plateau: full height inside r, smooth falloff over `soft`
function plateau(x, y, cx, cy, r, soft, h) {
  const d = Math.hypot(x - cx, y - cy);
  return h * (1 - smoothstep(r, r + soft, d));
}

// rectangular "building" with ramped smooth sides
function block(x, y, cx, cy, hw, hh, soft, h) {
  const dx = Math.max(0, Math.abs(x - cx) - hw);
  const dy = Math.max(0, Math.abs(y - cy) - hh);
  return h * (1 - smoothstep(0, soft, Math.hypot(dx, dy)));
}

// Features are laid out in 0..1 arena fractions so the field scales with size.
export function heightAt(map, x, y) {
  if (map !== 'ridge') return 0;
  const A = MAPS.ridge.arena;
  const u = x / A, v = y / A;
  let h = 0;
  // central mesa
  h = Math.max(h, plateau(u, v, 0.5, 0.5, 0.10, 0.09, 10));
  // two corner highlands
  h = Math.max(h, plateau(u, v, 0.16, 0.16, 0.09, 0.11, 14));
  h = Math.max(h, plateau(u, v, 0.84, 0.84, 0.09, 0.11, 14));
  // long ridge across the west side
  h = Math.max(h, block(u, v, 0.22, 0.62, 0.03, 0.16, 0.07, 8));
  // rideable rooftop blocks
  h = Math.max(h, block(u, v, 0.70, 0.28, 0.055, 0.055, 0.05, 7));
  h = Math.max(h, block(u, v, 0.42, 0.80, 0.075, 0.04, 0.05, 5));
  // gentle rolling floor so the flat parts aren't dead level
  h += 1.1 * (Math.sin(u * 19) * Math.cos(v * 16) + 1) * smoothstep(0.02, 0.1, Math.min(u, v, 1 - u, 1 - v));
  return h;
}
