import './style.css';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

// --- CONFIGURACIÓN DE LA ESCENA ---
const CONFIG = {
    CELL_SIZE: 2,           // Unidades de Three.js por celda lógica
    TILE_DIMENSIONS: 4,     // Una tile es de 4x4 celdas
};

// --- ESTADO DEL JUEGO (DATA MODEL) ---
const ASSETS = {
    tiles: {
        'tile_base': { src: '/assets/images/tiles/tile4x4.png', width: 4, height: 4 },
        'tile_cyan': { src: '/assets/images/tiles/tile4x4_blue.png', width: 4, height: 4 },
        'tile_orange': { src: '/assets/images/tiles/tile4x4_orange.png', width: 4, height: 4 },
        'tile_8x2': { src: '/assets/images/tiles/tile4x4.png', width: 8, height: 2 },
        'wall_1': { src: '/assets/images/tiles/pared1.png' },
        'door_1': { src: '/assets/images/tiles/puerta1.png' },
    },
    standees: {
        'hero_1': { src: '/assets/images/standees/barbaro.png', height: 3 },
        'hero_2': { src: '/assets/images/standees/esqueleto.png', height: 3 },
    }
};

// Sistema de salas
const ROOMS = {
    rooms: [
        {
            id: 1,
            tile: { type: 'tile_base', x: 0, y: 0 },
            walls: ['N', 'S', 'E', 'W'],
            doors: [
                { side: 'N', gridPos: { x: 1, y: -1 }, leadsTo: 2 }
            ],
            entities: [{ id: 101, type: 'hero_1', x: 1, y: 1 }]
        },
        {
            id: 2,
            tile: { type: 'tile_cyan', x: 0, y: -4 },
            walls: ['N', 'S', 'E', 'W'],
            doors: [
                { side: 'S', gridPos: { x: 1, y: -1 }, leadsTo: 1 }
            ],
            entities: [{ id: 102, type: 'hero_2', x: 1, y: -5 }]
        },
        {
            id: 3,
            tile: { type: 'tile_orange', x: -4, y: 0 },
            walls: ['N', 'S', 'E', 'W'],
            doors: [
                { side: 'E', gridPos: { x: -1, y: 1 }, leadsTo: 1 }
            ],
            entities: []
        }
    ],
    visitedRooms: [1], // Empezamos en la sala 1
    currentRoom: 1
};

const GAME_STATE = {
    placedTiles: [],
    entities: []
};

// State de la sesión (UI)
const SESSION = {
    selectedUnitId: null,
    path: [], // Array de {x, y}
    pathMeshes: [], // Array de meshes visuales
    roomMeshes: {}, // { roomId: { tile: mesh, walls: [], doors: [], entities: [] } }
    isAnimating: false // Flag para bloquear interacciones durante animación
};

// --- CONFIGURACIÓN BÁSICA THREE.JS ---
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x202020);

// Renderer
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.querySelector('#app').appendChild(renderer.domElement);

// Cámara
const aspect = window.innerWidth / window.innerHeight;
const d = 15;
const camera = new THREE.OrthographicCamera(-d * aspect, d * aspect, d, -d, 1, 1000);
camera.position.set(20, 20, 20);
camera.lookAt(scene.position);

// --- CONTROLES MODIFICADOS ---
// Roto con el ratón derecho, zoom con la rueda del ratón y si hago presión en la rueda, hago el paneo.
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = true;
controls.maxPolarAngle = Math.PI / 2;

// Reasignación de botones
controls.mouseButtons = {
    LEFT: null,              // Dejamos el click izquierdo libre para nuestra lógica
    MIDDLE: THREE.MOUSE.PAN, // Paneo con botón central/rueda
    RIGHT: THREE.MOUSE.ROTATE // Rotación con derecho
};
controls.zoomToCursor = true; // Zoom a donde apunta el ratón

// Luces
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);

const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(10, 20, 5);
dirLight.castShadow = true;
scene.add(dirLight);

const gridHelper = new THREE.GridHelper(40, 40, 0x444444, 0x111111);
scene.add(gridHelper);

// Plano invisible para Raycasting en Y=0
const planeGeometry = new THREE.PlaneGeometry(200, 200);
const planeMaterial = new THREE.MeshBasicMaterial({ visible: false });
const raycastPlane = new THREE.Mesh(planeGeometry, planeMaterial);
raycastPlane.rotation.x = -Math.PI / 2;
scene.add(raycastPlane);


