import { Vector3 } from "three";
import { PlayerC } from "./PlayerC";
import { Crate, LootableC } from "./LootableC";
import { Trigger } from "./TriggerC";
import { GameEvents } from "../core/GameEvents";
import { GameLoop } from "../core/GameLoop";

const ATTACK_DAMAGE = 10;  // damage per bat-tip touch
const CONTACT_DIST = 0.8;  // bat tip → crate distance that counts as a touch

const _scratch = new Vector3(); // reused crate-position holder (no per-frame allocation)
const _center = new Vector3();  // averaged centre of the crates in reach
const _tip = new Vector3();     // current bat-tip world position

/**
 * Auto-attack: while the player stands near crates, swing the bat and damage a
 * crate when the bat tip touches it. Hits are gated by PlayerC's swing window
 * (no damage during the wind-up) and limited to once per crate per swing.
 */
export class CombatC {
  private static inRange = new Set<Crate>();      // crates currently in reach
  private static hitThisSwing = new Set<Crate>(); // crates already hit this swing
  private static lastSwingId = 0;

  static init() {
    // Proximity trigger around every crate → decides which crates are in reach.
    for (const crate of LootableC.crates) {
      crate.root.getWorldPosition(_scratch);
      crate.trigger = new Trigger(
        { x: _scratch.x, y: _scratch.y + 0.5, z: _scratch.z },
        { x: 1.1, y: 1.0, z: 1.1 },
        {
          onEnter: () => this.inRange.add(crate),
          onExit: () => this.inRange.delete(crate),
        }
      );
    }

    GameLoop.register(() => this.update());
  }

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

    // Moving → stop attacking immediately.
    if (PlayerC.isMoving()) {
      PlayerC.setAttacking(false);
      this.endSwing();
      return;
    }
    // No crates left → let the current swing finish, then idle.
    if (this.inRange.size === 0) {
      PlayerC.finishAttack();
      this.endSwing();
      return;
    }

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

    this.applyContact();
  }

  // Damage each touched crate once per swing. Nothing happens during the wind-up.
  private static applyContact() {
    const swingId = PlayerC.getActiveSwingId();
    if (swingId === 0) return; // bat not live (wind-up / between swings)

    // New swing → every crate can be hit again.
    if (swingId !== this.lastSwingId) {
      this.hitThisSwing.clear();
      this.lastSwingId = swingId;
    }

    const tip = PlayerC.getBatTip(_tip);
    if (!tip) return;

    for (const crate of [...this.inRange]) {
      if (crate.broken || this.hitThisSwing.has(crate)) continue;
      crate.root.getWorldPosition(_scratch);
      if (Math.hypot(tip.x - _scratch.x, tip.z - _scratch.z) <= CONTACT_DIST) {
        this.hitThisSwing.add(crate);
        GameEvents.onCrateHit.Invoke({ crate, damage: ATTACK_DAMAGE });
      }
    }
    this.pruneBroken();
  }

  private static endSwing() {
    this.hitThisSwing.clear();
    this.lastSwingId = 0;
  }

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