/**
 * Scene Asset Dependency Collector
 *
 * 分析 PlayCanvas 场景文件，收集所有依赖的资源 ID
 */
/**
 * 构建脚本名 → 资源 ID 的映射表
 * 遍历所有 script 类型的资源，从 data.scripts 中提取脚本名
 */
function buildScriptNameIndex(assets) {
    const index = new Map();
    for (const [assetId, asset] of Object.entries(assets)) {
        if (asset.type !== 'script')
            continue;
        const scripts = asset.data?.scripts;
        if (scripts && typeof scripts === 'object' && !Array.isArray(scripts)) {
            // ESM format: data.scripts is { scriptName: { attributes: ... } }
            for (const scriptName of Object.keys(scripts)) {
                if (!index.has(scriptName)) {
                    index.set(scriptName, assetId);
                }
            }
        }
        else if (Array.isArray(scripts)) {
            // Classic format: data.scripts is [{ name: '...' }, ...]
            for (const s of scripts) {
                if (s?.name && !index.has(s.name)) {
                    index.set(s.name, assetId);
                }
            }
        }
    }
    return index;
}
/**
 * 分析场景的资源依赖
 * @param sceneData 场景数据对象
 * @param assets 所有资源的映射表
 * @returns 场景的资源依赖信息
 */
export async function analyzeSceneDependencies(sceneData, assets) {
    const deps = {
        sceneId: sceneData.id || sceneData.scene || 'unknown',
        sceneName: sceneData.name || 'Unknown Scene',
        directAssets: new Set(),
        indirectAssets: new Set(),
        scripts: new Set(),
    };
    // 调试：检查场景数据结构
    const hasEntities = !!sceneData.entities;
    const entityCount = hasEntities ? Object.keys(sceneData.entities).length : 0;
    console.log(`  [SceneAnalyzer] 场景 "${deps.sceneName}" (ID: ${deps.sceneId})`);
    console.log(`    - 有 entities: ${hasEntities}, 实体数量: ${entityCount}`);
    // 构建脚本名 → 资源 ID 映射（用于解析 script 组件引用）
    const scriptNameIndex = buildScriptNameIndex(assets);
    // 1. 遍历所有实体
    if (sceneData.entities) {
        for (const [entityId, entity] of Object.entries(sceneData.entities)) {
            collectEntityAssets(entity, deps, assets, scriptNameIndex);
        }
    }
    else {
        console.warn(`    ⚠️ 警告: 场景没有 entities 字段，资源收集可能不完整！`);
    }
    // 2. 收集场景设置中引用的资源（如 skybox、环境贴图等）
    if (sceneData.settings) {
        collectSettingsAssets(sceneData.settings, deps, assets);
    }
    // 3. 递归收集间接依赖
    collectIndirectDependencies(deps, assets);
    return deps;
}
/**
 * 收集场景设置中引用的资源
 * 包括 skybox、环境贴图、光照贴图等
 */
