import * as BABYLON from "babylonjs";
import { sharedState } from "./game/state.js";
import { seededRng, generateMazeGrid, findDeadEnds } from "./game/maze.js";
import { gridCellToWorld, isWalkableCell } from "./game/grid.js";
import { playSfx, primeSfx } from "./game/sfx.js";
import chestTextureUrl from "../img/img_chest.png";
import wallTextureUrl from "../img/img_wall.png";
import groundTextureUrl from "../img/img_ground.png";
import doorTextureUrl from "../img/img_door.png";
import gameOverImageUrl from "../img/img_jobapplication.png";

// Initialize Babylon.js engine and scene
const canvas = document.getElementById("renderCanvas");
const engine = new BABYLON.Engine(canvas, true);
const canvasTime = document.getElementById("canvas-time");
const canvasKey = document.getElementById("canvas-key");
const canvasRounds = document.getElementById("canvas-rounds");
const gameOverOverlay = document.getElementById("game-over-overlay");
const gameOverImage = document.getElementById("game-over-image");
if (gameOverImage) {
    gameOverImage.src = gameOverImageUrl;
}

const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0.05, 0.07, 0.1, 1);

const camera = new BABYLON.UniversalCamera("cam", new BABYLON.Vector3(0, 1.6, 0), scene);
camera.minZ = 0.1;
camera.speed = 1.12;
camera.angularSensibility = 1000;
camera.inertia = 0.6;
camera.keysUp = [87]; // W
camera.keysDown = [83]; // S
camera.keysLeft = [65]; // A
camera.keysRight = [68]; // D
camera.checkCollisions = true;
camera.applyGravity = true;
camera.ellipsoid = new BABYLON.Vector3(0.35, 0.9, 0.35);
camera.attachControl(canvas, true);

const overviewCamera = new BABYLON.ArcRotateCamera(
    "overviewCam",
    -Math.PI / 2,
    Math.PI / 3.2,
    40,
    new BABYLON.Vector3(0, 0, 0),
    scene,
);
overviewCamera.lowerBetaLimit = 0.2;
overviewCamera.upperBetaLimit = Math.PI / 2.05;
overviewCamera.lowerRadiusLimit = 8;
overviewCamera.inertia = 0.7;

let cameraMode = "fp";
scene.activeCamera = camera;
let lastFootstepPosition = null;
let footstepAccumulator = 0;
let footstepElapsed = 0;
let gameOverActive = false;
let lowTimeAlertPlayed = false;

scene.gravity = new BABYLON.Vector3(0, -0.2, 0);
scene.collisionsEnabled = true;

canvas.addEventListener("click", () => {
    primeSfx();
    if (cameraMode === "fp" && document.pointerLockElement !== canvas) {
        canvas.requestPointerLock();
    }
});

function updateOverviewCameraForMaze(w, h) {
    const mazeSpan = Math.max(w, h) * cellSize;
    overviewCamera.radius = Math.max(mazeSpan * 1.05, 16);
    overviewCamera.target = new BABYLON.Vector3(0, 0, 0);
}

function showGameOverScreen() {
    gameOverActive = true;
    if (gameOverOverlay) {
        gameOverOverlay.hidden = false;
    }
    if (document.pointerLockElement === canvas && document.exitPointerLock) {
        document.exitPointerLock();
    }
}

function hideGameOverScreen() {
    gameOverActive = false;
    if (gameOverOverlay) {
        gameOverOverlay.hidden = true;
    }
}

function restartRunFromGameOver() {
    sharedState.runtime.runActive = true;
    sharedState.runtime.hasKey = false;
    sharedState.runtime.roundsCompleted = 0;
    sharedState.runtime.elapsedSeconds = ROUND_TIME_SECONDS;
    sharedState.runtime.message = "Run restarted.";
    sharedState.config.level = 1;
    hideGameOverScreen();
    generateLevel();
}

