'use strict';

const fs = require('fs');
const path = require('path');
const levelIdUtil = require('./level-id');

const LEVEL_MAP_TYPE = 'd4e5fanuMlNDh8qO0xdbn+K';
const DB_REL = 'assets/level-data/levels-database.json';
const PREFAB_BUNDLE_REL = 'assets/bundle-level-prefabs/level-prefabs';
const PREFAB_LEGACY_REL = 'assets/resources/level-prefabs';
/** @deprecated 使用 resolvePrefabDirRel */
const PREFAB_DIR_REL = PREFAB_LEGACY_REL;
const PREFAB_NAME_RE = /^Level(\d+)\.prefab$/i;
const TILE_NODE_RE = /^([gb])_(-?\d+)_(-?\d+)$/;

let spriteUuidCache = null;

function projectRoot(projectPath) {
    return projectPath || Editor.Project.path;
}

function dbPath(projectPath) {
    return path.join(projectRoot(projectPath), DB_REL);
}

function resolvePrefabDirRel(projectPath) {
    const root = projectRoot(projectPath);
    if (fs.existsSync(path.join(root, PREFAB_BUNDLE_REL))) return PREFAB_BUNDLE_REL;
    return PREFAB_LEGACY_REL;
}

function prefabDir(projectPath) {
    return path.join(projectRoot(projectPath), resolvePrefabDirRel(projectPath));
}

function prefabDbUrl(levelId, projectPath) {
    return `db://${resolvePrefabDirRel(projectPath)}/Level${levelId}.prefab`;
}

function prefabPath(levelId, projectPath) {
    return path.join(prefabDir(projectPath), `Level${levelId}.prefab`);
}

function readJsonFile(fp, fallback) {
    try {
        return JSON.parse(fs.readFileSync(fp, 'utf8'));
    } catch {
        return fallback;
    }
}

function writeJsonFile(fp, data) {
    fs.mkdirSync(path.dirname(fp), { recursive: true });
    fs.writeFileSync(fp, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
}

function buildSpriteUuidCache(projectPath) {
    if (spriteUuidCache) return spriteUuidCache;
    const cache = new Map();
    const texRoot = path.join(projectRoot(projectPath), 'assets/resources/textures');

    const walk = (dir) => {
        if (!fs.existsSync(dir)) return;
        for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
            const full = path.join(dir, ent.name);
            if (ent.isDirectory()) {
                walk(full);
                continue;
            }
            if (!/\.(png|jpg)\.meta$/i.test(ent.name)) continue;
            const tileKey = ent.name.replace(/\.(png|jpg)\.meta$/i, '');
            try {
                const meta = JSON.parse(fs.readFileSync(full, 'utf8'));
                if (meta.subMetas) {
                    for (const sub of Object.values(meta.subMetas)) {
                        if (sub && sub.uuid) cache.set(sub.uuid, tileKey);
                    }
                }
                if (meta.uuid) {
                    cache.set(meta.uuid, tileKey);
                    cache.set(`${meta.uuid}@f9941`, tileKey);
                }
            } catch {
                /* ignore broken meta */
            }
        }
    };

    walk(texRoot);
    spriteUuidCache = cache;
    return cache;
}

function tileKeyFromSpriteUuid(uuid, projectPath) {
    if (!uuid) return null;
    const cache = buildSpriteUuidCache(projectPath);
    const direct = cache.get(uuid);
    if (direct) return direct;
    const base = String(uuid).split('@')[0];
    for (const [key, val] of cache.entries()) {
        if (key.startsWith(base)) return val;
    }
    return null;
}

function parseJsonRecord(raw) {
    try {
        const parsed = JSON.parse(raw || '{}');
        return parsed && typeof parsed === 'object' ? parsed : {};
    } catch {
        return {};
    }
}

function parseLevelMapData(objs) {
    for (const obj of objs) {
        if (!obj || typeof obj !== 'object') continue;
        if (obj.__type__ !== LEVEL_MAP_TYPE && obj.groundJson === undefined) continue;
        if (obj.groundJson === undefined && obj.levelID === undefined) continue;
        return {
            levelID: parseInt(obj.levelID, 10) || 0,
            ground: parseJsonRecord(obj.groundJson),
            border: parseJsonRecord(obj.borderJson),
            theme: String(obj.theme || '').trim() || null,
        };
    }
    return null;
}

function spriteUuidOnNode(objs, node) {
    if (!node || !Array.isArray(node._components)) return null;
    for (const ref of node._components) {
        const comp = objs[(ref && ref.__id__) - 1];
        if (!comp || comp.__type__ !== 'cc.Sprite') continue;
        const frame = comp._spriteFrame;
        if (frame && frame.__uuid__) return frame.__uuid__;
    }
    return null;
}

function parseTilesFromNodes(objs, projectPath) {
    const ground = {};
    const border = {};
    for (const obj of objs) {
        if (!obj || obj.__type__ !== 'cc.Node' || typeof obj._name !== 'string') continue;
        const m = obj._name.match(TILE_NODE_RE);
        if (!m) continue;
        const x = parseInt(m[2], 10);
        const y = parseInt(m[3], 10);
        if (Number.isNaN(x) || Number.isNaN(y)) continue;
        const key = `${x},${y}`;
        const layer = m[1] === 'g' ? 'ground' : 'border';
        const tileKey = tileKeyFromSpriteUuid(spriteUuidOnNode(objs, obj), projectPath);
        if (layer === 'ground') {
            ground[key] = tileKey === 'JumpBlock' ? 'JumpBlock' : 'Baseblock';
        } else {
            const s = tileKey || 'WallBlock';
            border[key] = (s === 'Baseblock' || s === 'JumpBlock') ? 'WallBlock' : s;
        }
    }
    return { ground, border };
}