function collectSettingsAssets(settings, deps, assets) {
    if (!settings || typeof settings !== 'object')
        return;
    // 渲染设置中的资源引用
    if (settings.render) {
        const render = settings.render;
        // Skybox
        if (render.skybox) {
            const skyboxId = String(render.skybox);
            if (assets[skyboxId]) {
                deps.directAssets.add(skyboxId);
            }
        }
        // 环境贴图 (ambientBake 相关)
        if (render.ambientBakeSphereMesh) {
            const meshId = String(render.ambientBakeSphereMesh);
            if (assets[meshId]) {
                deps.directAssets.add(meshId);
            }
        }
    }
    // 物理设置中的资源引用（如果有）
    if (settings.physics) {
        findAssetIds(settings.physics, deps, assets, 0);
    }
    // 递归扫描整个 settings 对象以捕获任何其他资源引用
    // 但跳过已知的非资源字段
    const knownNonAssetFields = ['gravity', 'lightingCells', 'global_ambient', 'fog_color',
        'skyboxRotation', 'skyMeshPosition', 'skyMeshRotation', 'skyMeshScale', 'skyCenter'];
    const scanSettings = (obj, depth = 0) => {
        if (depth > 5)
            return;
        if (obj === null || obj === undefined)
            return;
        if (typeof obj === 'number') {
            const id = String(obj);
            // 检查是否是有效的资源 ID（数字且存在于 assets 中）
            if (assets[id]) {
                deps.directAssets.add(id);
            }
        }
        else if (Array.isArray(obj)) {
            // 跳过坐标/颜色数组（通常是 3 或 4 个小数或小整数）
            // 资源 ID 通常是大于 10000 的正整数，不会被误跳过
            if (obj.length <= 4 && obj.every(v => typeof v === 'number' && (!Number.isInteger(v) || // 浮点数（坐标/颜色）
                (v >= 0 && v < 1000) // 小正整数（颜色分量等）
            ))) {
                return;
            }
            for (const item of obj) {
                scanSettings(item, depth + 1);
            }
        }
        else if (typeof obj === 'object') {
            for (const [key, value] of Object.entries(obj)) {
                if (knownNonAssetFields.includes(key))
                    continue;
                scanSettings(value, depth + 1);
            }
        }
    };
    scanSettings(settings);
}
/**
 * 收集实体的资源引用
 */
function collectEntityAssets(entity, deps, assets, scriptNameIndex) {
    if (!entity || typeof entity !== 'object')
        return;
    // 检查实体是否引用了 template（template_id 字段）
    if (entity.template_id) {
        const templateId = String(entity.template_id);
        if (assets[templateId]) {
            deps.directAssets.add(templateId);
            console.log(`    [Template] 实体 "${entity.name || 'unnamed'}" 引用 template: ${templateId}`);
        }
    }
    // 扫描所有组件
    if (entity.components) {
        for (const [componentName, componentData] of Object.entries(entity.components)) {
            if (componentData && typeof componentData === 'object') {
                // 特殊处理 script 组件：通过脚本名解析资源 ID
                if (componentName === 'script') {
                    collectScriptComponentAssets(componentData, deps, assets, scriptNameIndex, entity.name);
                }
                // 递归扫描组件数据，查找资源 ID（对所有组件都做，包括 script）
                findAssetIds(componentData, deps, assets);
            }
        }
    }
    // 扫描其他可能包含资源引用的字段
    const fieldsToScan = ['script', 'scripts', 'data', 'attributes'];
    for (const field of fieldsToScan) {
        if (entity[field]) {
            findAssetIds(entity[field], deps, assets);
        }
    }
}
/**
 * 处理 script 组件，将脚本名解析为资源 ID
 * PlayCanvas script 组件结构:
 *   { order: ["scriptName1", "scriptName2"], scripts: { scriptName1: {...}, scriptName2: {...} } }
 * 脚本名不是资源 ID，需要通过 scriptNameIndex 映射到对应的 script 资产
 */
function collectScriptComponentAssets(scriptComponent, deps, assets, scriptNameIndex, entityName) {
    // 从 order 数组或 scripts 对象键中收集脚本名
    const scriptNames = new Set();
    if (Array.isArray(scriptComponent.order)) {
        for (const name of scriptComponent.order) {
            if (typeof name === 'string') {
                scriptNames.add(name);
            }
        }
    }
    if (scriptComponent.scripts && typeof scriptComponent.scripts === 'object') {
        for (const name of Object.keys(scriptComponent.scripts)) {
            scriptNames.add(name);
        }
    }
    // 将脚本名映射到资源 ID
    for (const scriptName of scriptNames) {
        const assetId = scriptNameIndex.get(scriptName);
        if (assetId && assets[assetId]) {
            deps.directAssets.add(assetId);
            deps.scripts.add(assetId);
            console.log(`    [Script] 实体 "${entityName || 'unnamed'}": 脚本 "${scriptName}" → 资源 ${assetId}`);
        }
    }
    // 同时扫描 script 属性值中可能引用的其他资源（如 asset 类型的属性）
    if (scriptComponent.scripts && typeof scriptComponent.scripts === 'object') {
        for (const [scriptName, scriptConfig] of Object.entries(scriptComponent.scripts)) {
            if (scriptConfig?.attributes && typeof scriptConfig.attributes === 'object') {
                findAssetIds(scriptConfig.attributes, deps, assets);
            }
        }
    }
}
/**
 * 递归查找 JSON 中的资源 ID
 * PlayCanvas 中资源 ID 通常是数字或数字字符串
 */