function switchCameraMode() {
    if (cameraMode === "fp") {
        if (document.pointerLockElement === canvas && document.exitPointerLock) {
            document.exitPointerLock();
        }
        camera.detachControl(canvas);
        overviewCamera.attachControl(canvas, true);
        scene.activeCamera = overviewCamera;
        cameraMode = "overview";
        sharedState.runtime.message = "Overview camera (press V to return to first-person).";
        return;
    }

    overviewCamera.detachControl(canvas);
    camera.attachControl(canvas, true);
    scene.activeCamera = camera;
    cameraMode = "fp";
    sharedState.runtime.message = "First-person camera (W/A/S/D + mouse).";
}

window.addEventListener("keydown", (event) => {
    if (event.code === "KeyW" || event.code === "KeyA" || event.code === "KeyS" || event.code === "KeyD" || event.code === "KeyV" || event.code === "KeyR") {
        primeSfx();
    }
    if (event.code === "KeyR" && gameOverActive) {
        restartRunFromGameOver();
        return;
    }
    if (event.code === "KeyV") {
        switchCameraMode();
    }
});

new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);

// Central sphere (hidden but kept for reference)
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", { diameter: 2 }, scene);
sphere.isVisible = false;
const sphereMaterial = new BABYLON.StandardMaterial("sphereMaterial", scene);
sphereMaterial.diffuseColor = new BABYLON.Color3(0.2, 0.55, 0.95);
sphereMaterial.emissiveColor = new BABYLON.Color3(0.05, 0.12, 0.2);
sphere.material = sphereMaterial;

// Main render loop
engine.runRenderLoop(() => {
    const level = sharedState.config.level;
    sphere.rotation.y += 0.01;
    sphere.scaling.x = 1 + (level - 1) * 0.05;
    sphere.scaling.z = 1 + (level - 1) * 0.05;
    if (canvasTime) {
        canvasTime.textContent = `${sharedState.runtime.elapsedSeconds.toFixed(1)}s`;
    }
    if (canvasKey) {
        canvasKey.textContent = sharedState.runtime.hasKey ? "yes" : "no";
    }
    if (canvasRounds) {
        canvasRounds.textContent = String(sharedState.runtime.roundsCompleted);
    }
    scene.render();
});

window.addEventListener("resize", () => {
    engine.resize();
});

// Maze data structures
let levelMeshes = [];
let chestMap = new Map(); // key: "x,y" -> {mesh, opened}
let keyChestKey = null;
let exitBox = null;
let exitGridPos = null; // track exit grid position for collision checking
let spawnGridPos = null; // track spawn grid position for validation
let spawnMarker = null;
let highlightedChest = null;
const cellSize = 2;
const ROUND_TIME_SECONDS = 60;

function setChestHighlight(mesh) {
    if (highlightedChest === mesh) {
        return;
    }

    if (highlightedChest) {
        highlightedChest.renderOutline = false;
    }

    highlightedChest = mesh;

    if (highlightedChest) {
        highlightedChest.outlineColor = new BABYLON.Color3(0.85, 0.85, 0.85);
        highlightedChest.outlineWidth = 0.08;
        highlightedChest.renderOutline = true;
    }
}

function clearLevelMeshes() {
    setChestHighlight(null);
    for (const m of levelMeshes) {
        try { m.dispose(); } catch(e) {}
    }
    levelMeshes = [];
    chestMap.clear();
    keyChestKey = null;
    if (exitBox) { try { exitBox.dispose(); } catch(e){}; exitBox = null; }
    if (spawnMarker) { try { spawnMarker.dispose(); } catch(e){}; spawnMarker = null; }
    exitGridPos = null;
    spawnGridPos = null;
}

function isReservedCell(x, y) {
    if (chestMap.has(`${x},${y}`)) return true;
    if (exitGridPos && exitGridPos.x === x && exitGridPos.y === y) return true;
    return false;
}

