"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const ts = __importStar(require("typescript"));
const index_js_1 = __importDefault(require("./../lang/index.js"));
const index_js_2 = require("./../uuid/index.js");
const index_js_3 = __importDefault(require("./../code-processor/index.js"));
const utils = __importStar(require("./../utils/index.js"));
const getModule_js_1 = __importDefault(require("./getModule.js"));
const includes_json_1 = __importDefault(require("./../data/includes.json"));
const clean_js_1 = __importDefault(require("./../start/clean.js"));
const index_js_4 = __importDefault(require("./../runTemp/index.js"));
const base_js_1 = require("./base.js");
const manifest_build_js_1 = require("./manifest.build.js");
const index_js_5 = __importDefault(require("./../mcx/index.js"));
const node_os_1 = __importDefault(require("node:os"));
const utils_1 = __importDefault(require("./utils"));
// 构建主类
class Build extends base_js_1.BaseBuild {
    baseDir;
    baseModDir;
    gamelibModule;
    constructor(buildPath, baseDir) {
        super();
        if (!buildPath)
            utils.Exit(index_js_1.default.build.config_invalid);
        this.baseDir = baseDir;
        this.baseCwd = path.isAbsolute(buildPath) ?
            buildPath :
            path.join(baseDir, buildPath);
        this.cwd = path.join(this.baseCwd, "behavior");
        this.ResOutDir = null;
        this.ResCwd = path.join(this.baseCwd, "resources");
        this.dependencies = {};
        this.gamelibModule = null;
        this.baseModDir = path.join(this.baseDir, 'lib/modules');
        // 锁定属性
        this.#ObjectPri(['baseModDir', 'baseDir']);
        this.#ObjectPriEn(["outdir", "dependencies", "gamelibModule", "cwd", "ResCwd", "baseModDir", "baseCwd"]);
        this.cacheDir = path.join(this.baseCwd, '.cache__mbler__');
    }
    #ObjectPri(list) {
        if (!Array.isArray(list))
            return;
        for (let item of list) {
            Object.defineProperty(this, item, {
                enumerable: false,
                writable: false
            });
        }
    }
    // 私有：设置不可枚举属性
    #ObjectPriEn(list) {
        if (!Array.isArray(list))
            return;
        for (let item of list) {
            Object.defineProperty(this, item, {
                enumerable: false
            });
        }
    }
    // 确保缓存目录存在
    async #ensureCacheDir() {
        try {
            await fs.mkdir(this.cacheDir, {
                recursive: true
            });
        }
        catch (err) {
            // 如果目录已存在，忽略错误
            if (err.code !== 'EEXIST') {
                throw err;
            }
        }
    }
    // 构建入口
    build() {
        return this.start().catch(err => {
            const errorMsg = utils.toString(err);
            (0, utils_1.default)(index_js_1.default.build.error_message_log + errorMsg);
        });
    }
    async start() {
        const startTime = Date.now();
        await this.#ensureCacheDir();
        const { configChanged, packageChanged } = await this.chackConfigHash();
        const clean = new clean_js_1.default(this.baseCwd, this.baseDir);
        await clean.run();
        await utils.waitGC();
        const data = await this.loadPackageData();
        this.d_data = data;
        this.outdir = this.getOutputDir(data.outdir?.behavior, path.join(this.baseCwd, "dist/dep"));
        this.ResOutDir = this.getOutputDir(data.outdir?.resources, path.join(this.baseCwd, "dist/res"));
        const requiredKeys = ['name', 'description', 'version', 'mcVersion'];
        if (!utils.hasKeys(data, requiredKeys, requiredKeys.length))
            throw new Error(`${index_js_1.default.build.package_file} 字段缺失：必需字段 ${JSON.stringify(requiredKeys)}`);
        this.gamelibModule = await (0, getModule_js_1.default)(this.baseDir);
        console.log(index_js_1.default.build.build_info_header);
        console.table({
            [index_js_1.default.build.project_path]: this.baseCwd,
            [index_js_1.default.build.output_dir]: JSON.stringify(data.outdir),
            [index_js_1.default.build.minify_enabled]: Boolean(data.minify),
            [index_js_1.default.build.build_time]: Build.times,
            [index_js_1.default.build.tip_dist]: process.env.MBLER_BUILD_MODULE || "dev"
        });
        // 并发处理资源、脚本、子包、includes
        await Promise.all([
            this.writeManifest(data),
            this.handleScripts(data),
            this.handleIncludes(),
            this.processSubpacks(data)
        ]);
        switch (data.script?.lang || "") {
            case "ts": // TypeScript 编译
                await this.compileTypeScriptUnified();
                break;
            case "mcx":
                await index_js_5.default.load({
                    ProjectDir: path.join(this.cwd, "scripts"),
                    config: {
                        useTS: true
                    },
                    output: path.join(this.outdir, "scripts"),
                    moduleList: this.Modules,
                    main: path.join(this.cwd, data.script?.main || "index.js"),
                    cacheDir: this.cacheDir,
                    moduleDir: path.join(this.outdir, "scripts/node_modules"),
                    isCache: true
                });
                break;
        }
        await Promise.all([this.processMinification(data),
            // 资源包处理
            this.processResources()
        ]);
        await this.processDist();
        // 最终完成
        console.log(`${index_js_1.default.build.build_success} ${(Date.now() - startTime) / 1000}`);
    }
    async writeManifest(data) {
        const manifest = await this.buildManifest(data);
        await this.writeFile(path.join(this.outdir, 'manifest.json'), manifest);
    }
    async buildManifest(data) {
        const manifest = (new manifest_build_js_1.ManiFest(data, "data")).data;
        const subpack = data.subpack || {};
        if (typeof data.ResDes === 'object')
            this.processResourceDependencies(data, manifest);
        if (typeof data.subpack === 'object' && Object.keys(data.subpack).length > 0) {
            manifest.subpack = Object.keys(data.subpack).map(id => ({
                folder_name: id,
                name: subpack[id] || "",
                memory_tier: 1
            }));
        }
        return manifest;
    }
    processResourceDependencies(data, manifest) {
        const { name = 'unknown', version } = data.ResDes || {};
        if (version && utils.isVerison(version)) {
            manifest.dependencies.push({
                name: (0, index_js_2.fromString)(name),
                version: utils.ToArray(version)
            });
        }
    }
    async handleScripts(data) {
        if (!data.script || typeof data.script !== 'object')
            return;
        const scriptsDir = path.join(this.cwd, 'scripts');
        const outScripts = path.join(this.outdir, 'scripts');
        if (!(await utils.FileExsit(scriptsDir)))
            return;
        // 复制主包脚本
        await utils.copy(scriptsDir, outScripts);
        this.dependencies = data.script.dependencies || {};
        await this.initNpmDes();
        if (Object.keys(this.dependencies).length > 0) {
            this.Modules = await this.handlerMod(this.dependencies, new Set());
        }
    }
    async processSubpacks(data) {
        if (typeof data.subpack !== 'object')
            return;
        const outdir = this.outdir;
        const content = new Build(this.cwd, this.baseDir);
        for (let [id, title] of Object.entries(data.subpack)) {
            try {
                const subpackPath = path.join(this.cwd, 'subpacks', id);
                const outSubpack = path.join(outdir, 'subpacks', id);
                await fs.mkdir(outSubpack, {
                    recursive: true
                });
                if (!(await utils.FileExsit(subpackPath))) {
                    (0, utils_1.default)(index_js_1.default.build.subpack_folder_not_found.replace('{id}', id));
                    continue;
                }
                const subScriptDir = path.join(subpackPath, 'scripts');
                const outSubScriptDir = path.join(outSubpack, 'scripts');
                if (await utils.FileExsit(subScriptDir)) {
                    await utils.copy(subScriptDir, outSubScriptDir);
                }
                // 复制子包其他资源
                content.cwd = subpackPath;
                content.outdir = outSubpack;
                await content.handleIncludes();
            }
            catch (err) {
                (0, utils_1.default)(index_js_1.default.build.error_processing_subpack.replace('{id}', id).replace('{error}', err.stack));
            }
        }
    }
    async compileTypeScriptUnified() {
        const tempMod = new index_js_4.default(path.join(node_os_1.default.tmpdir(), "mbler"));
        await tempMod.init();
        const tempDir = tempMod.dir;
        try {
            const rootFiles = await this.getAllTsFiles(this.outdir);
            if (!rootFiles || rootFiles.length === 0) {
                (0, utils_1.default)(index_js_1.default.build.ts_no_files);
                return;
            }
            let tsConfigJson = {
                compilerOptions: {
                    target: "esnext",
                    module: "esnext",
                    moduleResolution: "NodeNext",
                    esModuleInterop: true,
                    verbatimModuleSyntax: false,
                    allowSyntheticDefaultImports: true,
                    outDir: tempDir,
                    rootDir: this.outdir,
                    strict: true,
                    allowJs: true,
                    sourceMap: false,
                    incremental: true, // 启用增量编译
                    tsBuildInfoFile: path.join(this.cacheDir, 'tsbuildinfo') // 指定缓存文件
                },
                include: ["**/*.ts"]
            };
            if (await utils.FileExsit(path.join(this.baseCwd, "tsconfig.json")))
                tsConfigJson = JSON.parse(await fs.readFile(path.join(this.baseCwd, "tsconfig.json"), "utf-8"));
            const parsed = ts.parseJsonConfigFileContent(tsConfigJson, ts.sys, this.outdir, undefined, "tsconfig.json");
            const program = ts.createProgram({
                rootNames: rootFiles,
                options: parsed.options
            });
            const emitResult = program.emit();
            const allDiagnostics = ts
                .getPreEmitDiagnostics(program)
                .concat(emitResult.diagnostics);
            allDiagnostics.forEach(d => {
                const msg = ts.flattenDiagnosticMessageText(d.messageText, "\n");
                if (d.file) {
                    const { line, character } = d.file.getLineAndCharacterOfPosition(d.start);
                    (0, utils_1.default)(index_js_1.default.build.ts_diagnostics.replace('{file}', d.file.fileName).replace('{line}', (line + 1).toString()).replace('{character}', (character + 1).toString()).replace('{message}', msg));
                }
                else {
                    (0, utils_1.default)(msg);
                }
            });
            await this.removeJsFiles(path.join(this.outdir, 'scripts'));
            await this.copyCompiledOnly(tempDir, this.outdir);
            (0, utils_1.default)(index_js_1.default.build.ts_compilation_error + " 完成");
        }
        catch (err) {
            (0, utils_1.default)(index_js_1.default.build.ts_compilation_error + err.stack);
            throw err;
        }
        finally {
            await tempMod.remove();
        }
    }
    async handlerMod(modules, processed = new Set()) {
        const allModules = this.gamelibModule.getAll();
        const scriptOutDir = path.join(this.outdir, 'scripts/node_modules');
        let nextDeps = {};
        for (const [packageName, gitRepo] of Object.entries(modules)) {
            if (!allModules.includes(packageName) || processed.has(packageName))
                continue;
            const srcDir = await this.gamelibModule.getDir(packageName, {
                gitUrl: gitRepo,
                v: this.d_data.mcVersion
            });
            if (!srcDir || !(await utils.FileExsit(srcDir)))
                continue;
            const pkg = await utils.handlerPackage(srcDir, {
                des: Array.from(processed)
            });
            if (pkg.des)
                Object.assign(nextDeps, pkg.des);
            await utils.copy(srcDir, path.join(scriptOutDir, packageName));
            await utils.waitGC();
            processed.add(packageName);
        }
        if (Object.keys(nextDeps).length > 0)
            await this.handlerMod(nextDeps, processed);
        return Array.from(processed);
    }
    handleIncludes() {
        if (!Array.isArray(includes_json_1.default))
            return;
        const copyOperations = includes_json_1.default
            .filter(item => typeof item === 'string')
            .map(async (item) => {
            const src = path.join(this.cwd, 'res', item);
            const dest = path.join(this.outdir, item);
            if (await utils.FileExsit(src)) {
                await utils.copy(src, dest);
            }
        });
        return Promise.all(copyOperations);
    }
    async processMinification(data) {
        try {
            if (!Array.isArray(includes_json_1.default)) {
                (0, utils_1.default)(index_js_1.default.build.includes_not_array);
                return;
            }
            await (0, index_js_3.default)(this.outdir, {
                modules: this.Modules,
                sourceDir: path.join(this.outdir, 'scripts'),
                baseDir: this.baseDir,
                minify: Boolean(data.minify)
            });
            await utils.waitGC();
        }
        catch (err) {
            (0, utils_1.default)(index_js_1.default.build.minification_error + err);
        }
    }
}
exports.default = Build;
