"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TEMPLATE_REFERENCE_STATUSES = exports.MAX_TEMPLATE_REFERENCE_TOTAL_CHARS = exports.MAX_TEMPLATE_REFERENCE_CONTENT_CHARS = exports.MAX_TEMPLATE_REFERENCE_COUNT = void 0;
exports.resolveWorkflowTemplateReferences = resolveWorkflowTemplateReferences;
exports.resolveWorkflowTemplateReferencesForTemplates = resolveWorkflowTemplateReferencesForTemplates;
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
exports.MAX_TEMPLATE_REFERENCE_COUNT = 10;
exports.MAX_TEMPLATE_REFERENCE_CONTENT_CHARS = 2_000;
exports.MAX_TEMPLATE_REFERENCE_TOTAL_CHARS = 12_000;
const MAX_TEMPLATE_REFERENCE_FILE_BYTES = 64_000;
exports.TEMPLATE_REFERENCE_STATUSES = {
    resolved: "resolved",
    missingFile: "missing-file",
    missingAnchor: "missing-anchor",
    externalUrl: "external-url",
    unsupported: "unsupported",
    skippedLargeFile: "skipped-large-file",
    truncated: "truncated",
};
async function resolveWorkflowTemplateReferences(input) {
    return resolveWorkflowTemplateReferencesForTemplates({
        repoRoot: input.repoRoot,
        templates: [
            {
                templatePath: input.templatePath,
                templateBody: input.templateBody,
            },
        ],
    });
}
async function resolveWorkflowTemplateReferencesForTemplates(input) {
    const references = [];
    const seenTargets = new Set();
    for (const template of input.templates) {
        const parsed = scanTemplateLinks(template.templateBody);
        for (const candidate of parsed) {
            if (candidate.kind === "unsupported" || candidate.target === null) {
                references.push({
                    source: candidate.source,
                    status: exports.TEMPLATE_REFERENCE_STATUSES.unsupported,
                    resolvedPath: null,
                    content: null,
                    countsTowardBudget: false,
                });
                continue;
            }
            const normalizedTarget = normalizeTarget(candidate.target);
            if (seenTargets.has(normalizedTarget)) {
                continue;
            }
            seenTargets.add(normalizedTarget);
            references.push(await resolveCandidate({
                repoRoot: input.repoRoot,
                templatePath: template.templatePath,
                templateBody: template.templateBody,
            }, candidate.source, normalizedTarget));
        }
    }
    return applyReferenceBudget(references);
}
function scanTemplateLinks(body) {
    const references = [];
    const lines = body.split("\n");
    let inFence = false;
    for (const line of lines) {
        const trimmed = line.trimStart();
        if (/^(```|~~~)/u.test(trimmed)) {
            inFence = !inFence;
            continue;
        }
        if (inFence) {
            continue;
        }
        const referenceDefinition = trimmed.match(/^\[[^\]]+\]:\s+\S+/u);
        if (referenceDefinition) {
            references.push({
                source: referenceDefinition[0],
                target: null,
                kind: "unsupported",
            });
            continue;
        }
        references.push(...scanLineForLinks(line));
    }
    return references;
}
function scanLineForLinks(line) {
    const references = [];
    let index = 0;
    let inInlineCode = false;
    while (index < line.length) {
        const char = line[index];
        if (char === "`") {
            inInlineCode = !inInlineCode;
            index += 1;
            continue;
        }
        if (inInlineCode) {
            index += 1;
            continue;
        }
        const parsedReference = tryParseImageReference(line, index) ??
            tryParseBracketReference(line, index) ??
            tryParseAngleReference(line, index);
        if (parsedReference !== null) {
            references.push(...parsedReference.references);
            index = parsedReference.nextIndex;
            continue;
        }
        index += 1;
    }
    return references;
}
function tryParseImageReference(line, index) {
    if (line[index] !== "!" || line[index + 1] !== "[") {
        return null;
    }
    const parsedImage = parseBracketLink(line, index + 1);
    if (parsedImage === null) {
        return null;
    }
    return {
        references: [
            {
                source: line.slice(index, parsedImage.endIndex),
                target: null,
                kind: "unsupported",
            },
        ],
        nextIndex: parsedImage.endIndex,
    };
}
function tryParseBracketReference(line, index) {
    if (line[index] !== "[") {
        return null;
    }
    const parsedLink = parseBracketLink(line, index);
    if (parsedLink === null) {
        return null;
    }
    return {
        references: [parsedLink.reference],
        nextIndex: parsedLink.endIndex,
    };
}
function tryParseAngleReference(line, index) {
    if (line[index] !== "<") {
        return null;
    }
    const closingIndex = line.indexOf(">", index + 1);
    if (closingIndex === -1) {
        return null;
    }
    const raw = line.slice(index, closingIndex + 1);
    const inner = raw.slice(1, -1).trim();
    const nextIndex = closingIndex + 1;
    if (inner.length === 0 || /^\/[A-Za-z][A-Za-z0-9-]*$/u.test(inner)) {
        return { references: [], nextIndex };
    }
    return {
        references: [
            isAutolinkCandidate(inner)
                ? { source: inner, target: inner, kind: "supported" }
                : { source: raw, target: null, kind: "unsupported" },
        ],
        nextIndex,
    };
}
function parseBracketLink(line, startIndex) {
    const labelEnd = findClosingBracket(line, startIndex + 1, "[", "]");
    if (labelEnd === -1) {
        return null;
    }
    const nextChar = line[labelEnd + 1];
    if (nextChar === "(") {
        const targetEnd = findClosingBracket(line, labelEnd + 2, "(", ")");
        if (targetEnd === -1) {
            return null;
        }
        return {
            reference: {
                source: normalizeTarget(line.slice(labelEnd + 2, targetEnd)),
                target: line.slice(labelEnd + 2, targetEnd),
                kind: "supported",
            },
            endIndex: targetEnd + 1,
        };
    }
    if (nextChar === "[") {
        const referenceEnd = findClosingBracket(line, labelEnd + 2, "[", "]");
        if (referenceEnd === -1) {
            return null;
        }
        return {
            reference: {
                source: line.slice(startIndex, referenceEnd + 1),
                target: null,
                kind: "unsupported",
            },
            endIndex: referenceEnd + 1,
        };
    }
    return null;
}
function findClosingBracket(value, startIndex, openChar, closeChar) {
    let depth = 0;
    for (let index = startIndex; index < value.length; index += 1) {
        const char = value[index];
        if (char === "\\") {
            index += 1;
            continue;
        }
        if (char === openChar) {
            depth += 1;
            continue;
        }
        if (char === closeChar) {
            if (depth === 0) {
                return index;
            }
            depth -= 1;
        }
    }
    return -1;
}
function isAutolinkCandidate(value) {
    if (/\s/u.test(value)) {
        return false;
    }
    if (/^\/[A-Za-z][A-Za-z0-9-]*$/u.test(value)) {
        return false;
    }
    return /^(#|\/|\.{1,2}\/|[A-Za-z][A-Za-z0-9+.-]*:)/u.test(value);
}
async function resolveCandidate(input, source, normalizedTarget) {
    const parsedTarget = parseTarget(normalizedTarget);
    if (parsedTarget.kind === "external-url") {
        return {
            source,
            status: exports.TEMPLATE_REFERENCE_STATUSES.externalUrl,
            resolvedPath: null,
            content: null,
            countsTowardBudget: false,
        };
    }
    if (parsedTarget.kind === "unsupported") {
        return {
            source,
            status: exports.TEMPLATE_REFERENCE_STATUSES.unsupported,
            resolvedPath: null,
            content: null,
            countsTowardBudget: false,
        };
    }
    const absolutePath = resolveTargetPath(input, parsedTarget.filePath);
    if (absolutePath === null) {
        return {
            source,
            status: exports.TEMPLATE_REFERENCE_STATUSES.unsupported,
            resolvedPath: null,
            content: null,
            countsTowardBudget: false,
        };
    }
    let buffer;
    try {
        buffer = await (0, promises_1.readFile)(absolutePath);
    }
    catch {
        return {
            source,
            status: exports.TEMPLATE_REFERENCE_STATUSES.missingFile,
            resolvedPath: toRepoDisplayPath(input.repoRoot, absolutePath),
            content: null,
            countsTowardBudget: false,
        };
    }
    if (buffer.byteLength > MAX_TEMPLATE_REFERENCE_FILE_BYTES ||
        isBinaryBuffer(buffer)) {
        return {
            source,
            status: exports.TEMPLATE_REFERENCE_STATUSES.skippedLargeFile,
            resolvedPath: toRepoDisplayPath(input.repoRoot, absolutePath),
            content: null,
            countsTowardBudget: false,
        };
    }
    const fileContent = buffer.toString("utf8");
    const content = parsedTarget.anchor === null
        ? fileContent
        : extractAnchoredSection(fileContent, parsedTarget.anchor);
    if (content === null) {
        return {
            source,
            status: exports.TEMPLATE_REFERENCE_STATUSES.missingAnchor,
            resolvedPath: toRepoDisplayPath(input.repoRoot, absolutePath),
            content: null,
            countsTowardBudget: false,
        };
    }
    return {
        source,
        status: exports.TEMPLATE_REFERENCE_STATUSES.resolved,
        resolvedPath: toRepoDisplayPath(input.repoRoot, absolutePath),
        content: trimReferenceContent(content),
        countsTowardBudget: true,
    };
}
function normalizeTarget(value) {
    return value.trim().replace(/^<|>$/gu, "");
}
function parseTarget(value) {
    const schemeMatch = value.match(/^([A-Za-z][A-Za-z0-9+.-]*):/u);
    if (schemeMatch) {
        const scheme = schemeMatch[1]?.toLowerCase();
        if (scheme === "http" || scheme === "https") {
            return { kind: "external-url" };
        }
        return { kind: "unsupported" };
    }
    const hashIndex = value.indexOf("#");
    const filePath = hashIndex === -1 ? value : value.slice(0, hashIndex);
    const anchor = hashIndex === -1 ? null : value.slice(hashIndex + 1);
    if (filePath === "") {
        return { kind: "repo", filePath: ".", anchor };
    }
    return { kind: "repo", filePath, anchor };
}
function resolveTargetPath(input, filePath) {
    const resolved = filePath === "."
        ? input.templatePath
        : filePath.startsWith("/")
            ? node_path_1.default.resolve(input.repoRoot, `.${filePath}`)
            : node_path_1.default.resolve(node_path_1.default.dirname(input.templatePath), filePath);
    const relative = node_path_1.default.relative(input.repoRoot, resolved);
    if (relative === "" ||
        relative === "." ||
        (!relative.startsWith("..") && !node_path_1.default.isAbsolute(relative))) {
        return resolved;
    }
    return null;
}
function toRepoDisplayPath(repoRoot, absolutePath) {
    const relative = node_path_1.default
        .relative(repoRoot, absolutePath)
        .replaceAll(node_path_1.default.sep, "/");
    return relative ? `/${relative}` : "/";
}
function isBinaryBuffer(buffer) {
    for (const byte of buffer.subarray(0, Math.min(buffer.length, 512))) {
        if (byte === 0) {
            return true;
        }
    }
    return false;
}
function extractAnchoredSection(content, anchor) {
    const lines = content.split("\n");
    const slug = normalizeHeadingSlug(anchor);
    let startIndex = -1;
    let startLevel = -1;
    for (let index = 0; index < lines.length; index += 1) {
        const heading = parseHeading(lines[index] ?? "");
        if (heading === null) {
            continue;
        }
        if (heading.slug === slug) {
            startIndex = index;
            startLevel = heading.level;
            break;
        }
    }
    if (startIndex === -1) {
        return null;
    }
    let endIndex = lines.length;
    for (let index = startIndex + 1; index < lines.length; index += 1) {
        const heading = parseHeading(lines[index] ?? "");
        if (heading !== null && heading.level <= startLevel) {
            endIndex = index;
            break;
        }
    }
    return lines.slice(startIndex, endIndex).join("\n").trim();
}
function parseHeading(line) {
    const match = line.match(/^(#{1,6})\s+(.*?)\s*#*\s*$/u);
    if (!match) {
        return null;
    }
    return {
        level: (match[1] ?? "").length,
        slug: normalizeHeadingSlug(match[2] ?? ""),
    };
}
function normalizeHeadingSlug(value) {
    return value
        .trim()
        .toLowerCase()
        .replace(/[^\p{Letter}\p{Number}\s-]/gu, "")
        .replace(/\s+/gu, "-")
        .replace(/-+/gu, "-");
}
function trimReferenceContent(content) {
    const trimmed = content.trim();
    if (trimmed.length <= exports.MAX_TEMPLATE_REFERENCE_CONTENT_CHARS) {
        return trimmed;
    }
    return trimmed.slice(0, exports.MAX_TEMPLATE_REFERENCE_CONTENT_CHARS);
}
function applyReferenceBudget(references) {
    const limited = references.slice(0, exports.MAX_TEMPLATE_REFERENCE_COUNT);
    let usedChars = 0;
    return limited.map((reference) => {
        if (!reference.countsTowardBudget || reference.content === null) {
            return stripBudgetFields(reference);
        }
        if (usedChars + reference.content.length >
            exports.MAX_TEMPLATE_REFERENCE_TOTAL_CHARS) {
            return {
                source: reference.source,
                status: exports.TEMPLATE_REFERENCE_STATUSES.truncated,
                resolvedPath: reference.resolvedPath,
                content: null,
            };
        }
        usedChars += reference.content.length;
        return stripBudgetFields(reference);
    });
}
function stripBudgetFields(reference) {
    return {
        source: reference.source,
        status: reference.status,
        resolvedPath: reference.resolvedPath,
        content: reference.content,
    };
}
