/* ============================================================
   ai.js — CPU opponent. Produces a fake "input" object each
   frame implementing the same interface as InputState, so the
   Fighter code is identical for human and CPU.
   ============================================================ */

class AIInput {
  constructor() { this.h = {}; this.edge = {}; this._motion = null; }
  set(btn, v) { this.edge[btn] = v && !this.h[btn]; this.h[btn] = v; }
  clearEdges() { for (const k in this.edge) this.edge[k] = false; }
  down(b) { return !!this.h[b]; }
  pressed(b) { return !!this.edge[b]; }
  motion(seq) { return this._motion === seq.join(); }
  consumeMotion() { this._motion = null; }
}

class AIController {
  constructor(diff) {
    this.diff = diff;                 // 0 easy, 1 normal, 2 hard
    this.in = new AIInput();
    this.timer = 0;                   // decision cooldown
    this.action = "idle";
    this.actionTime = 0;
    this.reaction = [22, 13, 6][diff];
    this.aggression = [0.4, 0.62, 0.82][diff];
  }

  // returns a fake input for the fighter to consume this frame
  update(self, opp, game) {
    const inp = this.in;
    // reset to neutral each frame; we re-set wanted buttons
    const prev = { ...inp.h };
    inp.h = {}; inp._motion = null; inp._dash = null;

    const dist = Math.abs(opp.x - self.x);
    const dir = opp.x > self.x ? "right" : "left";
    const toward = dir;
    const away = dir === "right" ? "left" : "right";
    const facingOK = self.facing === Utils.sign(opp.x - self.x);

    // freeze if KO/knockdown
    if (!self.alive() || self.state === "ko" || self.state === "knockdown") {
      this.commit(inp, prev); return inp;
    }

    // --- threat reaction: block when opponent attacks in range ---
    const oppAttacking = opp.state === "attack" &&
      opp.aFrame <= (ATTACKS[opp.attack]?.startup ?? 0) + (ATTACKS[opp.attack]?.active ?? 0);
    const incomingProj = game.projectiles.some(p =>
      p.owner !== self && Math.sign(p.vx) === Utils.sign(self.x - p.x) &&
      Math.abs(p.x - self.x) < 360);

    if (this.timer > 0) this.timer--;

    // probabilistic blocking based on difficulty + reaction
    if ((oppAttacking && dist < 170) || incomingProj) {
      if (Math.random() < this.aggression * 0.9 || incomingProj) {
        inp.h["block"] = true;
        // crouch-block lows / sweeps
        if (opp.attack === "sweep") inp.h["down"] = true;
        // sometimes jump over a fireball if far enough
        if (incomingProj && dist > 200 && Math.random() < 0.4 * this.diff) {
          inp.h["up"] = true; inp.h["block"] = false;
        }
        this.commit(inp, prev); return inp;
      }
    }

    // pick a new behavior periodically
    if (this.timer <= 0) {
      this.timer = Utils.randInt(this.reaction, this.reaction + 18);
      const r = Math.random();
      if (dist > 360) {
        // far: approach, fireball, or EX fireball
        if (r < 0.18 && self.meter >= 50 && this.diff >= 1) this.action = "exfireball";
        else if (r < 0.32) this.action = "fireball";
        else if (r < 0.42 && this.diff >= 1) this.action = "dash";
        else this.action = "approach";
      } else if (dist > 150) {
        if (r < 0.18 && self.meter >= 25 && this.diff >= 1) this.action = "tatsu";
        else if (r < this.aggression) this.action = "approach";
        else if (r < this.aggression + 0.12) this.action = "jumpin";
        else if (r < this.aggression + 0.22 && this.diff >= 1) this.action = "dash";
        else this.action = "wait";
      } else {
        // close range — attack mix
        if (self.meter >= 100 && r < 0.45) this.action = "super";
        else if (r < 0.24) this.action = "punch";       // rekka string
        else if (r < 0.42) this.action = "kick";
        else if (r < 0.55) this.action = "sweep";
        else if (r < 0.66 && self.meter >= 25) this.action = "tatsu";
        else if (r < 0.78) this.action = "uppercut";
        else if (r < 0.9) this.action = "approach";
        else this.action = "backoff";
      }
      this.actionTime = this.timer;
    }

    // execute current behavior
    switch (this.action) {
      case "approach": inp.h[toward] = true; break;
      case "backoff": inp.h[away] = true; break;
      case "wait": break;
      case "jumpin":
        inp.h[toward] = true;
        if (self.onGround && this.actionTime - this.timer < 3) inp.h["up"] = true;
        if (dist < 140 && !self.onGround) inp.h["kick"] = true;
        break;
      case "punch": if (dist < 150) inp.h["punch"] = true; else inp.h[toward] = true; break;
      case "kick":  if (dist < 165) inp.h["kick"] = true;  else inp.h[toward] = true; break;
      case "sweep":
        if (dist < 150) { inp.h["down"] = true; inp.h["kick"] = true; }
        else inp.h[toward] = true; break;
      case "fireball":
        if (self.onGround) { inp.h["punch"] = true; inp._motion = ["down","fwd"].join(); }
        break;
      case "exfireball":
        if (self.onGround) { inp.h["kick"] = true; inp._motion = ["down","fwd"].join(); }
        break;
      case "tatsu":
        if (dist < 220) { inp.h["kick"] = true; inp._motion = ["down","back"].join(); }
        else inp.h[toward] = true; break;
      case "dash":
        if (self.onGround) inp._dash = self.facing;
        break;
      case "uppercut":
        if (dist < 150) { inp.h["punch"] = true; inp._motion = ["fwd","downfwd"].join(); }
        else inp.h[toward] = true; break;
      case "super":
        if (dist < 240) { inp.h["punch"] = true; inp.h["kick"] = true; }
        else inp.h[toward] = true; break;
    }

    this.commit(inp, prev);
    return inp;
  }

  commit(inp, prev) {
    // compute edges (pressed) from prev frame
    inp.edge = {};
    for (const k in inp.h) inp.edge[k] = inp.h[k] && !prev[k];
  }
}
