import { createLogger, defineConfig } from 'vite';
import path from 'path';
import fs from 'fs';
import { viteSingleFile } from 'vite-plugin-singlefile';
import viteImagemin from '@vheemstra/vite-plugin-imagemin';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminPngquant from 'imagemin-pngquant';
import imageminWebp from 'imagemin-webp';
import { PLATFORM_CONFIGS } from './platform-configs.js';
import { vitePlayCanvasPlugin } from './plugin-playcanvas.js';
import { vitePlatformPlugin } from './plugin-platform.js';
import { viteModelCompressionPlugin } from './plugin-model-compression.js';
import { viteESMBundlePlugin, bundleESMToIIFE } from './plugin-esm-html-generator.js';
import { viteBuildStatePlugin } from './plugin-build-state.js';
import { viteCompressJSPlugin } from './plugin-compress-js.js';
import { createPlatformAdapter } from '../platforms/index.js';
import { detectBuildMode, importMapToAlias } from '../utils/build-mode-detector.js';
export class ViteConfigBuilder {
    constructor(baseBuildDir, platform, options) {
        this.baseBuildDir = baseBuildDir;
        this.platform = platform;
        this.options = options;
        this.esmBundledScript = ''; // 预先打包的 ESM 脚本
        this.finalOutputDir = ''; // 用户指定的最终输出目录
        this.viteOutputDir = ''; // Vite 的临时输出目录（在 root 下）
        // 统一规范化 baseBuildDir：解析符号链接（macOS）和 Windows 8.3 短路径名
        // 必须使用 realpathSync.native —— 只有它能在 Windows 上将 HOMKER~1 → homkerliu
        // （fs.realpathSync 在 Windows 上不会解析 8.3 短路径）
        // Vite 内部也使用 native realpath，所以这里必须对齐，否则 path.relative 会算错
        this.baseBuildDir = fs.realpathSync.native(baseBuildDir).replace(/\\/g, '/');
    }
    async create() {
        // 1. 检测 Base Build 模式
        this.baseBuildMetadata = await detectBuildMode(this.baseBuildDir);
        console.log(`[ViteConfigBuilder] 检测到构建模式: ${this.baseBuildMetadata.mode}`);
        // 2. 决定最终输出格式
        const shouldConvertToIIFE = this.shouldConvertToIIFE();
        console.log(`[ViteConfigBuilder] 转换为 IIFE: ${shouldConvertToIIFE}`);
        const platformConfig = this.getPlatformConfig();
        const outputDir = this.options.outputDir || './dist';
        const playableOptions = this.getPlayableOptions(platformConfig);
        // baseBuildDir 已在构造函数中通过 realpathSync 规范化
        const rootDir = this.baseBuildDir;
        // 为了避免 Vite build-html 插件的路径问题，使用相对于 root 的输出目录
        // 注意：必须使用相对路径字符串，不能用绝对路径
        const viteOutputDir = '__dist__'; // 相对于 root 的路径
        const viteOutputDirAbsolute = path.join(rootDir, viteOutputDir);
        // 确保用户的 outputDir 是绝对路径（用于后续复制）
        const finalOutputDir = path.isAbsolute(outputDir)
            ? outputDir
            : path.resolve(process.cwd(), outputDir);
        // 保存最终输出目录，供 ViteBuilder 使用
        this.finalOutputDir = finalOutputDir;
        this.viteOutputDir = viteOutputDirAbsolute;
        console.log(`[ViteConfigBuilder] 路径配置:`);
        console.log(`  - root: ${rootDir}`);
        console.log(`  - viteOutputDir (相对): ${viteOutputDir}`);
        console.log(`  - viteOutputDir (绝对): ${viteOutputDirAbsolute}`);
        console.log(`  - finalOutputDir: ${finalOutputDir}`);
        // 3. 如果需要，预先打包 ESM 脚本（在主 Vite 构建之前）
        if (this.baseBuildMetadata.mode === 'esm' && shouldConvertToIIFE) {
            this.esmBundledScript = await bundleESMToIIFE({
                baseBuildDir: this.baseBuildDir,
                outputDir: viteOutputDirAbsolute,
                minify: platformConfig.minifyJS,
            });
            return this.createESMBundleConfig(platformConfig, viteOutputDir, playableOptions, rootDir);
        }
        else {
            return this.createClassicConfig(platformConfig, viteOutputDir, playableOptions, rootDir);
        }
    }
    /**
     * 获取 Vite 的临时输出目录
     */
    getViteOutputDir() {
        return this.viteOutputDir || '';
    }
    /**
     * 获取最终输出目录
     */
    getFinalOutputDir() {
        return this.finalOutputDir || '';
    }
    /**
     * 判断是否需要转换为 IIFE
     */
    shouldConvertToIIFE() {
        // 1. 处理 esmMode 选项（优先）
        const esmMode = this.options.esmMode || 'auto';
        if (esmMode === 'disabled') {
            return true; // 用户强制禁用 ESM，转换为 IIFE
        }
        if (esmMode === 'enabled') {
            return false; // 用户强制启用 ESM，保留原生格式
        }
        // 2. 向后兼容旧的 API（如果设置了这些选项）
        if (this.options.forceIIFE)
            return true;
        if (this.options.preserveESM)
            return false;
        // 3. 自动模式（默认）：根据项目和平台特性决定
        // 如果是 Classic 模式，不需要转换
        if (this.baseBuildMetadata?.mode === 'classic') {
            return false;
        }
        // 根据平台配置决定
        const platformConfig = PLATFORM_CONFIGS[this.platform];
        const esmSupport = platformConfig.esmSupport;
        if (!esmSupport || !esmSupport.enabled) {
            return true; // 平台不支持 ESM，必须转换
        }
        return esmSupport.preferIIFE ?? true; // 默认转换为 IIFE（更安全）
    }
    /**
     * 创建 ESM 打包配置（将 ESM 转为 IIFE）
     *
     * 策略：使用 index.html 作为入口，ESM 脚本已经预先打包到 this.esmBundledScript
     */
    createESMBundleConfig(platformConfig, outputDir, playableOptions, rootDir) {
        const logger = this.createLogger();
        const metadata = this.baseBuildMetadata;
        console.log(`[ViteConfigBuilder] ESM Bundle 配置:`);
        console.log(`  - root: ${rootDir}`);
        console.log(`  - outDir: ${outputDir}`);
        return defineConfig({
            root: rootDir,
            base: './',
            customLogger: logger,
            resolve: {
                // 将 Import Map 转换为 Vite alias
                alias: metadata.importMap
                    ? importMapToAlias(metadata.importMap.imports, rootDir)
                    : {},
            },
            build: {
                outDir: outputDir,
                emptyOutDir: true,
                cssMinify: this.shouldMinifyCSS(platformConfig) ? 'lightningcss' : false,
                minify: this.shouldMinifyJS(platformConfig) ? 'terser' : false,
                terserOptions: this.getTerserOptions(),
                assetsInlineLimit: Infinity,
                sourcemap: platformConfig.includeSourcemap,
                rollupOptions: {
                    // 不设置 input，让 Vite 自动从 root 目录发现 index.html
                    onwarn: this.createRollupWarnHandler(),
                },
            },
            plugins: this.createPlugins(platformConfig, outputDir, playableOptions, true),
        });
    }
    /**
     * 创建传统配置（Classic 模式或保留 ESM）
     */
    createClassicConfig(platformConfig, outputDir, playableOptions, rootDir) {
        const logger = this.createLogger();
        console.log(`[ViteConfigBuilder] Classic 配置:`);
        console.log(`  - root: ${rootDir}`);
        console.log(`  - outDir: ${outputDir}`);
        return defineConfig({
            root: rootDir,
            base: './',
            customLogger: logger,
            build: {
                outDir: outputDir,
                emptyOutDir: true,
                // CSS 压缩
                cssMinify: this.shouldMinifyCSS(platformConfig) ? 'lightningcss' : false,
                // JS 压缩
                minify: this.shouldMinifyJS(platformConfig) ? 'terser' : false,
                terserOptions: this.getTerserOptions(),
                // 资源内联阈值（所有资源都内联）
                assetsInlineLimit: Infinity,
                // 不生成 sourcemap（Playable Ads 不需要）
                sourcemap: platformConfig.includeSourcemap,
                rollupOptions: {
                    // 不设置 input，让 Vite 自动从 root 目录发现 index.html
                    onwarn: this.createRollupWarnHandler(),
                },
            },
            plugins: this.createPlugins(platformConfig, outputDir, playableOptions, false),
        });
    }
    createLogger() {
        const logger = createLogger();
        return {
            ...logger,
            warn(msg, options) {
                if (typeof msg === 'string' && msg.includes('can\'t be bundled without type="module"')) {
                    return;
                }
                logger.warn(msg, options);
            },
        };
    }
    createRollupWarnHandler() {
        return (warning, warn) => {
            const message = typeof warning === 'string' ? warning : warning.message;
            if (message && message.includes('can\'t be bundled without type="module"')) {
                return;
            }
            warn(warning);
        };
    }
    getPlatformConfig() {
        const baseConfig = PLATFORM_CONFIGS[this.platform];
        const resolvedOutputFormat = this.options.format ?? baseConfig.outputFormat;
        // 根据选项覆盖配置
        return {
            ...baseConfig,
            outputFormat: resolvedOutputFormat,
            minifyCSS: this.options.cssMinify ?? baseConfig.minifyCSS,
            minifyJS: this.options.jsMinify ?? baseConfig.minifyJS,
            compressImages: this.options.compressImages ?? baseConfig.compressImages,
            compressModels: this.options.compressModels ?? baseConfig.compressModels,
            modelCompression: {
                method: this.options.modelCompression ?? baseConfig.modelCompression?.method ?? 'draco',
                quality: baseConfig.modelCompression?.quality ?? 0.8,
            },
        };
    }
    shouldMinifyCSS(config) {
        return config.minifyCSS;
    }
    shouldMinifyJS(config) {
        return config.minifyJS;
    }
    /**
     * 统一的 Terser 混淆配置
     * 方案：开启顶层变量混淆 + 多轮压缩（不做属性混淆，避免破坏引擎运行时）
     *
     * 注意：当 compressJS 启用时，javascript-obfuscator 会做深度混淆，
     * Terser 仍然负责基础的压缩和变量名混淆。
     */
    getTerserOptions() {
        return {
            compress: {
                drop_console: true,
                drop_debugger: true,
                passes: 3,
            },
            mangle: {
                toplevel: true,
            },
            format: {
                comments: false,
            },
        };
    }
    createPlugins(platformConfig, outputDir, playableOptions, isESMBundle = false) {
        const plugins = [];
        // 0. ESM Bundle 插件（如果是 ESM 模式且需要转换为 IIFE）
        // ESM 脚本已经预先打包到 this.esmBundledScript
        if (isESMBundle && this.baseBuildMetadata?.mode === 'esm' && this.esmBundledScript) {
            plugins.push(viteESMBundlePlugin(this.esmBundledScript));
        }
        // 1. PlayCanvas 资源转换插件
        plugins.push(vitePlayCanvasPlugin({
            baseBuildDir: this.baseBuildDir,
            buildMode: this.baseBuildMetadata?.mode || 'classic',
            isESMBundle: isESMBundle,
            inlineScripts: true,
            convertDataUrls: platformConfig.outputFormat === 'html',
            outputFormat: playableOptions.outputFormat,
            patchXhrOut: playableOptions.patchXhrOut,
            inlineGameScripts: playableOptions.inlineGameScripts,
            compressEngine: playableOptions.compressEngine,
            compressConfigJson: playableOptions.compressConfigJson,
            configJsonInline: playableOptions.configJsonInline,
            mraidSupport: playableOptions.mraidSupport,
            ammoReplacement: this.options.ammoReplacement,
        }));
        // 2. 平台特定插件
        const platformAdapter = createPlatformAdapter(this.options);
        // 注意：outputDir 需要使用绝对路径，因为 copyBaseBuildAssets 需要绝对路径
        const absoluteOutputDir = this.viteOutputDir || path.resolve(this.baseBuildDir, outputDir);
        plugins.push(vitePlatformPlugin({
            platform: this.platform,
            adapter: platformAdapter,
            outputDir: absoluteOutputDir,
            baseBuildDir: this.baseBuildDir,
            outputFormat: platformConfig.outputFormat,
            externFiles: playableOptions.externFiles,
            mraidSupport: playableOptions.mraidSupport,
            // Google Ads ZIP 验证器只允许 .css/.js/.html/.gif/.png/.jpeg/.svg
            // 将不受支持的文件类型（如 .glb）内联为 base64 到 config.json 中
            inlineUnsupportedAssets: this.platform === 'google',
            // ZIP 格式输出时，对复制的 JS 文件进行 Terser 压缩混淆
            minifyJSInZip: platformConfig.outputFormat === 'zip' && platformConfig.minifyJS,
            ammoReplacement: this.options.ammoReplacement,
        }));
        // 3. 图片压缩插件
        if (platformConfig.compressImages) {
            const imageQuality = platformConfig.imageQuality || {
                jpg: 75,
                png: [0.7, 0.8],
                webp: 75,
            };
            const jpegQuality = this.options.imageQuality ?? imageQuality.jpg ?? 75;
            const pngQuality = Array.isArray(imageQuality.png) && imageQuality.png.length >= 2
                ? [imageQuality.png[0], imageQuality.png[1]]
                : [0.7, 0.8];
            const webpQuality = imageQuality.webp ?? 75;
            const imageminPlugin = viteImagemin({
                plugins: {
                    jpg: [imageminMozjpeg({ quality: jpegQuality })],
                    jpeg: [imageminMozjpeg({ quality: jpegQuality })],
                    png: [imageminPngquant({ quality: pngQuality })],
                },
                makeWebp: this.options.convertToWebP !== false ? {
                    plugins: {
                        jpg: [imageminWebp({ quality: webpQuality })],
                        jpeg: [imageminWebp({ quality: webpQuality })],
                        png: [imageminWebp({ quality: webpQuality })],
                    },
                } : undefined,
            });
            if (imageminPlugin) {
                plugins.push(imageminPlugin);
            }
        }
        // 4. 模型压缩插件
        if (platformConfig.compressModels) {
            plugins.push(viteModelCompressionPlugin({
                quality: platformConfig.modelCompression?.quality ?? 0.8,
                method: platformConfig.modelCompression?.method ?? 'draco',
                enabled: true,
            }));
        }
        // 5. 构建状态记录插件
        plugins.push(viteBuildStatePlugin({
            baseBuildDir: this.baseBuildDir,
            outputDir: absoluteOutputDir,
            platform: this.platform,
            analyze: this.options.analyze,
        }));
        // 6. 单文件输出插件（仅HTML格式）
        if (platformConfig.outputFormat === 'html') {
            plugins.push(viteSingleFile({
                removeViteModuleLoader: true,
            }));
        }
        // 7. JS 压缩保护插件（在单文件输出之后，对最终 HTML 中的所有 JS 进行 LZ4 压缩）
        if (playableOptions.compressJS) {
            plugins.push(viteCompressJSPlugin({
                enabled: true,
            }));
        }
        return plugins.filter(Boolean);
    }
    getPlayableOptions(config) {
        const playable = config.playable ?? {};
        const externFiles = this.resolveExternFiles(playable.externFiles);
        // 当输出格式为 HTML 时（尤其是平台默认 ZIP 但用户选了 HTML），
        // 需要自动启用 HTML 单文件所必需的选项
        const isHtmlOutput = config.outputFormat === 'html';
        // HTML 单文件格式必须内联游戏脚本，否则脚本 URL 无法加载
        const htmlRequiresInlineScripts = isHtmlOutput;
        return {
            outputFormat: config.outputFormat,
            patchXhrOut: this.options.patchXhrOut ?? playable.patchXhrOut ?? false,
            inlineGameScripts: this.options.inlineGameScripts ?? (htmlRequiresInlineScripts || (playable.inlineGameScripts ?? false)),
            compressEngine: this.options.compressEngine ?? playable.compressEngine ?? false,
            compressConfigJson: this.options.compressConfigJson ?? playable.compressConfigJson ?? false,
            compressJS: this.options.compressJS ?? false,
            configJsonInline: playable.configJsonInline ?? isHtmlOutput,
            externFiles: isHtmlOutput ? undefined : externFiles,
            mraidSupport: this.options.mraidSupport ?? playable.mraidSupport ?? false,
            snapchatCta: this.options.snapchatCta ?? playable.snapchatCta ?? false,
        };
    }
    resolveExternFiles(defaultValue) {
        const optionValue = this.options.externFiles;
        const raw = optionValue ?? defaultValue;
        if (!raw) {
            return undefined;
        }
        if (typeof raw === 'object') {
            return raw;
        }
        const folderName = this.options.snapchat?.folderName;
        const externalUrlPrefix = this.options.snapchat?.externalUrlPrefix;
        return {
            enabled: true,
            folderName,
            externalUrlPrefix,
        };
    }
}
