/* ============================================================
   game.js — main controller: loop, rounds, camera, background,
   HUD, menus, combos. Fixed 60Hz timestep with accumulator.
   ============================================================ */

const canvas = document.getElementById("game");
const ctx = canvas.getContext("2d");
const W = canvas.width, H = canvas.height;

const Game = {
  mode: "cpu",
  diff: 1,
  state: "menu",            // menu|intro|fight|roundend|matchend
  p1: null, p2: null,
  ai: null,
  p1in: new InputState(SCHEMES.p1),
  p2in: new InputState(SCHEMES.p2),
  fx: new Particles(),
  projectiles: [],
  beams: [],
  frameCount: 0,
  // cinematic camera / time
  timeScale: 1, slowmo: 0,
  zoom: 1, camX: 0, camY: 0,
  zoomTarget: 1, camTX: 0, camTY: 0,
  darken: 0, superFocus: null, superTimer: 0,
  combo: { 1: { n: 0, t: 0 }, 2: { n: 0, t: 0 } },
  rounds: { 1: 0, 2: 0 },
  roundNum: 1,
  timer: 99, timerAcc: 0,
  now: 0,
  shakeAmt: 0, flashAmt: 0,
  announceTimer: 0,
  acc: 0, last: 0,
  bgScroll: 0,

  start(mode, diff) {
    this.mode = mode; this.diff = diff;
    this.rounds = { 1: 0, 2: 0 };
    this.roundNum = 1;
    document.getElementById("p1name").textContent = "PLAYER 1";
    document.getElementById("p2name").textContent = mode === "cpu" ? "CPU" : "PLAYER 2";
    hide("menu"); hide("controls"); hide("result");
    show("hud");
    this.newRound();
    this.state = "intro";
  },

  newRound() {
    this.p1 = new Fighter(1, 360, 1, "PLAYER 1", false);
    this.p2 = new Fighter(2, 920, -1, "PLAYER 2", this.mode === "cpu");
    if (this.mode === "cpu") this.ai = new AIController(this.diff);
    this.projectiles = [];
    this.beams = [];
    this.zoom = 1; this.zoomTarget = 1; this.camX = this.camY = 0; this.camTX = this.camTY = 0;
    this.darken = 0; this.slowmo = 0; this.timeScale = 1; this.superFocus = null; this.superTimer = 0;
    this.fx = new Particles();
    this.combo = { 1: { n: 0, t: 0 }, 2: { n: 0, t: 0 } };
    this.timer = 99; this.timerAcc = 0;
    this.p1.state = this.p2.state = "intro";
    this.introTimer = 90;
    this.announce(this.roundNum >= 3 ? "FINAL ROUND" : "ROUND " + this.roundNum);
    Sound.bell();
    this.updateRoundDots();
  },

  beginFight() {
    this.state = "fight";
    this.p1.state = "idle"; this.p2.state = "idle";
    this.announce("FIGHT!");
    Sound.bell();
  },

  /* -------------------- main loop -------------------- */
  loop(t) {
    const dt = t - this.last; this.last = t;
    // slow-motion scales how fast the accumulator fills
    if (this.slowmo > 0) { this.slowmo--; this.timeScale = 0.35; }
    else this.timeScale = Utils.lerp(this.timeScale, 1, 0.2);
    this.acc += Math.min(dt, 50) * this.timeScale;
    const STEP = 1000 / 60;
    while (this.acc >= STEP) {
      this.now += STEP;
      this.frameCount++;
      this.step();
      this.acc -= STEP;
    }
    // camera easing runs every render frame for smoothness
    this.zoom = Utils.lerp(this.zoom, this.zoomTarget, 0.12);
    this.camX = Utils.lerp(this.camX, this.camTX, 0.12);
    this.camY = Utils.lerp(this.camY, this.camTY, 0.12);
    this.darken = Utils.lerp(this.darken, this.superTimer > 0 ? 0.55 : 0, 0.15);
    if (this.superTimer > 0) this.superTimer--;
    this.render();
    requestAnimationFrame(this.loop.bind(this));
  },

  step() {
    if (this.state === "menu") return;

    // intro countdown
    if (this.state === "intro") {
      this.introTimer--;
      if (this.introTimer <= 0) this.beginFight();
    }

    // timers / fx
    if (this.announceTimer > 0) this.announceTimer--;
    this.shakeAmt *= 0.86; this.flashAmt *= 0.86;
    this.bgScroll += 0.2;
    this.fx.update();
    for (const k of [1, 2]) if (this.combo[k].t > 0) { this.combo[k].t--; if (this.combo[k].t === 0) this.combo[k].n = 0; }

    // round clock
    if (this.state === "fight") {
      this.timerAcc += 1000 / 60;
      if (this.timerAcc >= 1000) { this.timerAcc -= 1000; this.timer = Math.max(0, this.timer - 1); if (this.timer <= 10) Sound.bell(); }
      if (this.timer <= 0) this.endRoundByTime();
    }

    // gather inputs
    const facing1 = this.p2.x >= this.p1.x ? 1 : -1;
    this.p1in.update(this.p1.facing, this.now);
    let in1 = this.p1in, in2;
    if (this.mode === "cpu") in2 = this.ai.update(this.p2, this.p1, this);
    else { this.p2in.update(this.p2.facing, this.now); in2 = this.p2in; }

    const live = this.state === "fight";
    this.p1.update(live ? in1 : null, this.p2, this);
    this.p2.update(live ? in2 : null, this.p1, this);

    // prevent overlap (soft body push)
    this.separate(this.p1, this.p2);

    // projectiles
    for (const p of this.projectiles) {
      p.update(this.fx);
      // hit opponents
      const target = p.owner === this.p1 ? this.p2 : this.p1;
      if (!p.dead && target.invuln <= 0 && target.alive() &&
          Utils.aabb(p.box, target.hurtbox())) {
        const dir = Utils.sign(target.x - p.x) || p.dir;
        const a = { damage: p.damage, hitstun: p.hitstun, kb: 9, height: "mid", push: 6, meter: 6 };
        target.takeHit(a, dir, p.owner, this, { x: p.x, y: p.y });
        p.dead = true;
      }
      // projectile vs projectile
      for (const q of this.projectiles) {
        if (q !== p && !q.dead && !p.dead && q.owner !== p.owner && Utils.aabb(p.box, q.box)) {
          p.dead = q.dead = true;
          this.fx.spark(p.x, p.y, "#fff", 16, 8); this.fx.ring(p.x, p.y, "#fff");
        }
      }
    }
    this.projectiles = this.projectiles.filter(p => !p.dead);

    // beams (super) — repeated hits while overlapping
    for (const b of this.beams) {
      b.update();
      const target = b.owner === this.p1 ? this.p2 : this.p1;
      if (!b.dead && b.hitTimer <= 0 && target.alive() && Utils.aabb(b.box, target.hurtbox())) {
        const dir = b.dir;
        const a = { damage: b.damage, hitstun: 14, kb: 5, height: "mid", push: 2, meter: 0, multi: true };
        // bypass invuln for the super beam so it always connects
        const sv = target.invuln; target.invuln = 0;
        target.takeHit(a, dir, b.owner, this, { x: target.x, y: target.y - 80 });
        target.invuln = Math.max(sv - 1, 0);
        b.hitTimer = 4;
        this.fx.spark(target.x, target.y - 80, "#fff", 10, 7);
      }
    }
    this.beams = this.beams.filter(b => !b.dead);

    // KO check
    if (this.state === "fight" && (!this.p1.alive() || !this.p2.alive())) {
      this.endRoundByKO();
    }
  },

  separate(a, b) {
    const minDist = 50;
    const d = b.x - a.x;
    const ad = Math.abs(d);
    if (ad < minDist && a.onGround && b.onGround) {
      const push = (minDist - ad) / 2;
      const s = Utils.sign(d) || 1;
      a.x -= s * push; b.x += s * push;
      a.x = Utils.clamp(a.x, STAGE_L, STAGE_R);
      b.x = Utils.clamp(b.x, STAGE_L, STAGE_R);
    }
  },

  /* -------------------- combat events -------------------- */
  onHit(victim, attacker, a, tip) {
    const heavy = a.damage >= 9;
    if (heavy) { Sound.hitHeavy(); this.shake(6 + a.damage * 0.3); this.slowmo = Math.max(this.slowmo, 4); }
    else { Sound.hitLight(); this.shake(3); }
    this.flash(heavy ? 0.25 : 0.12);

    this.fx.spark(tip.x, tip.y, "#fff", heavy ? 18 : 10, heavy ? 9 : 6);
    this.fx.ring(tip.x, tip.y, attacker.color);
    this.fx.star(tip.x, tip.y, "#fff", heavy ? 30 : 18);
    if (heavy) { this.fx.blood(tip.x, tip.y, attacker.facing); this.fx.lightning(tip.x, tip.y - 30, tip.x + Utils.rand(-30, 30), tip.y + 30, "#fff"); }

    // combo tracking
    const c = this.combo[attacker.id];
    c.n++; c.t = 90;
    this.combo[victim.id].n = 0; this.combo[victim.id].t = 0;
    if (c.n >= 2) this.fx.text(victim.x, victim.y - 170, c.n + " HIT" + (c.n > 1 ? "S" : ""), "#ffce3b", 26 + Math.min(c.n, 12));
  },

  onParry(defender, attacker, tip) {
    Sound.block(); Sound.bell();
    this.slowmo = Math.max(this.slowmo, 14);
    this.flash(0.4); this.shake(5);
    for (let i = 0; i < 3; i++) this.fx.star(tip.x, tip.y, "#fff", 28);
    this.fx.shockwave(defender.x, defender.y - 80, "#fff", 120, 6);
    this.fx.ring(tip.x, tip.y, "#fff");
    this.fx.text(defender.x, defender.y - 175, "PARRY!", "#ffffff", 32);
  },

  // ground pound / dive impact
  groundPound(self, opp) {
    Sound.hitHeavy(); this.shake(14); this.flash(0.3);
    this.slowmo = Math.max(this.slowmo, 5);
    this.fx.shockwave(self.x, GROUND_Y, self.color, 220, 12);
    this.fx.dust(self.x, GROUND_Y, 1); this.fx.dust(self.x, GROUND_Y, -1);
    for (let i = 0; i < 16; i++) this.fx.spark(self.x, GROUND_Y, self.color, 1, 9);
    // splash damage if opponent grounded and near
    if (opp.alive() && opp.onGround && Math.abs(opp.x - self.x) < 150) {
      const a = ATTACKS.airslam;
      const dir = Utils.sign(opp.x - self.x) || self.facing;
      const sv = opp.invuln; opp.invuln = 0;
      opp.takeHit(a, dir, self, this, { x: opp.x, y: opp.y - 60 });
      opp.invuln = sv;
    }
  },

  // begin the cinematic super: freeze, darken, zoom in on the caster
  startSuper(caster) {
    this.superFocus = caster;
    this.superTimer = 70;
    this.slowmo = 26;
    this.zoomTarget = 1.45;
    this.camTX = caster.x - W / 2; this.camTY = (caster.y - 90) - H / 2;
    this.fx.text(caster.x, caster.y - 210, "SUPER!", caster.color, 52);
    setTimeout(() => { this.zoomTarget = 1; this.camTX = 0; this.camTY = 0; }, 900);
  },

  /* -------------------- rounds -------------------- */
  endRoundByKO() {
    this.state = "roundend";
    const p1dead = !this.p1.alive();
    const winner = p1dead ? this.p2 : this.p1;
    const loser = p1dead ? this.p1 : this.p2;
    loser.state = "ko"; loser.knockdown(loser.facing * -1);
    this.rounds[winner.id]++;
    this.announce("K.O.");
    Sound.ko(); this.shake(16); this.flash(0.6);
    this.finishRound(winner);
  },

  endRoundByTime() {
    this.state = "roundend";
    let winner;
    if (this.p1.hp > this.p2.hp) winner = this.p1;
    else if (this.p2.hp > this.p1.hp) winner = this.p2;
    else winner = null; // draw
    if (winner) this.rounds[winner.id]++;
    this.announce(winner ? "TIME UP" : "DRAW");
    Sound.bell();
    this.finishRound(winner);
  },

  finishRound(winner) {
    this.updateRoundDots();
    if (winner) winner.state = "win";
    const matchOver = this.rounds[1] >= 2 || this.rounds[2] >= 2;
    setTimeout(() => {
      if (matchOver) this.endMatch();
      else { this.roundNum++; this.newRound(); this.state = "intro"; this.introTimer = 90; }
    }, 2200);
  },

  endMatch() {
    this.state = "matchend";
    const p1won = this.rounds[1] > this.rounds[2];
    const name = p1won ? "PLAYER 1" : (this.mode === "cpu" ? "CPU" : "PLAYER 2");
    document.getElementById("resultText").textContent = name + " WINS!";
    document.getElementById("resultText").style.color = p1won ? "var(--p1)" : "var(--p2)";
    show("result");
  },

  /* -------------------- camera / fx -------------------- */
  shake(v) { this.shakeAmt = Math.max(this.shakeAmt, v); },
  flash(v) { this.flashAmt = Math.max(this.flashAmt, v); },
  announce(txt) {
    const el = document.getElementById("announce");
    el.textContent = txt; el.classList.remove("hidden");
    el.style.animation = "none"; void el.offsetWidth; el.style.animation = "";
    this.announceTimer = 70;
  },

  updateRoundDots() {
    const mk = (id, wins) => {
      const el = document.getElementById("p" + id + "rounds");
      el.innerHTML = "";
      for (let i = 0; i < 2; i++) {
        const d = document.createElement("div");
        d.className = "dot" + (i < wins ? " win" : "");
        el.appendChild(d);
      }
    };
    mk(1, this.rounds[1]); mk(2, this.rounds[2]);
  },

  /* -------------------- rendering -------------------- */
  render() {
    ctx.clearRect(0, 0, W, H);
    this.drawBackground();             // background unaffected by zoom

    ctx.save();
    // camera zoom + pan (centered on focus point), plus screen shake
    if (Math.abs(this.zoom - 1) > 0.002 || Math.abs(this.camX) > 0.5 || Math.abs(this.camY) > 0.5) {
      ctx.translate(W / 2, H / 2);
      ctx.scale(this.zoom, this.zoom);
      ctx.translate(-W / 2 - this.camX, -H / 2 - this.camY);
    }
    if (this.shakeAmt > 0.4) {
      ctx.translate((Math.random() - 0.5) * this.shakeAmt, (Math.random() - 0.5) * this.shakeAmt);
    }

    // super darken vignette (drawn over stage, behind fighters)
    if (this.darken > 0.02) {
      ctx.save();
      ctx.fillStyle = `rgba(5,2,12,${this.darken})`;
      ctx.fillRect(-W, -H, W * 3, H * 3);
      if (this.superFocus) {
        const grd = ctx.createRadialGradient(this.superFocus.x, this.superFocus.y - 80, 20,
                                             this.superFocus.x, this.superFocus.y - 80, 320);
        grd.addColorStop(0, "rgba(0,0,0,0)");
        grd.addColorStop(1, `rgba(5,2,12,${this.darken})`);
        ctx.fillStyle = grd; ctx.fillRect(-W, -H, W * 3, H * 3);
      }
      // speed lines
      ctx.globalAlpha = this.darken * 0.5; ctx.strokeStyle = "#fff"; ctx.lineWidth = 2;
      for (let i = 0; i < 22; i++) {
        const yy = (i / 22) * H + ((this.frameCount * 12) % 60);
        ctx.beginPath(); ctx.moveTo(0, yy); ctx.lineTo(120, yy + 8); ctx.stroke();
        ctx.beginPath(); ctx.moveTo(W, yy); ctx.lineTo(W - 120, yy + 8); ctx.stroke();
      }
      ctx.restore();
    }

    if (this.p1 && this.p2) {
      this.p2.draw(ctx);
      this.p1.draw(ctx);
      for (const p of this.projectiles) p.draw(ctx);
      for (const b of this.beams) b.draw(ctx);
      this.fx.draw(ctx);
    }
    ctx.restore();

    // white flash overlay
    if (this.flashAmt > 0.02) {
      ctx.fillStyle = `rgba(255,255,255,${this.flashAmt})`;
      ctx.fillRect(0, 0, W, H);
    }

    if (this.state !== "menu") this.updateHUD();
  },

  drawBackground() {
    // sky gradient
    const g = ctx.createLinearGradient(0, 0, 0, H);
    g.addColorStop(0, "#1a1336");
    g.addColorStop(0.45, "#3a2350");
    g.addColorStop(0.75, "#7a3b59");
    g.addColorStop(1, "#c2683f");
    ctx.fillStyle = g; ctx.fillRect(0, 0, W, H);

    // sun
    ctx.fillStyle = "rgba(255,210,120,0.9)";
    ctx.beginPath(); ctx.arc(W * 0.5, H * 0.42, 90, 0, Math.PI * 2); ctx.fill();
    ctx.fillStyle = "rgba(255,180,90,0.18)";
    ctx.beginPath(); ctx.arc(W * 0.5, H * 0.42, 150, 0, Math.PI * 2); ctx.fill();

    // distant skyline (parallax)
    this.drawSkyline(0.25, "#231a3a", 120, 0.3);
    this.drawSkyline(0.5, "#1a1430", 180, 0.6);

    // ground
    const gy = GROUND_Y;
    const gg = ctx.createLinearGradient(0, gy, 0, H);
    gg.addColorStop(0, "#2a2238");
    gg.addColorStop(1, "#15101f");
    ctx.fillStyle = gg; ctx.fillRect(0, gy, W, H - gy);
    // floor edge
    ctx.fillStyle = "#3c3350"; ctx.fillRect(0, gy, W, 6);
    // floor planks
    ctx.strokeStyle = "rgba(0,0,0,0.25)"; ctx.lineWidth = 2;
    for (let x = (-this.bgScroll * 2 % 80); x < W; x += 80) {
      ctx.beginPath(); ctx.moveTo(x, gy + 6); ctx.lineTo(x - 30, H); ctx.stroke();
    }
  },

  drawSkyline(speed, color, baseH, alpha) {
    ctx.save(); ctx.globalAlpha = alpha; ctx.fillStyle = color;
    const off = (this.bgScroll * speed) % 160;
    for (let x = -off; x < W + 160; x += 90) {
      const h = baseH + ((Math.floor(x / 90) * 53) % 90);
      ctx.fillRect(x, GROUND_Y - h, 64, h);
      // windows
      ctx.fillStyle = "rgba(255,220,150,0.10)";
      for (let wy = GROUND_Y - h + 12; wy < GROUND_Y - 12; wy += 22)
        for (let wx = x + 8; wx < x + 56; wx += 18)
          ctx.fillRect(wx, wy, 8, 10);
      ctx.fillStyle = color;
    }
    ctx.restore();
  },

  updateHUD() {
    const set = (id, w) => { const e = document.getElementById(id); if (e) e.style.width = w + "%"; };
    set("p1hp", this.p1.hp); set("p1hpd", this.p1.hp);
    set("p2hp", this.p2.hp); set("p2hpd", this.p2.hp);
    set("p1meter", this.p1.meter); set("p2meter", this.p2.meter);
    document.getElementById("p1meter").parentElement.classList.toggle("full", this.p1.meter >= 100);
    document.getElementById("p2meter").parentElement.classList.toggle("full", this.p2.meter >= 100);
    document.getElementById("timer").textContent = String(this.timer).padStart(2, "0");

    const showCombo = (id, slot) => {
      const c = this.combo[id], el = document.getElementById("combo" + slot);
      if (c.n >= 2) { el.textContent = c.n + " HIT COMBO"; el.classList.add("show"); el.style.color = id === 1 ? "var(--p1)" : "var(--p2)"; }
      else el.classList.remove("show");
    };
    showCombo(1, 1); showCombo(2, 2);
  },
};