function buildLevelFromGrid(grid) {
    clearLevelMeshes();
    const h = grid.length;
    const w = grid[0].length;
    const halfW = (w * cellSize) / 2;
    const halfH = (h * cellSize) / 2;

    const floor = BABYLON.MeshBuilder.CreateGround('levelGround', { width: w*cellSize, height: h*cellSize }, scene);
    floor.position = new BABYLON.Vector3(0, 0, 0);
    floor.checkCollisions = true;
    const fm = new BABYLON.StandardMaterial('floorMat', scene);
    fm.specularColor = new BABYLON.Color3(0.1, 0.1, 0.1);
    fm.diffuseTexture = new BABYLON.Texture(groundTextureUrl, scene);
    fm.diffuseTexture.uScale = Math.max(1, Math.floor(w / 2));
    fm.diffuseTexture.vScale = Math.max(1, Math.floor(h / 2));
    fm.diffuseColor = new BABYLON.Color3(0.9, 0.9, 0.9);
    floor.material = fm;
    levelMeshes.push(floor);

    const wallMat = new BABYLON.StandardMaterial('wallMat', scene);
    wallMat.diffuseTexture = new BABYLON.Texture(wallTextureUrl, scene);
    wallMat.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95);

    for (let y = 0; y < h; y++) {
        for (let x = 0; x < w; x++) {
            if (grid[y][x] === 1) {
                const box = BABYLON.MeshBuilder.CreateBox(`wall_${x}_${y}`, { size: cellSize }, scene);
                box.position = new BABYLON.Vector3(x*cellSize - halfW + cellSize/2, cellSize/2, y*cellSize - halfH + cellSize/2);
                box.material = wallMat;
                box.checkCollisions = true;
                levelMeshes.push(box);
            }
        }
    }
}

function placeChestsOnDeadEnds(grid, deadEnds, minCount, seed) {
    const rng = seededRng(seed);
    for (let i = deadEnds.length - 1; i > 0; i--) {
        const j = Math.floor(rng() * (i+1));
        [deadEnds[i], deadEnds[j]] = [deadEnds[j], deadEnds[i]];
    }
    const chosen = deadEnds.slice(0, Math.min(minCount, deadEnds.length));
    const halfW = (grid[0].length * cellSize) / 2;
    const halfH = (grid.length * cellSize) / 2;

    const chestMat = new BABYLON.StandardMaterial('chestMat', scene);
    chestMat.diffuseTexture = new BABYLON.Texture(chestTextureUrl, scene);
    chestMat.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95);

    for (const [x,y] of chosen) {
        const c = BABYLON.MeshBuilder.CreateBox(`chest_${x}_${y}`, { width: cellSize*0.8, height: cellSize*0.6, depth: cellSize*0.6 }, scene);
        c.position = new BABYLON.Vector3(x*cellSize - halfW + cellSize/2, cellSize*0.3, y*cellSize - halfH + cellSize/2);
        c.material = chestMat;
        c.isPickable = true;
        levelMeshes.push(c);
        chestMap.set(`${x},${y}`, { mesh: c, opened: false });
    }

    if (chosen.length > 0) {
        const k = Math.floor(rng() * chosen.length);
        const [kx, ky] = chosen[k];
        keyChestKey = `${kx},${ky}`;
        const entry = chestMap.get(keyChestKey);
        if (entry) {
            const km = new BABYLON.StandardMaterial('keyChestMat', scene);
            km.diffuseTexture = new BABYLON.Texture(chestTextureUrl, scene);
            km.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95);
            km.emissiveColor = new BABYLON.Color3(0.3, 0.22, 0.02);
            entry.mesh.material = km;
        }
    }
}

function placeExit(grid, seed) {
    const dead = findDeadEnds(grid);
    const rng = seededRng(seed+1);
    if (dead.length === 0) return;
    
    // Filter out dead ends that have chests
    const availableDead = dead.filter(([x, y]) => !chestMap.has(`${x},${y}`));
    if (availableDead.length === 0) {
        // Fallback: use any dead end if no chest-free spot available
        // This shouldn't happen in normal cases
        const idx = Math.floor(rng() * dead.length);
        const [x,y] = dead[idx];
        exitGridPos = { x, y };
        // Continue below
    } else {
        const idx = Math.floor(rng() * availableDead.length);
        const [x,y] = availableDead[idx];
        exitGridPos = { x, y };
    }
    
    const [x,y] = [exitGridPos.x, exitGridPos.y];
    if (!isWalkableCell(grid, x, y)) {
        console.warn("Exit selected on non-walkable cell", { x, y });
        return;
    }
    const exitWorld = gridCellToWorld(grid, x, y, cellSize);
    const ex = exitWorld.x;
    const ez = exitWorld.z;

    const plane = BABYLON.MeshBuilder.CreatePlane('exitDoor', {
        width: cellSize * 1.35,
        height: cellSize * 1.85,
        sideOrientation: BABYLON.Mesh.DOUBLESIDE,
    }, scene);
    const exitMat = new BABYLON.StandardMaterial('exitMat', scene);
    exitMat.diffuseTexture = new BABYLON.Texture(doorTextureUrl, scene);
    exitMat.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95);
    exitMat.emissiveColor = new BABYLON.Color3(0.07, 0.07, 0.07);
    plane.material = exitMat;
    plane.position = new BABYLON.Vector3(ex, cellSize * 0.92, ez);
    plane.billboardMode = BABYLON.Mesh.BILLBOARDMODE_Y;
    exitBox = plane;
    levelMeshes.push(plane);
}

