import { build as viteBuild } from 'vite';
import fs from 'fs/promises';
import path from 'path';
import { ViteConfigBuilder } from './vite/config-builder.js';
import { GenericViteConfigBuilder } from './vite/config-builder-generic.js';
import { PLATFORM_CONFIGS } from './vite/platform-configs.js';
/**
 * Vite 构建器 - 使用 Vite 构建 Playable Ads
 *
 * 职责：
 * 1. 验证输入是有效的基础构建
 * 2. 根据引擎类型创建对应的 Vite 配置
 * 3. 执行 Vite 构建
 * 4. 将输出复制到最终目录
 * 5. 验证输出大小
 * 6. 生成报告
 *
 * 引擎路由：
 * - PlayCanvas：使用 ViteConfigBuilder（处理 config.json、__start__.js 等）
 * - 其他引擎：使用 GenericViteConfigBuilder（通用 HTML 内联 + 平台 SDK 注入）
 */
export class ViteBuilder {
    constructor(baseBuildDir, options) {
        this.engine = 'playcanvas';
        this.baseBuildDir = baseBuildDir;
        this.options = options;
        const platformConfig = PLATFORM_CONFIGS[options.platform];
        this.sizeReport = {
            engine: 0,
            assets: {},
            total: 0,
            limit: platformConfig.sizeLimit,
        };
    }
    /**
     * 执行构建
     */
    async build() {
        // 1. 读取构建元数据获取引擎类型
        const metadata = await this.readBuildMetadata();
        this.engine = this.options.engine ?? metadata?.engine ?? 'playcanvas';
        console.log(`[ViteBuilder] 使用引擎: ${this.engine}`);
        // 2. 验证输入
        await this.validateBaseBuild(metadata);
        // 3. 根据引擎类型创建对应的配置构建器
        let configBuilder;
        if (this.engine === 'playcanvas') {
            console.log('[ViteBuilder] 使用 PlayCanvas 配置构建器');
            configBuilder = new ViteConfigBuilder(this.baseBuildDir, this.options.platform, this.options);
        }
        else {
            console.log('[ViteBuilder] 使用通用配置构建器（外部引擎）');
            configBuilder = new GenericViteConfigBuilder(this.baseBuildDir, this.options.platform, this.options, metadata);
        }
        const viteConfig = await configBuilder.create();
        // 4. 执行 Vite 构建
        try {
            await viteBuild(viteConfig);
        }
        catch (error) {
            // 打印完整的 Vite/Rollup 错误信息，便于定位构建失败原因
            console.error('[ViteBuilder] Vite 构建失败:');
            if (error.id) {
                console.error(`  - 问题文件: ${error.id}`);
            }
            if (error.loc) {
                console.error(`  - 位置: 行 ${error.loc.line}, 列 ${error.loc.column}`);
            }
            if (error.frame) {
                console.error(`  - 代码片段:\n${error.frame}`);
            }
            if (error.plugin) {
                console.error(`  - 插件: ${error.plugin}`);
            }
            if (error.pluginCode) {
                console.error(`  - 插件错误码: ${error.pluginCode}`);
            }
            if (error.cause) {
                console.error(`  - 原因:`, error.cause);
            }
            console.error(`  - 完整错误:`, error.stack || error.message || error);
            throw error;
        }
        // 5. 将输出从临时目录复制到最终目录
        const viteOutputDir = configBuilder.getViteOutputDir();
        const finalOutputDir = configBuilder.getFinalOutputDir();
        if (viteOutputDir && finalOutputDir && viteOutputDir !== finalOutputDir) {
            await this.copyOutputToFinalDir(viteOutputDir, finalOutputDir);
        }
        // 6. 验证输出大小
        const outputPath = this.getOutputPath();
        await this.validateSize(outputPath);
        // 7. 生成报告
        this.generateReport(outputPath);
        return outputPath;
    }
    /**
     * 读取构建元数据
     */
    async readBuildMetadata() {
        const metadataPath = path.join(this.baseBuildDir, '.build-metadata.json');
        try {
            const content = await fs.readFile(metadataPath, 'utf-8');
            const metadata = JSON.parse(content);
            console.log(`[ViteBuilder] 读取构建元数据: engine=${metadata.engine}, mode=${metadata.mode}`);
            return metadata;
        }
        catch (error) {
            console.warn('[ViteBuilder] 无法读取构建元数据，假设为 PlayCanvas');
            return null;
        }
    }
    /**
     * 验证基础构建
     * 根据引擎类型使用不同的验证规则
     */
    async validateBaseBuild(metadata) {
        const engine = this.options.engine ?? metadata?.engine ?? 'playcanvas';
        if (engine === 'playcanvas') {
            await this.validatePlayCanvasBuild();
        }
        else {
            await this.validateGenericBuild();
        }
    }
    /**
     * 验证 PlayCanvas 构建
     */
    async validatePlayCanvasBuild() {
        const isESM = await this.detectESMFormat();
        const requiredFiles = isESM
            ? ['index.html', 'config.json']
            : ['index.html', 'config.json', '__start__.js'];
        const missingFiles = [];
        for (const file of requiredFiles) {
            try {
                await fs.access(path.join(this.baseBuildDir, file));
            }
            catch {
                missingFiles.push(file);
            }
        }
        if (missingFiles.length > 0) {
            const formatType = isESM ? 'ESM' : 'Classic';
            throw new Error(`PlayCanvas 基础构建产物缺少必需文件: ${missingFiles.join(', ')}\n` +
                `检测到 ${formatType} 格式，请确保输入目录包含完整的构建产物。`);
        }
        console.log('[ViteBuilder] PlayCanvas 构建验证通过');
    }
    /**
     * 验证通用构建（外部引擎）
     */
    async validateGenericBuild() {
        // 外部引擎只需要 index.html
        try {
            await fs.access(path.join(this.baseBuildDir, 'index.html'));
        }
        catch {
            throw new Error(`外部引擎构建产物缺少 index.html\n` +
                `构建目录: ${this.baseBuildDir}\n` +
                `请确保 Base Build 成功生成了有效的 HTML 入口文件。`);
        }
        console.log('[ViteBuilder] 外部引擎构建验证通过');
    }
    /**
     * 检测是否为 ESM 格式
     */
    async detectESMFormat() {
        const esmIndicators = [
            path.join(this.baseBuildDir, 'js/index.mjs'),
            path.join(this.baseBuildDir, 'esm-scripts'),
        ];
        for (const indicator of esmIndicators) {
            try {
                await fs.access(indicator);
                return true;
            }
            catch {
                // 继续检查
            }
        }
        return false;
    }
    /**
     * 将 Vite 输出复制到最终目录
     */
    async copyOutputToFinalDir(srcDir, destDir) {
        try {
            // 先清空目标目录，避免旧文件残留
            await fs.rm(destDir, { recursive: true, force: true });
            // 确保目标目录存在
            await fs.mkdir(destDir, { recursive: true });
            // 获取平台配置，使用用户指定的格式或平台默认格式
            const platformConfig = PLATFORM_CONFIGS[this.options.platform];
            const outputFormat = this.options.format || platformConfig.outputFormat;
            // 如果是 ZIP 格式，只复制 ZIP 文件
            if (outputFormat === 'zip') {
                const zipFileName = 'playable.zip';
                const srcZipPath = path.join(srcDir, zipFileName);
                const destZipPath = path.join(destDir, zipFileName);
                // 检查 ZIP 文件是否存在
                try {
                    await fs.access(srcZipPath);
                    await fs.copyFile(srcZipPath, destZipPath);
                    console.log(`[ViteBuilder] 已复制 ZIP 文件到: ${destZipPath}`);
                }
                catch {
                    console.warn(`[ViteBuilder] 警告: ZIP 文件不存在: ${srcZipPath}`);
                    // 如果 ZIP 文件不存在，回退到复制所有文件
                    await this.copyAllFiles(srcDir, destDir);
                }
            }
            else {
                // HTML 格式或其他格式，复制所有文件
                await this.copyAllFiles(srcDir, destDir);
            }
        }
        catch (error) {
            console.error(`[ViteBuilder] 复制输出失败:`, error);
            throw error;
        }
    }
    /**
     * 递归复制所有文件
     */
    async copyAllFiles(srcDir, destDir) {
        // 确保目标目录存在
        await fs.mkdir(destDir, { recursive: true });
        // 读取源目录中的所有文件
        const files = await fs.readdir(srcDir);
        for (const file of files) {
            // 跳过以 __ 开头的临时目录
            if (file.startsWith('__')) {
                continue;
            }
            const srcPath = path.join(srcDir, file);
            const destPath = path.join(destDir, file);
            const stat = await fs.stat(srcPath);
            if (stat.isDirectory()) {
                // 递归复制目录
                await this.copyAllFiles(srcPath, destPath);
            }
            else {
                // 复制文件
                await fs.copyFile(srcPath, destPath);
            }
        }
        console.log(`[ViteBuilder] 已将输出复制到: ${destDir}`);
    }
    /**
     * 获取输出路径
     */
    getOutputPath() {
        const platformConfig = PLATFORM_CONFIGS[this.options.platform];
        const outputDir = this.options.outputDir || './dist';
        // 使用用户指定的格式，如果没有则使用平台默认格式
        const outputFormat = this.options.format || platformConfig.outputFormat;
        if (outputFormat === 'zip') {
            return path.join(outputDir, 'playable.zip');
        }
        return path.join(outputDir, platformConfig.outputFileName);
    }
    /**
     * 验证输出大小
     */
    async validateSize(outputPath) {
        try {
            const stats = await fs.stat(outputPath);
            this.sizeReport.total = stats.size;
            this.sizeReport.assets[path.basename(outputPath)] = stats.size;
            // 计算 Base Build 的总大小
            const baseBuildSize = await this.calculateBaseBuildSize();
            if (stats.size > this.sizeReport.limit) {
                const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
                const limitMB = (this.sizeReport.limit / 1024 / 1024).toFixed(2);
                console.warn(`⚠️  警告: 文件大小 ${sizeMB} MB 超过限制 ${limitMB} MB`);
            }
            // ⚠️ 重要：检查输出大小是否异常小
            // 如果输出比 Base Build 小很多，可能意味着资源没有正确打包
            if (baseBuildSize > 0 && stats.size < baseBuildSize * 0.3) {
                console.log('\n');
                console.warn(`⚠️  ==================== 打包大小异常警告 ====================`);
                console.warn(`❗ 输出文件大小 (${this.formatBytes(stats.size)}) 明显小于 Base Build (${this.formatBytes(baseBuildSize)})`);
                console.warn(`   这可能意味着部分资源没有被正确打包！\n`);
                console.warn(`   💡 可能的原因:`);
                console.warn(`      1. 资源文件不存在或路径错误`);
                console.warn(`      2. 场景选择后，部分依赖资源未被包含`);
                console.warn(`      3. config.json 中引用的资源与实际文件不匹配`);
                console.warn(`\n   💡 建议:`);
                console.warn(`      1. 检查上方是否有 "缺失资源警告" 信息`);
                console.warn(`      2. 确认 Base Build 目录包含所有资源文件`);
                console.warn(`      3. 尝试不选择场景进行完整打包`);
                console.warn(`⚠️  ============================================================\n`);
            }
        }
        catch (error) {
            console.warn(`警告: 无法读取输出文件: ${outputPath}`);
        }
    }
    /**
     * 计算 Base Build 目录的总大小
     */
    async calculateBaseBuildSize() {
        let totalSize = 0;
        try {
            const calculateDirSize = async (dirPath) => {
                let size = 0;
                const entries = await fs.readdir(dirPath, { withFileTypes: true });
                for (const entry of entries) {
                    const fullPath = path.join(dirPath, entry.name);
                    if (entry.isDirectory()) {
                        // 跳过临时目录
                        if (entry.name.startsWith('__') || entry.name.startsWith('.')) {
                            continue;
                        }
                        size += await calculateDirSize(fullPath);
                    }
                    else {
                        try {
                            const stat = await fs.stat(fullPath);
                            size += stat.size;
                        }
                        catch {
                            // 忽略无法访问的文件
                        }
                    }
                }
                return size;
            };
            totalSize = await calculateDirSize(this.baseBuildDir);
        }
        catch (error) {
            console.warn(`[ViteBuilder] 无法计算 Base Build 大小:`, error);
        }
        return totalSize;
    }
    /**
     * 生成报告
     */
    generateReport(outputPath) {
        // 报告已在 validateSize 中生成
        // 这里可以添加额外的报告逻辑
    }
    /**
     * 获取大小报告
     */
    getSizeReport() {
        return this.sizeReport;
    }
    /**
     * 格式化字节数
     */
    formatBytes(bytes) {
        if (bytes === 0)
            return '0 Bytes';
        const k = 1024;
        const sizes = ['Bytes', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
    }
}
