import * as THREE from 'three'
import { QueryFilterFlags } from '@dimforge/rapier3d';

export class CharacterControls {
    walkDirection = new THREE.Vector3()
    rotateAngle = new THREE.Vector3(0, 1, 0)
    cameraY = 3
    #cameraZ = 5

    get cameraZ(){
        return this.#cameraZ;
    }

    set cameraZ(v){
        this.#cameraZ = Math.min(Math.max(v, 2), 10);
    }
    
    // constants
    fadeDuration = 0.2
    runVelocity = 11
    walkVelocity = 7
    lerp = (x, y, a) => x * (1 - a) + y * a;

    constructor(model, animationsMap, engine, currentAction, po, pointerControls) {
        this.model = model
        this.animationsMap = animationsMap
        this.currentAction = currentAction
        this.animationsMap[currentAction].play()
        
        this.characterController = engine.physics.world.createCharacterController(0.1);
        this.characterController.setUp({x:0, y:1, z:0});
        po.rigidBody.setTranslation(this.model.position)
        po.characterController = this.characterController;
        //this.characterController.enableSnapToGround(0.5);
        // // Don’t allow climbing slopes larger than 45 degrees.
        // this.characterController.setMaxSlopeClimbAngle(45 * Math.PI / 180);
        // Automatically slide down on slopes smaller than 30 degrees.
        // this.characterController.setMinSlopeSlideAngle(30 * Math.PI / 180);
        // this.characterController.enableAutostep(0.5, 0.2, true);
        this.characterController.setApplyImpulsesToDynamicBodies(true);
        // this.characterController.setCharacterMass(50);
        this.po = po;
        this.pointerControls = pointerControls

        this.orbitControl = engine.orbitControls
        this.camera = engine.camera

        this.direction = this.model.rotation.y;
        this.directionVelocity = 0;
        this.actionStart = 0;
        this.cameraDelta = 0;
        this.cameraIdleDelta = 0;
    }

    update(world, delta, pointerControls) {
        let input = this.getInput(pointerControls)

        let play = this.currentAction || 'idle', velocity = this.walkVelocity;
        this.fadeDuration = 0.2;
        if (input[1] && pointerControls.running) {
            play = 'run';
            velocity = this.runVelocity
        } else if (input[1] > 0) {
            play = 'walk'
        } else if (input[1] < 0) {
            play = 'backward'
            velocity = this.walkVelocity / 3
        } else if (input[0] < 0) {
            play = 'right'
        } else if (input[0] > 0) {
            play = 'left'
        } else if (!this.currentAction.startsWith('idle')){
            play = 'idle'
        } else if (this.actionStart == -1){
            this.actionStart = 0;
            play = 'idle';
        }

        if (this.currentAction.startsWith('idle') && play.startsWith('idle') && this.actionStart > 10){
            let idx = 1 + parseInt(this.currentAction.split('.')[1] || 0);
            if (this.animationsMap[`idle.${idx}`]) {
                play = `idle.${idx}`
            }else{
                play = 'idle'
            }
            this.fadeDuration = 1;
        }

        if (this.currentAction.startsWith('idle') && play != 'idle'){
            this.cameraIdleDelta += delta * 0.33;
        }else {
            this.cameraIdleDelta = 0;
        }

        if (this.currentAction != play) {
            const toPlay = this.animationsMap[play]
            const current = this.animationsMap[this.currentAction]

            current.fadeOut(this.fadeDuration)
            //toPlay.timeScale = 1;
            toPlay.reset().fadeIn(this.fadeDuration).play();

            this.currentAction = play
            this.actionStart = 0;
        }

        this.actionStart += delta;
        this.cameraDelta += delta * ( pointerControls.cameraLeft * -1 +  pointerControls.cameraRight * 1)
        this.walkDirection.x = this.walkDirection.y = this.walkDirection.z = 0

        if (pointerControls.kb.KeyR && this.cameraY < 5){
            this.cameraY+=delta;
        }
        if (pointerControls.kb.KeyF && this.cameraY > 1){
            this.cameraY-=delta;
        }

        if (pointerControls.motion) {
            this.directionVelocity = this.directionVelocity * 2.5 * Math.abs(input[0])
            this.direction += input[0] * delta * 2.5 //this.directionVelocity;
            this.model.rotation.y = this.direction;
            this.walkDirection.set(0, 0, input[1])
            this.walkDirection.applyAxisAngle(this.rotateAngle, this.direction)
            this.walkDirection.normalize();
        }

        this.walkDirection.x = this.walkDirection.x * velocity * delta// + this.model.position.x
        this.walkDirection.z = this.walkDirection.z * velocity * delta// + this.model.position.z

        //const translation = this.rigidBody.translation();

        this.characterController.computeColliderMovement(
            this.po.collider,    // The collider we would like to move.
            this.walkDirection, // The movement we would like to apply if there wasn’t any obstacle.
            QueryFilterFlags['EXCLUDE_SENSORS']
        );

        // for (let i = 0; i < this.characterController.numComputedCollisions(); i++) {
        //     let collision = this.characterController.computedCollision(i);
        //     //console.log(collision)
        //     // Do something with that collision information.
        // }

        let correctedMovement = this.characterController.computedMovement();

        let v = new THREE.Vector3();
        v.copy(this.po.rigidBody.translation());
        v.add(correctedMovement)

        this.po.rigidBody.setNextKinematicTranslation(v);

        this.model.position.copy(this.po.rigidBody.nextTranslation())

        let cameraPosition = new THREE.Vector3().copy(this.camera.position)
        let cameraDesiredPosition = new THREE.Vector3(
            this.model.position.x + this.#cameraZ* Math.sin(this.model.rotation.y + Math.PI + this.cameraDelta + this.cameraIdleDelta),
            this.cameraY,
            this.model.position.z + this.#cameraZ* Math.cos(this.model.rotation.y + Math.PI + this.cameraDelta + this.cameraIdleDelta)
        )

        cameraPosition.lerp(cameraDesiredPosition, delta*2)
        this.camera.position.copy(cameraPosition)
        this.orbitControl.target.set(this.model.position.x, this.cameraY - 1, this.model.position.z)
        this.camera.lookAt(this.orbitControl.target)
    }

    getInput(pointerControls) {
        return [
            pointerControls.moveLeft * 1 + pointerControls.moveRight * -1, 
            pointerControls.moveForward * 1 + pointerControls.moveBackward * -1
        ]
    }

    idleReset(){
        this.actionStart = -1;
        //this.currentAction = 'idle'
    }
}