// Boot + frame loop. Sizes the GL + HUD canvases to the viewport (DPR-aware), wires input, and runs
// a fixed-timestep accumulator (Bloodtread loop idiom): step update(STEP), then renderWorld + renderHud.
import { gl, glCanvas, hudCanvas } from './render/context.js';
import { view, cam, state, sprites } from './state.js';
import { STEP, MAX_STEPS } from './config.js';
import { updateCameraMetrics } from './render/camera.js';
import { update, startRun, arena, chooseUpgrade, mapColliders, mapSolids, mapEnemyHoles, mapCircleBlocked, reloadMapSolidDefs, mapSolidSource } from './update.js';
import { renderWorld, playerAnimationDebug, enemyFacingDebug } from './render/world.js';
import { renderHud, hudRects, upRects, bomzharaArtStatus } from './render/hud.js';
import { loadGameSprites, gameSpriteArtStatus } from './render/assets.js';
import { initInput, onPress } from './input.js';
import { player, bullets, enemies, parts, pickups, puddles, input } from './state.js';
import { unlockGameAudio, toggleGameAudioMuted, audioDebug } from './audio.js';

// debug/QA hook (experiment prototype): lets a headless test read run state
window.__BZ = {
  state: state, player: player, bullets: bullets, enemies: enemies, parts: parts, pickups: pickups, puddles: puddles, input: input,
  start: function () { startRun(); },
  reloadMapSolids: function () { return reloadMapSolidDefs(); },
  mapColliders: function () { return mapColliders(arena()).map(function (r) { return { id: r.id, type: r.type, x0: Math.round(r.x0), y0: Math.round(r.y0), x1: Math.round(r.x1), y1: Math.round(r.y1) }; }); },
  mapSolids: function () { return mapSolids(arena()).map(function (r) { return { id: r.id, x0: Math.round(r.x0), y0: Math.round(r.y0), x1: Math.round(r.x1), y1: Math.round(r.y1) }; }); },
  mapEnemyHoles: function () { return mapEnemyHoles(arena()).map(function (r) { return { id: r.id, x0: Math.round(r.x0), y0: Math.round(r.y0), x1: Math.round(r.x1), y1: Math.round(r.y1) }; }); },
  mapCircleBlocked: function (x, y, r) { return mapCircleBlocked(x, y, r || 1, arena()); },
  toggleAudio: toggleGameAudioMuted,
  audio: audioDebug,
};

window.render_game_to_text = function () {
  var visibleEnemies = [];
  for (var i = 0; i < Math.min(enemies.count, 8); i++) {
    var vx = enemies.vx[i];
    var facing = enemyFacingDebug(i);
    visibleEnemies.push({ x: Math.round(enemies.x[i]), y: Math.round(enemies.y[i]), vx: Math.round(vx), facing: facing.logical, spriteFacing: facing.sprite, gate: !!enemies.gateActive[i], gateX: Math.round(enemies.gateX[i]), gateY: Math.round(enemies.gateY[i]), real: !!enemies.real[i], type: enemies.type[i] });
  }
  return JSON.stringify({
    coords: 'origin top-left, +x right, +y down, CSS pixels',
    mode: state.mode,
    timeLeft: Math.max(0, Math.ceil(state.goalTime - state.survived)),
    objective: 'survive until ambulance arrives',
    art: { bomzharaLoaded: bomzharaArtStatus.loaded, bomzharaError: bomzharaArtStatus.error, bomzharaDeathLoaded: bomzharaArtStatus.deathLoaded, bomzharaDeathError: bomzharaArtStatus.deathError, spritesLoaded: gameSpriteArtStatus.loaded, spritesPending: gameSpriteArtStatus.pending, spritesError: gameSpriteArtStatus.error },
    audio: audioDebug(),
    map: { buildingLoaded: !!sprites.textures.map_building, source: mapSolidSource(), collisionRects: mapColliders(arena()).length, solidRects: mapSolids(arena()).length, enemyHoles: mapEnemyHoles(arena()).length, playerBlocked: mapCircleBlocked(player.x, player.y, player.r, arena()) },
    animation: { spriteOverlays: true, projectileSpin: true, enemySpriteFacing: true, legacyEnemyShapes: false, fizzSnowFire: true },
    weird: { kind: state.weirdKind, t: +state.weirdT.toFixed(2), siren: +state.sirenPulse.toFixed(2), blackout: +state.blackoutPulse.toFixed(2), window: +state.windowPulse.toFixed(2), possessed: [state.possessedX, state.possessedY] },
    horrorSeen: state.horrorSeen,
    input: { moveX: +input.moveX.toFixed(2), moveY: +input.moveY.toFixed(2) },
    player: { x: Math.round(player.x), y: Math.round(player.y), hp: +player.hp.toFixed(1), vodka: +player.vodka.toFixed(1), belochka: +player.belochka.toFixed(2) },
    playerAnimation: playerAnimationDebug(),
    firing: { fireCd: +player.fireCd.toFixed(2), attackT: +player.attackT.toFixed(2), attackMax: +player.attackMax.toFixed(2), throwQueued: !!player.throwQueued, throwT: +player.throwT.toFixed(2), fireMul: +player.up.fireMul.toFixed(2), recoil: +player.recoil.toFixed(2) },
    abilities: { molotov: player.up.molotov, ricochetSeek: player.up.ricochetSeek, shield: player.shield, shieldMax: player.up.shieldMax, ambulance: player.up.ambulance },
    bullets: bullets.count,
    enemies: enemies.count,
    puddles: puddles.count,
    visibleEnemies: visibleEnemies,
    kills: state.kills,
    level: state.level,
  });
};

