import fs, { readFile, readdir } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import fastifyStatic from "@fastify/static";
import chokidar from "chokidar";
import Fastify from "fastify";
import { FastifySSEPlugin } from "fastify-sse-v2";
import { captureEvent, shutdownAnalytics } from "../../common/analytics/lib.js";
import { logger } from "../../common/logger.js";
import { printDiagnosticsToConsole } from "../../common/output.js";
import { corsPlugin } from "./cors-plugin.js";
import { dirExists, fileExists } from "./xfs.js";
const POLICIES_FILENAME = "policies.json";
const CONFIG_DIR = "config";
export class ApiServer {
    fastify;
    listenerHost;
    watcher;
    sseContext;
    cliArgs;
    workingDir;
    listenerPort;
    isStandalone;
    constructor(options = {
        cliArgs: {},
        workingDir: ".",
        port: 9100,
        isStandalone: true,
    }) {
        this.cliArgs = options.cliArgs;
        this.workingDir = options.workingDir;
        this.listenerPort = options.port;
        this.isStandalone = options.isStandalone;
        this.listenerHost = "localhost";
        this.watcher = chokidar.watch(path.join(this.workingDir, CONFIG_DIR));
        this.fastify = Fastify({
            logger: logger,
            trustProxy: true,
            disableRequestLogging: true,
            bodyLimit: 104857600,
        });
    }
    init = async () => {
        const cliArgs = this.cliArgs;
        this.fastify.register(corsPlugin, { origin: true });
        this.fastify.register(FastifySSEPlugin);
        const editorRootPath = fileURLToPath(new URL("../", import.meta.url));
        this.fastify.register(fastifyStatic, {
            root: editorRootPath,
        });
        this.fastify.register((instance, _options, done) => {
            instance.get("/", {
                handler: async (_request, reply) => {
                    await captureEvent({ argv: cliArgs, event: "route-designer: /" });
                    let html = await readFile(path.join(editorRootPath, "index.html"), "utf-8");
                    html = html.replace("__SERVER_DATA__", JSON.stringify({
                        server_address: this.listenerHost,
                        server_port: this.listenerPort,
                    }));
                    reply.type("text/html").send(html);
                },
            });
            instance.get("/open-api-content", {
                handler: async (request, reply) => {
                    await captureEvent({
                        argv: cliArgs,
                        event: "route-designer: /open-api-content",
                    });
                    const { path: filePath } = request.query;
                    if (filePath === null || filePath === undefined) {
                        return reply.code(404).send("You must select a file to work with");
                    }
                    const openApiFilePath = path.join(this.workingDir, "config", filePath);
                    if (!(await fileExists(openApiFilePath))) {
                        return reply
                            .code(404)
                            .send({ message: `Cannot find file named '${filePath}'` });
                    }
                    const policiesPath = path.join(this.workingDir, "config", POLICIES_FILENAME);
                    if (!(await fileExists(policiesPath))) {
                        return reply.code(404).send({
                            message: `Cannot find a '${POLICIES_FILENAME}' file in the '${CONFIG_DIR}'`,
                        });
                    }
                    const openApiFileContent = await fs.readFile(openApiFilePath, "utf-8");
                    const policiesContent = await fs.readFile(policiesPath, "utf-8");
                    const payload = [
                        {
                            path: filePath,
                            content: JSON.parse(openApiFileContent),
                        },
                        {
                            path: POLICIES_FILENAME,
                            content: JSON.parse(policiesContent),
                        },
                    ];
                    reply.code(200).send(payload);
                },
            });
            instance.get("/open-api-files", {
                handler: async (request, reply) => {
                    await captureEvent({
                        argv: cliArgs,
                        event: "route-designer: /open-api-files",
                    });
                    const projectConfigPath = path.join(this.workingDir, CONFIG_DIR);
                    if (!(await dirExists(projectConfigPath))) {
                        request.log.warn(`Cannot find the project '${CONFIG_DIR}' folder. Make sure '${this.workingDir}' has a '${CONFIG_DIR}' folder.`);
                        return reply.code(404).send({
                            message: `Cannot find the project '${CONFIG_DIR}' folder. Make sure '${this.workingDir}' has a '${CONFIG_DIR}' folder.`,
                        });
                    }
                    const files = await readdir(projectConfigPath);
                    const openApiFiles = files.filter((fileName) => fileName.endsWith(".oas.json"));
                    reply.code(200).send(openApiFiles);
                },
            });
            instance.route({
                method: "POST",
                url: "/save-open-api-updates",
                handler: async (request, reply) => {
                    await captureEvent({
                        argv: cliArgs,
                        event: "route-designer: /save-open-api-updates",
                    });
                    const bodyContent = request.body;
                    for (const fileData of bodyContent) {
                        const openApiFilePath = path.join(this.workingDir, "config", fileData.filePath);
                        if (!(await fileExists(openApiFilePath))) {
                            reply.code(404).send({
                                message: `Cannot find file named '${fileData.filePath}'`,
                            });
                            return;
                        }
                        await fs.writeFile(openApiFilePath, JSON.stringify(fileData.content, null, 2));
                    }
                    reply.code(200).send({ message: "Changes were saved successfully" });
                },
            });
            instance.get("/updates", {
                handler: async (request, reply) => {
                    await captureEvent({
                        argv: cliArgs,
                        event: "route-designer: /updates",
                    });
                    if (this.watcher && this.watcher.closed === true) {
                        this.watcher = chokidar.watch(path.join(this.workingDir, CONFIG_DIR));
                    }
                    this.watcher.on("change", (filePath) => {
                        const eventData = JSON.stringify({ type: "update", filePath });
                        reply.sse({ id: Date.now().toString(), data: eventData });
                    });
                    request.raw.on("close", async () => {
                        await this.watcher.close();
                    });
                    reply.sse({
                        id: Date.now().toString(),
                        data: JSON.stringify({ filePath: POLICIES_FILENAME, type: "init" }),
                        retry: 10,
                    });
                    this.sseContext = reply.sseContext;
                },
            });
            instance.get("/modules-data", {
                handler: async (request, reply) => {
                    await captureEvent({
                        argv: cliArgs,
                        event: "route-designer: /modules-data",
                    });
                    const projectModulesPath = path.join(this.workingDir, "modules");
                    if (!(await dirExists(projectModulesPath))) {
                        request.log.warn(`Cannot find the project 'modules' folder. Make sure '${this.workingDir}' has a 'modules' folder.`);
                        return reply.code(200).send([]);
                    }
                    const files = await readdir(projectModulesPath);
                    const modulesFiles = files.filter((fileName) => fileName.endsWith(".ts"));
                    const results = [];
                    for (const moduleFileName of modulesFiles) {
                        const fullPath = path.join(projectModulesPath, moduleFileName);
                        const fileContent = await fs.readFile(fullPath, "utf-8");
                        const exports = [];
                        const defaultExportRegex = /(?:export\s+(default))/g;
                        if (defaultExportRegex.exec(fileContent)) {
                            exports.push({ name: "default", isHandlerInvocable: true });
                        }
                        const functionRegex = /(?:export\s+(function|class))\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
                        let match;
                        while ((match = functionRegex.exec(fileContent))) {
                            exports.push({ name: match[2], isHandlerInvocable: true });
                        }
                        results.push({
                            name: moduleFileName,
                            filepath: `./modules/${moduleFileName}`,
                            methods: exports,
                        });
                    }
                    reply.code(200).send(results);
                },
            });
            instance.get("/ping", {
                handler: async (_request, reply) => {
                    reply.code(200).send();
                },
            });
            done();
        });
    };
    start = async () => {
        await this.init();
        this.fastify.log.level = "silent";
        const start = async () => {
            return new Promise((resolve, reject) => {
                this.fastify.listen({ port: this.listenerPort, host: this.listenerHost }, (err) => {
                    if (err) {
                        reject(err);
                    }
                    this.fastify.log.level = "info";
                    if (this.isStandalone) {
                        printDiagnosticsToConsole("Started route designer");
                        printDiagnosticsToConsole("Ctrl+C to exit");
                        printDiagnosticsToConsole("");
                        printDiagnosticsToConsole(`📘 Route Designer: http://${this.listenerHost}:${this.listenerPort}`);
                        printDiagnosticsToConsole("");
                        printDiagnosticsToConsole("");
                    }
                    resolve();
                });
            });
        };
        await start();
    };
    ready = async () => {
        return this.fastify.ready();
    };
    close = async () => {
        if (this.isStandalone) {
            printDiagnosticsToConsole("Closing route designer");
        }
        try {
            void shutdownAnalytics();
            if (this.sseContext) {
                this.sseContext.source.end();
            }
            if (this.watcher) {
                await this.watcher.close();
            }
            await this.fastify.close();
        }
        catch (err) {
            console.log(err);
        }
        process.exit();
    };
}
//# sourceMappingURL=server.js.map