import store from "../store/store"
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import Stats from 'three/examples/jsm/libs/stats.module.js';
// import { keyframes } from "popmotion";
import { Voxel, DEFAULT_MATERIAL, SWITCHED_ON_MATERIAL, SWITCHED_OFF_MATERIAL } from '../game/voxel';
import { Player } from '../game/player';
import { LevelInitializer, levels } from "./levels";
import { STEP_EXECUTION, RESET_EXECUTION } from "../store/executionState/types";
import { ImprovedNoise } from 'three/examples/jsm/math/ImprovedNoise.js';
import { nextLevel } from "../store/level/slice";
import { INIT_CODEBLOCKS } from "../store/codeBlocks/types";
import { openModal, closeModal } from "../store/gameScreenState/slice";
import { SAVESTATE, SaveState, SET_LEVEL_DONE } from "../store/saveState/types";
// import { LevelInitializer } from '../game/levels';
// import { meanBy } from "lodash"



interface ThreeScene {
    camera: THREE.PerspectiveCamera,
    scene: THREE.Scene,
    renderer: THREE.WebGLRenderer,
    controls: OrbitControls,
    frameId: number,
    stats: Stats
}

class Game implements ThreeScene {
    camera: THREE.PerspectiveCamera;
    scene: THREE.Scene;
    renderer: THREE.WebGLRenderer;
    controls: OrbitControls;
    frameId: number;
    stats: Stats;
    player: Player;
    levelIdx: number;
    unsibscribe: () => void;

    constructor() {
        this.camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 1, 3000);
        this.camera.position.set(-40, 40, 30);
        this.camera.up.set(0, 0, 1);

        // renderer
        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(window.innerWidth, window.innerHeight);

        // Enhanced rendering settings for atmospheric lighting
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
        this.renderer.toneMappingExposure = 1.2;
        this.renderer.outputColorSpace = THREE.SRGBColorSpace;

