import { build as viteBuild } from 'vite';
import fs from 'fs/promises';
import path from 'path';
/**
 * 递归复制目录
 */
async function copyDir(src, dest) {
    await fs.mkdir(dest, { recursive: true });
    const entries = await fs.readdir(src, { withFileTypes: true });
    for (const entry of entries) {
        const srcPath = path.join(src, entry.name);
        const destPath = path.join(dest, entry.name);
        if (entry.isDirectory()) {
            await copyDir(srcPath, destPath);
        }
        else {
            await fs.copyFile(srcPath, destPath);
        }
    }
}
/**
 * 复制脚本文件到工作目录用于打包
 * 只复制 js/ 目录、脚本目录（LiteCreator、Generated、Temp 等）和 planck.mjs 等脚本文件
 * 不复制资源文件（files/、images/ 等）
 */
async function copyScriptFilesForBundling(baseBuildDir, workDir) {
    await fs.mkdir(workDir, { recursive: true });
    // 读取入口文件获取脚本路径
    const entryPath = path.join(baseBuildDir, 'js/index.mjs');
    let scriptDirs = new Set();
    try {
        const entryContent = await fs.readFile(entryPath, 'utf-8');
        const scriptPathsMatch = entryContent.match(/const SCRIPT_IMPORT_PATHS = (\[[\s\S]*?\]);/);
        if (scriptPathsMatch) {
            const scriptPaths = JSON.parse(scriptPathsMatch[1]);
            // 提取脚本所在的顶级目录
            for (const p of scriptPaths) {
                // 例如 '../LiteCreator/Gameplay/...' -> 'LiteCreator'
                // 注意：排除直接的文件（如 '../planck.mjs'），只提取目录
                const match = p.match(/^\.\.\/([^\/]+)\//);
                if (match) {
                    scriptDirs.add(match[1]);
                }
            }
        }
    }
    catch (e) {
        console.log('[ESMBundle] 无法解析脚本路径，将复制所有 .mjs 和 .js 文件');
    }
    // 始终复制 js/ 目录
    const jsSrcDir = path.join(baseBuildDir, 'js');
    const jsDestDir = path.join(workDir, 'js');
    try {
        await copyDir(jsSrcDir, jsDestDir);
        console.log('[ESMBundle] 已复制 js/ 目录到工作目录');
    }
    catch (e) {
        console.error('[ESMBundle] 复制 js/ 目录失败:', e);
    }
    // 复制识别到的脚本目录
    for (const dir of scriptDirs) {
        const srcDir = path.join(baseBuildDir, dir);
        const destDir = path.join(workDir, dir);
        try {
            await fs.access(srcDir);
            await copyDir(srcDir, destDir);
            console.log(`[ESMBundle] 已复制 ${dir}/ 目录到工作目录`);
        }
        catch (e) {
            // 目录不存在，跳过
        }
    }
    // 复制根目录下的 .mjs 文件（如 planck.mjs）
    try {
        const rootFiles = await fs.readdir(baseBuildDir, { withFileTypes: true });
        for (const file of rootFiles) {
            if (file.isFile() && (file.name.endsWith('.mjs') || file.name.endsWith('.js'))) {
                const srcPath = path.join(baseBuildDir, file.name);
                const destPath = path.join(workDir, file.name);
                try {
                    // 检查目标路径是否存在
                    try {
                        const destStat = await fs.stat(destPath);
                        if (destStat.isDirectory()) {
                            // 如果目标是目录，递归删除它
                            await fs.rm(destPath, { recursive: true, force: true });
                            console.log(`[ESMBundle] 删除了同名目录: ${file.name}`);
                        }
                        else {
                            // 如果是文件，直接删除
                            await fs.rm(destPath, { force: true });
                        }
                    }
                    catch {
                        // 目标不存在，无需删除
                    }
                    await fs.copyFile(srcPath, destPath);
                    console.log(`[ESMBundle] 已复制 ${file.name} 到工作目录`);
                }
                catch (copyErr) {
                    // 如果 copyFile 失败，尝试读取内容并写入（绕过权限问题）
                    try {
                        const content = await fs.readFile(srcPath, 'utf-8');
                        await fs.writeFile(destPath, content, 'utf-8');
                        console.log(`[ESMBundle] 已写入 ${file.name} 到工作目录（使用备用方法）`);
                    }
                    catch (writeErr) {
                        console.warn(`[ESMBundle] 无法复制 ${file.name}:`, copyErr);
                    }
                }
            }
        }
    }
    catch (e) {
        console.error('[ESMBundle] 复制根目录脚本文件失败:', e);
    }
}
/**
 * 从 index.html 中提取 Import Map
 * 用于解析裸模块说明符（如 'gameRule'）
 */
async function extractImportMap(baseBuildDir) {
    try {
        const htmlPath = path.join(baseBuildDir, 'index.html');
        const html = await fs.readFile(htmlPath, 'utf-8');
        // 提取 import map 内容
        const importMapMatch = html.match(/<script\s+type=["']importmap["'][^>]*>([\s\S]*?)<\/script>/i);
        if (!importMapMatch) {
            return {};
        }
        const importMapJson = JSON.parse(importMapMatch[1]);
        return importMapJson.imports || {};
    }
    catch (e) {
        console.log('[ESMBundle] 未能提取 Import Map:', e);
        return {};
    }
}
/**
 * 生成统一的 createScript 延迟执行包装代码
 * 这段代码只需要在入口文件中添加一次，所有用户脚本都可以使用
 */
function generateCreateScriptDeferWrapper() {
    return `
// ========== createScript Deferred Execution Wrapper ==========
// This wrapper ensures createScript calls work even before app.scripts is initialized.
// It intercepts pc.createScript and queues calls if app.scripts is not ready yet.
(function() {
    if (typeof pc === 'undefined') return;
    
    const _originalCreateScript = pc.createScript;
    if (!_originalCreateScript) return;
    
    const _pendingCreates = [];
    let _appReady = false;
    
    // Check if app is ready
    function checkAppReady() {
        return typeof pc !== 'undefined' && pc.app && pc.app.scripts;
    }
    
    // Process pending createScript calls
    function processPending() {
        if (!checkAppReady()) return;
        _appReady = true;
        pc.createScript = _originalCreateScript;
        _pendingCreates.forEach(function(args) {
            try {
                args.callback(_originalCreateScript.apply(pc, args.params));
            } catch (e) {
                console.error('[createScript] Failed to register script:', args.params[0], e);
            }
        });
        _pendingCreates.length = 0;
    }
    
    // Override createScript to queue calls if app not ready
    if (!checkAppReady()) {
        pc.createScript = function(name, app) {
            if (checkAppReady() || _appReady) {
                return _originalCreateScript.call(pc, name, app);
            }
            // Return a placeholder that will be populated later
            const placeholder = function() {};
            placeholder.__deferred = true;
            placeholder.attributes = { add: function() {} };
            placeholder.prototype = {};
            _pendingCreates.push({
                params: [name, app],
                callback: function(real) {
                    Object.assign(placeholder, real);
                    Object.setPrototypeOf(placeholder, Object.getPrototypeOf(real));
                }
            });
            
            // Register handler to process when ready
            if (_pendingCreates.length === 1) {
                window.__pendingScriptRegistrations = window.__pendingScriptRegistrations || [];
                window.__pendingScriptRegistrations.push(processPending);
            }
            
            return placeholder;
        };
    }
})();
// ========== End createScript Deferred Execution Wrapper ==========

`;
}
/**
 * 扫描并修改 ESM 脚本文件：
 * 1. 提取继承 Script 的类信息（包括间接继承）
 * 2. 在每个类定义后添加 pc.registerScript() 调用
 *
 * PlayCanvas Engine 2.x ESM 脚本需要通过 pc.registerScript() 显式注册
 *
 * 检测方式：
 * 1. 直接继承: export class XXX extends Script
 * 2. 间接继承: export class XXX extends YYY，且 YYY 是已知的 Script 子类
 *
 * 重要：仅凭 static scriptName 不能判断是否是 PlayCanvas Script！
 * 用户可能在非 Script 类中使用 scriptName 作为普通标识符。
 * 必须通过继承链确认是否继承自 Script/pc.Script。
 */
/**
 * 处理 ESM Script 类：添加注册代码和 attributes 定义
 *
 * @param baseBuildDir - Base Build 产物目录（脚本文件所在目录）
 * @param scriptPaths - 脚本路径列表（相对于 js/index.mjs）
 * @param preparedScriptsDir - 可选：预处理脚本的输出目录。如果提供，修改后的脚本会写入此目录而不是覆盖原文件
 * @param configDir - 可选：config.json 所在目录。如果不提供，默认使用 baseBuildDir
 */
async function processESMScriptClasses(baseBuildDir, scriptPaths, preparedScriptsDir, configDir) {
    const scriptClasses = [];
    const jsDir = path.join(baseBuildDir, 'js');
    // config.json 所在目录，默认为 baseBuildDir
    const configBaseDir = configDir || baseBuildDir;
    // 用于缓存从 config.json 读取的脚本 schema
    let scriptSchemas = null;
    // 第一遍：收集所有已知的 Script 基类
    // 仅 PlayCanvas 内置的 Script 基类
    const knownScriptBases = new Set([
        'Script', 'pc.Script',
        // 从代码中发现的继承 Script 的自定义基类会被动态添加
    ]);
    // 存储每个文件的内容和检测结果，避免重复读取
    const fileContents = new Map();
    const allClassDefs = new Map();
    // 第一遍扫描：收集所有类定义
    for (const relativePath of scriptPaths) {
        const scriptPath = path.resolve(jsDir, relativePath);
        try {
            // 检查路径是否存在且是文件（不是目录）
            const stat = await fs.stat(scriptPath);
            if (!stat.isFile()) {
                console.log(`[ESMBundle] 跳过非文件路径: ${relativePath}`);
                continue;
            }
        }
        catch {
            continue;
        }
        const content = await fs.readFile(scriptPath, 'utf-8');
        fileContents.set(relativePath, { content, modified: false });
        // 匹配所有 export class XXX extends YYY 模式
        const classPattern = /export\s+class\s+(\w+)\s+extends\s+([\w.]+)\s*\{/g;
        let match;
        while ((match = classPattern.exec(content)) !== null) {
            const className = match[1];
            const baseClass = match[2];
            // 查找 static scriptName = 'xxx' 定义
            const scriptNamePattern = new RegExp(`class\\s+${className}[\\s\\S]*?static\\s+scriptName\\s*=\\s*["']([\\w]+)["']`, 'm');
            const scriptNameMatch = content.match(scriptNamePattern);
            const scriptName = scriptNameMatch ? scriptNameMatch[1] : null;
            allClassDefs.set(className, {
                className,
                baseClass,
                scriptName,
                filePath: relativePath,
            });
            // 如果直接继承 Script，添加到已知基类
            if (baseClass === 'Script' || baseClass === 'pc.Script') {
                knownScriptBases.add(className);
            }
        }
    }
    // 迭代传播：找出所有间接继承 Script 的类
    // 只有继承链追溯到 Script/pc.Script 的类才会被标记
    let changed = true;
    while (changed) {
        changed = false;
        for (const [className, info] of allClassDefs) {
            if (!knownScriptBases.has(className) && knownScriptBases.has(info.baseClass)) {
                knownScriptBases.add(className);
                changed = true;
            }
        }
    }
    // 输出调试信息：已识别的 Script 基类
    console.log(`[ESMBundle] 已识别的 Script 基类: ${Array.from(knownScriptBases).filter(n => n !== 'Script' && n !== 'pc.Script').join(', ')}`);
    // 第二遍：处理所有 Script 类
    for (const relativePath of scriptPaths) {
        const fileData = fileContents.get(relativePath);
        if (!fileData)
            continue;
        let { content, modified } = fileData;
        const classesInFile = [];
        // 查找该文件中的所有 Script 类
        for (const [className, info] of allClassDefs) {
            if (info.filePath !== relativePath)
                continue;
            // 判断是否是 PlayCanvas Script 类
            // 必须直接或间接继承 Script/pc.Script
            // 注意：仅有 static scriptName 不足以判断，因为非 Script 类也可能使用这个属性
            const isScript = knownScriptBases.has(className);
            if (!isScript) {
                // 如果有 scriptName 但不继承 Script，输出警告
                if (info.scriptName) {
                    console.log(`[ESMBundle] 跳过 ${className}：有 scriptName='${info.scriptName}' 但不继承 Script (继承自 ${info.baseClass})`);
                }
                continue;
            }
            // 确定 scriptName
            let scriptName;
            if (info.scriptName) {
                scriptName = info.scriptName;
            }
            else {
                // 使用类名转换（首字母小写）
                scriptName = className.charAt(0).toLowerCase() + className.slice(1);
                console.log(`[ESMBundle] 警告: ${className} 没有定义 scriptName，使用默认值: ${scriptName}`);
            }
            // 检查是否有 static attributes 定义
            const hasStaticAttributes = /static\s+attributes\s*=/.test(content.substring(content.indexOf(`class ${className}`), content.indexOf(`class ${className}`) + 3000));
            const hasExternalAttributes = content.includes(`${className}.attributes`);
            const hasAttributes = hasStaticAttributes || hasExternalAttributes;
            scriptClasses.push({
                className,
                scriptName,
                filePath: relativePath,
            });
            classesInFile.push({ className, scriptName, hasAttributes });
        }
        // 如果文件中有 Script 类，处理 attributes 和注册代码
        if (classesInFile.length > 0) {
            // 1. 从 config.json 读取脚本 schema（一次读取，缓存使用）
            // config.json 结构：assets[assetId].data.scripts[scriptName].attributes
            // 注意：使用 configBaseDir 而不是 baseBuildDir，因为 baseBuildDir 可能是工作目录（没有 config.json）
            if (!scriptSchemas) {
                try {
                    const configPath = path.join(configBaseDir, 'config.json');
                    console.log(`[ESMBundle] 读取 config.json: ${configPath}`);
                    const configContent = await fs.readFile(configPath, 'utf-8');
                    const configJson = JSON.parse(configContent);
                    scriptSchemas = {};
                    // 从 assets 中提取所有脚本的 schema
                    if (configJson.assets) {
                        for (const assetId of Object.keys(configJson.assets)) {
                            const asset = configJson.assets[assetId];
                            if (asset.type === 'script' && asset.data && asset.data.scripts) {
                                for (const [scriptName, scriptDef] of Object.entries(asset.data.scripts)) {
                                    if (scriptDef && scriptDef.attributes) {
                                        scriptSchemas[scriptName] = {
                                            attributes: scriptDef.attributes,
                                            attributesOrder: scriptDef.attributesOrder
                                        };
                                    }
                                }
                            }
                        }
                    }
                    console.log(`[ESMBundle] 从 config.json 提取了 ${Object.keys(scriptSchemas).length} 个脚本 schema`);
                    // 打印一些示例 schema 用于调试
                    const sampleSchemas = Object.entries(scriptSchemas).slice(0, 5);
                    for (const [name, schema] of sampleSchemas) {
                        const attrNames = Object.keys(schema.attributes || {});
                        console.log(`  [ESMBundle] - ${name}: ${attrNames.length} attributes`);
                    }
                }
                catch (e) {
                    console.warn('[ESMBundle] 无法读取 config.json 中的脚本 schema:', e);
                    scriptSchemas = {};
                }
            }
            // 2. 检查是否已经有注册代码
            const hasRegistration = content.includes('pc.registerScript(') ||
                content.includes('window.pc.registerScript(') ||
                content.includes('// ESM Script Auto-Registration');
            if (!hasRegistration) {
                // 生成每个类的注册和 schema 添加调用
                // 从 config.json 获取 schema，因为 ESM 类可能没有静态 attributes
                // 关键：registerScript 不会自动创建 ScriptAttributes，需要手动创建
                const schemas = scriptSchemas;
                const registrationParts = classesInFile.map(({ className, scriptName }) => {
                    // 获取该脚本的 schema（如果存在于 config.json）
                    const schema = schemas[scriptName];
                    let attributesCode = '';
                    // 调试：打印 schema 查找结果
                    if (scriptName === 'stick3D' || scriptName === 'followCamera') {
                        console.log(`[ESMBundle] DEBUG: ${scriptName} schema lookup:`, schema ? `found ${Object.keys(schema.attributes || {}).length} attrs` : 'NOT FOUND');
                    }
                    if (schema && schema.attributes) {
                        // 为每个属性生成 add 调用
                        const attrEntries = Object.entries(schema.attributes);
                        console.log(`[ESMBundle] 为 ${scriptName} 添加 ${attrEntries.length} 个属性定义`);
                        // 生成 schema 对象（用于 addSchema）
                        const schemaJson = JSON.stringify({
                            attributes: schema.attributes,
                            attributesOrder: schema.attributesOrder || Object.keys(schema.attributes)
                        });
                        // 旧代码：给类添加 attributes 属性（不再需要，因为 Script 基类没有 getter）
                        // 新代码：只存储 schema 数据，在延迟执行时通过 addSchema 注册
                        attributesCode = `
            // Store schema data for ${scriptName} (will be registered via addSchema later)
            window.__esmScriptSchemas = window.__esmScriptSchemas || {};
            window.__esmScriptSchemas['${scriptName}'] = ${schemaJson};`;
                    }
                    else {
                        // 没有 schema 时
                        console.log(`[ESMBundle] 警告: ${scriptName} 没有找到 schema`);
                        attributesCode = `
            // No schema for ${scriptName}`;
                    }
                    // 返回两部分代码：
                    // 1. 立即执行：存储类引用和 schema 数据到全局对象（不需要 pc.app）
                    // 2. 延迟执行：注册脚本 + 添加 schema（需要 pc.app.scripts）
                    return {
                        scriptName,
                        className,
                        attributesCode,
                        // 立即执行的代码 - 存储类引用和 schema 数据
                        immediateCode: `(function() {
          if (typeof ${className} !== 'undefined') {
            window.__esmScriptClasses = window.__esmScriptClasses || {};
            window.__esmScriptClasses['${scriptName}'] = ${className};${attributesCode}
          }
        })()`,
                        // 延迟执行的代码 - 注册脚本 + 添加 schema
                        deferredCode: `(function() {
          var cls = window.__esmScriptClasses && window.__esmScriptClasses['${scriptName}'];
          var schema = window.__esmScriptSchemas && window.__esmScriptSchemas['${scriptName}'];
          if (cls) {
            window.pc.registerScript(cls, '${scriptName}');
            // Add schema to ScriptRegistry for initializeAttributes to work
            if (schema && window.pc.app && window.pc.app.scripts && window.pc.app.scripts.addSchema) {
              window.pc.app.scripts.addSchema('${scriptName}', schema);
            }
          }
        })()`
                    };
                });
                // 分别生成立即执行和延迟执行的代码
                const immediateCode = registrationParts.map(p => p.immediateCode).join(';\n');
                const deferredCode = registrationParts.map(p => p.deferredCode).join(';\n');
                const registrationCode = `
// ========== ESM Script Auto-Registration ==========
// Auto-generated: register ESM Script classes with PlayCanvas
// Strategy: 
// 1. Immediately store class references and schema data to global objects (no pc.app needed)
// 2. Defer registerScript() + addSchema() calls until after app.init() when pc.app.scripts is available
// 
// Key insight: ESM scripts extend pc.Script (gG), not pc.ScriptType (gX).
// For pc.Script subclasses, initializeAttributes uses getSchema() to get attribute definitions,
// so we must call addSchema() to register the schema with ScriptRegistry.

// Step 1: Store class references and schema data (immediate)
${immediateCode};

// Step 2: Register scripts and schemas (may be deferred if pc.app.scripts not ready)
(function() {
    var _pc = typeof window !== 'undefined' && window.pc;
    var canRegister = _pc && 
                      typeof _pc.registerScript === 'function' && 
                      _pc.app && _pc.app.scripts;
    
    if (!canRegister) {
        // Defer registration until after app.init()
        window.__deferredESMScripts = window.__deferredESMScripts || [];
        window.__deferredESMScripts.push(function() {
            ${deferredCode.split('\n').map(c => '            ' + c).join('\n')}
        });
        return;
    }
    // If can register immediately
    ${deferredCode}
})();
// ========== End ESM Script Auto-Registration ==========
`;
                content = content + registrationCode;
                modified = true;
                console.log(`[ESMBundle] 为 ${relativePath} 添加了 ${classesInFile.length} 个脚本的注册代码`);
            }
        }
        // 写回文件
        if (modified) {
            // 如果提供了 preparedScriptsDir，写入临时目录而不是覆盖原文件
            // 这样可以保持 baseBuildDir 中的原始脚本不变
            const targetDir = preparedScriptsDir ? path.join(preparedScriptsDir, 'js') : jsDir;
            const targetPath = path.resolve(targetDir, relativePath);
            // 确保目标目录存在
            await fs.mkdir(path.dirname(targetPath), { recursive: true });
            await fs.writeFile(targetPath, content);
            if (preparedScriptsDir) {
                console.log(`[ESMBundle] 写入预处理脚本到临时目录: ${relativePath}`);
            }
        }
    }
    console.log(`[ESMBundle] 共处理 ${scriptClasses.length} 个 ESM Script 类`);
    return scriptClasses;
}
/**
 * 预处理用户脚本文件
 * 对于没有导出的脚本，将代码包装为延迟执行
 * 对于有导出的脚本，不再单独添加包装代码（包装代码统一在入口文件中）
 */
async function preprocessUserScripts(baseBuildDir, scriptPaths, configDir) {
    if (scriptPaths.length === 0) {
        return [];
    }
    console.log(`[ESMBundle] 预处理 ${scriptPaths.length} 个用户脚本...`);
    // 首先处理所有 ESM Script 类，添加注册代码
    // 注意：configDir 用于读取 config.json（可能与 baseBuildDir 不同，例如工作目录场景）
    const esmScriptClasses = await processESMScriptClasses(baseBuildDir, scriptPaths, undefined, configDir);
    // js/index.mjs 的目录
    const jsDir = path.join(baseBuildDir, 'js');
    for (const relativePath of scriptPaths) {
        // 脚本路径相对于 js/index.mjs
        const scriptPath = path.resolve(jsDir, relativePath);
        try {
            // 检查路径是否存在且是文件（不是目录）
            const stat = await fs.stat(scriptPath);
            if (!stat.isFile()) {
                console.log(`[ESMBundle] 跳过非文件路径: ${relativePath}`);
                continue;
            }
        }
        catch {
            console.log(`[ESMBundle] 脚本不存在，跳过: ${relativePath}`);
            continue;
        }
        let content = await fs.readFile(scriptPath, 'utf-8');
        // 检查是否包含 createScript 调用（Legacy Script 语法）
        if (!content.includes('createScript')) {
            continue;
        }
        // 保留所有 import 语句，但把其他代码包装起来
        const lines = content.split('\n');
        const importLines = [];
        const codeLines = [];
        let inImportSection = true;
        for (const line of lines) {
            const trimmed = line.trim();
            // 检查是否是 import 语句（支持多行 import）
            if (inImportSection && (trimmed.startsWith('import ') || trimmed.startsWith('export ') && trimmed.includes(' from '))) {
                importLines.push(line);
            }
            else if (inImportSection && trimmed === '') {
                // 空行也保留在 import 区域
                importLines.push(line);
            }
            else {
                inImportSection = false;
                codeLines.push(line);
            }
        }
        // 只有实际有代码的脚本才需要包装
        const codeContent = codeLines.join('\n').trim();
        if (!codeContent) {
            continue;
        }
        // 检查是否有导出语句（如 export class）
        const hasExport = codeContent.includes('export ');
        if (hasExport) {
            // 对于有导出的脚本，不需要单独添加包装代码
            // 因为统一的 createScript 包装代码已经在入口文件中添加
            // 这些脚本可以直接使用，createScript 会被自动队列化
            console.log(`[ESMBundle] 脚本有导出，依赖入口包装器: ${relativePath}`);
        }
        else {
            // 没有导出的脚本，可以整体包装
            const wrappedCode = `${importLines.join('\n')}

// Defer script registration until app.scripts is available
(function() {
    const registerScript = function() {
${codeLines.map(l => '        ' + l).join('\n')}
    };
    
    // Check if app.scripts is already available
    if (typeof pc !== 'undefined' && pc.app && pc.app.scripts) {
        registerScript();
    } else {
        // Queue for later execution
        window.__pendingScriptRegistrations = window.__pendingScriptRegistrations || [];
        window.__pendingScriptRegistrations.push(registerScript);
    }
})();
`;
            await fs.writeFile(scriptPath, wrappedCode);
            console.log(`[ESMBundle] 已包装脚本: ${relativePath}`);
        }
    }
    return esmScriptClasses;
}
/**
 * 预先打包 ESM 脚本为 IIFE
 * 这个函数在主 Vite 构建之前调用
 *
 * 重要：此函数会在临时目录中创建 baseBuildDir 的工作副本，
 * 所有修改都在工作副本中进行，不会影响原始的 baseBuildDir。
 */
export async function bundleESMToIIFE(options) {
    console.log('[ESMBundle] 开始打包 ESM 脚本...');
    const esmEntryPath = path.join(options.baseBuildDir, 'js/index.mjs');
    try {
        await fs.access(esmEntryPath);
    }
    catch {
        console.log('[ESMBundle] 未找到 ESM 入口，跳过打包');
        return '';
    }
    try {
        // 读取并预处理入口文件
        let entryContent = await fs.readFile(esmEntryPath, 'utf-8');
        // 检测是否是预打包格式（由其他工具如 Rollup/Vite 打包的 ESM）
        const isPreBundled = entryContent.includes('__esmScriptDynamicImportRuntime__') ||
            entryContent.includes('function __esmScriptDynamicImportRuntime__');
        if (isPreBundled) {
            console.log('[ESMBundle] 检测到预打包的 ESM 格式，使用特殊处理流程...');
            return await bundlePreBundledESM(options, entryContent);
        }
        // 创建工作副本目录（在输出目录下创建，避免修改原始 baseBuildDir）
        const workDir = path.join(options.outputDir, '__esm-work__');
        console.log('[ESMBundle] 创建工作副本目录:', workDir);
        // 清理之前的工作目录（如果存在）
        await fs.rm(workDir, { recursive: true, force: true });
        // 复制必要的文件到工作目录（只复制脚本相关文件）
        await copyScriptFilesForBundling(options.baseBuildDir, workDir);
        // 在工作目录中进行所有修改
        entryContent = entryContent.replace(/^\/\/ Import PlayCanvas Engine\s*\nimport \* as pc from ['"]\.\/playcanvas-engine\.mjs['"];\s*\n/m, '// PlayCanvas Engine is already loaded globally\nconst pc = window.pc;\n');
        // 移除 window.pc = { ...pc } 这行，因为 pc 已经是 window.pc
        entryContent = entryContent.replace(/^\/\/ Export for global access.*\nwindow\.pc = \{ \.\.\.pc \};\s*\n/m, '');
        // 提取 SCRIPT_IMPORT_PATHS 数组
        const scriptPathsMatch = entryContent.match(/const SCRIPT_IMPORT_PATHS = (\[[\s\S]*?\]);/);
        let scriptPaths = [];
        if (scriptPathsMatch) {
            try {
                scriptPaths = JSON.parse(scriptPathsMatch[1]);
                console.log(`[ESMBundle] 找到 ${scriptPaths.length} 个脚本导入路径`);
            }
            catch (e) {
                console.log('[ESMBundle] 解析脚本路径失败:', e);
            }
        }
        // 不再直接添加静态导入语句到入口文件
        // 因为这会导致脚本在 app.init() 之前执行，此时 pc.app.scripts 不存在
        // 用户脚本将在后处理阶段被包装并延迟执行
        // 预处理用户脚本文件，将 createScript 调用包装为延迟执行
        // 同时扫描并返回 ESM Script 类信息，用于生成注册代码
        // 注意：使用工作目录 workDir 进行脚本修改，但使用原始 baseBuildDir 读取 config.json
        const esmScriptClasses = await preprocessUserScripts(workDir, scriptPaths, options.baseBuildDir);
        // 生成统一的 createScript 包装代码（只添加一次）
        const deferWrapper = generateCreateScriptDeferWrapper();
        // 现在可以安全地添加静态导入，因为包装代码会拦截 createScript 调用
        if (scriptPaths.length > 0) {
            const staticImports = scriptPaths.map((p) => `import '${p}';`).join('\n');
            // 在文件开头（引擎导入之后）插入包装代码和静态导入
            entryContent = entryContent.replace('// PlayCanvas Engine is already loaded globally\nconst pc = window.pc;\n', `// PlayCanvas Engine is already loaded globally
const pc = window.pc;

${deferWrapper}
// Static imports for bundling (scripts use the unified createScript wrapper above)
${staticImports}
`);
            console.log('[ESMBundle] 已添加统一的 createScript 包装器和静态导入语句');
        }
        else {
            // 即使没有用户脚本，也添加包装代码以防万一
            entryContent = entryContent.replace('// PlayCanvas Engine is already loaded globally\nconst pc = window.pc;\n', `// PlayCanvas Engine is already loaded globally
const pc = window.pc;

${deferWrapper}`);
        }
        // 修改 loadUserScripts 函数，使其在 IIFE 模式下不尝试动态加载
        // 因为脚本已经被静态打包了
        entryContent = entryContent.replace(/async function loadUserScripts\(scriptPaths\) \{[\s\S]*?console\.log\('\[ESM\] Scripts loaded\. GameRule will be initialized after scene loads\.'\);\s*\}/, `async function loadUserScripts(scriptPaths) {
    // In IIFE bundle mode, all scripts are already statically imported and bundled
    // Execute deferred script registration now that app.scripts is available
    if (window.__pendingScriptRegistrations) {
        console.log('[ESM-IIFE] Executing ' + window.__pendingScriptRegistrations.length + ' deferred script registrations...');
        window.__pendingScriptRegistrations.forEach(function(fn) {
            try {
                fn();
            } catch (e) {
                console.error('[ESM-IIFE] Script registration error:', e);
            }
        });
        window.__pendingScriptRegistrations = null;
    }
    console.log('[ESM-IIFE] Scripts registration complete');
}`);
        // 写入修改后的入口文件到工作目录（不是 baseBuildDir）
        const modifiedEntryPath = path.join(workDir, 'js/index-bundled.mjs');
        await fs.mkdir(path.dirname(modifiedEntryPath), { recursive: true });
        await fs.writeFile(modifiedEntryPath, entryContent);
        console.log('[ESMBundle] 已创建修改后的入口文件到工作目录');
        // 创建临时输出目录
        const tempOutputDir = path.join(options.outputDir, '__esm-temp__');
        // 提取 Import Map 以解析裸模块说明符
        const importMap = await extractImportMap(options.baseBuildDir);
        console.log('[ESMBundle] 提取到 Import Map:', Object.keys(importMap));
        // 使用 Vite 打包 ESM 为 IIFE
        // 重要：使用工作目录 workDir 作为根目录，而不是 baseBuildDir
        await viteBuild({
            root: workDir,
            logLevel: 'warn',
            configFile: false, // 不使用配置文件，避免循环
            build: {
                outDir: tempOutputDir,
                emptyOutDir: true,
                lib: {
                    entry: modifiedEntryPath, // 使用修改后的入口文件
                    name: 'PlayCanvasESMBundle',
                    fileName: 'esm-bundle',
                    formats: ['iife'],
                },
                // 使用 terser 压缩 JS
                minify: options.minify ? 'terser' : false,
                terserOptions: options.minify ? {
                    compress: {
                        drop_console: true,
                        drop_debugger: true,
                        passes: 3,
                    },
                    mangle: {
                        toplevel: true,
                    },
                    format: {
                        comments: false,
                    },
                } : undefined,
                rollupOptions: {
                    // 将 playcanvas 和引擎文件设为外部依赖
                    // 引擎已经在 HTML 中单独内联，不需要再次打包
                    external: (id) => {
                        // 裸模块 'playcanvas'
                        if (id === 'playcanvas')
                            return true;
                        // 相对路径的引擎文件
                        if (id.includes('playcanvas-engine'))
                            return true;
                        return false;
                    },
                    output: {
                        // 定义外部依赖的全局变量名
                        globals: {
                            'playcanvas': 'pc',
                        },
                    },
                    plugins: [
                        // 自定义插件：处理模块解析
                        {
                            name: 'resolve-import-map',
                            resolveId(source) {
                                // playcanvas 及引擎文件使用全局 pc 对象
                                if (source === 'playcanvas' || source.includes('playcanvas-engine')) {
                                    return { id: 'playcanvas', external: true };
                                }
                                // 如果是 Import Map 中定义的裸模块
                                if (importMap[source]) {
                                    // 跳过 playcanvas 相关的映射
                                    if (source === 'playcanvas' || importMap[source].includes('playcanvas')) {
                                        return { id: 'playcanvas', external: true };
                                    }
                                    // 使用工作目录 workDir 解析路径
                                    const resolved = path.resolve(workDir, importMap[source]);
                                    console.log(`[ESMBundle] 解析 Import Map: ${source} -> ${resolved}`);
                                    return resolved;
                                }
                                return null;
                            }
                        }
                    ],
                    onwarn: (warning, warn) => {
                        // 忽略某些警告
                        if (warning.code === 'THIS_IS_UNDEFINED')
                            return;
                        if (warning.code === 'CIRCULAR_DEPENDENCY')
                            return;
                        warn(warning);
                    },
                },
            },
        });
        // 读取打包后的文件
        const bundledFilePath = path.join(tempOutputDir, 'esm-bundle.iife.js');
        const bundledScriptContent = await fs.readFile(bundledFilePath, 'utf-8');
        // 注册代码已经在 processESMScriptClasses 中添加到各个脚本文件
        // 并随 IIFE 打包进来，不需要额外追加
        if (esmScriptClasses.length > 0) {
            console.log(`[ESMBundle] ${esmScriptClasses.length} 个 ESM Script 类的注册代码已包含在 IIFE 中`);
        }
        // DEBUG: 保留临时文件用于调试
        // await fs.rm(tempOutputDir, { recursive: true, force: true });
        // await fs.rm(workDir, { recursive: true, force: true });
        console.log('[ESMBundle] DEBUG: 保留临时目录用于调试:', tempOutputDir, workDir);
        console.log(`[ESMBundle] ESM 脚本打包完成 (${(bundledScriptContent.length / 1024).toFixed(2)} KB)`);
        return bundledScriptContent;
    }
    catch (error) {
        console.error('[ESMBundle] ESM 脚本打包失败:', error);
        return '';
    }
}
/**
 * 处理预打包的 ESM 格式（由其他工具如 Rollup/Vite 打包）
 * 这种格式的特点：
 * 1. index.mjs 包含 __esmScriptDynamicImportRuntime__ 函数
 * 2. 用户脚本已经被打包到 esm-*.js 文件中
 * 3. 脚本类没有 registerScript 调用
 */
async function bundlePreBundledESM(options, entryContent) {
    const jsDir = path.join(options.baseBuildDir, 'js');
    // 1. 查找 esm-*.js 文件（打包后的脚本文件）
    const jsFiles = await fs.readdir(jsDir);
    const esmBundleFiles = jsFiles.filter(f => f.startsWith('esm-') && f.endsWith('.js'));
    if (esmBundleFiles.length === 0) {
        console.log('[ESMBundle-PreBundled] 未找到 esm-*.js 文件');
        return '';
    }
    console.log(`[ESMBundle-PreBundled] 找到 ${esmBundleFiles.length} 个打包文件: ${esmBundleFiles.join(', ')}`);
    // 2. 扫描每个文件，提取继承 Script 的类
    const scriptClasses = [];
    // 已知的 Script 基类（包括引擎内置和用户自定义基类）
    const knownScriptBases = new Set(['Script', 'pc.Script']);
    const allClassDefs = new Map();
    for (const file of esmBundleFiles) {
        const filePath = path.join(jsDir, file);
        const content = await fs.readFile(filePath, 'utf-8');
        // 匹配所有 class XXX extends YYY 模式
        const classPattern = /class\s+(\w+)\s+extends\s+([\w.]+)\s*\{/g;
        let match;
        while ((match = classPattern.exec(content)) !== null) {
            const className = match[1];
            const baseClass = match[2];
            // 查找 static scriptName = 'xxx' 定义
            // 注意：在打包后的代码中，scriptName 可能在类定义之后
            const scriptNamePattern = new RegExp(`_define_property[^(]*\\(${className},"scriptName","([^"]+)"\\)|` +
                `${className}\\.scriptName\\s*=\\s*["']([\\w]+)["']|` +
                `static\\s+scriptName\\s*=\\s*["']([\\w]+)["']`, 'g');
            // 在类定义后的一段范围内搜索 scriptName
            const classDefEnd = match.index + match[0].length;
            const searchRange = content.slice(classDefEnd, classDefEnd + 2000);
            const scriptNameMatch = searchRange.match(scriptNamePattern) || content.match(scriptNamePattern);
            let scriptName = null;
            if (scriptNameMatch) {
                // 提取 scriptName（可能在不同位置）
                for (const m of scriptNameMatch) {
                    const extracted = m.match(/["'](\w+)["']/);
                    if (extracted) {
                        scriptName = extracted[1];
                        break;
                    }
                }
            }
            allClassDefs.set(className, { className, baseClass, scriptName });
            // 如果直接继承 Script，添加到已知基类
            if (baseClass === 'Script' || baseClass === 'pc.Script') {
                knownScriptBases.add(className);
            }
        }
    }
    // 迭代传播：找出所有间接继承 Script 的类
    let changed = true;
    while (changed) {
        changed = false;
        for (const [className, info] of allClassDefs) {
            if (!knownScriptBases.has(className) && knownScriptBases.has(info.baseClass)) {
                knownScriptBases.add(className);
                changed = true;
            }
        }
    }
    // 收集所有 Script 类
    for (const [className, info] of allClassDefs) {
        if (knownScriptBases.has(className) && className !== 'Script' && className !== 'pc.Script') {
            // 确定 scriptName
            let scriptName = info.scriptName;
            if (!scriptName) {
                // 使用类名转换（首字母小写）
                scriptName = className.charAt(0).toLowerCase() + className.slice(1);
            }
            scriptClasses.push({ className, scriptName });
        }
    }
    console.log(`[ESMBundle-PreBundled] 识别出 ${scriptClasses.length} 个 Script 类`);
    // 3. 从 config.json 读取脚本 schema
    // config.json 结构：
    // - application_properties.scripts: 脚本资产 ID 数组
    // - assets[assetId].data.scripts[scriptName].attributes: 实际的属性定义
    let scriptSchemas = {};
    try {
        const configPath = path.join(options.baseBuildDir, 'config.json');
        const configContent = await fs.readFile(configPath, 'utf-8');
        const configJson = JSON.parse(configContent);
        // 从 assets 中提取所有脚本的 schema
        if (configJson.assets) {
            for (const assetId of Object.keys(configJson.assets)) {
                const asset = configJson.assets[assetId];
                // 检查是否是脚本资产
                if (asset.type === 'script' && asset.data && asset.data.scripts) {
                    // 遍历该脚本资产中定义的所有脚本类型
                    for (const [scriptName, scriptDef] of Object.entries(asset.data.scripts)) {
                        if (scriptDef && scriptDef.attributes) {
                            scriptSchemas[scriptName] = {
                                attributes: scriptDef.attributes,
                                attributesOrder: scriptDef.attributesOrder
                            };
                        }
                    }
                }
            }
            console.log(`[ESMBundle-PreBundled] 从 config.json 提取了 ${Object.keys(scriptSchemas).length} 个脚本 schema`);
            // 打印一些示例 schema 用于调试
            const sampleSchemas = Object.entries(scriptSchemas).slice(0, 3);
            for (const [name, schema] of sampleSchemas) {
                const attrNames = Object.keys(schema.attributes || {});
                console.log(`  - ${name}: ${attrNames.length} attributes [${attrNames.slice(0, 3).join(', ')}${attrNames.length > 3 ? '...' : ''}]`);
            }
        }
    }
    catch (e) {
        console.warn('[ESMBundle-PreBundled] 无法读取 config.json 中的脚本 schema:', e);
    }
    // 4. 生成注册代码（包含从 config.json 获取的 schema）
    // 关键：ESM scripts extend pc.Script (gG), not pc.ScriptType (gX)
    // 对于 pc.Script 子类，initializeAttributes 使用 getSchema() 获取属性定义
    // 所以必须调用 addSchema() 将 schema 注册到 ScriptRegistry
    const registrationParts = scriptClasses.map(({ className, scriptName }) => {
        // 获取该脚本的 schema（如果存在）
        const schema = scriptSchemas[scriptName];
        let attributesCode = '';
        if (schema && schema.attributes) {
            // 生成 schema 对象（用于 addSchema）
            const schemaJson = JSON.stringify({
                attributes: schema.attributes,
                attributesOrder: schema.attributesOrder || Object.keys(schema.attributes)
            });
            attributesCode = `
            // Store schema data for ${scriptName}
            window.__esmScriptSchemas = window.__esmScriptSchemas || {};
            window.__esmScriptSchemas['${scriptName}'] = ${schemaJson};`;
        }
        else {
            // 没有 schema
            attributesCode = `
            // No schema for ${scriptName}`;
        }
        return {
            // 立即执行的代码 - 存储类引用和 schema 数据
            immediateCode: `(function() {
          if (typeof ${className} !== 'undefined') {
            window.__esmScriptClasses = window.__esmScriptClasses || {};
            window.__esmScriptClasses['${scriptName}'] = ${className};${attributesCode}
          }
        })()`,
            // 延迟执行的代码 - 注册脚本 + 添加 schema
            deferredCode: `(function() {
          var cls = window.__esmScriptClasses && window.__esmScriptClasses['${scriptName}'];
          var schema = window.__esmScriptSchemas && window.__esmScriptSchemas['${scriptName}'];
          if (cls) {
            window.pc.registerScript(cls, '${scriptName}');
            if (schema && window.pc.app && window.pc.app.scripts && window.pc.app.scripts.addSchema) {
              window.pc.app.scripts.addSchema('${scriptName}', schema);
            }
          }
        })()`
        };
    });
    // 分别生成立即执行和延迟执行的代码
    const immediateCode = registrationParts.map(p => p.immediateCode).join(';\n');
    const deferredCode = registrationParts.map(p => p.deferredCode).join(';\n');
    const registrationCode = `
// ========== ESM Script Auto-Registration (Pre-Bundled) ==========
// Key insight: ESM scripts extend pc.Script (gG), not pc.ScriptType (gX).
// For pc.Script subclasses, initializeAttributes uses getSchema() to get attribute definitions,
// so we must call addSchema() to register the schema with ScriptRegistry.

// Step 1: Store class references and schema data (immediate)
${immediateCode};

// Step 2: Register scripts and schemas (deferred until pc.app.scripts is available)
(function() {
    window.__deferredESMScripts = window.__deferredESMScripts || [];
    window.__deferredESMScripts.push(function() {
        ${deferredCode.split('\n').map(c => '        ' + c).join('\n')}
    });
})();
// ========== End ESM Script Auto-Registration ==========
`;
    // 4. 读取所有 ESM bundle 文件内容
    let combinedScriptContent = '';
    for (const file of esmBundleFiles) {
        const filePath = path.join(jsDir, file);
        const content = await fs.readFile(filePath, 'utf-8');
        combinedScriptContent += content + '\n';
    }
    // 5. 处理入口文件
    // 移除动态导入运行时相关代码
    entryContent = entryContent.replace(/function\s+__esmScriptDynamicImportRuntime__[\s\S]*?switch\s*\(specifier\)\s*\{[\s\S]*?\}\s*\}/g, '// Dynamic import runtime removed for IIFE bundle');
    // 移除对引擎的 ESM 导入
    entryContent = entryContent.replace(/import\s+\*\s+as\s+(\w+)\s+from\s+['"][^'"]*playcanvas[^'"]*['"];?\s*/g, 'const $1 = window.pc;\n');
    // 移除其他 ESM 导入
    entryContent = entryContent.replace(/import\s*\{[^}]*\}\s*from\s*['"][^'"]+['"];?\s*/g, '');
    entryContent = entryContent.replace(/import\s+['"][^'"]+['"];?\s*/g, '');
    // 移除 export 语句
    entryContent = entryContent.replace(/export\s*\{[^}]*\};?\s*/g, '');
    // 6. 组合最终内容
    const finalContent = `
(function(global) {
// ========== Pre-Bundled ESM to IIFE Conversion ==========
var pc = global.pc;

// Script Registration Code
${registrationCode}

// Combined Script Content
${combinedScriptContent}

// Entry Point Content
${entryContent}

// ========== End Pre-Bundled ESM to IIFE Conversion ==========
})(typeof window !== 'undefined' ? window : this);
`;
    console.log(`[ESMBundle-PreBundled] 打包完成 (${(finalContent.length / 1024).toFixed(2)} KB)`);
    return finalContent;
}
/**
 * ESM Bundle 插件
 *
 * 在 ESM 模式下，将 ESM 用户脚本打包为 IIFE 格式，以便在不支持 ESM 的广告平台上运行。
 *
 * 注意：ESM 打包必须在主构建之前完成，通过 bundleESMToIIFE() 函数预先调用
 */
export function viteESMBundlePlugin(bundledScriptContent) {
    return {
        name: 'vite-plugin-esm-bundle',
        enforce: 'pre', // 在其他插件之前执行
        transformIndexHtml: {
            order: 'pre',
            handler(html) {
                if (!bundledScriptContent) {
                    return html;
                }
                console.log('[ESMBundle] 注入打包后的 ESM 脚本到 HTML');
                // 1. 移除 Import Map
                html = html.replace(/<script\s+type=["']importmap["'][^>]*>[\s\S]*?<\/script>/gi, '');
                // 2. 移除 ESM 脚本引用，替换为打包后的 IIFE
                // 注意：在 String.replace() 中，替换字符串中的 $ 是特殊字符
                // $& 会插入匹配的子串，$' 会插入匹配子串右边的内容等
                // 因此需要将 bundledScriptContent 中的 $ 转义为 $$
                const escapedContent = bundledScriptContent.replace(/\$/g, '$$$$');
                html = html.replace(/<script\s+type=["']module["'][^>]*src=["'][^"']*index\.mjs["'][^>]*><\/script>/gi, `<script>
/* Bundled ESM Scripts (IIFE) */
${escapedContent}
</script>`);
                // 3. 移除其他 type="module" 脚本
                html = html.replace(/<script\s+type=["']module["'][^>]*>[\s\S]*?<\/script>/gi, '');
                return html;
            },
        },
    };
}