function spawnCameraAt(grid) {
    const h = grid.length;
    const w = grid[0].length;
    let bestCell = null;
    let bestDist = -1;

    // Choose a valid spawn that is far from exit when possible.
    for (let y = 1; y < h - 1; y++) {
        for (let x = 1; x < w - 1; x++) {
            if (!isWalkableCell(grid, x, y)) continue;
            if (isReservedCell(x, y)) continue;

            const d = exitGridPos ? Math.hypot(x - exitGridPos.x, y - exitGridPos.y) : 0;
            if (d > bestDist) {
                bestDist = d;
                bestCell = { x, y };
            }
        }
    }

    if (!bestCell) {
        console.warn("No valid spawn cell found.");
        return;
    }

    spawnGridPos = bestCell;
    const spawnWorld = gridCellToWorld(grid, bestCell.x, bestCell.y, cellSize);
    const px = spawnWorld.x;
    const pz = spawnWorld.z;

    try {
        if (camera && camera.position) {
            camera.position = new BABYLON.Vector3(px, 1.6, pz);
        }
    } catch (e) {}
    lastFootstepPosition = camera && camera.position ? camera.position.clone() : new BABYLON.Vector3(px, 1.6, pz);
    footstepAccumulator = 0;
    footstepElapsed = 0;
}

function generateLevel() {
    hideGameOverScreen();
    lowTimeAlertPlayed = false;
    if (canvasTime) {
        canvasTime.classList.remove("low-time");
    }
    const cfg = sharedState.config;
    const seed = cfg.seed;
    const roundScale = Math.max(0, cfg.level - 1);
    const w = Math.max(9, cfg.mazeWidth + roundScale * 2);
    const h = Math.max(9, cfg.mazeHeight + roundScale * 2);
    const chestCount = Math.max(1, cfg.minChestDeadEnds + roundScale);
    const grid = generateMazeGrid(w, h, seed + cfg.level);
    updateOverviewCameraForMaze(w, h);
    const dead = findDeadEnds(grid);
    buildLevelFromGrid(grid);
    placeChestsOnDeadEnds(grid, dead, chestCount, seed + cfg.level);
    placeExit(grid, seed + cfg.level);
    spawnCameraAt(grid);

    const placementValid =
        !!exitGridPos &&
        !!spawnGridPos &&
        isWalkableCell(grid, exitGridPos.x, exitGridPos.y) &&
        isWalkableCell(grid, spawnGridPos.x, spawnGridPos.y) &&
        !(exitGridPos.x === spawnGridPos.x && exitGridPos.y === spawnGridPos.y);

    if (!placementValid) {
        sharedState.runtime.message = `Placement warning: spawn/exit invalid on level ${cfg.level}.`;
        console.warn("Invalid spawn/exit placement", { exitGridPos, spawnGridPos });
    } else {
        sharedState.runtime.message = `Level ${cfg.level} generated (spawn ${spawnGridPos.x},${spawnGridPos.y} / exit ${exitGridPos.x},${exitGridPos.y}).`;
    }
    lastFootstepPosition = camera && camera.position ? camera.position.clone() : lastFootstepPosition;
    footstepAccumulator = 0;
    footstepElapsed = 0;
    window.requestAnimationFrame(()=>{ /* let scene update */ });
}

