"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_WORKFLOW_TEMPLATE_UPDATE_MODE = exports.workflowTemplateTargetsSurface = exports.resolveWorkflowTemplateTargets = exports.resolveWorkflowTemplateRecordTargets = exports.WorkflowTemplateCatalogError = void 0;
exports.getWorkflowTemplatesDirectoryPath = getWorkflowTemplatesDirectoryPath;
exports.createWorkflowTemplateDigest = createWorkflowTemplateDigest;
exports.isSystemWorkflowTemplatePath = isSystemWorkflowTemplatePath;
exports.loadWorkflowTemplates = loadWorkflowTemplates;
exports.resolveWorkflowTemplateBindingSelection = resolveWorkflowTemplateBindingSelection;
exports.resolveImplicitWorkflowTemplateSelector = resolveImplicitWorkflowTemplateSelector;
exports.normalizeWorkflowTemplateSelectorForCatalog = normalizeWorkflowTemplateSelectorForCatalog;
exports.hasAutoSelectableWorkflowTemplate = hasAutoSelectableWorkflowTemplate;
exports.suggestWorkflowTemplatePaths = suggestWorkflowTemplatePaths;
exports.parseWorkflowTemplateFileName = parseWorkflowTemplateFileName;
const node_crypto_1 = require("node:crypto");
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const paths_1 = require("../core/paths");
const catalog_1 = require("../i18n/catalog");
const types_1 = require("./types");
const template_errors_1 = require("./template-errors");
const template_frontmatter_1 = require("./template-frontmatter");
const template_bindings_1 = require("./template-bindings");
const default_system_workflows_1 = require("./default-system-workflows");
const workflow_template_targets_1 = require("./workflow-template-targets");
var template_errors_2 = require("./template-errors");
Object.defineProperty(exports, "WorkflowTemplateCatalogError", { enumerable: true, get: function () { return template_errors_2.WorkflowTemplateCatalogError; } });
var workflow_template_targets_2 = require("./workflow-template-targets");
Object.defineProperty(exports, "resolveWorkflowTemplateRecordTargets", { enumerable: true, get: function () { return workflow_template_targets_2.resolveWorkflowTemplateRecordTargets; } });
Object.defineProperty(exports, "resolveWorkflowTemplateTargets", { enumerable: true, get: function () { return workflow_template_targets_2.resolveWorkflowTemplateTargets; } });
Object.defineProperty(exports, "workflowTemplateTargetsSurface", { enumerable: true, get: function () { return workflow_template_targets_2.workflowTemplateTargetsSurface; } });
const WORKFLOW_TEMPLATE_DIGEST_LENGTH = 16;
exports.DEFAULT_WORKFLOW_TEMPLATE_UPDATE_MODE = "flexible";
function getWorkflowTemplatesDirectoryPath(repoRoot) {
    return node_path_1.default.join(repoRoot, types_1.WORKFLOW_TEMPLATE_ROOT_DIRECTORY_NAME, types_1.WORKFLOW_TEMPLATE_DIRECTORY_NAME);
}
function createWorkflowTemplateDigest(source) {
    return (0, node_crypto_1.createHash)("sha256")
        .update(source, "utf8")
        .digest("hex")
        .slice(0, WORKFLOW_TEMPLATE_DIGEST_LENGTH);
}
function isSystemWorkflowTemplatePath(templatePath) {
    return templatePath.startsWith(`${paths_1.SYSTEM_WORKFLOW_TEMPLATE_DIRECTORY}/`);
}
async function loadWorkflowTemplates(workflowTemplatesDirectory, locale = catalog_1.DEFAULT_APP_LOCALE) {
    const templates = [];
    const templateByPath = new Map();
    for (const entry of await collectWorkflowTemplateSourceEntries(workflowTemplatesDirectory)) {
        const parsed = parseWorkflowTemplateFileName(entry.relativePath, locale);
        if (parsed === null) {
            continue;
        }
        if (entry.kind === "packaged-system" &&
            templateByPath.has(parsed.templatePath)) {
            continue;
        }
        const body = await (0, promises_1.readFile)(entry.filePath, "utf8");
        const parsedTemplateSource = (0, template_frontmatter_1.parseWorkflowTemplateSource)(entry.relativePath, body, locale);
        const digest = createWorkflowTemplateDigest(body);
        const targets = (0, workflow_template_targets_1.resolveWorkflowTemplateTargets)(parsedTemplateSource.metadata);
        const record = {
            templatePath: parsed.templatePath,
            fileName: node_path_1.default.posix.basename(entry.relativePath),
            filePath: entry.filePath,
            system: isSystemWorkflowTemplatePath(parsed.templatePath),
            source: entry.kind === "packaged-system" ? "packaged_system" : "repository",
            body: parsedTemplateSource.body,
            bodyStartLineNumber: parsedTemplateSource.bodyStartLineNumber,
            digest,
            targets,
            autoSelect: parsedTemplateSource.metadata?.autoSelect ??
                template_frontmatter_1.DEFAULT_WORKFLOW_TEMPLATE_AUTO_SELECT,
            updateMode: parsedTemplateSource.metadata?.updateMode ??
                exports.DEFAULT_WORKFLOW_TEMPLATE_UPDATE_MODE,
            metadata: parsedTemplateSource.metadata,
        };
        if (templateByPath.has(record.templatePath)) {
            throw new template_errors_1.WorkflowTemplateCatalogError("duplicate-template-id", (0, catalog_1.translate)(locale, "workflow.templateHealth.duplicateId", {
                templatePath: record.templatePath,
                firstFile: templateByPath.get(record.templatePath)?.filePath ?? "",
                secondFile: record.filePath,
            }), {
                templatePath: record.templatePath,
                firstFile: templateByPath.get(record.templatePath)?.filePath ?? "",
                secondFile: record.filePath,
            });
        }
        templateByPath.set(record.templatePath, record);
        templates.push(record);
    }
    return { templates, templateByPath };
}
function resolveWorkflowTemplateBindingSelection(input, locale = catalog_1.DEFAULT_APP_LOCALE) {
    if (input.command?.kind === "invalid") {
        return {
            kind: "invalid-command",
            command: input.command,
            availableTemplatePaths: getWorkflowTemplatePaths(input.catalog, input.workflowSurface),
        };
    }
    const workflowSurface = input.workflowSurface;
    const baseSelection = input.command?.kind === "update" &&
        input.command.workflowSelection !== undefined
        ? input.command.workflowSelection
        : (input.stickySelection ??
            input.defaultSelection ??
            resolveImplicitWorkflowTemplateSelector({
                catalog: input.catalog,
                ...(workflowSurface !== undefined ? { workflowSurface } : {}),
            }));
    const nextSelection = normalizeWorkflowTemplateSelectorForCatalog({
        catalog: input.catalog,
        selection: baseSelection,
        ...(workflowSurface !== undefined ? { workflowSurface } : {}),
    });
    const source = input.command?.kind === "update" &&
        input.command.workflowSelection !== undefined
        ? "command"
        : input.stickySelection
            ? "sticky"
            : input.defaultSelection
                ? "config"
                : "implicit";
    if (nextSelection.mode === "auto" || nextSelection.mode === "auto_select") {
        return {
            kind: "resolved",
            source,
            binding: (0, template_bindings_1.createWorkflowTemplateBinding)({
                selector: nextSelection,
                resolution: { kind: "none" },
            }),
            template: null,
        };
    }
    if (nextSelection.mode === "no_template") {
        const template = workflowSurface === undefined
            ? null
            : (0, default_system_workflows_1.resolveDefaultSystemWorkflowTemplate)({
                catalog: input.catalog,
                surface: workflowSurface,
            });
        if (template !== null) {
            return {
                kind: "resolved",
                source,
                binding: (0, template_bindings_1.createWorkflowTemplateBinding)({
                    selector: nextSelection,
                    resolution: (0, template_bindings_1.buildWorkflowTemplateResolution)(template),
                }),
                template,
            };
        }
        const missingTemplatePath = workflowSurface === undefined
            ? null
            : (0, default_system_workflows_1.resolveDefaultSystemWorkflowTemplatePath)(workflowSurface);
        if (missingTemplatePath !== null) {
            return {
                kind: "missing-template",
                source,
                selection: nextSelection,
                missingTemplatePath,
                availableTemplatePaths: getWorkflowTemplatePaths(input.catalog, workflowSurface),
                suggestions: suggestWorkflowTemplatePaths(input.catalog, missingTemplatePath, undefined, workflowSurface),
            };
        }
        return {
            kind: "resolved",
            source,
            binding: (0, template_bindings_1.createWorkflowTemplateBinding)({
                selector: nextSelection,
                resolution: { kind: "none" },
            }),
            template: null,
        };
    }
    if (nextSelection.templatePath.length === 0) {
        throw new Error((0, catalog_1.translate)(locale, "workflow.runContext.selection.missingTemplatePath"));
    }
    const templatePath = nextSelection.templatePath;
    const template = input.catalog.templateByPath.get(templatePath);
    if (!template || !workflowSurfaceTargetsTemplate(template, workflowSurface)) {
        return {
            kind: "missing-template",
            source,
            selection: nextSelection,
            missingTemplatePath: templatePath,
            availableTemplatePaths: getWorkflowTemplatePaths(input.catalog, workflowSurface),
            suggestions: suggestWorkflowTemplatePaths(input.catalog, templatePath, undefined, workflowSurface),
        };
    }
    const resolution = (0, template_bindings_1.buildWorkflowTemplateResolution)(template);
    return {
        kind: "resolved",
        source,
        binding: (0, template_bindings_1.createWorkflowTemplateBinding)({
            selector: nextSelection,
            resolution,
        }),
        template,
    };
}
function resolveImplicitWorkflowTemplateSelector(input) {
    const workflowSurface = input.workflowSurface;
    if (workflowSurface !== undefined &&
        !(0, template_bindings_1.isWorkflowTemplateBindingSurface)(workflowSurface)) {
        return (0, types_1.createNoTemplateWorkflowTemplateSelector)();
    }
    return hasAutoSelectableWorkflowTemplate({
        catalog: input.catalog,
        ...(workflowSurface !== undefined ? { workflowSurface } : {}),
    })
        ? (0, types_1.createAutoSelectWorkflowTemplateSelector)()
        : (0, types_1.createNoTemplateWorkflowTemplateSelector)();
}
function normalizeWorkflowTemplateSelectorForCatalog(input) {
    if (input.selection.mode !== "auto_select" ||
        hasAutoSelectableWorkflowTemplate({
            catalog: input.catalog,
            ...(input.workflowSurface !== undefined
                ? { workflowSurface: input.workflowSurface }
                : {}),
        })) {
        return input.selection;
    }
    return (0, types_1.createNoTemplateWorkflowTemplateSelector)();
}
function hasAutoSelectableWorkflowTemplate(input) {
    return input.catalog.templates.some((template) => !template.system &&
        (template.autoSelect ?? true) &&
        workflowSurfaceTargetsTemplate(template, input.workflowSurface));
}
function getWorkflowTemplatePaths(catalog, surface) {
    return [...catalog.templates]
        .filter((template) => {
        return workflowSurfaceTargetsTemplate(template, surface);
    })
        .map((template) => template.templatePath);
}
function workflowSurfaceTargetsTemplate(template, surface) {
    return (surface === undefined ||
        ((0, template_bindings_1.isWorkflowTemplateBindingSurface)(surface) &&
            (0, workflow_template_targets_1.workflowTemplateTargetsSurface)(template, surface)));
}
function suggestWorkflowTemplatePaths(catalog, query, maxSuggestions = 3, surface) {
    const candidates = getWorkflowTemplatePaths(catalog, surface);
    return [...candidates]
        .map((candidate) => ({
        candidate,
        distance: levenshteinDistance(candidate, query),
        prefixBonus: candidate.startsWith(query) ? -1 : 0,
    }))
        .sort((left, right) => {
        if (left.distance !== right.distance) {
            return left.distance - right.distance;
        }
        if (left.prefixBonus !== right.prefixBonus) {
            return left.prefixBonus - right.prefixBonus;
        }
        return left.candidate.localeCompare(right.candidate);
    })
        .slice(0, maxSuggestions)
        .map((entry) => entry.candidate);
}
function parseWorkflowTemplateFileName(relativePath, locale = catalog_1.DEFAULT_APP_LOCALE) {
    if (!relativePath.endsWith(types_1.WORKFLOW_TEMPLATE_FILE_SUFFIX)) {
        return null;
    }
    const normalizedPath = (0, types_1.normalizeWorkflowTemplatePath)(relativePath);
    if (normalizedPath === null) {
        throw new template_errors_1.WorkflowTemplateCatalogError("invalid-template-name", (0, catalog_1.translate)(locale, "workflow.templateHealth.invalidId", {
            fileName: relativePath,
            reason: (0, catalog_1.translate)(locale, "workflow.templateHealth.invalidIdentifier"),
        }), {
            fileName: relativePath,
            reasonKey: "workflow.templateHealth.invalidIdentifier",
        });
    }
    if (normalizedPath.length === 0) {
        throw new template_errors_1.WorkflowTemplateCatalogError("invalid-template-name", (0, catalog_1.translate)(locale, "workflow.templateHealth.invalidId", {
            fileName: relativePath,
            reason: (0, catalog_1.translate)(locale, "workflow.templateHealth.invalidIdentifier"),
        }), {
            fileName: relativePath,
            reasonKey: "workflow.templateHealth.invalidIdentifier",
        });
    }
    return { templatePath: normalizedPath };
}
async function collectWorkflowTemplateSourceEntries(workflowTemplatesDirectory) {
    return [
        ...(await collectRepositoryWorkflowTemplateSourceEntries(workflowTemplatesDirectory)),
        ...(await collectPackagedSystemWorkflowTemplateSourceEntries()),
    ];
}
async function collectRepositoryWorkflowTemplateSourceEntries(workflowTemplatesDirectory) {
    let filePaths;
    try {
        filePaths = await collectWorkflowTemplateFilePaths(workflowTemplatesDirectory);
    }
    catch (error) {
        if (isMissingDirectoryError(error)) {
            return [];
        }
        throw error;
    }
    return filePaths.sort().map((filePath) => ({
        kind: "repository",
        relativePath: toWorkflowTemplateSelector(workflowTemplatesDirectory, filePath),
        filePath,
    }));
}
async function collectPackagedSystemWorkflowTemplateSourceEntries() {
    const sourceDirectory = (0, paths_1.getPackageSystemWorkflowsDirectory)();
    const filePaths = await collectWorkflowTemplateFilePaths(sourceDirectory);
    if (filePaths.length === 0) {
        throw new Error(`No packaged system workflow files found in ${sourceDirectory}.`);
    }
    return filePaths.sort().map((filePath) => ({
        kind: "packaged-system",
        relativePath: node_path_1.default.posix.join(paths_1.SYSTEM_WORKFLOW_TEMPLATE_DIRECTORY, toWorkflowTemplateSelector(sourceDirectory, filePath)),
        filePath,
    }));
}
function isMissingDirectoryError(error) {
    return (error instanceof Error &&
        "code" in error &&
        error.code === "ENOENT");
}
async function collectWorkflowTemplateFilePaths(directory) {
    const entries = (await (0, promises_1.readdir)(directory, {
        withFileTypes: true,
    }));
    const filePaths = [];
    for (const entry of entries) {
        const entryPath = node_path_1.default.join(directory, entry.name);
        if (entry.isDirectory()) {
            filePaths.push(...(await collectWorkflowTemplateFilePaths(entryPath)));
            continue;
        }
        if (entry.isFile()) {
            filePaths.push(entryPath);
        }
    }
    return filePaths;
}
function toWorkflowTemplateSelector(workflowTemplatesDirectory, filePath) {
    return node_path_1.default
        .relative(workflowTemplatesDirectory, filePath)
        .split(node_path_1.default.sep)
        .join("/");
}
function levenshteinDistance(left, right) {
    const rows = Array.from({ length: left.length + 1 }, () => new Array(right.length + 1).fill(0));
    for (let i = 0; i <= left.length; i += 1) {
        const row = rows[i];
        if (row === undefined) {
            continue;
        }
        row[0] = i;
    }
    for (let j = 0; j <= right.length; j += 1) {
        const firstRow = rows[0];
        if (firstRow === undefined) {
            continue;
        }
        firstRow[j] = j;
    }
    for (let i = 1; i <= left.length; i += 1) {
        for (let j = 1; j <= right.length; j += 1) {
            const substitutionCost = left[i - 1] === right[j - 1] ? 0 : 1;
            const deletion = (rows[i - 1]?.[j] ?? 0) + 1;
            const insertion = (rows[i]?.[j - 1] ?? 0) + 1;
            const substitution = (rows[i - 1]?.[j - 1] ?? 0) + substitutionCost;
            const row = rows[i];
            if (row === undefined) {
                continue;
            }
            row[j] = Math.min(deletion, insertion, substitution);
        }
    }
    return rows[left.length]?.[right.length] ?? 0;
}
