import * as THREE from 'three';
import { LevelInitializer } from './levels';
import { Voxel, SingleSideNeighbors, buildNeighborhoodMap, buttonVoxelToggle, resetVoxels } from './voxel';
// import { keyframes } from "popmotion";
import { ExecutionState } from '../store/executionState/types'
import game, { hasWonGame } from "./game"
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import store from "../store/store"
import { levelDone } from '../store/saveState/actions';
import { wonLevel } from '../store/executionState/actions';

// interface Player
// model control from https://github.com/mrdoob/three.js/blob/master/examples/webgl_animation_skinning_morph.html
// var states = ['Idle', 'Walking', 'Running', 'Dance', 'Death', 'Sitting', 'Standing'];
// var emotes = ['Jump', 'Yes', 'No', 'Wave', 'Punch', 'ThumbsUp'];
export const PLAYER_MODEL_FILENAME = '/assets/models/RobotExpressive.glb'
//export const PLAYER_MODEL_FILENAME = '/assets/models/robot.glb'
export class Player {
    scene: THREE.Scene;
    model: THREE.Object3D;
    mixer: THREE.AnimationMixer;
    clock: THREE.Clock;
    direction: string;
    level: LevelInitializer;
    voxels: Array<Voxel>;
    voxelStandingOn: Voxel;
    ready: boolean = false;
    actions: {}
    animations: THREE.AnimationClip[]

    constructor(scene: THREE.Scene, level: LevelInitializer) {
        this.scene = scene;
        this.level = level;
        this.clock = new THREE.Clock()
        let loader = new GLTFLoader();
        loader.load(PLAYER_MODEL_FILENAME, (gltf) => {
            this.model = gltf.scene;
            this.animations = gltf.animations;
            this.mixer = new THREE.AnimationMixer(this.model)
            this.actions = {};
            for (var i = 0; i < this.animations.length; i++) {
                var clip = this.animations[i];
                var action = this.mixer.clipAction(clip);
                this.actions[clip.name] = action;
                if (clip.name != "Idle") action.repetitions = 1
            }
            this.actions["Idle"].play()
            this.model.children[0].rotation.x = Math.PI * 0.5;
            this.model.children[0].rotation.y = Math.PI;
            this.model.scale.set(0.3, 0.3, 0.3)
            this.scene.add(this.model);
            this.voxels = buildNeighborhoodMap(this.level.voxels)
            this.reset()
            this.ready = true;
        })
    }

    update() {
        if (!this.ready) return; // Early return if not ready
        var dt = this.clock.getDelta();
        if (this.mixer) this.mixer.update(dt)
    }

    setToVoxelPosition(v: Voxel) {
        this.model.position.set(v.x, v.y, v.z + 1)
    }

    findVoxelByPos(x: number, y: number, z: number): Voxel | undefined {
        return this.voxels.find(v => v.x === x && v.y === y && v.z === z)
    }

    reset() {
        this.voxels = resetVoxels(this.voxels)
        this.voxelStandingOn = this.voxels[this.level.playerPos.voxelIdx]
        this.setToVoxelPosition(this.voxelStandingOn)
        this.direction = this.level.playerPos.direction
        this.initRotation(this.direction)
    }

    doneAnimating() {
        // if (this.mixer) this.mixer.stopAllAction()
        // this.actions["waling"]
        game.step()
    }

    initRotation(dir: string) {
        let getAngle = dir => {
            switch (dir) {
                case "east":
                    return 1.5 * Math.PI
                case "west":
                    return 0.5 * Math.PI
                case "south":
                    return Math.PI
            }
            return 0;
        }
        this.model.rotation.z = getAngle(dir)
    }

    setRotation(l: number) {
        let z0 = this.model.rotation.z
        let rz = z0 + l * 0.5 * Math.PI
        // Simplified rotation animation
        const startRotation = this.model.rotation.z;
        const startTime = Date.now();
        const duration = 1000;
        const animateStep = () => {
            const elapsed = Date.now() - startTime;
            const progress = Math.min(elapsed / duration, 1);
            this.model.rotation.z = startRotation + (rz - startRotation) * progress;
            if (progress < 1) {
                requestAnimationFrame(animateStep);
            } else {
                this.doneAnimating();
            }
        };
        animateStep();
    }