function mergeMapSources(mapData, fromNodes) {
    const md = mapData || { ground: {}, border: {}, theme: null, levelID: 0 };
    const nodes = fromNodes || { ground: {}, border: {} };
    const ground = Object.keys(nodes.ground).length ? nodes.ground : md.ground || {};
    const border = Object.keys(nodes.border).length ? nodes.border : md.border || {};
    return {
        levelID: md.levelID || 0,
        ground,
        border,
        theme: md.theme || null,
    };
}

function mergeEntry(levelId, mapData, prev) {
    const extId = levelIdUtil.isExternalLevelId(levelId) ? levelId : levelIdUtil.externalLevelId(levelId);
    const out = {
        levelID: extId,
        boundary: (prev && prev.boundary) || { x: 20, y: 20 },
        spawns: Array.isArray(prev && prev.spawns) ? [...prev.spawns] : [],
        cocosPrefab: levelIdUtil.prefabResourcePath(extId),
    };
    if (prev && prev.unityPrefab) out.unityPrefab = prev.unityPrefab;
    if (mapData) {
        if (mapData.ground && Object.keys(mapData.ground).length) out.ground = mapData.ground;
        if (mapData.border && Object.keys(mapData.border).length) out.border = mapData.border;
        if (mapData.theme) out.theme = mapData.theme;
        if (mapData.levelID > 0) out.levelID = levelIdUtil.isExternalLevelId(mapData.levelID) ? mapData.levelID : levelIdUtil.externalLevelId(mapData.levelID);
    } else if (prev) {
        for (const k of ['ground', 'border', 'theme', 'entityTextures']) {
            if (prev[k] !== undefined) out[k] = prev[k];
        }
    }
    return out;
}

function parsePrefabFile(prefabFile, projectPath) {
    const objs = readJsonFile(prefabFile, null);
    if (!Array.isArray(objs)) return null;
    const mapData = parseLevelMapData(objs);
    const fromNodes = parseTilesFromNodes(objs, projectPath);
    return mergeMapSources(mapData, fromNodes);
}

function loadDatabase(projectPath) {
    const fp = dbPath(projectPath);
    if (!fs.existsSync(fp)) {
        return levelIdUtil.normalizeDb({ levels: {} });
    }
    return levelIdUtil.normalizeDb(readJsonFile(fp, { levels: {} }));
}

function saveDatabase(db, projectPath) {
    levelIdUtil.normalizeDb(db);
    levelIdUtil.updateDbStats(db);
    db.generatedAt = new Date().toISOString();
    if (!db.version) db.version = 2;
    if (!db.source) db.source = 'Cocos level-prefabs LevelMapData + levels-database spawns';
    writeJsonFile(dbPath(projectPath), db);
}

async function refreshDatabaseAsset() {
    const assetUrl = `db://${DB_REL}`;
    try {
        await Editor.Message.request('asset-db', 'refresh-asset', assetUrl);
        const uuid = await Editor.Message.request('asset-db', 'query-uuid', assetUrl);
        if (uuid) await Editor.Message.request('asset-db', 'refresh-asset', uuid);
    } catch (e) {
        console.warn('[level-map-editor] levels-database refresh failed', e);
    }
}

function importLevelFromPrefab(levelId, options = {}) {
    const projectPath = projectRoot(options.projectPath);
    const fp = prefabPath(levelId, projectPath);
    if (!fs.existsSync(fp)) {
        return { ok: false, reason: 'prefab missing', levelId };
    }
    const mapData = parsePrefabFile(fp, projectPath);
    if (!mapData) {
        return { ok: false, reason: 'parse failed', levelId };
    }
    const db = loadDatabase(projectPath);
    const key = String(levelId);
    const prev = db.levels[key];
    db.levels[key] = mergeEntry(levelId, mapData, prev);
    levelIdUtil.touchDatabase(db, levelId);
    saveDatabase(db, projectPath);
    return {
        ok: true,
        levelId,
        ground: Object.keys(mapData.ground || {}).length,
        border: Object.keys(mapData.border || {}).length,
        theme: mapData.theme,
    };
}

function extractLevelIdFromUrl(url) {
    if (!url || typeof url !== 'string') return null;
    const m = url.match(/level-prefabs\/Level(\d+)\.prefab/i);
    return m ? parseInt(m[1], 10) : null;
}

function extractLevelIdFromFilename(name) {
    if (!name || typeof name !== 'string') return null;
    const m = name.match(PREFAB_NAME_RE);
    return m ? parseInt(m[1], 10) : null;
}

module.exports = {
    DB_REL,
    PREFAB_DIR_REL,
    PREFAB_BUNDLE_REL,
    PREFAB_LEGACY_REL,
    resolvePrefabDirRel,
    prefabDbUrl,
    prefabPath,
    dbPath,
    parsePrefabFile,
    importLevelFromPrefab,
    refreshDatabaseAsset,
    extractLevelIdFromUrl,
    extractLevelIdFromFilename,
    clearSpriteUuidCache() {
        spriteUuidCache = null;
    },
};