function findAssetIds(obj, deps, assets, depth = 0) {
    // 防止无限递归
    if (depth > 15)
        return;
    if (obj === null || obj === undefined)
        return;
    // 检查是否是资源 ID
    if (typeof obj === 'number' || typeof obj === 'string') {
        const assetId = String(obj);
        // 验证是否是真实的资源 ID
        if (assets[assetId]) {
            const asset = assets[assetId];
            deps.directAssets.add(assetId);
            // 如果是脚本资源，单独记录
            if (asset.type === 'script') {
                deps.scripts.add(assetId);
            }
        }
        return;
    }
    // 递归处理数组
    if (Array.isArray(obj)) {
        for (const item of obj) {
            findAssetIds(item, deps, assets, depth + 1);
        }
        return;
    }
    // 递归处理对象
    if (typeof obj === 'object') {
        for (const [key, value] of Object.entries(obj)) {
            // 跳过一些不包含资源引用的字段
            if (key === 'position' || key === 'rotation' || key === 'scale' ||
                key === 'enabled' || key === 'name' || key === 'parent' ||
                key === 'children') {
                continue;
            }
            findAssetIds(value, deps, assets, depth + 1);
        }
    }
}
/**
 * 收集间接依赖（材质 → 纹理，模型 → 材质等）
 */
function collectIndirectDependencies(deps, assets) {
    const scriptNameIndex = buildScriptNameIndex(assets);
    const visited = new Set();
    const queue = Array.from(deps.directAssets);
    while (queue.length > 0) {
        const assetId = queue.shift();
        if (visited.has(assetId))
            continue;
        visited.add(assetId);
        const asset = assets[assetId];
        if (!asset)
            continue;
        const foundIds = collectAssetDeps(asset, assets, scriptNameIndex);
        for (const id of foundIds) {
            if (!visited.has(id) && !deps.directAssets.has(id) && !deps.indirectAssets.has(id)) {
                deps.indirectAssets.add(id);
                queue.push(id);
            }
        }
    }
}
/**
 * 统一的单个资源依赖收集（核心 switch/case）
 * 根据资源类型精确解析其依赖的其他资源 ID。
 * 供 collectIndirectDependencies 和 collectAllTemplatesDependencies 共用。
 */
function collectAssetDeps(asset, assets, scriptNameIndex) {
    const foundIds = new Set();
    const data = asset.data;
    switch (asset.type) {
        case 'material':
            // Material: precisely scan known texture/asset reference fields
            if (data)
                collectMaterialDeps(data, assets, foundIds);
            break;
        case 'model':
            // Model: mapping of mesh → material ID
            if (data?.mapping && typeof data.mapping === 'object') {
                for (const val of Object.values(data.mapping)) {
                    if (typeof val === 'object' && val !== null) {
                        const matVal = val.material;
                        if (matVal !== null && matVal !== undefined) {
                            const id = String(matVal);
                            if (assets[id])
                                foundIds.add(id);
                        }
                    }
                    else if (typeof val === 'number' || typeof val === 'string') {
                        const id = String(val);
                        if (assets[id])
                            foundIds.add(id);
                    }
                }
            }
            // Also scan other data fields for additional references
            if (data)
                scanComponentForAssetIds(data, assets, foundIds, 0);
            break;
        case 'cubemap':
            // Cubemap: textures array holds 6 face texture IDs
            if (data?.textures && Array.isArray(data.textures)) {
                for (const textureId of data.textures) {
                    if (textureId !== null && textureId !== undefined) {
                        const id = String(textureId);
                        if (assets[id])
                            foundIds.add(id);
                    }
                }
            }
            break;
        case 'texture':
            // Texture: usually a leaf node, no further deps
            break;
        case 'sprite':
            // Sprite: textureAtlasAsset reference
            if (data?.textureAtlasAsset) {
                const id = String(data.textureAtlasAsset);
                if (assets[id])
                    foundIds.add(id);
            }
            break;
        case 'textureatlas':
            // TextureAtlas: find associated texture by name + file hash
            findAssociatedTexture(asset, assets, foundIds);
            break;
        case 'template':
            // Template: precisely parse entities for component/script references
            collectTemplateDeps(asset, assets, scriptNameIndex, foundIds);
            break;
        case 'render':
            // Render: containerAsset (GLB/GLTF model)
            if (data?.containerAsset) {
                const id = String(data.containerAsset);
                if (assets[id])
                    foundIds.add(id);
            }
            if (data)
                scanComponentForAssetIds(data, assets, foundIds, 0);
            break;
        case 'container':
            // Container (GLB/GLTF): may reference render and material assets
            if (data)
                scanComponentForAssetIds(data, assets, foundIds, 0);
            break;
        default:
            // Other types: generic scan
            if (data)
                scanComponentForAssetIds(data, assets, foundIds, 0);
            break;
    }
    return foundIds;
}
/**
 * 分析多个场景的资源依赖并合并
 * @param scenes 场景列表
 * @param assets 所有资源的映射表
 * @param globalScriptIds 全局脚本 ID 列表（来自 settings.scripts）
 * @returns 合并后的资源 ID 集合
 */