// --- HELPERS LÓGICOS ---
function worldToGrid(x, z) {
    return {
        x: Math.floor(x / CONFIG.CELL_SIZE),
        y: Math.floor(z / CONFIG.CELL_SIZE)
    };
}

function gridToWorld(gridX, gridY) {
    return {
        x: (gridX * CONFIG.CELL_SIZE) + (CONFIG.CELL_SIZE / 2),
        z: (gridY * CONFIG.CELL_SIZE) + (CONFIG.CELL_SIZE / 2)
    };
}

function isAdjacent(p1, p2) {
    const dx = Math.abs(p1.x - p2.x);
    const dy = Math.abs(p1.y - p2.y);
    // Adyacencia ortogonal (cruz)
    return (dx === 1 && dy === 0) || (dx === 0 && dy === 1);
}

// --- CREACIÓN DE MARCADORES (CANVAS TEXTURE) ---
function createPathMarker(stepNumber) {
    const canvas = document.createElement('canvas');
    canvas.width = 128;
    canvas.height = 128;
    const ctx = canvas.getContext('2d');

    // Fondo Amarillo Semi-transparente
    ctx.fillStyle = 'rgba(255, 255, 0, 0.5)';
    ctx.fillRect(0, 0, 128, 128);

    // Borde
    ctx.strokeStyle = 'rgba(255, 200, 0, 0.8)';
    ctx.lineWidth = 10;
    ctx.strokeRect(0, 0, 128, 128);

    // Número
    ctx.fillStyle = '#000000';
    ctx.font = 'bold 60px Arial';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(stepNumber.toString(), 64, 64);

    const texture = new THREE.CanvasTexture(canvas);
    // Importante para pixel art o gráficos nítidos, aunque aquí es texto
    texture.minFilter = THREE.LinearFilter;

    // Crear el mesh
    const geometry = new THREE.PlaneGeometry(CONFIG.CELL_SIZE * 0.9, CONFIG.CELL_SIZE * 0.9);
    const material = new THREE.MeshBasicMaterial({
        map: texture,
        transparent: true,
        side: THREE.DoubleSide // Visible desde ambos lados
    });

    const mesh = new THREE.Mesh(geometry, material);
    mesh.rotation.x = -Math.PI / 2;
    mesh.position.y = 0.05; // Ligeramente elevado sobre el suelo
    return mesh;
}

function updatePathVisuals() {
    // 1. Limpiar anteriores
    SESSION.pathMeshes.forEach(mesh => scene.remove(mesh));
    SESSION.pathMeshes = [];

    // 2. Crear nuevos
    SESSION.path.forEach((pos, index) => {
        const marker = createPathMarker(index + 1);
        const worldPos = gridToWorld(pos.x, pos.y);
        marker.position.x = worldPos.x;
        marker.position.z = worldPos.z;
        scene.add(marker);
        SESSION.pathMeshes.push(marker);
    });
}

// --- MANEJO VISUAL DE SELECCIÓN ---
function updateSelectionVisuals() {
    GAME_STATE.entities.forEach(entity => {
        if (!entity.mesh) return;

        if (entity.id === SESSION.selectedUnitId) {
            // SELECCIONADO: Amarillo + Opacidad 50%
            entity.mesh.material.color.setHex(0xffff00);
            entity.mesh.material.opacity = 0.5;
            entity.mesh.material.transparent = true;
        } else {
            // NO SELECCIONADO: Blanco (color original) + Opacidad 100%
            entity.mesh.material.color.setHex(0xffffff);
            entity.mesh.material.opacity = 1.0;
        }
    });
}

