const worldCtx = {
    group: null,
    path: null,
    spawn: null,
    home: null,
    maxMilestone: 0
};

const ACERA_DEPTH = 1.6;
const BUILDING_DEPTH = 6;

function _materialFromHex(hex) {
    return new THREE.MeshLambertMaterial({ color: new THREE.Color(hex) });
}

function _seededRand(seed) {
    let s = seed % 2147483647;
    if (s <= 0) s += 2147483646;
    return function() {
        s = (s * 16807) % 2147483647;
        return (s - 1) / 2147483646;
    };
}

function _disposeGroup(group) {
    group.traverse((obj) => {
        if (obj.geometry) obj.geometry.dispose();
        if (obj.material) {
            if (Array.isArray(obj.material)) obj.material.forEach((m) => m.dispose());
            else obj.material.dispose();
        }
    });
}

function clearWorld() {
    if (worldCtx.group) {
        sceneCtx.scene.remove(worldCtx.group);
        _disposeGroup(worldCtx.group);
        worldCtx.group = null;
    }
    worldCtx.path = null;
    worldCtx.maxMilestone = 0;
}

function _floorColor(c) {
    if (c.type === 'plaza') return '#5e5a48';
    if (c.type === 'callejon') return '#1c1c20';
    return PALETA_PSX.asfalto;
}

function _buildFloor(group, c) {
    const r = c.rect;
    const w = r.maxX - r.minX;
    const d = r.maxZ - r.minZ;
    const cx = (r.minX + r.maxX) / 2;
    const cz = (r.minZ + r.maxZ) / 2;
    const mat = _materialFromHex(_floorColor(c));
    const mesh = new THREE.Mesh(new THREE.PlaneGeometry(w, d), mat);
    mesh.rotation.x = -Math.PI / 2;
    mesh.position.set(cx, 0.005, cz);
    group.add(mesh);

    if (c.type === 'plaza') {
        const fuente = new THREE.Mesh(
            new THREE.CylinderGeometry(1.4, 1.6, 0.6, 12),
            _materialFromHex('#7a7a7a')
        );
        fuente.position.set(cx, 0.3, cz);
        group.add(fuente);
        const agua = new THREE.Mesh(
            new THREE.CylinderGeometry(1.1, 1.1, 0.05, 12),
            new THREE.MeshBasicMaterial({ color: '#3a8aaa' })
        );
        agua.position.set(cx, 0.6, cz);
        group.add(agua);
    }
}

function _adjacentSegmentsOnEdge(c, edge, path) {
    const r = c.rect;
    const eps = 0.05;
    const out = [];
    for (const o of path) {
        if (o === c) continue;
        const or = o.rect;
        if (edge === 'minX' && Math.abs(or.maxX - r.minX) < eps) {
            const a = Math.max(or.minZ, r.minZ);
            const b = Math.min(or.maxZ, r.maxZ);
            if (a < b) out.push({ a, b });
        } else if (edge === 'maxX' && Math.abs(or.minX - r.maxX) < eps) {
            const a = Math.max(or.minZ, r.minZ);
            const b = Math.min(or.maxZ, r.maxZ);
            if (a < b) out.push({ a, b });
        } else if (edge === 'minZ' && Math.abs(or.maxZ - r.minZ) < eps) {
            const a = Math.max(or.minX, r.minX);
            const b = Math.min(or.maxX, r.maxX);
            if (a < b) out.push({ a, b });
        } else if (edge === 'maxZ' && Math.abs(or.minZ - r.maxZ) < eps) {
            const a = Math.max(or.minX, r.minX);
            const b = Math.min(or.maxX, r.maxX);
            if (a < b) out.push({ a, b });
        }
    }
    return out;
}

function _subtractIntervals(base, holes) {
    let result = [base];
    for (const h of holes) {
        if (h.a >= h.b) continue;
        const next = [];
        for (const seg of result) {
            if (h.b <= seg.a || h.a >= seg.b) {
                next.push(seg);
            } else {
                if (h.a > seg.a) next.push({ a: seg.a, b: h.a });
                if (h.b < seg.b) next.push({ a: h.b, b: seg.b });
            }
        }
        result = next;
    }
    return result;
}

