"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MinecraftDownloader = void 0;
/**
 * @author NovaStepStudios
 * @alias StepnickaSantiago
 * @license Apache-2.0
 * @link https://www.apache.org/licenses/LICENSE-2.0
 */
const events_1 = require("events");
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const p_limit_1 = __importDefault(require("p-limit"));
const Natives_1 = require("./Minecraft/Natives");
const Libraries_1 = require("./Minecraft/Libraries");
const Version_1 = require("./Minecraft/Version");
const Assets_1 = require("./Minecraft/Assets");
const Lwjgl_1 = require("./Minecraft/Lwjgl");
const Logging_1 = require("./Minecraft/Logging");
const Runtime_1 = __importDefault(require("./Minecraft/Runtime"));
const Bundle_1 = __importDefault(require("./Minecraft/Bundle"));
class MinecraftDownloader extends events_1.EventEmitter {
    tasks = [];
    completedSteps = 0;
    totalSteps = 0;
    controller = new AbortController();
    constructor() {
        super();
    }
    async start(options) {
        const { root, version, concurrency = 2, installJava = true, bundle } = options;
        if (!root)
            throw new Error("Debe especificar la carpeta raíz (root).");
        if (!version)
            throw new Error("Debe especificar la versión de Minecraft.");
        this.tasks = [];
        this.completedSteps = 0;
        this.controller = new AbortController();
        this.ensureLauncherProfiles(root);
        this.tasks.push(() => this.downloadLoggingXml(root, version));
        if (bundle?.length) {
            this.tasks.push(async () => {
                const bundleManager = new Bundle_1.default({ path: root, ignored: [] });
                await bundleManager.checkBundle(bundle);
                await bundleManager.checkFiles(bundle);
                this.completedSteps++;
                this.emitProgress("Bundle procesado", 100);
            });
        }
        // Detectar legacy assets
        const versionStr = typeof version === "string" ? version : version.id;
        const [major = 0, minor = 0, patch = 0] = versionStr.split(".").map(n => Number(n) || 0);
        const legacyAssets = major < 1 || (major === 1 && minor < 6) || (major === 1 && minor === 6 && patch < 1);
        const downloaders = [
            ["LWJGL", Lwjgl_1.LwjglDownloader],
            ["Nativos", Natives_1.MinecraftNativesDownloader],
            ["Librerías", Libraries_1.MinecraftLibrariesDownloader],
            [
                "Assets",
                class extends Assets_1.MinecraftAssetsDownloader {
                    constructor(rootPath, versionId) {
                        super(rootPath, versionId, 20, 10, true, legacyAssets);
                    }
                },
            ],
            ["Cliente", Version_1.MinecraftClientDownloader],
        ];
        if (installJava !== false) {
            let javaVersion;
            if (installJava === "auto" || installJava === true)
                javaVersion = this.getRecommendedJavaVersion(version);
            else if (typeof installJava === "string")
                javaVersion = installJava;
            else
                javaVersion = this.getRecommendedJavaVersion(version);
            const userVariant = options.variantJava || "release";
            const variantMap = {
                release: "gamma",
                snapshot: "gamma-snapshot",
                alpha: "alpha",
                beta: "beta",
            };
            const variantJava = variantMap[userVariant] || "gamma";
            downloaders.unshift([
                "Java",
                class extends Runtime_1.default {
                    constructor(rootPath) {
                        super({ root: rootPath, javaVersion, variant: variantJava });
                    }
                },
            ]);
        }
        this.totalSteps = downloaders.length;
        for (const [name, ClassRef] of downloaders) {
            this.tasks.push(() => this.runDownloader(name, ClassRef, root, version));
        }
        this.emitProgress("Iniciando descarga...", 0);
        try {
            const limit = concurrency === false ? (fn) => fn() : (0, p_limit_1.default)(concurrency || 1);
            await Promise.all(this.tasks.map(task => limit(() => this.guardTask(task))));
            if (!this.controller.signal.aborted) {
                this.emitProgress("Descarga completada", 100);
                this.emit("done");
            }
        }
        catch (err) {
            this.emit("error", err);
        }
    }
    async createInstancie(options) {
        const { root } = options;
        if (!root)
            throw new Error("Debes especificar la carpeta raíz (root)");
        const safeId = (options.manifest?.name || `inst_${Date.now()}`)
            .toLowerCase()
            .replace(/\s+/g, "-")
            .replace(/[^a-z0-9-_]/g, "");
        const instancieId = options.instancieId || safeId;
        const instanciePath = path_1.default.join(root, "instancies", instancieId);
        if (!fs_1.default.existsSync(instanciePath))
            fs_1.default.mkdirSync(instanciePath, { recursive: true });
        const config = {
            id: instancieId,
            manifest: {
                name: options.manifest?.name || "Instancia sin nombre",
                description: options.manifest?.description || "",
                icon: options.manifest?.icon || "",
                created: new Date().toISOString(),
            },
            userConfig: options.userConfig || { authenticator: null },
            gameConfig: options.gameConfig || {
                resolution: { width: "854", height: "480", fullscreen: false },
                memory: { min: "512M", max: "2G" },
                javaArgs: [],
                gameArgs: [],
            },
        };
        fs_1.default.writeFileSync(path_1.default.join(instanciePath, "configInstancie.json"), JSON.stringify(config, null, 2), "utf-8");
        this.emit("info", `configInstancie.json creado en ${instanciePath}`);
        return this.start({
            root: instanciePath,
            version: options.version,
            concurrency: options.concurrency,
            installJava: options.installJava,
            variantJava: options.variantJava,
            bundle: options.bundle,
        });
    }
    getRecommendedJavaVersion(version) {
        const ver = typeof version === "string" ? version : version.id;
        if (/^1\.1[7-9]|^1\.20/.test(ver))
            return "17";
        return "22";
    }
    stop() {
        this.controller.abort();
        this.emit("warn", "Descarga cancelada por el usuario.");
    }
    async guardTask(task) {
        if (this.controller.signal.aborted)
            return;
        try {
            await task();
        }
        catch (err) {
            this.emit("warn", `Tarea fallida: ${err.message}`);
        }
    }
    async downloadLoggingXml(root, version) {
        if (this.controller.signal.aborted)
            return;
        try {
            const versionId = typeof version === "string" ? version : version.id;
            const xmlPath = await (0, Logging_1.downloadLoggingXml)(versionId, root);
            this.completedSteps++;
            this.emitStepDone("Logging XML");
            this.emit("info", `Logging XML descargado en: ${xmlPath}`);
            this.emitProgress("Logging XML", 100);
        }
        catch (err) {
            this.handleError(err, "Logging XML", version, root);
        }
    }
    runDownloader(name, ClassRef, root, version) {
        return new Promise((resolve, reject) => {
            if (this.controller.signal.aborted)
                return resolve();
            let instance;
            try {
                instance = new ClassRef(root, version);
            }
            catch (err) {
                this.handleError(err, name, version, root);
                return reject(err);
            }
            ["info", "warn", "error"].forEach(evt => instance.on(evt, (...args) => {
                if (evt === "error") {
                    this.handleError(args[0], name, version, root);
                    reject(args[0]);
                }
                else {
                    this.emit(evt, `[${name}]`, ...args);
                }
            }));
            instance.on("progress", (progress) => {
                const stepPercent = typeof progress === "number" ? progress : progress.stepPercent ?? 0;
                this.emitProgress(name, stepPercent);
            });
            instance.on("done", () => {
                this.completedSteps++;
                this.emitStepDone(name);
                this.emitProgress(`${name} completado`, 100);
                resolve();
            });
            if (typeof instance.start === "function")
                instance.start().catch((err) => reject(err));
            else
                process.nextTick(() => instance.emit("done"));
        });
    }
    emitStepDone(name) {
        this.emit("step-done", name);
    }
    handleError(err, task, version, root) {
        const additionalInfo = JSON.stringify(err, Object.getOwnPropertyNames(err), 2);
        this.saveErrorLog(err, {
            task,
            version: typeof version === "string" ? version : version.id,
            root,
            step: task,
            profile: "UnknownUser",
            additionalInfo,
        });
        this.emit("error", new Error(`[${task}] ${err.message}`));
    }
    saveErrorLog(err, context = {}) {
        const now = new Date();
        const dateStr = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, "0")}-${now
            .getDate()
            .toString()
            .padStart(2, "0")}`;
        const logDir = path_1.default.resolve(context.root || "./", "logs");
        if (!fs_1.default.existsSync(logDir))
            fs_1.default.mkdirSync(logDir, { recursive: true });
        const fileName = `minecraft-core-master_${dateStr}_${now.getTime()}_${context.profile || "UnknownUser"}.log`;
        const logContent = `
