import { existsSync, writeFileSync } from "node:fs";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { confirm } from "@inquirer/prompts";
import { addOperationIdsAsNecessary, BASE_TEMPLATE, detectFormatFromContent, guessFileNameFromUrl, isUrl, mergeOpenApiDocuments, parseOpenApiFileFromString, } from "@zuplo/openapi-tools";
import chokidar from "chokidar";
import { format } from "prettier";
import { logger } from "../../common/logger.js";
import { printCriticalFailureToConsoleAndExit, printDiagnosticsToConsole, printResultToConsoleAndExitGracefully, } from "../../common/output.js";
async function performMerge(argv) {
    let normalizedFilePath;
    if (isUrl(argv.source)) {
        const parsedUrl = new URL(argv.source);
        const tempDir = tmpdir();
        const guessedFileName = guessFileNameFromUrl(argv.source);
        normalizedFilePath = path.join(tempDir, guessedFileName);
        await mkdir(path.dirname(normalizedFilePath), { recursive: true });
        try {
            const response = await fetch(parsedUrl);
            if (!response.ok) {
                throw new Error(`Failed to download the remote OpenAPI file. Server responded with ${response.status} ${response.statusText}`);
            }
            const text = await response.text();
            try {
                const format = detectFormatFromContent(text);
                normalizedFilePath = `${normalizedFilePath}.${format === "json" ? "json" : "yaml"}`;
                await writeFile(normalizedFilePath, text, { flag: "w+" });
            }
            catch (err) {
                logger.error(err, "Failed to parse the remote OpenAPI file");
                throw new Error("Failed to parse the remote OpenAPI file as either JSON or YAML.", { cause: err });
            }
        }
        catch (err) {
            logger.error(err, "Failed to download the remote OpenAPI file");
            throw new Error("Failed to download the remote OpenAPI file.", {
                cause: err,
            });
        }
    }
    else {
        normalizedFilePath = path.resolve(argv.source);
        if (!existsSync(normalizedFilePath)) {
            throw new Error(`The file ${normalizedFilePath} to import does not exist.`);
        }
    }
    const rawOpenApiSpec = await readFile(normalizedFilePath);
    const extName = path.extname(normalizedFilePath);
    const fileContent = rawOpenApiSpec.toString();
    const parsedOpenApiSpec = await parseOpenApiFileFromString(extName, fileContent);
    const destinationFilePath = path.resolve(argv.destination);
    let originalDocument;
    if (!existsSync(destinationFilePath)) {
        originalDocument = (await parseOpenApiFileFromString(".json", BASE_TEMPLATE));
    }
    else {
        const existingOpenApiSpec = await readFile(destinationFilePath);
        const existingFileContent = existingOpenApiSpec.toString();
        originalDocument = (await parseOpenApiFileFromString(extName, existingFileContent));
    }
    const mergeMode = argv["merge-mode"] || "path-method";
    const prependPath = argv["prepend-path"];
    const prependServerPath = prependPath ? false : argv["server-paths"];
    const { created, merged, retained, mergedDocument } = mergeOpenApiDocuments(originalDocument, parsedOpenApiSpec, {
        mergeMode,
        prependServerPath,
        prependPath,
    });
    printDiagnosticsToConsole("This import will...");
    printDiagnosticsToConsole("");
    if (created.size > 0) {
        printDiagnosticsToConsole(`Create ${created.size} new ${created.size > 1 ? "operations" : "operation"}`);
        printDiagnosticsToConsole("");
        created.forEach((operation) => {
            printDiagnosticsToConsole(operation);
        });
        printDiagnosticsToConsole("");
    }
    if (merged.size > 0) {
        printDiagnosticsToConsole(`Merge ${merged.size} ${merged.size > 1 ? "operations" : "operation"}`);
        printDiagnosticsToConsole("");
        merged.forEach((operation) => {
            printDiagnosticsToConsole(operation);
        });
        printDiagnosticsToConsole("");
    }
    if (retained.size > 0) {
        printDiagnosticsToConsole(`Retain ${retained.size} ${retained.size > 1 ? "operations" : "operation"}`);
        printDiagnosticsToConsole("");
        retained.forEach((operation) => {
            printDiagnosticsToConsole(operation);
        });
        printDiagnosticsToConsole("");
    }
    if (argv.prompt) {
        printDiagnosticsToConsole("");
        const answer = await confirm({ message: "Proceed?", default: true });
        if (!answer) {
            throw new Error("Import cancelled.");
        }
    }
    addOperationIdsAsNecessary(mergedDocument);
    const formattedOpenApi = await format(JSON.stringify(mergedDocument), {
        parser: "json-stringify",
    });
    if (!existsSync(destinationFilePath)) {
        const dir = path.dirname(destinationFilePath);
        await mkdir(dir, { recursive: true });
    }
    writeFileSync(destinationFilePath, formattedOpenApi, {
        flag: "w",
    });
    printDiagnosticsToConsole(`Import successful. File written to ${destinationFilePath}`);
}
export async function importOpenApi(argv) {
    if (!argv.watch) {
        try {
            await performMerge(argv);
            await printResultToConsoleAndExitGracefully(`Import successful. File written to ${path.resolve(argv.destination)}`);
        }
        catch (error) {
            logger.error(error, "Merge failed");
            await printCriticalFailureToConsoleAndExit(`${error.message}`);
        }
        return;
    }
    if (isUrl(argv.source)) {
        throw new Error("Watch mode is not supported for URL sources. Please use a local file.");
    }
    const sourcePath = path.resolve(argv.source);
    if (!existsSync(sourcePath)) {
        throw new Error(`The file ${sourcePath} to import does not exist.`);
    }
    printDiagnosticsToConsole(`Starting watch mode for ${sourcePath}...`);
    printDiagnosticsToConsole("Press Ctrl+C to stop watching.\n");
    try {
        await performMerge(argv);
        printDiagnosticsToConsole("\nWatching for changes...");
    }
    catch (error) {
        logger.error(error, "Initial merge failed");
        printDiagnosticsToConsole(`Error during initial merge: ${error}`);
        printDiagnosticsToConsole("\nWatching for changes...");
    }
    const watcher = chokidar.watch(sourcePath, {
        persistent: true,
        ignoreInitial: true,
    });
    watcher.on("change", async () => {
        try {
            printDiagnosticsToConsole(`\nFile ${sourcePath} changed, re-merging...`);
            await performMerge(argv);
            printDiagnosticsToConsole("Re-merge completed successfully.\n");
        }
        catch (error) {
            logger.error(error, "Re-merge failed");
            printDiagnosticsToConsole(`Error during re-merge: ${error}\n`);
        }
    });
    watcher.on("error", (error) => {
        logger.error(error, "Watcher error");
        printDiagnosticsToConsole(`Watcher error: ${error}`);
    });
    return new Promise((resolve) => {
        process.once("SIGINT", async () => {
            printDiagnosticsToConsole("\nStopping watch mode...");
            await watcher.close();
            resolve();
        });
        process.once("SIGTERM", async () => {
            printDiagnosticsToConsole("\nStopping watch mode...");
            await watcher.close();
            resolve();
        });
    });
}
//# sourceMappingURL=handler.js.map