/* delayed-damage HP bar: yellow trails the green. Override updateHUD
   to animate the delay bar separately. */
(function () {
  const realSet = Game.updateHUD.bind(Game);
  Game._dly = { p1: 100, p2: 100 };
  Game.updateHUD = function () {
    realSet();
    for (const id of [1, 2]) {
      const hp = this["p" + id].hp;
      this._dly["p" + id] = Math.max(hp, this._dly["p" + id] - 0.9);
      const e = document.getElementById("p" + id + "hpd");
      if (e) e.style.width = this._dly["p" + id] + "%";
    }
  };
})();

/* -------------------- DOM helpers + menu wiring -------------------- */
function show(id) { document.getElementById(id).classList.remove("hidden"); }
function hide(id) { document.getElementById(id).classList.add("hidden"); }

document.querySelectorAll("#menu .btns button").forEach(b => {
  b.addEventListener("click", () => {
    const m = b.dataset.mode;
    if (m === "controls") { hide("menu"); show("controls"); return; }
    Game.start(m, Game.diff);
  });
});
document.querySelectorAll(".diff").forEach(b => {
  b.addEventListener("click", () => {
    document.querySelectorAll(".diff").forEach(x => x.classList.remove("active"));
    b.classList.add("active"); Game.diff = parseInt(b.dataset.diff);
  });
});
document.getElementById("backBtn").addEventListener("click", () => { hide("controls"); show("menu"); });
document.getElementById("rematchBtn").addEventListener("click", () => { hide("result"); Game.start(Game.mode, Game.diff); });
document.getElementById("menuBtn").addEventListener("click", () => {
  hide("result"); hide("hud"); show("menu"); Game.state = "menu";
});

// idle attract loop on menu so the canvas isn't blank
Game.state = "menu";
Game.last = performance.now();
requestAnimationFrame(Game.loop.bind(Game));