    jump() {
        if (!this.voxelStandingOn?.neighs) {
            this.doneAnimating()
            return
        }
        let x0 = this.voxelStandingOn
        let nextVoxel = this.voxelStandingOn.neighs[this.direction]
        if (nextVoxel["up"]) {
            const targetX = nextVoxel["up"].x
            const targetY = nextVoxel["up"].y
            const targetZ = nextVoxel["up"].z
            this.voxelStandingOn = this.findVoxelByPos(targetX, targetY, targetZ) || nextVoxel["up"]
        }
        if (nextVoxel["down"]) {
            const targetX = nextVoxel["down"].x
            const targetY = nextVoxel["down"].y
            const targetZ = nextVoxel["down"].z
            this.voxelStandingOn = this.findVoxelByPos(targetX, targetY, targetZ) || nextVoxel["down"]
        }
        this.actions['Jump'].reset()
        this.actions['Jump'].play()
        if (nextVoxel["down"] || nextVoxel["up"]) {

            let x1 = this.voxelStandingOn
            let v0x = x1.x - x0.x
            let v0y = x1.y - x0.y
            let v0z = x1.z - x0.z + 0.5 * 9.81

            // Simplified jump animation
        const startTime = Date.now();
        const duration = 1000;
        const animateStep = () => {
            const elapsed = Date.now() - startTime;
            const progress = Math.min(elapsed / duration, 1);
            const t = progress;
            this.model.position.set(x0.x + v0x * t,
                x0.y + v0y * t,
                x0.z + v0z * t - 0.5 * 9.81 * t * t + 1)
            if (progress < 1) {
                requestAnimationFrame(animateStep);
            } else {
                this.doneAnimating();
            }
        };
        animateStep();

            return;
        }
        else {
            this.doneAnimating()
        }
    }

    forward() {
        if (!this.voxelStandingOn?.neighs) {
            this.doneAnimating()
            return
        }
        let nextVoxel = this.voxelStandingOn.neighs[this.direction]
        if (nextVoxel["forward"]) {
            const targetX = nextVoxel["forward"].x
            const targetY = nextVoxel["forward"].y
            const targetZ = nextVoxel["forward"].z
            this.voxelStandingOn = this.findVoxelByPos(targetX, targetY, targetZ) || nextVoxel["forward"]
            let x = this.model.position.x
            let y = this.model.position.y
            this.actions['Walking'].reset()
            this.actions['Walking'].play()
            // Simplified walk animation
        const startTime = Date.now();
        const startX = x;
        const startY = y;
        const duration = 800;
        const animateStep = () => {
            const elapsed = Date.now() - startTime;
            const progress = Math.min(elapsed / duration, 1);
            const t = progress;
            this.model.position.set(startX * (1 - t) + this.voxelStandingOn.x * t,
                startY * (1 - t) + this.voxelStandingOn.y * t,
                this.model.position.z);
            if (progress < 1) {
                requestAnimationFrame(animateStep);
            } else {
                this.doneAnimating();
            }
        };
        animateStep();
        }
        else {
            this.actions["No"].reset()
            this.actions["No"].play()
            setTimeout(this.doneAnimating, 800)
        }

    }

    action() {
        this.actions['Jump'].reset()
        this.actions['Jump'].play()
        if (this.voxelStandingOn.actionable) {
            switch (this.voxelStandingOn.actionable.type) {
                case "BUTTON":
                    buttonVoxelToggle(this.voxelStandingOn)
                    // console.log("PUSH DE BOTON")
                    if (hasWonGame(this)) {
                        store.dispatch(wonLevel())
                        // console.log("WON THE GAME")
                    }
                    break;
            }
        }
        setTimeout(this.doneAnimating, 800)
    }

    turn_left() {
        switch (this.direction) {
            case "east":
                this.direction = "north"
                break
            case "north":
                this.direction = "west"
                break
            case "west":
                this.direction = "south"
                break
            case "south":
                this.direction = "east"
        }
        this.setRotation(1)
    }

    turn_right() {
        switch (this.direction) {
            case "east":
                this.direction = "south"
                break
            case "north":
                this.direction = "east"
                break
            case "west":
                this.direction = "north"
                break
            case "south":
                this.direction = "west"
        }
        this.setRotation(-1)
    }
}


