const npcsCtx = {
    list: [],
    container: null,
    onCaught: null
};

function _crearMeshNPC(npc) {
    const tex = getSpriteTexture(npc.id);
    const mat = new THREE.SpriteMaterial({
        map: tex,
        transparent: true,
        alphaTest: 0.5,
        fog: true
    });
    const mesh = new THREE.Sprite(mat);
    mesh.scale.set(1.6, 2.4, 1);
    mesh.position.y = 1.2;
    return mesh;
}

function spawnNPCs(nivel) {
    clearNPCs();
    const container = new THREE.Group();
    sceneCtx.scene.add(container);
    npcsCtx.container = container;

    const path = nivel.path;
    const spawnPos = nivel.spawn;
    const spawnCorridor = path.find((c) => {
        const r = c.rect;
        return spawnPos.x >= r.minX && spawnPos.x <= r.maxX && spawnPos.z >= r.minZ && spawnPos.z <= r.maxZ;
    });
    const eligibles = path.filter((c) => c.type !== 'callejon' && !c.isHome && c !== spawnCorridor);
    if (eligibles.length === 0) return;

    const arquetipos = nivel.arquetipos;
    const num = nivel.numNPCs;
    const margin = NPC_RADIO_COLISION + 0.4;

    for (let i = 0; i < num; i++) {
        const arquetipoId = arquetipos[i % arquetipos.length];
        const npc = NPCS[arquetipoId];
        if (!npc) continue;

        const corredor = eligibles[i % eligibles.length];
        const r = corredor.rect;
        const innerW = Math.max(0, (r.maxX - r.minX) - 2 * margin);
        const innerD = Math.max(0, (r.maxZ - r.minZ) - 2 * margin);
        const sx = r.minX + margin + Math.random() * innerW;
        const sz = r.minZ + margin + Math.random() * innerD;

        const mesh = _crearMeshNPC(npc);
        mesh.position.set(sx, 1.05, sz);
        container.add(mesh);

        const dir = corredor.dir;
        const patrolAxis = (Math.abs(dir[0]) > 0.5) ? 'z' : 'x';
        let patrolMin, patrolMax;
        if (patrolAxis === 'x') {
            patrolMin = r.minX + margin;
            patrolMax = r.maxX - margin;
        } else {
            patrolMin = r.minZ + margin;
            patrolMax = r.maxZ - margin;
        }

        npcsCtx.list.push({
            mesh: mesh,
            arquetipo: npc,
            estado: 'patrol',
            pos: { x: sx, y: 1.05, z: sz },
            corredor: corredor,
            heading: { x: dir[0], z: dir[1] },
            patrolAxis: patrolAxis,
            patrolDir: Math.random() < 0.5 ? -1 : 1,
            patrolMin: patrolMin,
            patrolMax: patrolMax,
            cooldownDeteccion: 0,
            cooldownBocadillo: 4 + Math.random() * 6,
            velocidadEfectiva: Math.min(
                NPC_VELOCIDAD_MAX,
                npc.velocidad * (nivel.velocidadMultiplicador || 1.0)
            )
        });
    }
}

function clearNPCs() {
    if (npcsCtx.container) {
        sceneCtx.scene.remove(npcsCtx.container);
        npcsCtx.container.traverse((obj) => {
            if (obj.geometry) obj.geometry.dispose();
            if (obj.material) obj.material.dispose();
        });
    }
    npcsCtx.container = null;
    npcsCtx.list = [];
}

function _detRangeMul() {
    if (typeof getDetectionRangeMultiplier === 'function') return getDetectionRangeMultiplier();
    return 1.0;
}

