"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatWorkflowTemplateValidationSummary = exports.formatWorkflowTemplateValidationReport = void 0;
exports.validateWorkflowTemplateTree = validateWorkflowTemplateTree;
exports.validateWorkflowTemplateCatalog = validateWorkflowTemplateCatalog;
exports.validateWorkflowTemplateRecord = validateWorkflowTemplateRecord;
exports.validateWorkflowTemplateStructure = validateWorkflowTemplateStructure;
exports.scanWorkflowTemplateDirectives = scanWorkflowTemplateDirectives;
const types_1 = require("./types");
const workflow_template_targets_1 = require("./workflow-template-targets");
var validation_format_1 = require("./validation-format");
Object.defineProperty(exports, "formatWorkflowTemplateValidationReport", { enumerable: true, get: function () { return validation_format_1.formatWorkflowTemplateValidationReport; } });
Object.defineProperty(exports, "formatWorkflowTemplateValidationSummary", { enumerable: true, get: function () { return validation_format_1.formatWorkflowTemplateValidationSummary; } });
function validateWorkflowTemplateTree(input) {
    const diagnostics = [];
    const activeStack = [
        ...(input.activeStack ?? []),
        input.rootTemplate.templatePath,
    ];
    const currentDepth = input.currentDepth ?? 0;
    const occurrenceCountByLine = new Map();
    const scan = scanWorkflowTemplateDirectives(input.rootTemplate);
    diagnostics.push(...scan.diagnostics);
    const directChildWorkflows = [];
    for (const callSite of scan.directives) {
        const nextDepth = currentDepth + 1;
        if (activeStack.includes(callSite.templateName)) {
            diagnostics.push({
                templatePath: input.rootTemplate.templatePath,
                lineNumber: callSite.lineNumber,
                ...withSourceLineNumber(callSite.sourceLineNumber),
                code: "nested.cycle",
                referencedTemplatePath: callSite.templateName,
                cyclePath: [...activeStack, callSite.templateName],
            });
            continue;
        }
        if (nextDepth > input.maxNestedWorkflowDepth) {
            diagnostics.push({
                templatePath: input.rootTemplate.templatePath,
                lineNumber: callSite.lineNumber,
                ...withSourceLineNumber(callSite.sourceLineNumber),
                code: "nested.depth-limit",
                referencedTemplatePath: callSite.templateName,
                depth: nextDepth,
                maxDepth: input.maxNestedWorkflowDepth,
            });
            continue;
        }
        const template = input.catalog.templateByPath.get(callSite.templateName);
        if (!template) {
            diagnostics.push({
                templatePath: input.rootTemplate.templatePath,
                lineNumber: callSite.lineNumber,
                ...withSourceLineNumber(callSite.sourceLineNumber),
                code: "nested.missing-template",
                referencedTemplatePath: callSite.templateName,
            });
            continue;
        }
        if (!workflowTemplateTargetsOverlap(template, input.rootTemplate)) {
            diagnostics.push({
                templatePath: input.rootTemplate.templatePath,
                lineNumber: callSite.lineNumber,
                code: "nested.surface-mismatch",
                referencedTemplatePath: callSite.templateName,
            });
            continue;
        }
        const occurrenceIndex = (occurrenceCountByLine.get(callSite.lineNumber) ?? 0) + 1;
        occurrenceCountByLine.set(callSite.lineNumber, occurrenceIndex);
        directChildWorkflows.push({
            templateName: template.templatePath,
            callSiteId: buildWorkflowCallSiteId(input.rootTemplate.templatePath, callSite.lineNumber, occurrenceIndex),
            lineNumber: callSite.lineNumber,
            sourceLine: callSite.sourceLine,
        });
        const childResult = validateWorkflowTemplateTree({
            catalog: input.catalog,
            rootTemplate: template,
            maxNestedWorkflowDepth: input.maxNestedWorkflowDepth,
            activeStack,
            currentDepth: nextDepth,
        });
        diagnostics.push(...childResult.diagnostics);
    }
    return {
        directChildWorkflows,
        diagnostics: dedupeAndSortDiagnostics(diagnostics),
    };
}
function workflowTemplateTargetsOverlap(left, right) {
    const rightTargets = (0, workflow_template_targets_1.resolveWorkflowTemplateRecordTargets)(right);
    return (0, workflow_template_targets_1.resolveWorkflowTemplateRecordTargets)(left).some((target) => rightTargets.includes(target));
}
function validateWorkflowTemplateCatalog(input) {
    const diagnostics = [];
    for (const template of [...input.catalog.templates].sort((left, right) => left.templatePath.localeCompare(right.templatePath))) {
        diagnostics.push(...validateWorkflowTemplateRecord({
            catalog: input.catalog,
            rootTemplate: template,
            maxNestedWorkflowDepth: input.maxNestedWorkflowDepth,
        }).diagnostics);
    }
    return dedupeAndSortDiagnostics(diagnostics);
}
function validateWorkflowTemplateRecord(input) {
    const treeResult = validateWorkflowTemplateTree(input);
    return {
        directChildWorkflows: treeResult.directChildWorkflows,
        diagnostics: dedupeAndSortDiagnostics([
            ...validateReachableWorkflowTemplateStructures(input),
            ...treeResult.diagnostics,
        ]),
    };
}
function validateWorkflowTemplateStructure(template) {
    if (template.body.trim().length === 0) {
        return [
            {
                templatePath: template.templatePath,
                lineNumber: 1,
                ...withSourceLineNumber(resolveTemplateBodyStartSourceLineNumber(template)),
                code: "structure.empty-body",
            },
        ];
    }
    return [];
}
function resolveTemplateBodyStartSourceLineNumber(template) {
    return template.bodyStartLineNumber !== undefined &&
        template.bodyStartLineNumber > 1
        ? template.bodyStartLineNumber
        : undefined;
}
function withSourceLineNumber(sourceLineNumber) {
    return sourceLineNumber === undefined ? {} : { sourceLineNumber };
}
function validateReachableWorkflowTemplateStructures(input) {
    const diagnostics = [];
    const visitedTemplatePaths = new Set();
    const visit = (template, activeStack, currentDepth) => {
        if (visitedTemplatePaths.has(template.templatePath)) {
            return;
        }
        visitedTemplatePaths.add(template.templatePath);
        diagnostics.push(...validateWorkflowTemplateStructure(template));
        const nextActiveStack = [...activeStack, template.templatePath];
        for (const callSite of scanWorkflowTemplateDirectives(template)
            .directives) {
            const nextDepth = currentDepth + 1;
            if (nextActiveStack.includes(callSite.templateName) ||
                nextDepth > input.maxNestedWorkflowDepth) {
                continue;
            }
            const childTemplate = input.catalog.templateByPath.get(callSite.templateName);
            if (childTemplate === undefined) {
                continue;
            }
            visit(childTemplate, nextActiveStack, nextDepth);
        }
    };
    visit(input.rootTemplate, input.activeStack ?? [], input.currentDepth ?? 0);
    return diagnostics;
}
function scanWorkflowTemplateDirectives(template) {
    const directives = [];
    const diagnostics = [];
    const lines = template.body.split("\n");
    let inFence = false;
    const bodyStartLineNumber = template.bodyStartLineNumber ?? 1;
    const seenTemplatePaths = new Set();
    for (const [index, line] of lines.entries()) {
        const lineNumber = index + 1;
        const sourceLineNumber = bodyStartLineNumber > 1 ? bodyStartLineNumber + index : undefined;
        const trimmed = line.trim();
        if (/^(```|~~~)/u.test(trimmed)) {
            inFence = !inFence;
            continue;
        }
        if (inFence || !line.includes("@workflow")) {
            continue;
        }
        if (!/@workflow\b/u.test(line)) {
            continue;
        }
        const standaloneMatch = trimmed.match(/^@workflow(?:\s+(.+))?$/u);
        if (!standaloneMatch) {
            diagnostics.push({
                templatePath: template.templatePath,
                lineNumber,
                ...withSourceLineNumber(sourceLineNumber),
                code: "nested.invalid-inline-directive",
            });
            continue;
        }
        const rawSelector = standaloneMatch[1]?.trim() ?? "";
        if (rawSelector.length === 0) {
            diagnostics.push({
                templatePath: template.templatePath,
                lineNumber,
                ...withSourceLineNumber(sourceLineNumber),
                code: "nested.invalid-path",
                referencedTemplatePath: rawSelector,
            });
            continue;
        }
        const selectorParts = rawSelector.split(/\s+/u);
        if (selectorParts.length !== 1) {
            diagnostics.push({
                templatePath: template.templatePath,
                lineNumber,
                ...withSourceLineNumber(sourceLineNumber),
                code: "nested.invalid-trailing-text",
                ...(selectorParts[0] !== undefined
                    ? { referencedTemplatePath: selectorParts[0] }
                    : {}),
            });
            continue;
        }
        const templateName = selectorParts[0] ?? "";
        const normalizedTemplatePath = (0, types_1.normalizeWorkflowTemplatePath)(templateName);
        if (normalizedTemplatePath === null) {
            diagnostics.push({
                templatePath: template.templatePath,
                lineNumber,
                ...withSourceLineNumber(sourceLineNumber),
                code: "nested.invalid-path",
                referencedTemplatePath: templateName,
            });
            continue;
        }
        if (seenTemplatePaths.has(normalizedTemplatePath)) {
            diagnostics.push({
                templatePath: template.templatePath,
                lineNumber,
                ...withSourceLineNumber(sourceLineNumber),
                code: "nested.duplicate-template",
                referencedTemplatePath: normalizedTemplatePath,
            });
            continue;
        }
        seenTemplatePaths.add(normalizedTemplatePath);
        directives.push({
            templateName: normalizedTemplatePath,
            lineNumber,
            ...(sourceLineNumber !== undefined ? { sourceLineNumber } : {}),
            sourceLine: trimmed,
        });
    }
    return {
        directives,
        diagnostics,
    };
}
function dedupeAndSortDiagnostics(diagnostics) {
    const byKey = new Map();
    for (const diagnostic of diagnostics) {
        const key = [
            diagnostic.templatePath,
            diagnostic.lineNumber,
            diagnostic.sourceLineNumber ?? 0,
            diagnostic.columnNumber ?? 0,
            diagnostic.code,
            diagnostic.referencedTemplatePath ?? "",
            diagnostic.depth ?? "",
            diagnostic.maxDepth ?? "",
        ].join(":");
        if (!byKey.has(key)) {
            byKey.set(key, diagnostic);
        }
    }
    return [...byKey.values()].sort((left, right) => {
        const templatePathOrder = left.templatePath.localeCompare(right.templatePath);
        if (templatePathOrder !== 0) {
            return templatePathOrder;
        }
        const leftDisplayLine = left.sourceLineNumber ?? left.lineNumber;
        const rightDisplayLine = right.sourceLineNumber ?? right.lineNumber;
        if (leftDisplayLine !== rightDisplayLine) {
            return leftDisplayLine - rightDisplayLine;
        }
        if ((left.columnNumber ?? 0) !== (right.columnNumber ?? 0)) {
            return (left.columnNumber ?? 0) - (right.columnNumber ?? 0);
        }
        return left.code.localeCompare(right.code);
    });
}
function buildWorkflowCallSiteId(templateName, lineNumber, occurrenceIndex) {
    return `${templateName}:${lineNumber}:${occurrenceIndex}`;
}