// Expose API for p5 to call
window.mazeGameApi = { generateLevel };

// Pointer interaction for chests
scene.onPointerObservable.add((pi) => {
    if (pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) return;
    if (!sharedState.runtime.runActive || gameOverActive) return;
    const pick = scene.pick(scene.pointerX, scene.pointerY);
    if (!pick || !pick.hit || !pick.pickedMesh) return;
    const m = pick.pickedMesh;
    if (!m.name.startsWith('chest_')) return;
    const coords = m.name.split('_').slice(1).join(',');
    const entry = chestMap.get(coords);
    if (!entry) return;
    if (entry.opened) {
        primeSfx();
        playSfx("chestClose", 0.8);
        playSfx("lose", 0.85);
        sharedState.runtime.runActive = false;
        sharedState.runtime.message = 'Opened chest again — game over.';
        showGameOverScreen();
        return;
    }
    primeSfx();
    playSfx("chestOpen", 0.8);
    entry.opened = true;
    if (coords === keyChestKey) {
        sharedState.runtime.hasKey = true;
        playSfx("key", 0.85);
        sharedState.runtime.message = 'You found the key! Find the exit.';
    } else {
        sharedState.runtime.message = 'This chest was empty.';
    }
});

// Level transition check
scene.registerBeforeRender(() => {
    const targetRay = camera.getForwardRay(cellSize * 3.5);
    const targetPick = scene.pickWithRay(targetRay, (mesh) => mesh.name.startsWith('chest_'));
    if (targetPick && targetPick.hit && targetPick.pickedMesh) {
        setChestHighlight(targetPick.pickedMesh);
    } else {
        setChestHighlight(null);
    }

    if (sharedState.runtime.runActive) {
        const dt = engine.getDeltaTime() / 1000;
        sharedState.runtime.elapsedSeconds = Math.max(0, sharedState.runtime.elapsedSeconds - dt);
        
        const isLowTime = sharedState.runtime.elapsedSeconds < 10;
        if (isLowTime && !lowTimeAlertPlayed) {
            lowTimeAlertPlayed = true;
            playSfx("clock", 0.75);
            if (canvasTime) {
                canvasTime.classList.add("low-time");
            }
        }
        if (!isLowTime && lowTimeAlertPlayed) {
            lowTimeAlertPlayed = false;
            if (canvasTime) {
                canvasTime.classList.remove("low-time");
            }
        }
        
        if (sharedState.runtime.elapsedSeconds <= 0) {
            sharedState.runtime.runActive = false;
            sharedState.runtime.message = "Time up — game over.";
            playSfx("lose", 0.85);
            showGameOverScreen();
            return;
        }
    }
    if (sharedState.runtime.runActive && cameraMode === "fp" && camera && camera.position && document.pointerLockElement === canvas) {
        const currentPosition = camera.position;
        if (!lastFootstepPosition) {
            lastFootstepPosition = currentPosition.clone();
        }
        const horizontalDistance = Math.hypot(
            currentPosition.x - lastFootstepPosition.x,
            currentPosition.z - lastFootstepPosition.z,
        );
        footstepAccumulator += horizontalDistance;
        footstepElapsed += engine.getDeltaTime();
        if (footstepAccumulator > 0.75 && footstepElapsed > 220) {
            playSfx("step", 0.65);
            footstepAccumulator = 0;
            footstepElapsed = 0;
            lastFootstepPosition = currentPosition.clone();
        }
    }
    if (sharedState.runtime.hasKey && exitBox && camera && camera.position) {
        const pos = camera.position;
        const ex = exitBox.position.x, ez = exitBox.position.z;
        const dist = Math.hypot(pos.x - ex, pos.z - ez);
        if (dist < cellSize * 0.9) {
            playSfx("win", 0.85);
            sharedState.config.level += 1;
            sharedState.runtime.hasKey = false;
            sharedState.runtime.roundsCompleted += 1;
            sharedState.runtime.elapsedSeconds = ROUND_TIME_SECONDS;
            sharedState.runtime.message = `Level ${sharedState.config.level} starting.`;
            generateLevel();
        }
    }
});

// Export shared state for p5 to use
export { sharedState };