function inRect(r, x, y) { return r && x >= r.x && x <= r.x + r.w && y >= r.y && y <= r.y + r.h; }

function resize() {
  var dpr = Math.min(window.devicePixelRatio || 1, 2);
  var w = window.innerWidth, h = window.innerHeight;
  view.cssW = w; view.cssH = h; view.dpr = dpr;
  glCanvas.width = Math.round(w * dpr); glCanvas.height = Math.round(h * dpr);
  glCanvas.style.width = w + 'px'; glCanvas.style.height = h + 'px';
  hudCanvas.width = Math.round(w * dpr); hudCanvas.height = Math.round(h * dpr);
  hudCanvas.style.width = w + 'px'; hudCanvas.style.height = h + 'px';
  if (gl) gl.viewport(0, 0, glCanvas.width, glCanvas.height);
  updateCameraMetrics();
  var a = arena(); cam.x = a.cx; cam.y = a.cy;
}

function press(x, y, isKey) {
  unlockGameAudio();
  unlockWindAudio();
  if (isKey) {
    if (state.mode === 'MENU' || state.mode === 'DEAD' || state.mode === 'WIN') { startRun(); return true; }
    if (state.mode === 'UPGRADE') { chooseUpgrade(0); return true; }
    return false;
  }
  if (state.mode === 'MENU' && inRect(hudRects.play, x, y)) { startRun(); return true; }
  if ((state.mode === 'DEAD' || state.mode === 'WIN') && inRect(hudRects.retry, x, y)) { startRun(); return true; }
  if (state.mode === 'UPGRADE') { for (var i = 0; i < upRects.length; i++) if (inRect(upRects[i], x, y)) { chooseUpgrade(i); return true; } return true; }
  return false;
}

var last = 0, acc = 0;
function stepFrame(dt) {
  update(dt);
}

window.advanceTime = function (ms) {
  var steps = Math.max(1, Math.round(ms / (1000 / 60)));
  for (var i = 0; i < steps; i++) update(STEP);
  renderWorld();
  renderHud();
};

function frame(now) {
  var gap = (now - last) / 1000; last = now;
  if (gap > 0.25) gap = 0.0167;
  acc += gap; var steps = 0;
  while (acc >= STEP && steps < MAX_STEPS) { stepFrame(STEP); acc -= STEP; steps++; }
  renderWorld();
  renderHud();
  updateWindAudio();
  requestAnimationFrame(frame);
}

var audioCtx = null, windGain = null, windSrc = null;
function unlockWindAudio() {
  if (audioCtx || !window.AudioContext && !window.webkitAudioContext) return;
  try {
    var Ctx = window.AudioContext || window.webkitAudioContext;
    audioCtx = new Ctx();
    var buffer = audioCtx.createBuffer(1, audioCtx.sampleRate, audioCtx.sampleRate);
    var data = buffer.getChannelData(0);
    for (var i = 0; i < data.length; i++) data[i] = Math.random() * 2 - 1;
    windSrc = audioCtx.createBufferSource();
    windSrc.buffer = buffer; windSrc.loop = true;
    var filter = audioCtx.createBiquadFilter();
    filter.type = 'bandpass'; filter.frequency.value = 420; filter.Q.value = 0.7;
    windGain = audioCtx.createGain(); windGain.gain.value = 0;
    windSrc.connect(filter); filter.connect(windGain); windGain.connect(audioCtx.destination);
    windSrc.start();
  } catch (e) { audioCtx = null; windGain = null; windSrc = null; }
}

function updateWindAudio() {
  if (!audioCtx || !windGain) return;
  var target = (state.mode === 'PLAY' && state.weirdKind === 'window') ? 0.045 + (state.windowPulse || 0.2) * 0.075 : 0;
  try {
    if (audioCtx.state === 'suspended') audioCtx.resume();
    windGain.gain.setTargetAtTime(target, audioCtx.currentTime, 0.08);
  } catch (e) {}
}

function boot() {
  if (!gl) return;                 // context.js already showed the WebGL2 fallback
  loadGameSprites();
  resize();
  window.addEventListener('resize', resize);
  window.addEventListener('orientationchange', function () { setTimeout(resize, 80); });
  initInput();
  onPress(press);
  window.addEventListener('keydown', function (e) {
    if (e.code === 'KeyM' && !e.repeat) toggleGameAudioMuted();
  });
  state.mode = 'MENU';
  last = performance.now();
  requestAnimationFrame(frame);
}

boot();
