import { UpdateController } from "@24tools/playable_template";
import { Vector3 } from "three";
import { PlayerC } from "./PlayerC";
import { Crate, LootableC } from "./LootableC";
import { Trigger } from "./TriggerC";

// Health removed from a crate per strike (the bat touches it during a swing).
const ATTACK_DAMAGE = 10;
// Bat-tip → crate horizontal distance under which the bat is "touching" it.
const HIT_DIST = 0.8;
// The swing only reaches crates within this asymmetric arc of the facing dir
// (negative = player's left, positive = right): left cut at 90°, right 130°.
const ARC_LEFT = (90 * Math.PI) / 180;
const ARC_RIGHT = (130 * Math.PI) / 180;

const _tmp = new Vector3();
const _center = new Vector3();
const _fwd = new Vector3();
const _pos = new Vector3();
const _tip = new Vector3();

/**
 * "Stand near crates → auto-attack" loop.
 *
 * Each crate has a Trigger zone (= which crates are in reach). While the player
 * is stopped, the Loot swing plays and the player faces the crates' centre.
 * The Loot clip has TWO strikes (left swing, then right). Per STRIKE, every
 * crate the bat tip actually reaches takes one hit (front arc only) — so one
 * swing damages all the crates it sweeps over, and a crate caught by both
 * swings takes two sequential hits, rather than being hit at random times.
 */
export class CombatC {
  private static inRange = new Set<Crate>();
  private static hitThisStrike = new Set<Crate>(); // crates already hit in the current strike
  private static lastStrike = -1;

  static init() {
    // A proximity trigger around every crate. Its size = the attack reach.
    for (const crate of LootableC.crates) {
      crate.root.getWorldPosition(_tmp);
      crate.trigger = new Trigger(
        { x: _tmp.x, y: _tmp.y + 0.5, z: _tmp.z },
        { x: 1.1, y: 1.0, z: 1.1 },
        {
          onEnter: () => this.inRange.add(crate),
          onExit: () => this.inRange.delete(crate),
        }
      );
    }

    UpdateController.Instance.onUpdate.addDelegate(() => this.update());
  }

  private static update() {
    this.pruneBroken();

    // Moving → stop at once.
    if (PlayerC.isMoving()) {
      PlayerC.setAttacking(false);
      this.resetCycle();
      return;
    }
    // Nothing left in reach → let the current swing finish, then idle.
    if (this.inRange.size === 0) {
      PlayerC.finishAttack();
      this.resetCycle();
      return;
    }

    // Face the centre of the crates in reach and keep swinging.
    _center.set(0, 0, 0);
    for (const c of this.inRange) {
      c.root.getWorldPosition(_tmp);
      _center.add(_tmp);
    }
    _center.divideScalar(this.inRange.size);
    PlayerC.setAttacking(true, _center);

    this.applyBatContact();
  }

  // Per strike: damage every in-reach crate the bat tip reaches (front arc).
  private static applyBatContact() {
    // New strike → all crates can be hit once again.
    const strike = PlayerC.getSwingCycle();
    if (strike !== this.lastStrike) {
      this.hitThisStrike.clear();
      this.lastStrike = strike;
    }

    const tip = PlayerC.getBatTip(_tip);
    if (!tip) return;
    PlayerC.getForward(_fwd);
    PlayerC.getPosition(_pos);
    const rx = _fwd.z, rz = -_fwd.x; // player's right

    for (const c of [...this.inRange]) {
      if (c.broken || this.hitThisStrike.has(c)) continue;
      c.root.getWorldPosition(_tmp);

      // Front-arc gate (the bat can't reach behind the player).
      const dx = _tmp.x - _pos.x, dz = _tmp.z - _pos.z;
      const len = Math.hypot(dx, dz) || 1;
      const angle = Math.atan2((rx * dx + rz * dz) / len, (_fwd.x * dx + _fwd.z * dz) / len);
      if (angle < -ARC_LEFT || angle > ARC_RIGHT) continue;

      // Bat tip actually reached this crate → hit it once this strike.
      if (Math.hypot(tip.x - _tmp.x, tip.z - _tmp.z) <= HIT_DIST) {
        this.hitThisStrike.add(c);
        LootableC.damageCrate(c, ATTACK_DAMAGE);
      }
    }
    this.pruneBroken();
  }

  private static resetCycle() {
    this.hitThisStrike.clear();
    this.lastStrike = -1;
  }

  private static pruneBroken() {
    for (const c of this.inRange) {
      if (c.broken) { this.inRange.delete(c); this.hitThisStrike.delete(c); }
    }
  }
}