        // camera controller
        this.controls = new OrbitControls(this.camera, this.renderer.domElement);
        this.controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
        // this.controls.autoRotateSpeed = 0.1;
        // this.controls.autoRotate = true;
        this.controls.dampingFactor = 0.25;
        this.controls.screenSpacePanning = false;
        this.controls.minDistance = 8;
        this.controls.maxDistance = 13;
        // this.controls.minPolarAngle = -Math.PI;
        this.controls.maxPolarAngle = Math.PI / 2 - 0.1;
        // stats
        this.stats = new Stats();
        this.levelIdx = store.getState().level.idx;
        this.unsibscribe = store.subscribe(this.handleLevelChange)
    }

    handleLevelChange = () => {
        let state = store.getState();
        let levelIdx = state.level.idx;
        if (levelIdx != this.levelIdx) {
            this.levelIdx = levelIdx;
            console.log("Game initializing new level now", levelIdx, this.levelIdx);
            this.init(state.level);
        }

    }

    init = (lev: LevelInitializer) => {
        this.scene = new THREE.Scene();

        // Gradient background: orange → pink → purple → dark
        const canvas = document.createElement('canvas');
        canvas.width = 2;
        canvas.height = 512;
        const ctx = canvas.getContext('2d')!;
        const gradient = ctx.createLinearGradient(0, 0, 0, 512);
        gradient.addColorStop(0, '#0a0515');    // dark at top
        gradient.addColorStop(0.4, '#2a1040');  // purple
        gradient.addColorStop(0.7, '#ff4080');  // pink
        gradient.addColorStop(1, '#ff6b35');    // orange at bottom
        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, 2, 512);
        const bgTexture = new THREE.CanvasTexture(canvas);
        bgTexture.magFilter = THREE.LinearFilter;
        this.scene.background = bgTexture;

        // Fog to blend distant terrain into sky
        this.scene.fog = new THREE.FogExp2(0x2a1040, 0.008);

        // Groundplane
        initGround(this.scene);

        let level = lev

        // Create mutable copy of voxels for both Player and initBoxes
        const mutableVoxels = level.voxels.map(v => ({ ...v }));

        this.player = new Player(this.scene, { ...level, voxels: mutableVoxels })
        initBoxes(this.scene, mutableVoxels, this.player);

        // Lights - Synthwave sunset style
        // Subtle ambient light for base visibility
        this.scene.add(new THREE.AmbientLight(0x3a1a4a, 2.4));

        // Hemisphere light: orange sky, purple ground
        this.scene.add(new THREE.HemisphereLight(0xff6b35, 0x2a1040, 2));

        // Dramatic point lights with warm pink tones
        let pl = new THREE.PointLight(0xffffff, 10, 0,0.3);
        pl.position.set(40, -40, 25);
        pl.castShadow = true;
        pl.shadow.mapSize.width = 2048;
        pl.shadow.mapSize.height = 2048;
        pl.shadow.bias = -0.001;
        this.scene.add(pl);
        let pl1 = new THREE.PointLight(0xffffff, 10, 0,0.3);
        pl1.position.set(-40, 40, 25);
        pl1.castShadow = true;
        pl1.shadow.mapSize.width = 2048;
        pl1.shadow.mapSize.height = 2048;
        pl1.shadow.bias = -0.001;
        this.scene.add(pl1);

    }

    step = () => {
        let state = store.getState()
        if (state.executionState.autostep && state.executionState.running) {
            store.dispatch({ "type": STEP_EXECUTION })
        }

    }

    animate = () => {
        requestAnimationFrame(this.animate);
        this.controls.update();
        if (this.player?.ready) {
            this.player.update()
        }
        this.stats.update();
        this.renderer.render(this.scene, this.camera);
    }

    start = () => {
        if (!this.frameId && this.player?.ready) {
            this.frameId = requestAnimationFrame(this.animate)
        }
    }

    canStart = () => {
        return this.player?.ready === true;
    }

    stop = () => {
        cancelAnimationFrame(this.frameId);
    }

    reset = () => {
        this.player.reset()
        store.dispatch({ type: RESET_EXECUTION })
    }

    onWindowResize = () => {
        // conso e.log(this);
        let w = Math.max(window.innerWidth * 0.6, 300);
        if (this.camera) {
            this.camera.aspect = w / window.innerHeight;
            this.camera.updateProjectionMatrix();
        }
        this.renderer.setSize(w, window.innerHeight);
    }

    nextLevel = () => {
        store.dispatch(nextLevel());
        let state = store.getState();
        store.dispatch({ type: INIT_CODEBLOCKS, level: state.level });
        store.dispatch({ type: RESET_EXECUTION });
        if (!(state.saveState.showTips && state.level.introMessages)) store.dispatch(closeModal());
    }

}

const game = new Game()
export default game


function initBoxes(scene: THREE.Scene, voxels: Array<Voxel>, player: Player) {
    const h = 1;
    let ctx = 0;
    // const off = 2

    // var texture = new THREE.TextureLoader().load('assets/textures/crate.gif');
    // var material = new THREE.MeshBasicMaterial({ map: texture });
    // let meanX = meanBy(voxels, (v) => v.x)
    // let meanY = meanBy(voxels, (v) => v.y)

    voxels.forEach((v, idx) => {
        let { x, y, z } = v
        var geometry = new THREE.BoxGeometry(h, h, h);
        // geometry.translate(-meanX, -meanY, (0.5) * h);
        geometry.translate(0, 0, (0.5) * h);

        let material = DEFAULT_MATERIAL();
        if (v.actionable) {
            switch (v.actionable.type) {
                case "BUTTON":
                    material = v.actionable.pushed ? SWITCHED_ON_MATERIAL()
                        : SWITCHED_OFF_MATERIAL();
                    break;

            }

        }
        let mesh = new THREE.Mesh(geometry, material);
        mesh.castShadow = true;
        mesh.receiveShadow = true;
        mesh.position.set(x, y, z);

        const edges = new THREE.EdgesGeometry(geometry);
        const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x000000, linewidth: 2 }));
        mesh.add(line);
        console.log(player.voxelStandingOn)
        if (idx != player.level.playerPos.voxelIdx) {
            mesh.position.setZ(10)
            setTimeout(() => {
                // Simplified animation using setTimeout
                const startZ = mesh.position.z;
                const startTime = Date.now();
                const duration = 1500;
                const animateStep = () => {
                    const elapsed = Date.now() - startTime;
                    const progress = Math.min(elapsed / duration, 1);
                    mesh.position.setZ(startZ + (z - startZ) * progress);
                    if (progress < 1) {
                        requestAnimationFrame(animateStep);
                    }
                };
                animateStep();
            }, ctx++ * 200);
        }

        // Clone the voxel and add the model property
        const extendedVoxel = { ...v, model: mesh };
        voxels[idx] = extendedVoxel;
        scene.add(mesh);
    })
}



