import * as dntShim from "../../../_dnt.shims.js";
import { Command, SEP, WebSocketServer, express, getPort, http, log, open, yamlParseFile, } from "../../../deps.js";
import { getTypeStrFromPath } from "../../types.js";
import { ignoreF } from "../sync/sync.js";
import { requireLogin } from "../../core/auth.js";
import { resolveWorkspace } from "../../core/context.js";
import { mergeConfigWithConfigFile, readConfigFile, } from "../../core/conf.js";
import { exts, removeExtensionToPath } from "../script/script.js";
import { inferContentTypeFromFilePath } from "../../utils/script_common.js";
import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.js";
import { parseMetadataFile } from "../../utils/metadata.js";
const PORT = 3001;
async function dev(opts) {
    const workspace = await resolveWorkspace(opts);
    await requireLogin(opts);
    log.info("Started dev mode");
    const conf = await readConfigFile();
    let currentLastEdit = undefined;
    const watcher = dntShim.Deno.watchFs(".");
    const base = await dntShim.Deno.realPath(".");
    opts = await mergeConfigWithConfigFile(opts);
    const ignore = await ignoreF(opts);
    const changesTimeouts = {};
    async function watchChanges() {
        for await (const event of watcher) {
            // console.log(">>>> event", event);
            const key = event.paths.join(",");
            if (changesTimeouts[key]) {
                clearTimeout(changesTimeouts[key]);
            }
            // @ts-ignore
            changesTimeouts[key] = setTimeout(async () => {
                delete changesTimeouts[key];
                await loadPaths(event.paths);
            }, 100);
        }
    }
    const DOT_FLOW_SEP = ".flow" + SEP;
    async function loadPaths(pathsToLoad) {
        const paths = pathsToLoad.filter((path) => exts.some((ext) => path.endsWith(ext) || path.endsWith(DOT_FLOW_SEP + "flow.yaml")));
        if (paths.length == 0) {
            return;
        }
        const cpath = (await dntShim.Deno.realPath(paths[0])).replace(base + SEP, "");
        if (!ignore(cpath, false)) {
            const typ = getTypeStrFromPath(cpath);
            log.info("Detected change in " + cpath + " (" + typ + ")");
            if (typ == "flow") {
                const localPath = cpath.split(DOT_FLOW_SEP)[0] + DOT_FLOW_SEP;
                const localFlow = (await yamlParseFile(localPath + "flow.yaml"));
                await replaceInlineScripts(localFlow.value.modules, async (path) => await dntShim.Deno.readTextFile(localPath + path), log, localPath, SEP, undefined);
                currentLastEdit = {
                    type: "flow",
                    flow: localFlow,
                    uriPath: localPath,
                };
                log.info("Updated " + localPath);
                broadcastChanges(currentLastEdit);
            }
            else if (typ == "script") {
                const content = await dntShim.Deno.readTextFile(cpath);
                const splitted = cpath.split(".");
                const wmPath = splitted[0];
                const lang = inferContentTypeFromFilePath(cpath, conf.defaultTs);
                const typed = (await parseMetadataFile(removeExtensionToPath(cpath), undefined))?.payload;
                currentLastEdit = {
                    type: "script",
                    content,
                    path: wmPath,
                    language: lang,
                    tag: typed?.tag,
                    lock: typed?.lock,
                };
                log.info("Updated " + wmPath);
                broadcastChanges(currentLastEdit);
            }
        }
    }
    const connectedClients = new Set();
    // Function to send a message to all connected clients
    function broadcastChanges(lastEdit) {
        for (const client of connectedClients.values()) {
            client.send(JSON.stringify(lastEdit));
        }
    }
    async function startApp() {
        const app = express.default();
        const server = http.createServer(app);
        const wss = new WebSocketServer({ server });
        // WebSocket server event listeners
        wss.on("connection", (ws) => {
            connectedClients.add(ws);
            console.log("New client connected");
            ws.on("open", () => {
                if (currentLastEdit) {
                    broadcastChanges(currentLastEdit);
                }
            });
            ws.on("close", () => {
                connectedClients.delete(ws);
                console.log("Client disconnected");
            });
            ws.on("message", (message) => {
                let data;
                try {
                    data = JSON.parse(message.toString());
                }
                catch (e) {
                    console.log("Received invalid JSON: " + message + " " + e);
                    return;
                }
                if (data.type === "load") {
                    loadPaths([data.path]);
                }
            });
        });
        // Start the server
        const port = await getPort.default({ port: 3001 });
        const url = `${workspace.remote}dev?workspace=${workspace.workspaceId}&local=true&wm_token=${workspace.token}` +
            (port === PORT ? "" : `&port=${port}`);
        console.log(`Go to ${url}`);
        try {
            open.openApp(open.apps.browser, { arguments: [url] }).catch((error) => {
                console.error(`Failed to open browser, please navigate to ${url}, error: ${error}`);
            });
            console.log("Opened browser for you");
        }
        catch (error) {
            console.error(`Failed to open browser, please navigate to ${url}, ${error}`);
        }
        console.log("Dev server will automatically point to the last script edited locally");
        server.listen(port, () => {
            console.log(`Server listening on port ${port}`);
        });
    }
    await Promise.all([startApp(), watchChanges()]);
    console.log("Stopped dev mode");
}
const command = new Command()
    .description("Launch a dev server that will spawn a webserver with HMR")
    .option("--includes <pattern...:string>", "Filter paths givena glob pattern or path")
    // deno-lint-ignore no-explicit-any
    .action(dev);
export default command;