function _placeEdgeWall(group, c, edge, a, b, rand) {
    const r = c.rect;
    const aceraMat = _materialFromHex(PALETA_PSX.acera);
    const tejadoMat = _materialFromHex(PALETA_PSX.tejado);
    const along = b - a;

    let acX, acZ, acW, acD, bldX, bldZ, axis;
    if (edge === 'minX') {
        acX = r.minX - ACERA_DEPTH / 2; acZ = (a + b) / 2;
        acW = ACERA_DEPTH; acD = along;
        bldX = r.minX - ACERA_DEPTH - BUILDING_DEPTH / 2; bldZ = acZ;
        axis = 'z';
    } else if (edge === 'maxX') {
        acX = r.maxX + ACERA_DEPTH / 2; acZ = (a + b) / 2;
        acW = ACERA_DEPTH; acD = along;
        bldX = r.maxX + ACERA_DEPTH + BUILDING_DEPTH / 2; bldZ = acZ;
        axis = 'z';
    } else if (edge === 'minZ') {
        acX = (a + b) / 2; acZ = r.minZ - ACERA_DEPTH / 2;
        acW = along; acD = ACERA_DEPTH;
        bldX = acX; bldZ = r.minZ - ACERA_DEPTH - BUILDING_DEPTH / 2;
        axis = 'x';
    } else {
        acX = (a + b) / 2; acZ = r.maxZ + ACERA_DEPTH / 2;
        acW = along; acD = ACERA_DEPTH;
        bldX = acX; bldZ = r.maxZ + ACERA_DEPTH + BUILDING_DEPTH / 2;
        axis = 'x';
    }

    const acera = new THREE.Mesh(new THREE.BoxGeometry(acW, 0.2, acD), aceraMat);
    acera.position.set(acX, 0.1, acZ);
    group.add(acera);

    const numBuildings = Math.max(1, Math.floor(along / 6));
    for (let i = 0; i < numBuildings; i++) {
        const segLen = along / numBuildings * 0.94;
        const altura = 4 + rand() * 9;
        const colorIdx = Math.floor(rand() * PALETA_PSX.edificios.length);
        const mat = _materialFromHex(PALETA_PSX.edificios[colorIdx]);

        let bw, bd, bx, bz;
        const t = (i + 0.5) / numBuildings;
        if (axis === 'z') {
            bw = BUILDING_DEPTH; bd = segLen;
            bx = bldX; bz = a + along * t;
        } else {
            bw = segLen; bd = BUILDING_DEPTH;
            bx = a + along * t; bz = bldZ;
        }

        const bld = new THREE.Mesh(new THREE.BoxGeometry(bw, altura, bd), mat);
        bld.position.set(bx, altura / 2, bz);
        group.add(bld);

        const tej = new THREE.Mesh(new THREE.BoxGeometry(bw + 0.4, 0.4, bd + 0.4), tejadoMat);
        tej.position.set(bx, altura + 0.2, bz);
        group.add(tej);
    }
}

function _buildEdge(group, c, edge, path, rand) {
    const r = c.rect;
    const baseInterval = (edge === 'minX' || edge === 'maxX')
        ? { a: r.minZ, b: r.maxZ }
        : { a: r.minX, b: r.maxX };
    const holes = _adjacentSegmentsOnEdge(c, edge, path);
    const segs = _subtractIntervals(baseInterval, holes);
    for (const s of segs) {
        if (s.b - s.a < 0.4) continue;
        _placeEdgeWall(group, c, edge, s.a, s.b, rand);
    }
}

function _placeFarolas(group, c, rand) {
    if (c.type === 'callejon') return;
    const r = c.rect;
    const farolaMat = new THREE.MeshBasicMaterial({ color: PALETA_PSX.farola });
    const posteMat = _materialFromHex('#202020');
    const dirX = Math.abs(c.dir[0]) > 0.5;
    const along = dirX ? (r.maxX - r.minX) : (r.maxZ - r.minZ);
    const numFarolas = Math.max(1, Math.floor(along / 14));
    for (let i = 0; i < numFarolas; i++) {
        const t = (i + 0.5) / numFarolas;
        for (const sign of [-1, 1]) {
            let px, pz;
            if (dirX) {
                px = r.minX + along * t;
                pz = sign > 0 ? r.maxZ - 0.3 : r.minZ + 0.3;
            } else {
                pz = r.minZ + along * t;
                px = sign > 0 ? r.maxX - 0.3 : r.minX + 0.3;
            }
            const poste = new THREE.Mesh(new THREE.BoxGeometry(0.18, 4.5, 0.18), posteMat);
            poste.position.set(px, 2.25, pz);
            group.add(poste);
            const luz = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.4, 0.5), farolaMat);
            luz.position.set(px, 4.6, pz);
            group.add(luz);
        }
    }
}

function _buildHome(group, home) {
    const portalAlto = 5.5;
    const portal = new THREE.Mesh(
        new THREE.BoxGeometry(3.5, portalAlto, 0.8),
        new THREE.MeshBasicMaterial({ color: PALETA_PSX.portalCasa })
    );
    portal.position.set(home.x, portalAlto / 2, home.z);
    group.add(portal);

    const halo = new THREE.Mesh(
        new THREE.RingGeometry(2.2, 3.0, 24),
        new THREE.MeshBasicMaterial({ color: PALETA_PSX.portalCasa, transparent: true, opacity: 0.4, side: THREE.DoubleSide })
    );
    halo.rotation.x = -Math.PI / 2;
    halo.position.set(home.x, 0.05, home.z);
    group.add(halo);
}

