import { Delegate, ResourcesC, UpdateController } from "@24tools/playable_template";
import { Character } from "./Character/Character";
import { ResourcesType } from "./Enums/ResourcesType";
import { MeshType } from "./Enums/MeshType";
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
import { BaseAnimation } from "./Enums/BaseAnimation";
import { PhysicsBody, PhysicsLayer } from "../PhysicsC";
import { Object3D, Vector3 } from "three";
import { ThreeC } from "../ThreeC";
import { PlayerInput } from "./Input/PlayerInput";
import { MoveC } from "./Movment/MoveC";
import { Vector3CToT, Vector3TToC } from "./Helper";
import { RotationC } from "./Movment/RotationC";
import { FollowCameraC } from "./Movment/CameraMovment/FollowCamera";
import { PlayerCombat } from "./PlayerCombat";
import { PhysicsTriggerC } from "../Map/PhysicsTriggerC";
import { PropHpUIC } from "../Map/PropHpUIC";

export class Player {
    private static inited = false;
    private static isRunning = false;

    private static updateDelegate: Delegate<number>;

    private static container: Object3D = new Object3D();
    private static input: PlayerInput;
    private static movement: MoveC;
    private static rotation: RotationC;
    private static spawnPosition = new Vector3(0, 0, -4);

    static character: Character;
    static physics: PhysicsBody;

    static init() {
        if (this.inited) return;
        this.inited = true;

        const asset = ResourcesC.getResource<GLTF>(ResourcesType.Mesh, MeshType.Character);
        this.character = new Character(asset);

        this.container.position.copy(this.spawnPosition);
        this.character.setDefaultWeapons();
        this.character.playAnimation(BaseAnimation.Idle);
        this.container.add(this.character.tObj);
        ThreeC.addToScene(this.container);

        this.initPhysics();
        PhysicsTriggerC.setPlayerBody(this.physics.getPhysicsBody());

        this.input = new PlayerInput();
        const moveSpeed = 3;
        const rotationSpeed = 8;
        this.movement = new MoveC(this.input, moveSpeed);
        this.rotation = new RotationC(this.container, rotationSpeed);

        PlayerCombat.init({
            character: this.character,
            getPhysicsBody: () => this.physics.getPhysicsBody(),
            isMoving: () => this.isMoving(),
            onStopRunning: () => this.stopRunning(),
            onResumeRunning: () => this.resumeRunning(),
            onSetTurnTarget: (worldPosition) => this.rotation.setTargetWorldPosition(worldPosition),
            onUpdateRotation: (delta) => this.rotation.update(delta),
            isRotationComplete: () => this.rotation.isComplete(),
            onSyncRotation: () => this.syncRotationToFacing(),
        });

        this.updateDelegate = new Delegate<number>((delta) => this.update(delta));
        UpdateController.Instance.onUpdate.addListener(this.updateDelegate);

        FollowCameraC.init(this.container);
    }

    static getWorldPosition() {
        return this.container.getWorldPosition(new Vector3());
    }

    static isMoving() {
        return this.movement.Direction.lengthSq() > 0;
    }

    static isCombatBusy() {
        return PlayerCombat.isBusy;
    }

    static isAutoAttackActive() {
        return PlayerCombat.isAutoAttackActive;
    }

    static startAutoAttack(
        onStrike: () => void,
        onComplete: () => void,
        targetWorldPosition: Vector3,
    ) {
        PlayerCombat.startAutoAttack(onStrike, onComplete, targetWorldPosition);
    }

    static stopAutoAttack() {
        PlayerCombat.stopAutoAttack();
        PropHpUIC.hideAll();
    }

    static retargetAutoAttack(targetWorldPosition: Vector3) {
        PlayerCombat.retargetAutoAttack(targetWorldPosition);
    }

    static playAttack(onStrike: () => void) {
        PlayerCombat.playAttack(onStrike);
    }

    private static initPhysics() {
        this.physics = new PhysicsBody(
            this.container,
            false,
            1,
            PhysicsLayer.Player,
            PhysicsLayer.Wall | PhysicsLayer.Trigger,
        );
    }

    private static startRunning() {
        if (PlayerCombat.isBusy) return;
        if (this.isRunning) return;
        if (this.character.isPlayingAnimation(BaseAnimation.Run)) {
            this.isRunning = true;
            return;
        }
        this.character.playAnimation(BaseAnimation.Run);
        this.isRunning = true;
    }

    private static resumeRunning() {
        if (PlayerCombat.isBusy) return;
        this.isRunning = true;
        if (this.character.isPlayingAnimation(BaseAnimation.Run)) return;
        this.character.crossFadeToAnimation(BaseAnimation.Run, false, 0.15);
    }

    private static stopRunning() {
        if (!this.isRunning) return;
        if (!PlayerCombat.isBusy) {
            this.character.playAnimation(BaseAnimation.Idle);
        }
        this.animationValue = 1;
        this.isRunning = false;
    }

    private static set animationValue(value: number) {
        this.character.AnimationSpeed = value;
        this.character.AnimationWeight = value * 12.5 + 87.5;
    }

    private static update(delta: number) {
        this.movement.update(delta);

        if (this.isMoving()) {
            PlayerCombat.cancelOnMoveInput();
        }

        if (PlayerCombat.update(delta)) {
            this.syncVisual(delta);
            return;
        }

        const direction = this.movement.Direction;
        const weight = this.movement.Weight;
        const isMoving = direction.lengthSq() > 0;

        if (isMoving) {
            this.startRunning();
            this.animationValue = weight;
            FollowCameraC.inputDirection.copy(direction).normalize();
            this.rotation.setTargetDirection(direction);
        } else {
            this.stopRunning();
        }

        this.rotation.update(delta);

        this.physics.getPhysicsBody().velocity.copy(Vector3TToC(direction));
        this.physics.getPhysicsBody().wakeUp();
        this.syncVisual(delta);
    }

    private static syncRotationToFacing() {
        this.rotation.syncToCurrentFacing();
    }

    private static syncVisual(delta: number) {
        const lerpSpeed = 10;
        const targetPos = Vector3CToT(this.physics.getPhysicsBody().position);
        this.container.position.lerp(targetPos, delta * lerpSpeed);
    }
}