export async function collectScenesAssets(scenes, assets, globalScriptIds) {
    const allAssetIds = new Set();
    for (const scene of scenes) {
        const deps = await analyzeSceneDependencies(scene, assets);
        // 合并直接依赖和间接依赖
        for (const id of deps.directAssets)
            allAssetIds.add(id);
        for (const id of deps.indirectAssets)
            allAssetIds.add(id);
        for (const id of deps.scripts)
            allAssetIds.add(id);
    }
    // 包含全局脚本（在 settings.scripts 中声明的）
    if (globalScriptIds) {
        for (const id of globalScriptIds) {
            allAssetIds.add(id);
        }
    }
    // 收集所有 template 资源及其依赖（含 preload=false 的插件模板如 _Guide、_ExplodeBall）
    // template 通常由 GameplaySystem 等运行时通过 assets.find(name) 按名称动态加载
    console.log(`\n📦 收集 Template 资源依赖!!!...`);
    const templateDeps = collectAllTemplatesDependencies(assets);
    let templateAssetsCount = 0;
    for (const id of templateDeps) {
        if (!allAssetIds.has(id)) {
            allAssetIds.add(id);
            templateAssetsCount++;
        }
    }
    console.log(`  - 从 Template 添加了 ${templateAssetsCount} 个额外资源`);
    // 当场景有脚本时，包含所有 audio 资产
    // 脚本常通过 app.assets.find("sfx_xxx") 按名称查找音频，静态分析无法识别
    const hasScripts = allAssetIds.size > 0 && (Array.from(allAssetIds).some(id => assets[id]?.type === 'script') ||
        (globalScriptIds && globalScriptIds.size > 0));
    if (hasScripts) {
        let audioCount = 0;
        for (const [assetId, asset] of Object.entries(assets)) {
            if (asset.type === 'audio' && !allAssetIds.has(assetId)) {
                allAssetIds.add(assetId);
                audioCount++;
            }
        }
        if (audioCount > 0) {
            console.log(`  - 场景有脚本，添加了 ${audioCount} 个 audio 资产（按名称动态引用）`);
        }
    }
    return allAssetIds;
}
/**
 * 收集所有 template 资源及其依赖（含 preload=false 的插件模板）
 * 插件模板（如 _Guide、_ExplodeBall）常由 GameplaySystem 按名称动态加载，preload 可能为 false
 */