function initGround(scene: THREE.Scene) {
    var planeGeometry = new THREE.PlaneGeometry(400, 400, 127, 127)
    planeGeometry.rotateX(Math.PI / -2);
    var data = generateHeight(128, 128);
    let vertices = planeGeometry.attributes.position.array;

    for (var i = 0, j = 0, l = vertices.length; i < l; i++ , j += 3) {
        (vertices[j + 1] as number) = data[i];
    }
    planeGeometry.computeVertexNormals();

    var planeWire = new THREE.Mesh(planeGeometry,
        new THREE.MeshPhongMaterial({
            color: 0xff00ff,
            specular: 0x0,
            wireframeLinewidth: 2,
            wireframe: true
        })
    );
    planeWire.position.y = 0;
    planeWire.rotateX(Math.PI * 0.5)

    var planeSolid = new THREE.Mesh(planeGeometry,
        new THREE.MeshPhongMaterial({
            color: 0x0a0510,
            specular: 0x0,
            shininess: 0x0,
        })
    );
    planeSolid.position.y = 0;
    planeSolid.rotateX(Math.PI * 0.5)
    planeSolid.receiveShadow = true;

    var grid = new THREE.GridHelper(200, 20, 0xff00ff, 0xff00ff);
    grid.rotateX(Math.PI * 0.5);
    grid.position.z = 0.05;
    (grid.material as THREE.LineBasicMaterial).opacity = 0.2;
    (grid.material as THREE.LineBasicMaterial).transparent = true;

    var gridFar = new THREE.GridHelper(200, 40, 0xff00ff, 0xff00ff);
    gridFar.rotateX(Math.PI * 0.5);
    gridFar.position.z = 0.02;
    (gridFar.material as THREE.LineBasicMaterial).opacity = 0.1;
    (gridFar.material as THREE.LineBasicMaterial).transparent = true;

    scene.add(planeWire);
    scene.add(planeSolid);
    //scene.add(gridFar);
    //scene.add(grid);
}


function generateHeight(width, height) {
    var size = width * height, data = new Uint8Array(size),
        perlin = new ImprovedNoise(), quality = 1, z = Math.random() * 20;

    for (var j = 0; j < 4; j++) {
        for (var i = 0; i < size; i++) {
            var ix = i % width, iy = ~ ~(i / width);
            data[i] += Math.abs(perlin.noise(ix / quality, iy / quality, z) * quality * 1.75);
        }
        quality *= 5;
    }

    for (var i = 0; i < size; i++) {
        var x = ((i % width) - 64);
        var y = ((~ ~(i / width)) - 64);
        var distFromCenter = Math.sqrt(x * x + y * y);
        
        var flatRadius = 4;
        var mountainStart = 50;
        
        if (distFromCenter < flatRadius) {
            data[i] = 0;
        } else if (distFromCenter < mountainStart) {
            var t = (distFromCenter - flatRadius) / (mountainStart - flatRadius);
            var rise = Math.pow(t, 1.5);
            data[i] = data[i] * rise * 1.5;
        } else {
            var valleyWidth = 25;
            var valleyDepth = Math.max(0, 1 - Math.abs(x) / valleyWidth);
            valleyDepth = Math.pow(valleyDepth, 2);
            data[i] = data[i] * (2.5 - valleyDepth * 1.2);
        }
    }
    return data;
}


// check if all button-voxels are currently pressed
export const hasWonGame = (player: Player) => {
    for (let v of player.voxels) {
        if (v.actionable && v.actionable.type == "BUTTON") {
            if (!v.actionable.pushed) return false;
        }
    }
    let idx = store.getState().level.idx
    store.dispatch({ type: SET_LEVEL_DONE, levelIdx: idx });
    saveState();
    return true;
}


export function saveState() {
    let state = store.getState().saveState
    localStorage.setItem(SAVESTATE, btoa(JSON.stringify(state)))
}
