import { EasyEvent, JoystickC, UpdateController } from "@24tools/playable_template";
import { Vector3 } from "three";
import { PhysicsTriggerC } from "./PhysicsTriggerC";
import { PropC, PropRegistry } from "./PropC";
import { PropHpUIC } from "./PropHpUIC";
import { Player } from "../Presets/Player";

export class GatherC {
  static readonly onCombatIdle = new EasyEvent<{}>();

  private static activeProps = new Set<PropC>();
  private static pendingAutoAttack = false;
  private static combatIdleNotified = false;

  static init() {
    PropC.onBroken.addDelegate(() => this.onPropDestroyed());

    PhysicsTriggerC.onTriggerEnter.addDelegate((payload) => {
      const prop = PropRegistry.get(payload.lootableObject);
      if (!prop || prop.isBroken) return;

      console.log(`[GatherC] enter gather zone: ${payload.lootableObject.name}`);
      this.activeProps.add(prop);
      this.scheduleAutoAttackCheck();
    });

    PhysicsTriggerC.onTriggerExit.addDelegate((payload) => {
      console.log(`[GatherC] exit gather zone: ${payload.lootableObject.name}`);

      const prop = PropRegistry.get(payload.lootableObject);
      if (prop) this.activeProps.delete(prop);

      this.removeBrokenProps();
      this.resyncActivePropsFromPhysics();
      this.handleRemainingTargets();
    });

    JoystickC.onJoysticEnd.addDelegate(() => {
      this.scheduleAutoAttackCheck();
    });

    JoystickC.onJoysticMove.addDelegate(() => {
      if (!Player.isMoving()) {
        this.scheduleAutoAttackCheck();
      }
    });

    UpdateController.Instance.onUpdate.addDelegate(() => {
      if (this.pendingAutoAttack) {
        this.pendingAutoAttack = false;
        this.tryStartAutoAttack();
        return;
      }

      this.tryResumeAutoAttack();
      this.notifyCombatIdleIfNeeded();
    });
  }

  private static onPropDestroyed() {
    this.removeBrokenProps();
    this.resyncActivePropsFromPhysics();
    this.handleRemainingTargets();
  }

  private static scheduleAutoAttackCheck() {
    this.pendingAutoAttack = true;
  }

  private static onAttackStrike() {
    for (const prop of this.getAttackableProps()) {
      prop.takeDamage(1);
    }

    this.removeBrokenProps();
    this.resyncActivePropsFromPhysics();
    this.handleRemainingTargets();
  }

  private static tryResumeAutoAttack() {
    if (this.getAttackableProps().length === 0) return;
    if (Player.isAutoAttackActive() || Player.isCombatBusy()) return;
    if (Player.isMoving()) return;
    this.tryStartAutoAttack();
  }

  private static handleRemainingTargets() {
    const props = this.getAttackableProps();
    if (props.length === 0) {
      if (Player.isAutoAttackActive() || Player.isCombatBusy()) {
        Player.stopAutoAttack();
      }
      this.notifyCombatIdleIfNeeded();
      return;
    }

    if (Player.isMoving()) {
      this.scheduleAutoAttackCheck();
      return;
    }

    const targetPosition = this.getTargetPosition(props);

    if (Player.isAutoAttackActive()) {
      Player.retargetAutoAttack(targetPosition);
      return;
    }

    if (Player.isCombatBusy()) {
      this.scheduleAutoAttackCheck();
      return;
    }

    this.tryStartAutoAttack();
  }

  private static tryStartAutoAttack() {
    const props = this.getAttackableProps();
    if (props.length === 0) return;
    if (Player.isMoving() || Player.isCombatBusy()) return;

    const targetPosition = this.getTargetPosition(props);
    PropHpUIC.showFor(props);
    Player.startAutoAttack(
      () => this.onAttackStrike(),
      () => {},
      targetPosition,
    );
  }

  private static resyncActivePropsFromPhysics() {
    this.activeProps.clear();

    for (const lootableObject of PhysicsTriggerC.getActiveLootableObjects()) {
      const prop = PropRegistry.get(lootableObject);
      if (prop && !prop.isBroken) {
        this.activeProps.add(prop);
      }
    }
  }

  private static getTargetPosition(props: PropC[]): Vector3 {
    const playerPosition = Player.getWorldPosition();
    const target = new Vector3();
    let closestDistanceSq = Infinity;

    for (const prop of props) {
      const propPosition = prop.object.getWorldPosition(new Vector3());
      const distanceSq = playerPosition.distanceToSquared(propPosition);
      if (distanceSq >= closestDistanceSq) continue;

      closestDistanceSq = distanceSq;
      target.copy(propPosition);
    }

    return target;
  }

  private static getAttackableProps() {
    return [...this.activeProps].filter((prop) => !prop.isBroken);
  }

  private static removeBrokenProps() {
    for (const prop of this.activeProps) {
      if (prop.isBroken) this.activeProps.delete(prop);
    }
  }

  private static notifyCombatIdleIfNeeded() {
    if (this.getAttackableProps().length > 0) {
      this.combatIdleNotified = false;
      return;
    }

    if (Player.isAutoAttackActive() || Player.isCombatBusy()) {
      this.combatIdleNotified = false;
      return;
    }

    if (this.combatIdleNotified) return;

    this.combatIdleNotified = true;
    this.onCombatIdle.Invoke({});
  }
}
