import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
import type { Plugin } from "vite";
import {
    WELL_KNOWN_DIRECTORY_BASE_NAME,
    VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES
} from "../bin/shared/constants";
import { id } from "tsafe/id";
import { rm } from "../bin/tools/fs.rm";
import { command as copyKeycloakResourcesToPublicCommand } from "../bin/copy-keycloak-resources-to-public";
import { assert } from "tsafe/assert";
import {
    getBuildContext,
    type BuildContext,
    type BuildOptions,
    type ResolvedViteConfig
} from "../bin/shared/buildContext";
import MagicString from "magic-string";
import { command as updateKcGenCommand } from "../bin/update-kc-gen";
import { replaceAll } from "../bin/tools/String.prototype.replaceAll";

export namespace keycloakify {
    export type Params = BuildOptions & {
        postBuild?: (buildContext: Omit<BuildContext, "bundler">) => Promise<void>;
    };
}

export function keycloakify(params: keycloakify.Params) {
    const { postBuild, ...buildOptions } = params;

    let projectDirPath: string | undefined = undefined;
    let urlPathname: string | undefined = undefined;
    let buildDirPath: string | undefined = undefined;
    let command: "build" | "serve" | undefined = undefined;
    let shouldGenerateSourcemap: boolean | undefined = undefined;

    const plugin = {
        name: "keycloakify",
        configResolved: async resolvedConfig => {
            shouldGenerateSourcemap = resolvedConfig.build.sourcemap !== false;

            run_post_build_script_case: {
                const envValue =
                    process.env[VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RUN_POST_BUILD_SCRIPT];

                if (envValue === undefined) {
                    break run_post_build_script_case;
                }

                const { buildContext, resourcesDirPath } = JSON.parse(envValue) as {
                    buildContext: BuildContext;
                    resourcesDirPath: string;
                };

                process.chdir(resourcesDirPath);

                await postBuild?.(buildContext);

                process.exit(0);
            }

            command = resolvedConfig.command;

            projectDirPath = resolvedConfig.root;
            urlPathname = (() => {
                let out = resolvedConfig.env.BASE_URL;

                if (
                    out.startsWith(".") &&
                    command === "build" &&
                    resolvedConfig.envPrefix?.includes("STORYBOOK_") !== true
                ) {
                    throw new Error(
                        [
                            `BASE_URL=${out} is not supported By Keycloakify. Use an absolute path instead.`,
                            `If this is a problem, please open an issue at https://github.com/keycloakify/keycloakify/issues/new`
                        ].join("\n")
                    );
                }

                if (out === undefined) {
                    return undefined;
                }

                if (!out.startsWith("/")) {
                    out = "/" + out;
                }

                if (!out.endsWith("/")) {
                    out += "/";
                }

                return out;
            })();

            buildDirPath = pathJoin(projectDirPath, resolvedConfig.build.outDir);

            resolve_vite_config_case: {
                const envValue =
                    process.env[VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RESOLVE_VITE_CONFIG];

                if (envValue === undefined) {
                    break resolve_vite_config_case;
                }

                console.log(VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RESOLVE_VITE_CONFIG);

                console.log(
                    JSON.stringify(
                        id<ResolvedViteConfig>({
                            publicDir: pathRelative(
                                projectDirPath,
                                resolvedConfig.publicDir
                            ),
                            assetsDir: resolvedConfig.build.assetsDir,
                            buildDir: resolvedConfig.build.outDir,
                            urlPathname,
                            buildOptions
                        })
                    )
                );

                process.exit(0);
            }

            const buildContext = getBuildContext({
                projectDirPath
            });

            await copyKeycloakResourcesToPublicCommand({ buildContext });

            await updateKcGenCommand({ buildContext });
        },
        transform: (code, id) => {
            id = replaceAll(id, "/", pathSep);

            assert(command !== undefined);
            assert(shouldGenerateSourcemap !== undefined);

            if (command !== "build") {
                return;
            }

            assert(projectDirPath !== undefined);

            {
                const isWithinSourceDirectory = id.startsWith(
                    pathJoin(projectDirPath, "src") + pathSep
                );

                if (!isWithinSourceDirectory) {
                    return;
                }
            }

            {
                const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
                const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx");
                const isSvelteFile = id.endsWith(".svelte");

                if (!isTypeScriptFile && !isJavascriptFile && !isSvelteFile) {
                    return;
                }
            }

            const transformedCode = new MagicString(code);

            transformedCode.replaceAll(
                /import\.meta\.env(?:(?:\.BASE_URL)|(?:\["BASE_URL"\]))/g,
                [
                    `(`,
                    `(window.kcContext === undefined || import.meta.env.MODE === "development")?`,
                    `import.meta.env.BASE_URL:`,
                    `(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/")`,
                    `)`
                ].join("")
            );

            if (!transformedCode.hasChanged()) {
                return;
            }

            if (!shouldGenerateSourcemap) {
                return transformedCode.toString();
            }

            const map = transformedCode.generateMap({
                source: id,
                includeContent: true,
                hires: true
            });

            return {
                code: transformedCode.toString(),
                map: map.toString()
            };
        },
        closeBundle: async () => {
            assert(command !== undefined);

            if (command !== "build") {
                return;
            }

            assert(buildDirPath !== undefined);

            await rm(
                pathJoin(
                    buildDirPath,
                    WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES
                ),
                {
                    recursive: true,
                    force: true
                }
            );
        },
        transformIndexHtml: html => {
            const doReadKcContextFromUrl =
                process.env.NODE_ENV === "development" &&
                process.env[
                    VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.READ_KC_CONTEXT_FROM_URL
                ] === "true";

            if (!doReadKcContextFromUrl) {
                return html;
            }

            const scriptContent = `
(()=>{

    const kcContext = (()=>{

        const paramName= "kcContext";

        read_from_url_case: {

            const url = new URL(window.location.href);

            const paramValue = url.searchParams.get(paramName);

            if( paramValue === null ){
                break read_from_url_case;
            }

            url.searchParams.delete(paramName);

            window.history.replaceState({}, "", url);

            const kcContext = JSON.parse(decodeURIComponent(paramValue));

            sessionStorage.setItem(paramName, JSON.stringify(kcContext));

            return kcContext;

        }

        read_from_session_storage_case: {

            const paramValue = sessionStorage.getItem(paramName);

            if( paramValue === null ){
                break read_from_session_storage_case;
            }

            return JSON.parse(paramValue);

        }

        return undefined;
    
    })();

    if( kcContext === undefined ){
            return;
    }

    window.kcContext = kcContext;

})();
`;

            return html.replace(/<head>/, `<head><script>${scriptContent}</script>`);
        }
    } satisfies Plugin;

    return plugin as any;
}
