import { readFile, writeFile, stat, mkdir, rename, unlink, } from "node:fs/promises";
import path from "node:path";
import { exec } from "node:child_process";
import { promisify } from "node:util";
import glob from "fast-glob";
import { format } from "prettier";
import { printDiagnosticsToConsole } from "../../../common/output.js";
import chalk from "chalk";
import { PluginResult, runPlugins } from "./runner.js";
import { stringifyConfig } from "./stringify-config.js";
const exists = (path) => stat(path)
    .then(() => true)
    .catch(() => false);
const deleteUndefinedValues = (obj) => {
    Object.entries(obj).forEach(([key, value]) => {
        if (value === undefined) {
            delete obj[key];
        }
    });
};
const execAsync = promisify(exec);
export const migrateDevPortal = async (args) => {
    const absoluteBaseDir = path.isAbsolute(args.dir)
        ? args.dir
        : path.join(process.cwd(), args.dir);
    const tasks = [
        checkGitStatus,
        loadInputFiles,
        validateDevPortalEnabled,
        convertBasicConfig,
        convertSidebar,
        handleApiReferences,
        handleAuthentication,
        handleAdditionalFeatures,
        updatePackageJsonWorkspaces,
        createPackageJson,
        createTsConfig,
        moveMarkdownFiles,
        writeConfigFile,
        cleanupOldFiles,
    ];
    const pluginConfig = {
        absoluteBaseDir,
        force: args.force,
    };
    const { errors, warnings, notes } = await runPlugins(tasks, pluginConfig);
    if (errors.length > 0) {
        printDiagnosticsToConsole("❌ Migration failed with errors:");
        errors.forEach((error) => printDiagnosticsToConsole(`- [${error.pluginName}] ${error.message}`));
        process.exit(1);
    }
    printDiagnosticsToConsole(chalk.green("\n✅ Migration completed successfully! 🎉"));
    if (notes.length > 0) {
        printDiagnosticsToConsole("\n📝 Migration notes:");
        notes.forEach((note) => printDiagnosticsToConsole(`- ${note}`));
    }
    if (warnings.length > 0) {
        printDiagnosticsToConsole("\n⚠️  Plugin warnings:");
        warnings.forEach((warning) => printDiagnosticsToConsole(`- [${warning.pluginName}] ${warning.message}`));
    }
    printDiagnosticsToConsole("\nNext steps:");
    printDiagnosticsToConsole("1. Switch to the docs directory:");
    printDiagnosticsToConsole(`   ${chalk.cyan(`cd ${path.join(absoluteBaseDir, "docs")}`)}`);
    printDiagnosticsToConsole("\n2. Install dependencies:");
    printDiagnosticsToConsole(`   ${chalk.cyan("npm install")}`);
    printDiagnosticsToConsole("\n3. Start the dev portal:");
    printDiagnosticsToConsole(`   ${chalk.cyan("npm run dev")}`);
    printDiagnosticsToConsole("");
};
const checkGitStatus = async (config) => {
    if (config.force)
        return new PluginResult();
    try {
        await execAsync("git rev-parse --git-dir", { cwd: config.absoluteBaseDir });
        const status = await execAsync("git status --porcelain", {
            cwd: config.absoluteBaseDir,
        });
        if (status.stdout.trim() !== "") {
            return new PluginResult({
                pluginName: "check-git-status",
                message: "There are uncommitted changes in the repository.\nCommit or stash them before running the migration, or use --force to proceed anyway.",
                severity: "error",
            });
        }
    }
    catch {
    }
    return new PluginResult();
};
const loadInputFiles = async (config, { data }) => {
    const dir = config.absoluteBaseDir;
    try {
        const packageJson = JSON.parse(await readFile(path.join(dir, "package.json"), "utf-8"));
        const inputFile = path.join(dir, "config/dev-portal.json");
        const sidebarFile = path.join(dir, "docs/sidebar.json");
        const devPortalConfig = JSON.parse(await readFile(inputFile, "utf-8"));
        const sidebar = JSON.parse(await readFile(sidebarFile, "utf-8"));
        data.packageJson = packageJson;
        data.devPortalConfig = devPortalConfig;
        data.sidebar = sidebar;
        return new PluginResult();
    }
    catch (error) {
        return new PluginResult({
            pluginName: "load-input-files",
            message: `Failed to load input files: ${error.message}`,
            severity: "error",
        });
    }
};
const validateDevPortalEnabled = async (_, { data }) => {
    if (data.devPortalConfig?.enabled === false) {
        return new PluginResult({
            pluginName: "validate-dev-portal-enabled",
            message: "Dev portal is not enabled in the config",
            severity: "error",
        });
    }
    return new PluginResult();
};
const convertBasicConfig = async (_, { data }) => {
    try {
        if (!data.devPortalConfig) {
            return new PluginResult({
                pluginName: "convert-basic-config",
                message: "Dev portal config not found",
                severity: "error",
            });
        }
        const zudokuConfig = {};
        if (data.devPortalConfig.faviconUrl) {
            zudokuConfig.metadata = {
                ...zudokuConfig.metadata,
                favicon: data.devPortalConfig.faviconUrl,
            };
        }
        if (data.devPortalConfig.pageTitle) {
            zudokuConfig.metadata = {
                ...zudokuConfig.metadata,
                title: data.devPortalConfig.pageTitle,
            };
        }
        if (data.devPortalConfig.sitePathname) {
            zudokuConfig.basePath = data.devPortalConfig.sitePathname;
        }
        data.zudokuConfig = zudokuConfig;
        return new PluginResult();
    }
    catch (error) {
        return new PluginResult({
            pluginName: "convert-basic-config",
            message: `Failed to convert basic config: ${error.message}`,
            severity: "error",
        });
    }
};
const convertSidebar = async (_, { data }) => {
    try {
        if (!data.sidebar || !data.zudokuConfig) {
            return new PluginResult({
                pluginName: "convert-sidebar",
                message: "Required data not found",
                severity: "error",
            });
        }
        const convertSidebarToNavigation = (oldSidebar) => {
            const navigation = [];
            if (!oldSidebar.docs) {
                return navigation;
            }
            for (const item of oldSidebar.docs) {
                if (typeof item === "string") {
                    navigation.push(item);
                }
                else if (item.type === "doc") {
                    const docItem = {
                        type: "doc",
                        file: item.id,
                        label: item.label,
                    };
                    if ("defaultPage" in item && item.defaultPage) {
                        docItem.path = "/";
                    }
                    navigation.push(docItem);
                }
                else if (item.type === "category") {
                    const category = {
                        type: "category",
                        label: item.label,
                        items: [],
                    };
                    if (item.link) {
                        if (typeof item.link === "string") {
                            category.link = item.link;
                        }
                        else if (item.link.type === "doc") {
                            category.link = { type: "doc", file: item.link.id };
                        }
                    }
                    if (item.items) {
                        category.items = [];
                        for (const subItem of item.items) {
                            if (typeof subItem === "string") {
                                category.items.push({
                                    type: "doc",
                                    file: subItem,
                                });
                            }
                            else if (subItem.type === "doc") {
                                category.items.push({
                                    type: "doc",
                                    file: subItem.id,
                                    label: subItem.label,
                                });
                            }
                        }
                    }
                    navigation.push(category);
                }
            }
            return navigation;
        };
        const navigation = convertSidebarToNavigation(data.sidebar);
        data.zudokuConfig = {
            ...data.zudokuConfig,
            navigation,
        };
        const result = new PluginResult();
        if (navigation.length > 0) {
            result.notes.push(`Converted sidebar navigation: ${navigation.length} navigation items`);
        }
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "convert-sidebar",
            message: `Failed to convert sidebar: ${error.message}`,
            severity: "error",
        });
    }
};
const handleApiReferences = async (_, { data }) => {
    try {
        if (!data.sidebar || !data.zudokuConfig) {
            return new PluginResult({
                pluginName: "handle-api-references",
                message: "Required data not found",
                severity: "error",
            });
        }
        const apiRefItems = data.sidebar.docs?.filter((item) => typeof item !== "string" && item.type === "api-ref");
        const result = new PluginResult();
        if (!apiRefItems || apiRefItems.length === 0) {
            return result;
        }
        data.zudokuConfig.apis = apiRefItems.map((apiRefItem, index) => {
            const specInput = apiRefItem.defaultSpec
                ? `../config/${apiRefItem.defaultSpec}.oas.json`
                : "../config/routes.oas.json";
            const apiPath = index === 0 ? "/api" : `/api-${index + 1}`;
            result.notes.push(`Migrated API reference to ${apiPath}`);
            return {
                type: "file",
                input: specInput,
                path: apiPath,
            };
        });
        if (apiRefItems.length > 1) {
            result.warnings.push({
                message: "Multiple API references found. Adding multiple links to the navigation. For advanced use cases you can use an API catalog. See: https://zuplo.com/docs/dev-portal/zudoku/configuration/api-catalog.",
                pluginName: "handle-api-references",
                severity: "warning",
            });
        }
        const navigationLinks = apiRefItems.map((apiRefItem, index) => {
            const apiPath = index === 0 ? "/api" : `/api-${index + 1}`;
            return {
                type: "link",
                to: apiPath,
                label: apiRefItem.label,
            };
        });
        data.zudokuConfig.navigation = [
            ...(data.zudokuConfig.navigation || []),
            ...navigationLinks,
        ];
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "handle-api-references",
            message: `Failed to handle API references: ${error.message}`,
            severity: "error",
        });
    }
};
const handleAuthentication = async (_, { data }) => {
    try {
        if (!data.devPortalConfig || !data.zudokuConfig) {
            return new PluginResult({
                pluginName: "handle-authentication",
                message: "Required data not found",
                severity: "error",
            });
        }
        if (data.devPortalConfig.authentication) {
            const auth = data.devPortalConfig.authentication;
            let clientId;
            let audience;
            let issuer;
            let scope;
            if ("clientId" in auth) {
                clientId = auth.clientId;
                audience = auth.audience;
                issuer = auth.issuer;
                scope = auth.scope;
            }
            else if ("devPortalClient" in auth) {
                clientId = auth.devPortalClient?.clientId;
                audience = auth.devPortalClient?.audience;
                issuer = auth.authority;
            }
            if (auth.provider === "auth0") {
                data.zudokuConfig.authentication = {
                    type: "auth0",
                    clientId,
                    domain: issuer?.replace("https://", "").replace("/", ""),
                    audience,
                    scopes: scope?.split(" "),
                };
            }
            else if (auth.provider === "clerk") {
                const clerkPubKey = issuer?.match(/clerk\.accounts\.dev/)?.[0]
                    ? issuer.split("://")[1]?.split(".")[0]
                    : undefined;
                data.zudokuConfig.authentication = {
                    type: "clerk",
                    clerkPubKey: clerkPubKey || clientId,
                };
            }
            else {
                data.zudokuConfig.authentication = {
                    type: "openid",
                    clientId,
                    issuer,
                    audience,
                    scopes: scope?.split(" "),
                };
            }
        }
        if (data.zudokuConfig.authentication) {
            deleteUndefinedValues(data.zudokuConfig.authentication);
        }
        const result = new PluginResult();
        if (data.zudokuConfig.authentication) {
            result.notes.push(`Configured ${data.zudokuConfig.authentication.type} authentication`);
        }
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "handle-authentication",
            message: `Failed to handle authentication: ${error.message}`,
            severity: "error",
        });
    }
};
const handleAdditionalFeatures = async (_, { data }) => {
    try {
        if (!data.devPortalConfig || !data.sidebar || !data.zudokuConfig) {
            return new PluginResult({
                pluginName: "handle-additional-features",
                message: "Required data not found",
                severity: "error",
            });
        }
        const result = new PluginResult();
        if (data.devPortalConfig.apiKeys) {
            data.zudokuConfig.apiKeys = {
                enabled: true,
            };
        }
        const hasSpecsFiltering = data.sidebar.docs?.some((item) => {
            if (typeof item === "string")
                return false;
            if (item.type === "doc" && "specs" in item && item.specs)
                return true;
            if (item.type === "category" && "specs" in item && item.specs)
                return true;
            if (item.type === "category" && item.items) {
                return item.items.some((subItem) => typeof subItem !== "string" && "specs" in subItem && subItem.specs);
            }
            return false;
        });
        if (hasSpecsFiltering) {
            result.warnings.push({
                message: "The 'specs' property for filtering docs by OpenAPI specification is not supported in Zudoku",
                pluginName: "handle-additional-features",
                severity: "warning",
            });
        }
        if (data.devPortalConfig.monetization) {
            result.warnings.push({
                message: "Monetization configuration is not supported in the new dev portal.",
                pluginName: "handle-additional-features",
                severity: "warning",
            });
        }
        if (data.devPortalConfig.enableMetering) {
            result.warnings.push({
                message: "Metering is not supported in the new dev portal.",
                pluginName: "handle-additional-features",
                severity: "warning",
            });
        }
        if (data.devPortalConfig.authorizePathname &&
            data.devPortalConfig.authorizePathname !== "/oauth/authorize") {
            result.warnings.push({
                message: "Custom authorize pathname is not configurable in the new dev portal.",
                pluginName: "handle-additional-features",
                severity: "warning",
            });
        }
        if (data.zudokuConfig.apiKeys?.enabled) {
            result.notes.push("Enabled API keys support");
        }
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "handle-additional-features",
            message: `Failed to handle additional features: ${error.message}`,
            severity: "error",
        });
    }
};
const createPackageJson = async (config) => {
    try {
        const packageJsonFile = path.join(config.absoluteBaseDir, "docs/package.json");
        if (!config.force && (await exists(packageJsonFile))) {
            return new PluginResult({
                pluginName: "create-package-json",
                message: "package.json already exists",
                severity: "warning",
            });
        }
        const zudokuPackageJson = await fetch("https://cdn.jsdelivr.net/npm/zudoku/package.json").then((res) => res.json());
        const packageJsonContent = {
            name: "docs",
            version: "1.0.0",
            type: "module",
            private: true,
            scripts: {
                dev: "zudoku dev",
                build: "zudoku build",
                start: "zudoku start",
            },
            dependencies: {
                zudoku: zudokuPackageJson.version || "latest",
                react: zudokuPackageJson.dependencies.react || "^19.0.0",
                "react-dom": zudokuPackageJson.dependencies["react-dom"] || "^19.0.0",
            },
            devDependencies: {
                "@types/node": "^22",
                "@types/react": ">=19",
                "@types/react-dom": ">=19",
                typescript: zudokuPackageJson.devDependencies.typescript || "^5",
            },
        };
        await writeFile(packageJsonFile, JSON.stringify(packageJsonContent, null, 2));
        const result = new PluginResult();
        result.notes.push("Wrote docs/package.json");
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "create-package-json",
            message: `Failed to create package.json: ${error.message}`,
            severity: "error",
        });
    }
};
const createTsConfig = async (config) => {
    try {
        const tsConfigFile = path.join(config.absoluteBaseDir, "docs/tsconfig.json");
        if (!config.force && (await exists(tsConfigFile))) {
            return new PluginResult({
                pluginName: "create-tsconfig",
                message: "tsconfig.json already exists",
                severity: "warning",
            });
        }
        const tsConfigContent = await fetch("https://cdn.jsdelivr.net/npm/create-zudoku/dist/templates/zuplo/tsconfig.json").then((res) => res.text());
        await writeFile(tsConfigFile, tsConfigContent);
        const result = new PluginResult();
        result.notes.push("Wrote docs/tsconfig.json");
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "create-tsconfig",
            message: `Failed to create tsconfig.json: ${error.message}`,
            severity: "error",
        });
    }
};
const updatePackageJsonWorkspaces = async (config) => {
    try {
        const packageJsonFile = path.join(config.absoluteBaseDir, "package.json");
        if (!(await exists(packageJsonFile))) {
            return new PluginResult({
                pluginName: "update-package-json-workspaces",
                message: "Root package.json not found",
                severity: "warning",
            });
        }
        const packageJsonContent = JSON.parse(await readFile(packageJsonFile, "utf-8"));
        if (Array.isArray(packageJsonContent.workspaces)) {
            if (!packageJsonContent.workspaces.includes("docs")) {
                packageJsonContent.workspaces.push("docs");
            }
        }
        else if (!packageJsonContent.workspaces) {
            packageJsonContent.workspaces = ["docs"];
        }
        await writeFile(packageJsonFile, JSON.stringify(packageJsonContent, null, 2));
        const result = new PluginResult();
        result.notes.push("Updated package.json workspaces");
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "update-package-json-workspaces",
            message: `Failed to update package.json workspaces: ${error.message}`,
            severity: "error",
        });
    }
};
const moveMarkdownFiles = async (config) => {
    try {
        const docsDir = path.join(config.absoluteBaseDir, "docs");
        const pagesDir = path.join(docsDir, "pages");
        const markdownFiles = await glob(["*.md", "*.mdx"], {
            cwd: docsDir,
            absolute: false,
        });
        try {
            await mkdir(pagesDir, { recursive: true });
        }
        catch {
        }
        const result = new PluginResult();
        const movedFiles = [];
        for (const file of markdownFiles) {
            const sourcePath = path.join(docsDir, file);
            const targetPath = path.join(pagesDir, file);
            try {
                await rename(sourcePath, targetPath);
                movedFiles.push(file);
            }
            catch (error) {
                result.warnings.push({
                    message: `Failed to move ${file}: ${error.message}`,
                    pluginName: "move-markdown-files",
                    severity: "warning",
                });
            }
        }
        if (movedFiles.length > 0) {
            result.notes.push(`Moved ${movedFiles.length} markdown files to docs/pages/`);
        }
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "move-markdown-files",
            message: `Failed to move markdown files: ${error.message}`,
            severity: "error",
        });
    }
};
const cleanupOldFiles = async (config) => {
    try {
        const filesToCleanup = [
            path.join(config.absoluteBaseDir, "config/dev-portal.json"),
            path.join(config.absoluteBaseDir, "docs/sidebar.json"),
        ];
        const result = new PluginResult();
        const cleanedFiles = [];
        for (const filePath of filesToCleanup) {
            try {
                if (await exists(filePath)) {
                    await unlink(filePath);
                    cleanedFiles.push(path.relative(config.absoluteBaseDir, filePath));
                }
            }
            catch (error) {
                result.warnings.push({
                    message: `Failed to remove ${path.relative(config.absoluteBaseDir, filePath)}: ${error.message}`,
                    pluginName: "cleanup-old-files",
                    severity: "warning",
                });
            }
        }
        if (cleanedFiles.length > 0) {
            result.notes.push(`Cleaned up old files: ${cleanedFiles.join(", ")}`);
        }
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "cleanup-old-files",
            message: `Failed to cleanup old files: ${error.message}`,
            severity: "error",
        });
    }
};
const writeConfigFile = async (config, { data }) => {
    try {
        if (!data.zudokuConfig) {
            return new PluginResult({
                pluginName: "write-output-file",
                message: "Zudoku config not found",
                severity: "error",
            });
        }
        const outputFile = path.join(config.absoluteBaseDir, "docs/zudoku.config.tsx");
        const configContent = [];
        configContent.push(`// This file was generated by the Zuplo CLI migrate-dev-portal command.
// It is used to configure the Zuplo dev portal.
// You can edit this file to customize the dev portal.
// For more information, see https://zuplo.com/docs/dev-portal/zudoku/configuration/overview`);
        configContent.push(`import type { ZudokuConfig } from "zudoku";\n`);
        configContent.push(`export const config: ZudokuConfig = ${stringifyConfig(data.zudokuConfig)}`);
        await writeFile(outputFile, await format(configContent.join("\n"), { parser: "typescript" }));
        const result = new PluginResult();
        result.notes.push("Wrote docs/zudoku.config.tsx");
        return result;
    }
    catch (error) {
        return new PluginResult({
            pluginName: "write-output-file",
            message: `Failed to write output file: ${error.message}`,
            severity: "error",
        });
    }
};
//# sourceMappingURL=handler.js.map