============== MINECRAFT CORE MASTER ERROR LOG ================
DATE       : ${now.toISOString()}
PROFILE    : ${context.profile || "UnknownUser"}
VERSION    : ${context.version || "UnknownVersion"}
OS         : ${process.platform}
COMPONENT  : Downloader -> ${context.task || "General"}
STEP       : ${context.step || "N/A"}
PROGRESS   : ${context.progress || "0.00% / 100.00%"}
ROOT       : ${context.root || "UnknownRoot"}
ADDITIONAL : ${context.additionalInfo || "N/A"}

ERROR MESSAGE:
${err.message}

STACKTRACE:
${err.stack}
===============================================================
`;
        fs_1.default.writeFileSync(path_1.default.join(logDir, fileName), logContent, "utf-8");
        console.error(`❌ [ERROR LOG] Guardado en: ${path_1.default.join(logDir, fileName)}`);
    }
    ensureLauncherProfiles(root) {
        const filePath = path_1.default.join(root, "launcher_profiles.json");
        const dir = path_1.default.dirname(filePath);
        if (!fs_1.default.existsSync(dir))
            fs_1.default.mkdirSync(dir, { recursive: true });
        if (!fs_1.default.existsSync(filePath)) {
            fs_1.default.writeFileSync(filePath, JSON.stringify({ profiles: {}, version: 3 }, null, 2), "utf-8");
            this.emit("info", "launcher_profiles.json creado de forma básica");
        }
    }
    emitProgress(current, stepPercent) {
        const formattedStep = (stepPercent ?? 0).toFixed(2);
        const totalPercent = ((this.completedSteps / this.totalSteps) * 100).toFixed(2);
        this.emit("progress", { current, stepPercent: Number(formattedStep), totalPercent: Number(totalPercent) });
    }
}
exports.MinecraftDownloader = MinecraftDownloader;
