"use strict";
/**
 * @author NovaStepStudios
 * @alias StepnickaSantiago
 * @license Apache-2.0
 * @link https://www.apache.org/licenses/LICENSE-2.0
 * @external https://music.youtube.com/watch?v=DVXMmKadzYU&si=ofY-WTm-mFeVsehz Disfrutalo :)
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ArgumentBuilder = void 0;
const path_1 = __importDefault(require("path"));
const os_1 = __importDefault(require("os"));
const uuid_1 = require("uuid");
const fs_1 = __importDefault(require("fs"));
const child_process_1 = __importDefault(require("child_process"));
const platformMap = {
    win32: "windows",
    linux: "linux",
    darwin: "osx",
};
function isRuleAllowed(rules) {
    if (!rules || rules.length === 0)
        return true;
    const platform = platformMap[os_1.default.platform()] || os_1.default.platform();
    return (rules.some((rule) => !rule.os || rule.os.name === platform) &&
        rules.some((rule) => rule.action === "allow"));
}
function replaceVars(input, vars) {
    if (typeof input !== "string")
        return input;
    return input.replace(/\${([^}]+)}/g, (_, key) => vars[key] ?? "");
}
function resolveIfExists(p) {
    try {
        const r = path_1.default.resolve(p);
        return fs_1.default.existsSync(r) ? r : null;
    }
    catch {
        return null;
    }
}
function uniquePaths(paths = []) {
    const seen = new Set();
    return (paths || []).reduce((out, p) => {
        const r = resolveIfExists(p);
        if (r && !seen.has(r)) {
            seen.add(r);
            out.push(r);
        }
        return out;
    }, []);
}
function splitModuleAndClassPath(classPath) {
    const modulePath = [];
    const classPathFiltered = [];
    const unique = uniquePaths(classPath);
    for (const jar of unique) {
        const l = jar.toLowerCase();
        if (l.includes("securejarhandler") || l.includes("bootstraplauncher"))
            modulePath.push(jar);
        else
            classPathFiltered.push(jar);
    }
    return { modulePath: uniquePaths(modulePath), classPathFiltered: uniquePaths(classPathFiltered) };
}
function getJavaVersion(javaPath) {
    try {
        const output = child_process_1.default.execSync(`${javaPath} -version 2>&1`).toString();
        const match = output.match(/version "(?<v>\d+)/);
        if (match && match.groups?.v)
            return parseInt(match.groups.v);
        throw new Error();
    }
    catch {
        throw new Error("[Minecraft-Core] No se pudo detectar la versión de Java instalada.");
    }
}
function filterLwjglDuplicates(paths) {
    const latest = {};
    for (const p of paths) {
        const base = path_1.default.basename(p);
        const match = base.match(/^(?<lib>lwjgl(?:-[a-z]+)*)-(?<ver>\d+\.\d+\.\d+)\.jar$/);
        if (match?.groups) {
            const lib = match.groups["lib"];
            const ver = match.groups["ver"];
            if (lib && ver) {
                if (!latest[lib] || latest[lib] < ver) {
                    latest[lib] = ver;
                }
            }
        }
    }
    return paths.filter((p) => {
        const base = path_1.default.basename(p);
        const match = base.match(/^(?<lib>lwjgl(?:-[a-z]+)*)-(?<ver>\d+\.\d+\.\d+)\.jar$/);
        if (!match?.groups)
            return true;
        const lib = match.groups["lib"];
        const ver = match.groups["ver"];
        return !!(lib && ver && latest[lib] === ver);
    });
}
exports.ArgumentBuilder = {
    build({ opts, version, auth, classPath, }) {
        const args = [];
        const debug = !!opts.debug;
        const javaPath = opts.javaPath || "java";
        // Java version
        const javaVersion = getJavaVersion(javaPath);
        const requiredJava = version.javaVersion?.majorVersion ?? 8;
        if (javaVersion < requiredJava)
            throw new Error(`[Minecraft-Core] Java v${javaVersion} no compatible. Se requiere v${requiredJava}+`);
        // Memory
        const memory = opts.memory || {};
        const memPattern = /^[0-9]+[GM]$/i;
        args.push(memPattern.test(memory.max || "") ? `-Xmx${memory.max}` : "-Xmx2G");
        args.push(memPattern.test(memory.min || "") ? `-Xms${memory.min}` : "-Xms512M");
        // Game paths
        const gameRoot = path_1.default.resolve(opts.root);
        const versionsRoot = path_1.default.join(gameRoot, "versions");
        let actualVersion = version;
        while (actualVersion.inheritsFrom) {
            const parentId = actualVersion.inheritsFrom;
            const parentPath = path_1.default.join(versionsRoot, parentId, `${parentId}.json`);
            if (!fs_1.default.existsSync(parentPath))
                break;
            actualVersion = JSON.parse(fs_1.default.readFileSync(parentPath, "utf-8"));
        }
        const nativesPath = path_1.default.join(versionsRoot, actualVersion.id, "natives");
        if (!fs_1.default.existsSync(nativesPath))
            throw new Error(`[Minecraft-Core] Nativos no encontrados: ${nativesPath}`);
        args.push(`-Djava.library.path=${path_1.default.resolve(nativesPath)}`);
        args.push(`-Dorg.lwjgl.librarypath=${path_1.default.resolve(nativesPath)}`);
        if (requiredJava > 8)
            args.push("--enable-native-access=ALL-UNNAMED");
        // Normalizar classPath a array
        let classPathArray = [];
        if (typeof classPath === "string")
            classPathArray = classPath.split(path_1.default.delimiter).map((p) => p.replace(/^"(.*)"$/, "$1"));
        else if (Array.isArray(classPath))
            classPathArray = classPath;
        // Classpath
        const isNeoForge = version.id?.toLowerCase().includes("neoforge") || version.type?.toLowerCase() === "neoforge";
        if (isNeoForge) {
            args.unshift("--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED");
            const { modulePath, classPathFiltered } = splitModuleAndClassPath(classPathArray);
            const moduleSet = new Set(modulePath.map((p) => path_1.default.resolve(p)));
            const cleanedClassPath = classPathFiltered.filter((p) => !moduleSet.has(path_1.default.resolve(p)));
            if (!modulePath.length)
                throw new Error("[Minecraft-Core] Falta jar para module-path (NeoForge)");
            if (!cleanedClassPath.length)
                throw new Error("[Minecraft-Core] classPath vacío tras separar module-path (NeoForge)");
            if (debug)
                console.log("[Minecraft-Core][debug] module-path:", modulePath.length, "class-path:", cleanedClassPath.length);
            args.push("--module-path", modulePath.join(path_1.default.delimiter));
            args.push("--add-modules", "cpw.mods.bootstraplauncher");
            args.push("--class-path", cleanedClassPath.join(path_1.default.delimiter));
        }
        else {
            const filteredClassPath = filterLwjglDuplicates(uniquePaths(classPathArray));
            if (!filteredClassPath.length)
                throw new Error("[Minecraft-Core] classPath vacío o inválido");
            if (debug)
                console.log("[Minecraft-Core][debug] classpath count:", filteredClassPath.length);
            args.push("-cp", filteredClassPath.join(path_1.default.delimiter));
        }
        // Main class
        const mainClass = version.mainClass || (isNeoForge ? "cpw.mods.bootstraplauncher.BootstrapLauncher" : "net.minecraft.client.main.Main");
        if (!mainClass || !mainClass.trim())
            throw new Error("[Minecraft-Core] mainClass inválido");
        args.push(mainClass);
        // Vars
        const assetDir = path_1.default.join(gameRoot, version.assets === "pre-1.6" ? "resources" : "assets");
        const vars = {
            auth_player_name: auth.name || "Player",
            version_name: version.id,
            game_directory: gameRoot,
            assets_root: assetDir,
            assets_index_name: version.assets || version.assetIndex?.id || "legacy",
            auth_uuid: auth.uuid || (0, uuid_1.v4)(),
            auth_session: auth.accessToken || (0, uuid_1.v4)(),
            auth_access_token: auth.accessToken || (0, uuid_1.v4)(),
            user_type: auth.provider || "mojang",
            version_type: opts.version?.type || "release",
            user_properties: auth.userProperties?.value || "{}",
            resolution_width: opts.window?.width || 854,
            resolution_height: opts.window?.height || 480,
            fullscreen: opts.window?.fullscreen ? "false" : undefined,
            clientid: auth.clientId || "unknown",
            offline: auth.offline ?? false,
            demo: false,
        };
        // Raw args
        let rawArgs = [];
        if (version.minecraftArguments?.trim()) {
            rawArgs = version.minecraftArguments.split(" ").map((a) => replaceVars(a, vars));
        }
        else if (version.arguments?.game) {
            for (const arg of version.arguments.game) {
                if (typeof arg === "string") {
                    rawArgs.push(replaceVars(arg, vars));
                }
                else if (arg.rules && isRuleAllowed(arg.rules)) {
                    // Ignorar demo
                    const hasDemoFeature = arg.rules.some((r) => r.features?.is_demo_user);
                    if (hasDemoFeature)
                        continue;
                    if (Array.isArray(arg.value))
                        rawArgs.push(...arg.value.map((v) => replaceVars(v, vars)));
                    else
                        rawArgs.push(replaceVars(arg.value, vars));
                }
            }
        }
        // mcFlags y userProperties
        if (opts.mcFlags)
            rawArgs.push(...opts.mcFlags);
        if (!rawArgs.includes("--userProperties"))
            rawArgs.push("--userProperties", vars.user_properties);
        // Limpieza de duplicados y flags vacías
        const cleanedRaw = [];
        const seenFlags = new Set();
        for (let i = 0; i < rawArgs.length; i++) {
            const token = rawArgs[i];
            if (!token)
                continue;
            if (token.startsWith("--")) {
                const next = rawArgs[i + 1];
                if (seenFlags.has(token)) {
                    i++;
                    continue;
                }
                cleanedRaw.push(token);
                if (next && !next.startsWith("--")) {
                    cleanedRaw.push(next);
                    i++;
                }
                seenFlags.add(token);
            }
            else
                cleanedRaw.push(token);
        }
        // Assets, uuid, accessToken, gameDir
        const pushIfMissing = (flag, value) => {
            if (!cleanedRaw.includes(flag))
                cleanedRaw.push(flag, value);
        };
        pushIfMissing("--assetsDir", assetDir);
        if (version.assetIndex)
            pushIfMissing("--assetIndex", version.assetIndex.id);
        pushIfMissing("--uuid", vars.auth_uuid);
        pushIfMissing("--accessToken", vars.auth_access_token);
        // Quitar --demo si existe
        const demoIndex = cleanedRaw.indexOf("--demo");
        if (demoIndex !== -1) {
            cleanedRaw.splice(demoIndex, 1);
            cleanedRaw.splice(demoIndex, 1); // también elimina el valor siguiente, aunque generalmente no hay
        }
        if (opts.window?.fullscreen)
            cleanedRaw.push("--fullscreen");
        pushIfMissing("--gameDir", gameRoot);
        if (debug)
            console.log(`[Minecraft-Core][debug] rawArgs finales: ${cleanedRaw.length}`);
        args.push(...cleanedRaw);
        return args;
    },
};
