import * as dntShim from "../../../_dnt.shims.js";
import { SEP, colors, log, path, yamlParseFile, yamlStringify, } from "../../../deps.js";
import { readLockfile, checkifMetadataUptodate, blueColor, clearGlobalLock, updateMetadataGlobalLock, LockfileGenerationError, getRawWorkspaceDependencies, } from "../../utils/metadata.js";
import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.js";
import { generateHash, getHeaders, writeIfChanged } from "../../utils/utils.js";
import { exts } from "../script/script.js";
import { FSFSElement } from "../sync/sync.js";
import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.js";
import { workspaceDependenciesLanguages } from "../../utils/script_common.js";
import { extractNameFromFolder } from "../../utils/resource_folders.js";
const TOP_HASH = "__flow_hash";
async function generateFlowHash(rawWorkspaceDependencies, folder, defaultTs) {
    const elems = await FSFSElement(path.join(dntShim.Deno.cwd(), folder), [], true);
    const hashes = {};
    for await (const f of elems.getChildren()) {
        if (exts.some((e) => f.path.endsWith(e))) {
            // Embed workspace dependencies into hash
            hashes[f.path] = await generateHash((await f.getContentText()) + JSON.stringify(rawWorkspaceDependencies));
        }
    }
    return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) };
}
export async function generateFlowLockInternal(folder, dryRun, workspace, opts, justUpdateMetadataLock, noStaleMessage) {
    if (folder.endsWith(SEP)) {
        folder = folder.substring(0, folder.length - 1);
    }
    const remote_path = extractNameFromFolder(folder.replaceAll(SEP, "/"), "flow");
    if (!justUpdateMetadataLock && !noStaleMessage) {
        log.info(`Generating lock for flow ${folder} at ${remote_path}`);
    }
    // Always get out-of-sync workspace dependencies
    const rawWorkspaceDependencies = await getRawWorkspaceDependencies();
    let hashes = await generateFlowHash(rawWorkspaceDependencies, folder, opts.defaultTs);
    const conf = await readLockfile();
    if (await checkifMetadataUptodate(folder, hashes[TOP_HASH], conf, TOP_HASH)) {
        if (!noStaleMessage) {
            log.info(colors.green(`Flow ${remote_path} metadata is up-to-date, skipping`));
        }
        return;
    }
    else if (dryRun) {
        return remote_path;
    }
    if (Object.keys(rawWorkspaceDependencies).length > 0) {
        log.info((await blueColor())(`Found workspace dependencies (${workspaceDependenciesLanguages
            .map((l) => l.filename)
            .join("/")}) for ${folder}, using them`));
    }
    const flowValue = (await yamlParseFile(folder + SEP + "flow.yaml"));
    if (!justUpdateMetadataLock) {
        const changedScripts = [];
        //find hashes that do not correspond to previous hashes
        for (const [path, hash] of Object.entries(hashes)) {
            if (path == TOP_HASH) {
                continue;
            }
            if (!(await checkifMetadataUptodate(folder, hash, conf, path))) {
                changedScripts.push(path);
            }
        }
        log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`);
        await replaceInlineScripts(flowValue.value.modules, async (path) => await dntShim.Deno.readTextFile(folder + SEP + path), log, folder + SEP, SEP, changedScripts
        // (path: string, newPath: string) => Deno.renameSync(path, newPath),
        // (path: string) => Deno.removeSync(path)
        );
        //removeChangedLocks
        flowValue.value = await updateFlow(workspace, flowValue.value, remote_path, rawWorkspaceDependencies);
        const inlineScripts = extractInlineScriptsForFlows(flowValue.value.modules, {}, SEP, opts.defaultTs);
        inlineScripts.forEach((s) => {
            writeIfChanged(dntShim.Deno.cwd() + SEP + folder + SEP + s.path, s.content);
        });
        // Overwrite `flow.yaml` with the new lockfile references
        writeIfChanged(dntShim.Deno.cwd() + SEP + folder + SEP + "flow.yaml", yamlStringify(flowValue));
    }
    hashes = await generateFlowHash(rawWorkspaceDependencies, folder, opts.defaultTs);
    await clearGlobalLock(folder);
    for (const [path, hash] of Object.entries(hashes)) {
        await updateMetadataGlobalLock(folder, hash, path);
    }
    log.info(colors.green(`Flow ${remote_path} lockfiles updated`));
}
export async function updateFlow(workspace, flow_value, remotePath, rawWorkspaceDependencies) {
    let rawResponse;
    if (Object.keys(rawWorkspaceDependencies).length > 0) {
        log.info(colors.blue("Using raw workspace dependencies for flow dependencies"));
        // generate the script lock running a dependency job in Windmill and update it inplace
        const extraHeaders = getHeaders();
        rawResponse = await fetch(`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`, {
            method: "POST",
            headers: {
                Cookie: `token=${workspace.token}`,
                "Content-Type": "application/json",
                ...extraHeaders,
            },
            body: JSON.stringify({
                flow_value,
                path: remotePath,
                use_local_lockfiles: true,
                raw_workspace_dependencies: Object.keys(rawWorkspaceDependencies).length > 0
                    ? rawWorkspaceDependencies
                    : null,
            }),
        });
    }
    else {
        // Standard dependency resolution on the server
        const extraHeaders = getHeaders();
        rawResponse = await fetch(`${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`, {
            method: "POST",
            headers: {
                Cookie: `token=${workspace.token}`,
                "Content-Type": "application/json",
                ...extraHeaders,
            },
            body: JSON.stringify({
                flow_value,
                path: remotePath,
            }),
        });
    }
    let responseText = "reading response failed";
    try {
        const res = (await rawResponse.json());
        if (rawResponse.status != 200) {
            const msg = res?.["error"]?.["message"];
            if (msg) {
                throw new LockfileGenerationError(`Failed to generate lockfile: ${msg}`);
            }
            throw new LockfileGenerationError(`Failed to generate lockfile: ${rawResponse.statusText}, ${responseText}`);
        }
        return res.updated_flow_value;
    }
    catch (e) {
        try {
            responseText = await rawResponse.text();
        }
        catch {
            responseText = "";
        }
        throw new Error(`Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}`);
    }
}