function buildWorld(nivel) {
    clearWorld();
    const group = new THREE.Group();
    const path = nivel.path;
    const rand = _seededRand(nivel.id * 9991 + 17);

    for (const c of path) _buildFloor(group, c);
    for (const c of path) {
        _buildEdge(group, c, 'minX', path, rand);
        _buildEdge(group, c, 'maxX', path, rand);
        _buildEdge(group, c, 'minZ', path, rand);
        _buildEdge(group, c, 'maxZ', path, rand);
    }
    for (const c of path) _placeFarolas(group, c, rand);
    _buildHome(group, nivel.home);

    sceneCtx.scene.add(group);
    worldCtx.group = group;
    worldCtx.path = path;
    worldCtx.spawn = nivel.spawn;
    worldCtx.home = nivel.home;
    worldCtx.maxMilestone = 0;
}

function _puntoEnAlgunCorredor(x, z) {
    for (const c of worldCtx.path) {
        const r = c.rect;
        if (x >= r.minX && x <= r.maxX && z >= r.minZ && z <= r.maxZ) return true;
    }
    return false;
}

function isWalkable(x, z, r) {
    if (!worldCtx.path) return false;
    if (!r) return _puntoEnAlgunCorredor(x, z);
    return _puntoEnAlgunCorredor(x - r, z - r)
        && _puntoEnAlgunCorredor(x + r, z - r)
        && _puntoEnAlgunCorredor(x - r, z + r)
        && _puntoEnAlgunCorredor(x + r, z + r);
}

function isInCallejon(x, z) {
    if (!worldCtx.path) return false;
    for (const c of worldCtx.path) {
        if (c.type !== 'callejon') continue;
        const r = c.rect;
        if (x >= r.minX && x <= r.maxX && z >= r.minZ && z <= r.maxZ) return true;
    }
    return false;
}

function getCorridorAt(x, z) {
    if (!worldCtx.path) return null;
    for (const c of worldCtx.path) {
        const r = c.rect;
        if (x >= r.minX && x <= r.maxX && z >= r.minZ && z <= r.maxZ) return c;
    }
    return null;
}

function getPathDirection(x, z, currentDir) {
    if (!worldCtx.path) return [0, -1];
    const candidates = [];
    for (const c of worldCtx.path) {
        const r = c.rect;
        if (x >= r.minX && x <= r.maxX && z >= r.minZ && z <= r.maxZ) candidates.push(c);
    }
    if (candidates.length === 0) return [0, -1];
    if (candidates.length === 1) return candidates[0].dir;
    if (currentDir) {
        let best = candidates[0];
        let bestScore = best.dir[0] * currentDir[0] + best.dir[1] * currentDir[1];
        for (let i = 1; i < candidates.length; i++) {
            const c = candidates[i];
            const s = c.dir[0] * currentDir[0] + c.dir[1] * currentDir[1];
            if (s > bestScore) { best = c; bestScore = s; }
        }
        return best.dir;
    }
    return candidates[0].dir;
}

function getSpawnPosition() {
    if (!worldCtx.spawn) return { x: 0, y: 1.0, z: 0 };
    let x = worldCtx.spawn.x;
    let z = worldCtx.spawn.z;
    const r = (typeof PLAYER_RADIO_COLISION !== 'undefined' ? PLAYER_RADIO_COLISION : 0.7) + 0.1;
    const c = getCorridorAt(x, z);
    if (c) {
        const rect = c.rect;
        if (x < rect.minX + r) x = rect.minX + r;
        if (x > rect.maxX - r) x = rect.maxX - r;
        if (z < rect.minZ + r) z = rect.minZ + r;
        if (z > rect.maxZ - r) z = rect.maxZ - r;
    }
    return { x: x, y: 1.0, z: z };
}

function getHomePosition() {
    if (worldCtx.home) return { x: worldCtx.home.x, y: 1.0, z: worldCtx.home.z };
    return { x: 0, y: 1.0, z: 0 };
}

function actualizarProgreso(playerPos) {
    const c = getCorridorAt(playerPos.x, playerPos.z);
    if (!c) return;
    if (c.milestone > (worldCtx.maxMilestone || 0)) {
        worldCtx.maxMilestone = c.milestone;
    }
}

function progresoCasa() {
    return worldCtx.maxMilestone || 0;
}

function getPathCorridors() {
    return worldCtx.path || [];
}