function collectAllTemplatesDependencies(assets) {
    const result = new Set();
    const visited = new Set();
    const queue = [];
    // Build script name index for resolving script component references
    const scriptNameIndex = buildScriptNameIndex(assets);
    // Collect all templates (no longer restricted to preload=true)
    for (const [assetId, asset] of Object.entries(assets)) {
        if (asset.type === 'template') {
            result.add(assetId);
            queue.push(assetId);
        }
    }
    // BFS: recursively collect dependencies of all templates and their transitive deps
    // Reuses the same collectAssetDeps switch/case as collectIndirectDependencies
    while (queue.length > 0) {
        const assetId = queue.shift();
        if (visited.has(assetId))
            continue;
        visited.add(assetId);
        const asset = assets[assetId];
        if (!asset)
            continue;
        const foundIds = collectAssetDeps(asset, assets, scriptNameIndex);
        // Only log for template-type assets (no nested logging for transitive deps)
        if (asset.type === 'template' && foundIds.size > 0) {
            const depDetails = [...foundIds].map(id => {
                const dep = assets[id];
                return dep ? `${id}(${dep.type}:${dep.name})` : id;
            });
            console.log(`  📦 template "${asset.name}" (${assetId}) → ${foundIds.size} deps: [${depDetails.join(', ')}]`);
        }
        for (const id of foundIds) {
            if (!result.has(id)) {
                result.add(id);
                queue.push(id); // continue BFS for transitive deps
            }
        }
    }
    return result;
}
/**
 * Find the texture asset associated with a textureatlas.
 * In PlayCanvas, a textureatlas and its source texture are separate assets
 * linked by having the same name and the same file hash.
 */
function findAssociatedTexture(atlasAsset, assets, foundIds) {
    const atlasName = atlasAsset.name;
    const atlasHash = atlasAsset.file?.hash;
    for (const [assetId, asset] of Object.entries(assets)) {
        if (asset.type !== 'texture')
            continue;
        // Match by name and file hash (both should match)
        if (atlasHash && asset.file?.hash === atlasHash) {
            foundIds.add(assetId);
            return;
        }
        // Fallback: match by name if hash is not available
        if (!atlasHash && asset.name === atlasName) {
            foundIds.add(assetId);
            return;
        }
    }
}
/**
 * Collect texture dependencies from a PlayCanvas material's data object.
 * Specifically targets known texture/asset reference fields rather than blind scanning,
 * to avoid false positives from color arrays, tiling vectors, etc.
 */
function collectMaterialDeps(data, assets, foundIds) {
    // Known material fields that hold asset IDs (texture maps and cubemaps)
    const textureFields = [
        'diffuseMap', 'specularMap', 'metalnessMap', 'glossMap',
        'emissiveMap', 'opacityMap', 'normalMap', 'heightMap',
        'aoMap', 'lightMap', 'cubeMap', 'sphereMap',
        'clearCoatMap', 'clearCoatGlossMap', 'clearCoatNormalMap',
        'diffuseDetailMap', 'normalDetailMap',
        'sheenMap', 'sheenGlossMap',
        'msdfMap', // MSDF font atlas texture
    ];
    for (const field of textureFields) {
        const val = data[field];
        if (val !== null && val !== undefined) {
            const id = String(val);
            if (assets[id])
                foundIds.add(id);
        }
    }
    // Also check cubeMapProjection if it references an asset
    if (data.cubeMapProjectionBox?.asset) {
        const id = String(data.cubeMapProjectionBox.asset);
        if (assets[id])
            foundIds.add(id);
    }
}
/**
 * Collect dependencies from a template asset by analyzing its entities structure.
 * Uses precise parsing of PlayCanvas template data instead of blind recursive scanning.
 *
 * Template data structure:
 *   asset.data.entities = {
 *     "<resource_id>": {
 *       name, components: { script: { order, scripts }, element, model, ... },
 *       template_id, ...
 *     }
 *   }
 */