// --- ANIMACIÓN DE MOVIMIENTO ---
async function animateMovement() {
    if (SESSION.path.length === 0 || !SESSION.selectedUnitId) return;

    SESSION.isAnimating = true;

    const unit = GAME_STATE.entities.find(e => e.id === SESSION.selectedUnitId);
    if (!unit || !unit.mesh) {
        SESSION.isAnimating = false;
        return;
    }

    // Copiar el path para ir consumiéndolo
    const pathCopy = [...SESSION.path];

    // Función helper para animar un solo paso
    const animateStep = (targetGridPos) => {
        return new Promise((resolve) => {
            const startPos = { x: unit.mesh.position.x, z: unit.mesh.position.z };
            const targetWorldPos = gridToWorld(targetGridPos.x, targetGridPos.y);
            const endPos = { x: targetWorldPos.x, z: targetWorldPos.z };

            const duration = 300; // ms por paso
            const startTime = Date.now();
            const standeeHeight = ASSETS.standees[unit.type].height;

            const hop = () => {
                const elapsed = Date.now() - startTime;
                const progress = Math.min(elapsed / duration, 1);

                // Easing suave (ease-in-out)
                const eased = progress < 0.5
                    ? 2 * progress * progress
                    : 1 - Math.pow(-2 * progress + 2, 2) / 2;

                // Interpolación lineal en X y Z
                unit.mesh.position.x = startPos.x + (endPos.x - startPos.x) * eased;
                unit.mesh.position.z = startPos.z + (endPos.z - startPos.z) * eased;

                // Saltito parabólico en Y
                const hopHeight = 0.8; // Altura del salto
                const hopProgress = Math.sin(progress * Math.PI); // 0 -> 1 -> 0
                unit.mesh.position.y = (standeeHeight / 2) + (hopProgress * hopHeight);

                if (progress < 1) {
                    requestAnimationFrame(hop);
                } else {
                    // Asegurar posición final exacta
                    unit.mesh.position.x = endPos.x;
                    unit.mesh.position.z = endPos.z;
                    unit.mesh.position.y = standeeHeight / 2;
                    resolve();
                }
            };

            hop();
        });
    };

    // Mover paso a paso
    for (let i = 0; i < pathCopy.length; i++) {
        const step = pathCopy[i];

        // Animar el movimiento
        await animateStep(step);

        // Actualizar posición lógica de la unidad
        unit.x = step.x;
        unit.y = step.y;

        // Borrar el marcador de esta celda (el primero del array)
        SESSION.path.shift();
        updatePathVisuals();
    }

    // Centrar la cámara en la posición final (manteniendo el ángulo/zoom)
    const endTarget = unit.mesh.position.clone();
    endTarget.y = 0; // Target siempre a nivel de suelo
    const currentCameraOffset = camera.position.clone().sub(controls.target);

    controls.target.copy(endTarget);
    camera.position.copy(endTarget).add(currentCameraOffset);

    // Al terminar, deseleccionar
    SESSION.selectedUnitId = null;
    updateSelectionVisuals();
    SESSION.isAnimating = false;
}

// --- INTERACCIÓN ---
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();

window.addEventListener('pointerdown', (event) => {
    // Bloquear interacciones durante animación
    if (SESSION.isAnimating) return;

    // CLICK IZQUIERDO: Selección y Pathfinding
    if (event.button === 0) {

        // Calcular coordenadas normalizadas (-1 a +1)
        pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
        pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;

        raycaster.setFromCamera(pointer, camera);

        // 1. Detectar Click en Entidades (Selección)
        // Buscamos intersecciones con los meshes de las entidades
        const entityMeshes = GAME_STATE.entities.map(e => e.mesh).filter(m => m);
        const intersectsEntities = raycaster.intersectObjects(entityMeshes);

        if (intersectsEntities.length > 0) {
            // Hemos clickado una entidad
            const clickedMesh = intersectsEntities[0].object;
            const entity = GAME_STATE.entities.find(e => e.mesh === clickedMesh);
            if (entity) {
                console.log("Seleccionado:", entity.type);
                SESSION.selectedUnitId = entity.id;
                SESSION.path = []; // Resetear camino
                updatePathVisuals();
                updateSelectionVisuals(); // Actualizar color del standee
                return; // Cortamos aquí para no procesar click de suelo a la vez
            }
        }

        // 2. Si hay unidad seleccionada, procesar Click en Suelo (Move)
        if (SESSION.selectedUnitId) {
            const intersectsGround = raycaster.intersectObject(raycastPlane);

            if (intersectsGround.length > 0) {
                const point = intersectsGround[0].point;
                const gridPos = worldToGrid(point.x, point.z);

                // LOGICA DEL PATHFINDING MANUAL

                // Punto de Origen: La última casilla del path, O la casilla de la unidad si empieza
                let prevNode;
                if (SESSION.path.length > 0) {
                    prevNode = SESSION.path[SESSION.path.length - 1];
                } else {
                    const unit = GAME_STATE.entities.find(e => e.id === SESSION.selectedUnitId);
                    prevNode = { x: unit.x, y: unit.y };
                }

                // A. Caso Deshacer (Click en la última)
                if (SESSION.path.length > 0) {
                    const lastNode = SESSION.path[SESSION.path.length - 1];
                    if (lastNode.x === gridPos.x && lastNode.y === gridPos.y) {
                        SESSION.path.pop(); // Borrar último
                        updatePathVisuals();
                        return;
                    }
                }

                // B. Caso Añadir (Tiene que ser adyacente al anterior)
                if (isAdjacent(prevNode, gridPos)) {
                    // Comprobación opcional: Evitar bucles (no clickar en uno que ya está en el path)
                    const alreadyInPath = SESSION.path.some(p => p.x === gridPos.x && p.y === gridPos.y);
                    const isUnitPos = (gridPos.x === prevNode.x && gridPos.y === prevNode.y && SESSION.path.length === 0);

                    if (!alreadyInPath && !isUnitPos) {
                        SESSION.path.push(gridPos);
                        updatePathVisuals();
                    }
                }
            }
        }
    }

    // CLICK DERECHO: Ejecutar movimiento
    if (event.button === 2) {
        event.preventDefault(); // Evitar menú contextual

        if (SESSION.selectedUnitId && SESSION.path.length > 0) {
            animateMovement();
        }
    }
});

