import { EasyEvent } from "@24tools/playable_template";
import { AnimationAction, AnimationClip, AnimationMixer, LoopOnce, LoopRepeat, Object3D, Vector3 } from "three";
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
import { ThreeC } from "../../ThreeC";
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";

export class Character {
  tObj: Object3D;
  animMixer: AnimationMixer;
  animationList: AnimationClip[] = [];

  curClipAction: null | AnimationAction = null;

  onAnimLoop: EasyEvent<{}> = new EasyEvent<{}>();
  onAnimFinish: EasyEvent<{}> = new EasyEvent<{}>();

  constructor(prefab: GLTF, start_position = new Vector3()) {
    const tObj = clone(prefab.scene);

    tObj.castShadow = true;
    const animMixer = new AnimationMixer(tObj);

    animMixer.addEventListener("loop", () => {
      this.onAnimLoop.Invoke({});
    });
    animMixer.addEventListener("finished", () => {
      this.onAnimFinish.Invoke({});
    });

    if (start_position) tObj.position.copy(start_position);

    this.tObj = tObj;
    this.animMixer = animMixer;
    this.animationList = prefab.animations;

    ThreeC.addAnimMixer(animMixer);

    return this;
  }

  set AnimationSpeed(timeScale: number) {
    if (this.curClipAction) {
      this.curClipAction.timeScale = timeScale;
    }
  }

  set AnimationWeight(weight: number) {
    if (this.curClipAction) {
      this.curClipAction.weight = weight;
    }
  }

  setObjectVisible(name: string, visible: boolean) {
    this.tObj.traverse((child) => {
      if (child.name === name) {
        child.visible = visible;
      }
    });
  }

  setDefaultWeapons() {
    this.setObjectVisible("Tool_1", false);
    this.setObjectVisible("Tool_2", true);
    this.setObjectVisible("Character_Pistol", false);
  }

  setBatEquipped(equipped: boolean) {
    this.setObjectVisible("Tool_1", equipped);
    this.setObjectVisible("Tool_2", !equipped);
    this.setObjectVisible("Character_Pistol", false);
  }

  isPlayingAnimation(anim_id: number) {
    if (!this.curClipAction || anim_id < 0 || anim_id >= this.animationList.length) {
      return false;
    }

    return this.curClipAction.getClip() === this.animationList[anim_id];
  }

  playAnimation(anim_id: number, one_time = false, fade = 0.25, randomStart = false) {
    const oldClipAction = this.curClipAction;
    const clipAction = this.animMixer.clipAction(this.animationList[anim_id]);

    if (one_time) {
      clipAction.clampWhenFinished = true;
      clipAction.setLoop(LoopOnce, 1);
    } else {
      clipAction.clampWhenFinished = false;
      clipAction.setLoop(LoopRepeat, Infinity);
    }
    clipAction.timeScale = 1;

    if (oldClipAction) {
      oldClipAction.fadeOut(fade);
    }

    clipAction.reset();
    if (randomStart) {
      clipAction.time = Math.random() * this.animationList[anim_id].duration;
    }
    clipAction.play();
    clipAction.fadeIn(fade);
    this.curClipAction = clipAction;
  }

  /** Blends from the current pose instead of snapping the new clip to frame 0. */
  crossFadeToAnimation(anim_id: number, one_time = false, fade = 0.5) {
    const oldClipAction = this.curClipAction;
    const clipAction = this.animMixer.clipAction(this.animationList[anim_id]);

    if (one_time) {
      clipAction.clampWhenFinished = true;
      clipAction.setLoop(LoopOnce, 1);
    } else {
      clipAction.clampWhenFinished = false;
      clipAction.setLoop(LoopRepeat, Infinity);
    }
    clipAction.timeScale = 1;

    if (oldClipAction && oldClipAction !== clipAction) {
      clipAction.reset();
      clipAction.play();
      oldClipAction.crossFadeTo(clipAction, fade, false);
    } else {
      this.playAnimation(anim_id, one_time, fade);
      return;
    }

    this.curClipAction = clipAction;
  }
}