function collectTemplateDeps(asset, assets, scriptNameIndex, foundIds) {
    const entities = asset.data?.entities;
    if (!entities || typeof entities !== 'object')
        return;
    for (const entity of Object.values(entities)) {
        if (!entity || typeof entity !== 'object')
            continue;
        // 1. Nested template reference (template_id)
        if (entity.template_id) {
            const tid = String(entity.template_id);
            if (assets[tid])
                foundIds.add(tid);
        }
        // 2. Parse components
        if (entity.components && typeof entity.components === 'object') {
            for (const [compName, compData] of Object.entries(entity.components)) {
                if (!compData || typeof compData !== 'object')
                    continue;
                if (compName === 'script') {
                    // 2a. Script component: resolve script names → asset IDs via index
                    collectScriptDepsFromComponent(compData, assets, scriptNameIndex, foundIds);
                }
                else {
                    // 2b. Other components (element, model, sound, sprite, etc.):
                    //     scan for numeric/string asset IDs in their properties
                    scanComponentForAssetIds(compData, assets, foundIds, 0);
                }
            }
        }
    }
}
/**
 * Resolve script names in a script component to asset IDs,
 * and also scan script attribute values for asset references.
 */
function collectScriptDepsFromComponent(scriptComponent, assets, scriptNameIndex, foundIds) {
    // Collect script names from order + scripts keys
    const scriptNames = new Set();
    if (Array.isArray(scriptComponent.order)) {
        for (const name of scriptComponent.order) {
            if (typeof name === 'string')
                scriptNames.add(name);
        }
    }
    if (scriptComponent.scripts && typeof scriptComponent.scripts === 'object') {
        for (const name of Object.keys(scriptComponent.scripts)) {
            scriptNames.add(name);
        }
    }
    // Map script names → asset IDs
    for (const scriptName of scriptNames) {
        const assetId = scriptNameIndex.get(scriptName);
        if (assetId && assets[assetId]) {
            foundIds.add(assetId);
        }
    }
    // Scan script attribute values for asset references
    // (e.g. asset-type attributes like "fontAsset", "spriteAsset", etc.)
    if (scriptComponent.scripts && typeof scriptComponent.scripts === 'object') {
        for (const scriptConfig of Object.values(scriptComponent.scripts)) {
            if (scriptConfig?.attributes && typeof scriptConfig.attributes === 'object') {
                scanComponentForAssetIds(scriptConfig.attributes, assets, foundIds, 0);
            }
        }
    }
}
/**
 * Recursively scan a component data object for numeric/string asset ID references.
 * Skips known non-asset fields (position, rotation, scale, etc.).
 */
function scanComponentForAssetIds(obj, assets, foundIds, depth) {
    if (depth > 10)
        return;
    if (obj === null || obj === undefined)
        return;
    if (typeof obj === 'number') {
        const id = String(obj);
        if (assets[id])
            foundIds.add(id);
        return;
    }
    if (typeof obj === 'string') {
        if (assets[obj])
            foundIds.add(obj);
        return;
    }
    if (Array.isArray(obj)) {
        // Skip small numeric arrays (likely vectors / colors, not asset IDs)
        if (obj.length <= 4 && obj.every(v => typeof v === 'number' && (!Number.isInteger(v) || (v >= 0 && v < 1000)))) {
            return;
        }
        for (const item of obj) {
            scanComponentForAssetIds(item, assets, foundIds, depth + 1);
        }
        return;
    }
    if (typeof obj === 'object') {
        for (const [key, value] of Object.entries(obj)) {
            // Skip known non-asset fields
            if (key === 'name' || key === 'enabled' || key === 'position' ||
                key === 'rotation' || key === 'scale' || key === 'tags' ||
                key === 'children' || key === 'parent' || key === 'resource_id') {
                continue;
            }
            scanComponentForAssetIds(value, assets, foundIds, depth + 1);
        }
    }
}
/**
 * 打印场景依赖统计信息
 */
export function printSceneDependencies(deps) {
    console.log(`\n📊 场景: ${deps.sceneName} (ID: ${deps.sceneId})`);
    console.log(`  - 直接引用资源: ${deps.directAssets.size} 个`);
    console.log(`  - 间接依赖资源: ${deps.indirectAssets.size} 个`);
    console.log(`  - 脚本资源: ${deps.scripts.size} 个`);
    console.log(`  - 总计: ${deps.directAssets.size + deps.indirectAssets.size} 个资源`);
}