// Prevenir menú contextual del navegador
window.addEventListener('contextmenu', (event) => {
    event.preventDefault();
});


// --- CARGA Y RENDERIZADO ---
const textureLoader = new THREE.TextureLoader();

function loadTexture(path) {
    return new Promise((resolve) => {
        textureLoader.load(path, (tex) => {
            tex.colorSpace = THREE.SRGBColorSpace;
            tex.magFilter = THREE.NearestFilter;
            tex.minFilter = THREE.NearestFilter;
            resolve(tex);
        });
    });
}

async function initWorld() {
    const tileTextures = {};
    const standeeTextures = {};

    // Cargar Tiles
    for (const [key, def] of Object.entries(ASSETS.tiles)) {
        const tex = await loadTexture(def.src);
        tex.wrapS = THREE.RepeatWrapping;
        tex.wrapT = THREE.RepeatWrapping;
        // Repetición dinámica basada en tamaño (supone 2 unidades por repetición de textura base)
        tex.repeat.set(def.width / 2, def.height / 2);
        tileTextures[key] = tex;
    }
    // Cargar Standees
    for (const [key, def] of Object.entries(ASSETS.standees)) {
        standeeTextures[key] = await loadTexture(def.src);
    }

    // Instanciar Tiles (Suelo)
    GAME_STATE.placedTiles.forEach(tileData => {
        const def = ASSETS.tiles[tileData.type];
        const tex = tileTextures[tileData.type];
        const worldWidth = def.width * CONFIG.CELL_SIZE;
        const worldHeight = def.height * CONFIG.CELL_SIZE;

        const geometry = new THREE.PlaneGeometry(worldWidth, worldHeight);
        const material = new THREE.MeshStandardMaterial({
            map: tex,
            transparent: true,
            side: THREE.DoubleSide
        });
        const mesh = new THREE.Mesh(geometry, material);

        mesh.rotation.x = -Math.PI / 2;
        mesh.receiveShadow = true;

        const originPos = gridToWorld(tileData.x, tileData.y);

        // Ajuste de centro
        mesh.position.x = originPos.x + (worldWidth / 2) - (CONFIG.CELL_SIZE / 2);
        mesh.position.z = originPos.z + (worldHeight / 2) - (CONFIG.CELL_SIZE / 2);
        mesh.position.y = 0;

        if (tileData.rotation) {
            mesh.rotation.z = tileData.rotation;
        }

        scene.add(mesh);
    });

    // Instanciar Entidades
    GAME_STATE.entities.forEach(entity => {
        const def = ASSETS.standees[entity.type];
        const tex = standeeTextures[entity.type];

        const imgAspect = tex.image.width / tex.image.height;
        const height = def.height;
        const width = height * imgAspect;

        const geometry = new THREE.PlaneGeometry(width, height);
        const material = new THREE.MeshStandardMaterial({
            map: tex,
            transparent: true,
            alphaTest: 0.5,
            side: THREE.DoubleSide
        });

        const mesh = new THREE.Mesh(geometry, material);
        mesh.castShadow = true;

        const pos = gridToWorld(entity.x, entity.y);
        mesh.position.set(pos.x, height / 2, pos.z);

        scene.add(mesh);
        entity.mesh = mesh;
    });

    // --- PAREDES DE PRUEBA (ALREDEDOR DE TILE 1) ---
    // Tile 1 es 'tile_base' en 0,0. Tamaño 4x4 celdas -> 8x8 unidades world
    const tile1 = GAME_STATE.placedTiles.find(t => t.id === 1);
    if (tile1) {
        const wallTex = await loadTexture(ASSETS.tiles['wall_1'].src);
        wallTex.wrapS = THREE.RepeatWrapping;
        wallTex.wrapT = THREE.RepeatWrapping;
        wallTex.repeat.set(2, 2); // 2x2 repeticiones como solicitado

        const baseTileWorldSize = 4 * CONFIG.CELL_SIZE; // 8 unidades
        const wallHeight = 2.5; // Altura de la pared
        const halfSize = baseTileWorldSize / 2;

        // Calcular el centro exacto de la tile 1 tal como se hace al renderizarla
        // Copiamos la lógica de renderizado de tiles:
        const def = ASSETS.tiles[tile1.type];
        const worldWidth = def.width * CONFIG.CELL_SIZE;
        const worldHeight = def.height * CONFIG.CELL_SIZE;
        const originPos = gridToWorld(tile1.x, tile1.y);

        const centerX = originPos.x + (worldWidth / 2) - (CONFIG.CELL_SIZE / 2);
        const centerZ = originPos.z + (worldHeight / 2) - (CONFIG.CELL_SIZE / 2);

        const wallGeometry = new THREE.PlaneGeometry(baseTileWorldSize, wallHeight);
        const wallMaterial = new THREE.MeshStandardMaterial({
            map: wallTex,
            transparent: true,
            opacity: 1.0,
            side: THREE.DoubleSide
        });

        const createWall = (offsetX, offsetZ, rotationY, opacity) => {
            const wall = new THREE.Mesh(wallGeometry, wallMaterial.clone());
            wall.material.opacity = opacity;
            wall.material.transparent = opacity < 1.0; // Solo transparente si opacity < 1
            // Posicionamos relativo al CENTRO de la tile
            wall.position.set(centerX + offsetX, wallHeight / 2, centerZ + offsetZ);
            wall.rotation.y = rotationY;
            wall.castShadow = true;
            wall.receiveShadow = true;
            scene.add(wall);
            SESSION.walls.push(wall);
        };

        // Norte (Arriba en pantalla, Z menor) -> 100%
        createWall(0, -halfSize, 0, 1.0);
        // Sur (Abajo en pantalla, Z mayor) -> 50%
        createWall(0, halfSize, 0, 0.5);
        // Este (Derecha en pantalla, X mayor) -> 50%
        createWall(halfSize, 0, Math.PI / 2, 0.5);
        // Oeste (Izquierda en pantalla, X menor) -> 100%
        createWall(-halfSize, 0, Math.PI / 2, 1.0);

        // --- PUERTA EN PARED NORTE ---
        const doorTex = await loadTexture(ASSETS.tiles['door_1'].src);
        const doorWidth = 1.5;  // Ancho de la puerta
        const doorHeight = 2.0; // Alto de la puerta

        const doorGeometry = new THREE.PlaneGeometry(doorWidth, doorHeight);
        const doorMaterial = new THREE.MeshStandardMaterial({
            map: doorTex,
            transparent: true,
            alphaTest: 0.1,
            side: THREE.DoubleSide
        });

        const door = new THREE.Mesh(doorGeometry, doorMaterial);
        // Posicionar en la celda (1, -1) - segunda celda de la pared norte
        const doorGridPos = gridToWorld(1, -1);
        door.position.set(doorGridPos.x, doorHeight / 2, centerZ - halfSize + 0.05);
        door.rotation.y = 0; // Misma rotación que pared norte
        scene.add(door);
    }
}

initWorld();

function animate() {
    requestAnimationFrame(animate);
    controls.update();


    renderer.render(scene, camera);
}
animate();

window.addEventListener('resize', () => {
    const aspect = window.innerWidth / window.innerHeight;
    camera.left = -d * aspect;
    camera.right = d * aspect;
    camera.top = d;
    camera.bottom = -d;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
});
