import chalk from "chalk";
import { exec, type ExecException } from "child_process";
import { existsSync, mkdirSync, readdirSync, readFileSync } from "fs";
import path from "path";

/**
 * The path to the user folder.
 *
 * This is the same folder you are sent to if your type `%AppData%/../../` in the `WIN+R` Run dialog.
 */
export const userFolderPath: string = import.meta.dirname.split(path.sep).slice(0, 3).join(path.sep);
// Check if the user folder path is valid.
if (!new RegExp(`[A-Z]:${path.sep === "\\" ? "\\\\" : path.sep}Users${path.sep === "\\" ? "\\\\" : path.sep}`).test(userFolderPath)) {
    console.error(chalk.red(`Invalid user folder path, please make sure this package is installed globally: "${userFolderPath}"`));
    process.exit(1);
}

/**
 * The path to the Bedrock Launcher data folder.
 */
export const mcBedrockFolderPath: string = path.join(userFolderPath, "AppData/Roaming/.minecraft_bedrock");

// Check if Bedrock Launcher is installed.
if (!existsSync(mcBedrockFolderPath)) {
    console.error(chalk.red(`Bedrock Launcher is not installed.`));
    process.exit(1);
}

/**
 * The path to the Bedrock Launcher versions folder.
 */
const mcBedrockVersionsFolderPath: string = path.join(mcBedrockFolderPath, "versions");
/**
 * The list of folders for all the currently installed Minecraft Bedrock Edition versions.
 */
const bedrockLauncherVersionFolders: string[] = readdirSync(mcBedrockVersionsFolderPath).filter((versionFolder) => versionFolder.split("-").length === 5);

/**
 * The list of version numbers.
 */
const versionNumbers: (`${number}.${number}.${number}.${number}_${"Release" | "Preview"}` | "Unable to determine version.")[] =
    bedrockLauncherVersionFolders.map((versionFolder) => {
        const AppxManifestXML: string = path.join(mcBedrockVersionsFolderPath, versionFolder, "AppxManifest.xml");
        const AppxManifestXMLContent: string = readFileSync(AppxManifestXML, "utf-8");
        const AppxManifestXMLVersion: string | undefined = AppxManifestXMLContent.match(
            /\<Identity Name="(?:Microsoft\.MinecraftUWP|Microsoft\.MinecraftWindowsBeta)" Publisher="[^"]*" Version="([\d\.]+)"/
        )?.[1];
        if (!AppxManifestXMLVersion) {
            return "Unable to determine version." as const;
        }
        const AppxManifestXMLEdition: "Minecraft for Windows" | "Minecraft Windows Preview" | undefined = AppxManifestXMLContent.match(
            /\<DisplayName\>(Minecraft for Windows|Minecraft Windows Preview)\<\/DisplayName>/
        )?.[1] as "Minecraft for Windows" | "Minecraft Windows Preview" | undefined;
        const versionSegments = AppxManifestXMLVersion.split(".");
        return `${Number(versionSegments[0])}.${Number(versionSegments[1])}.${Number(versionSegments[2]?.slice(0, -2))}.${Number(
            versionSegments[2]?.slice(-2)
        )}_${AppxManifestXMLEdition === "Minecraft for Windows" ? "Release" : "Preview"}` as const;
    });

mkdirSync(path.join(mcBedrockFolderPath, "versionSymlinks"), { recursive: true });

for (let i = 0; i < bedrockLauncherVersionFolders.length; i++) {
    if (versionNumbers[i] === "Unable to determine version.") {
        console.warn(chalk.yellow(`Unable to determine version for folder "${bedrockLauncherVersionFolders[i]}". Skipping...`));
        continue;
    }
    await runCommmand(
        `mklink /J "${path.join(mcBedrockFolderPath, "versionSymlinks", versionNumbers[i]!)}" "${path.join(
            mcBedrockVersionsFolderPath,
            bedrockLauncherVersionFolders[i]!
        )}"`
    ).then((r) => {
        if (r.err !== null) {
            throw r.err;
        }
    });
}

/**
 * Runs a command.
 *
 * @param {string} command The command to run.
 * @returns A promise that resolves with the results of the command.
 */
export async function runCommmand(command: string): Promise<{ err: ExecException | null; stdout: string; stderr: string }> {
    return new Promise((resolve: (value: { err: ExecException | null; stdout: string; stderr: string }) => void) => {
        exec(command, (err: ExecException | null, stdout: string, stderr: string) => {
            resolve({ err, stdout, stderr });
        });
    });
}