function updateNPCs(dt) {
    const player = getPlayerPos();
    const detMul = _detRangeMul();

    for (const npc of npcsCtx.list) {
        if (npc.estado === 'caught' || npc.estado === 'done') continue;

        const dx = player.x - npc.pos.x;
        const dz = player.z - npc.pos.z;
        const dist = Math.sqrt(dx * dx + dz * dz);
        let dot = 0;
        if (dist > 0) {
            dot = (dx / dist) * npc.heading.x + (dz / dist) * npc.heading.z;
        }

        if (npc.cooldownDeteccion > 0) npc.cooldownDeteccion -= dt;
        if (npc.cooldownBocadillo > 0) npc.cooldownBocadillo -= dt;

        if (npc.estado === 'patrol') {
            const speed = npc.velocidadEfectiva * 0.4;
            if (npc.patrolAxis === 'x') {
                npc.pos.x += npc.patrolDir * speed * dt;
                if (npc.pos.x < npc.patrolMin) { npc.pos.x = npc.patrolMin; npc.patrolDir = 1; }
                else if (npc.pos.x > npc.patrolMax) { npc.pos.x = npc.patrolMax; npc.patrolDir = -1; }
            } else {
                npc.pos.z += npc.patrolDir * speed * dt;
                if (npc.pos.z < npc.patrolMin) { npc.pos.z = npc.patrolMin; npc.patrolDir = 1; }
                else if (npc.pos.z > npc.patrolMax) { npc.pos.z = npc.patrolMax; npc.patrolDir = -1; }
            }

            if (npc.cooldownBocadillo <= 0 && dist < 16 && Math.random() < 0.65) {
                npc.cooldownBocadillo = 8 + Math.random() * 10;
                if (typeof mostrarBocadillo === 'function' && typeof fraseAleatoriaNPC === 'function') {
                    mostrarBocadillo(npc, fraseAleatoriaNPC(npc.arquetipo.id), 2400);
                }
            } else if (npc.cooldownBocadillo <= 0) {
                npc.cooldownBocadillo = 5 + Math.random() * 5;
            }

            const rango = npc.arquetipo.rangoDeteccion * detMul;
            if (npc.cooldownDeteccion <= 0 && dist < rango && dot > npc.arquetipo.anchoConoDeteccion) {
                npc.estado = 'chase';
                if (typeof mostrarBocadillo === 'function' && typeof fraseAleatoriaNPC === 'function') {
                    const f = fraseAleatoriaNPC(npc.arquetipo.id);
                    mostrarBocadillo(npc, f, 2200);
                    if (typeof speakLine === 'function' && Math.random() < 0.5) {
                        speakLine(f, npc.arquetipo.id, { priority: 'low' });
                    }
                }
            }
        } else if (npc.estado === 'chase') {
            const rango = npc.arquetipo.rangoDeteccion * detMul;
            const invuln = (gameState.invulnUntil || 0) > performance.now();
            const playerEnCallejon = isInCallejon(player.x, player.z);
            const npcEnCallejon = isInCallejon(npc.pos.x, npc.pos.z);
            if (dist > rango * 1.5) {
                npc.estado = 'patrol';
                npc.cooldownDeteccion = 1.5;
            } else if (playerEnCallejon && !npcEnCallejon) {
                npc.estado = 'patrol';
                npc.cooldownDeteccion = 4.5;
                npc.patrolDir = -npc.patrolDir;
            } else if (!invuln && dist < (NPC_RADIO_COLISION + PLAYER_RADIO_COLISION)) {
                npc.estado = 'caught';
                if (npcsCtx.onCaught) npcsCtx.onCaught(npc);
            } else {
                const speed = npc.velocidadEfectiva;
                const inv = 1 / dist;
                const sx = dx * inv * speed * dt;
                const sz = dz * inv * speed * dt;
                const tx = npc.pos.x + sx;
                const tz = npc.pos.z + sz;
                const rNPC = NPC_RADIO_COLISION;
                const npcEnCallejon = isInCallejon(npc.pos.x, npc.pos.z);
                const blockX = !npcEnCallejon && isInCallejon(tx, npc.pos.z);
                const blockZ = !npcEnCallejon && isInCallejon(npc.pos.x, tz);
                const blockDiag = !npcEnCallejon && isInCallejon(tx, tz);
                if (!blockDiag && isWalkable(tx, tz, rNPC)) {
                    npc.pos.x = tx;
                    npc.pos.z = tz;
                } else if (!blockX && isWalkable(tx, npc.pos.z, rNPC)) {
                    npc.pos.x = tx;
                } else if (!blockZ && isWalkable(npc.pos.x, tz, rNPC)) {
                    npc.pos.z = tz;
                }
            }
        }

        npc.mesh.position.set(npc.pos.x, npc.pos.y, npc.pos.z);
    }

    _separateNPCs();
    for (const npc of npcsCtx.list) {
        if (npc.estado === 'caught' || npc.estado === 'done') continue;
        npc.mesh.position.set(npc.pos.x, npc.pos.y, npc.pos.z);
    }
}

function _separateNPCs() {
    const list = npcsCtx.list;
    const minDist = 1.4;
    const minDistSq = minDist * minDist;
    for (let i = 0; i < list.length; i++) {
        const a = list[i];
        if (a.estado === 'caught' || a.estado === 'done') continue;
        for (let j = i + 1; j < list.length; j++) {
            const b = list[j];
            if (b.estado === 'caught' || b.estado === 'done') continue;
            const dx = b.pos.x - a.pos.x;
            const dz = b.pos.z - a.pos.z;
            const distSq = dx * dx + dz * dz;
            if (distSq >= minDistSq || distSq <= 0.0001) continue;
            const dist = Math.sqrt(distSq);
            const overlap = (minDist - dist) * 0.5;
            const nx = dx / dist;
            const nz = dz / dist;
            const ax = a.pos.x - nx * overlap;
            const az = a.pos.z - nz * overlap;
            const bx = b.pos.x + nx * overlap;
            const bz = b.pos.z + nz * overlap;
            const rNPC = NPC_RADIO_COLISION;
            if (isWalkable(ax, az, rNPC)) { a.pos.x = ax; a.pos.z = az; }
            if (isWalkable(bx, bz, rNPC)) { b.pos.x = bx; b.pos.z = bz; }
        }
    }
}

function reanudarTrasEncuentro(npc, exito) {
    if (!npc) return;
    if (exito) {
        npc.estado = 'done';
        npc.mesh.visible = false;
    } else {
        npc.estado = 'patrol';
        npc.cooldownDeteccion = 3;
    }
}

function setOnCaught(cb) {
    npcsCtx.onCaught = cb;
}

function getNPCList() {
    return npcsCtx.list;
}
