"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadDocsCatalog = loadDocsCatalog;
exports.loadDocsCatalogFromDirectory = loadDocsCatalogFromDirectory;
exports.findDocTopic = findDocTopic;
exports.searchDocs = searchDocs;
exports.renderDocsPathList = renderDocsPathList;
exports.renderDocsIndex = renderDocsIndex;
exports.renderDocTopic = renderDocTopic;
exports.renderDocsSearchResults = renderDocsSearchResults;
exports.renderDocsSearchHeadingResults = renderDocsSearchHeadingResults;
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const yaml_1 = require("yaml");
const branding_1 = require("../core/branding");
const fs_errors_1 = require("../core/fs-errors");
const frontmatter_1 = require("../core/frontmatter");
const paths_1 = require("../core/paths");
const catalog_1 = require("../i18n/catalog");
const constants_1 = require("./constants");
const REFERENCES_DIRECTORY_NAME = "references";
const MARKDOWN_EXTENSION = ".md";
const CANONICAL_REFERENCES_PREFIX = "skills/alpaca/references/";
const REFERENCES_PREFIX = "references/";
const MARKDOWN_HEADING_PATTERN = /^(#{1,6})\s+(.+?)\s*#*\s*$/u;
const MARKDOWN_FENCE_PATTERN = /^\s*(```|~~~)/u;
const DOCS_FRONTMATTER_KEYS = new Set([
    "title",
    "description",
    "relatedCommands",
    "relatedDocs",
]);
async function loadDocsCatalog() {
    return loadDocsCatalogFromDirectory(getCanonicalDocsDirectory());
}
async function loadDocsCatalogFromDirectory(docsDirectory, rootDirectory = (0, paths_1.getPackageRoot)()) {
    let entries;
    try {
        entries = await (0, promises_1.readdir)(docsDirectory, {
            withFileTypes: true,
        });
    }
    catch (error) {
        if ((0, fs_errors_1.isMissingFileError)(error)) {
            return [];
        }
        throw error;
    }
    const markdownEntries = (await collectMarkdownEntries(docsDirectory, entries)).sort((left, right) => left.relativePath.localeCompare(right.relativePath));
    const docs = await Promise.all(markdownEntries.map(async (entry) => {
        return parseDocsFile(entry.filePath, await (0, promises_1.readFile)(entry.filePath, "utf8"), docsDirectory, rootDirectory);
    }));
    const sortedDocs = docs.sort((left, right) => left.topic.localeCompare(right.topic));
    validateRelatedDocs(sortedDocs);
    return sortedDocs;
}
async function collectMarkdownEntries(directoryPath, entries, baseDirectory = directoryPath) {
    const markdownEntries = [];
    for (const entry of entries) {
        const entryPath = node_path_1.default.join(directoryPath, entry.name);
        if (entry.isDirectory()) {
            markdownEntries.push(...(await collectMarkdownEntries(entryPath, await (0, promises_1.readdir)(entryPath, { withFileTypes: true }), baseDirectory)));
            continue;
        }
        if (entry.isFile() && entry.name.endsWith(MARKDOWN_EXTENSION)) {
            markdownEntries.push({
                filePath: entryPath,
                relativePath: formatRelativePath(entryPath, baseDirectory),
            });
        }
    }
    return markdownEntries;
}
function findDocTopic(docs, topicOrPath) {
    const lookupTopic = normalizeDocLookupValue(topicOrPath);
    return (docs.find((doc) => doc.topic === lookupTopic ||
        normalizeDocLookupValue(doc.path) === lookupTopic) ?? null);
}
function searchDocs(docs, query, context = constants_1.DEFAULT_DOCS_SEARCH_CONTEXT) {
    const searchTerms = normalizeSearchTerms(query);
    if (searchTerms.length === 0) {
        return [];
    }
    const normalizedContext = Math.max(0, context);
    const results = [];
    for (const doc of docs) {
        const lines = buildDocsSearchSourceLines(doc);
        if (!documentMatchesSearchTerms(lines, searchTerms)) {
            continue;
        }
        const windows = [];
        const matchedHeadingSet = new Set();
        for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
            const line = lines[lineIndex];
            if (line === undefined) {
                continue;
            }
            if (!lineMatchesAnySearchTerm(line.text, searchTerms)) {
                continue;
            }
            windows.push({
                start: Math.max(0, lineIndex - normalizedContext),
                end: Math.min(lines.length - 1, lineIndex + normalizedContext),
            });
            if (line.headingTrail.length > 0) {
                matchedHeadingSet.add(line.headingTrail.join(" > "));
            }
        }
        if (windows.length === 0) {
            continue;
        }
        results.push({
            doc,
            matchedHeadings: [...matchedHeadingSet],
            blocks: mergeContextWindows(windows).map((window) => {
                const blockLines = lines.slice(window.start, window.end + 1);
                return {
                    path: doc.path,
                    startLineNumber: lines[window.start]?.lineNumber ?? 1,
                    headings: collectMatchedHeadings(blockLines, searchTerms),
                    lines: blockLines.map((line) => ({
                        lineNumber: line.lineNumber,
                        text: line.text,
                        matched: lineMatchesAnySearchTerm(line.text, searchTerms),
                    })),
                };
            }),
        });
    }
    return results;
}
function renderDocsPathList(docs, locale) {
    if (docs.length === 0) {
        return (0, catalog_1.translate)(locale, "docs.command.empty");
    }
    return docs.map((doc) => doc.path).join("\n");
}
function renderDocsIndex(docs, locale) {
    if (docs.length === 0) {
        return (0, catalog_1.translate)(locale, "docs.command.empty");
    }
    return ["Docs:", ...docs.flatMap(renderDocsIndexEntry)].join("\n");
}
function renderDocsIndexEntry(doc) {
    return [
        `Topic: ${doc.topic}`,
        `Title: ${doc.title}`,
        `Description: ${doc.description}`,
        `Path: ${doc.path}`,
        "",
    ];
}
function renderDocTopic(doc) {
    const metadataLines = [
        `Topic: ${doc.topic}`,
        `Title: ${doc.title}`,
        `Description: ${doc.description}`,
        `Path: ${doc.path}`,
        ...(doc.relatedCommands.length > 0
            ? [
                "Related commands:",
                ...doc.relatedCommands.map((entry) => `  ${entry}`),
            ]
            : []),
        ...(doc.relatedDocs.length > 0
            ? [
                "Related docs:",
                ...doc.relatedDocs.map((entry) => `  ${branding_1.CLI_COMMAND_NAME} docs show ${entry}`),
            ]
            : []),
    ];
    return [...metadataLines, "", doc.body.trim()].join("\n");
}
function renderDocsSearchResults(results, locale) {
    return renderDocsSearchResultsWith(results, locale, renderDocsSearchResult);
}
function renderDocsSearchHeadingResults(results, locale) {
    return renderDocsSearchResultsWith(results, locale, renderDocsSearchHeadingResult);
}
function renderDocsSearchResultsWith(results, locale, render) {
    if (results.length === 0) {
        return (0, catalog_1.translate)(locale, "docs.search.empty");
    }
    return results.map(render).join("\n\n");
}
function renderDocsSearchResult(result) {
    return [
        ...renderDocsSearchResultHeader(result),
        "",
        "Matches:",
        ...result.blocks.flatMap(renderDocsSearchBlock),
    ].join("\n");
}
function renderDocsSearchBlock(block) {
    return [
        `  ${block.path}:${block.startLineNumber}`,
        ...(block.headings.length > 0
            ? [`  Heading: ${block.headings.join("; ")}`]
            : []),
        ...block.lines.map((line) => `${line.matched ? "  > " : "    "}${line.lineNumber}: ${line.text}`),
    ];
}
function renderDocsSearchHeadingResult(result) {
    return [
        ...renderDocsSearchResultHeader(result),
        "",
        "Headings:",
        ...renderDocsHeadings(getDocHeadings(result.doc)),
    ].join("\n");
}
function renderDocsSearchResultHeader(result) {
    return [
        `${result.doc.topic} - ${result.doc.title}`,
        `Description: ${result.doc.description}`,
        `Topic: ${result.doc.topic}`,
        `Path: ${result.doc.path}`,
        `Matched headings: ${result.matchedHeadings.length > 0
            ? result.matchedHeadings.join("; ")
            : "(none)"}`,
    ];
}
function renderDocsHeadings(headings) {
    if (headings.length === 0) {
        return ["  (none)"];
    }
    return headings.map((heading) => `  ${heading.lineNumber}: ${"#".repeat(heading.level)} ${heading.text}`);
}
function parseDocsFile(filePath, source, docsDirectory, rootDirectory) {
    const frontmatter = (0, frontmatter_1.parseFrontmatterBlock)(source, {
        closingDelimiter: "trimmed",
    });
    const relativePath = formatRelativePath(filePath, rootDirectory);
    if (frontmatter.kind === "missing-closing-delimiter") {
        throw new Error(`Docs file frontmatter is missing closing delimiter: ${relativePath}`);
    }
    if (frontmatter.kind !== "frontmatter") {
        throw new Error(`Docs file requires YAML frontmatter: ${relativePath}`);
    }
    const metadata = parseDocsMetadata(frontmatter.lines.join("\n"), relativePath);
    const docsRelativePath = formatRelativePath(filePath, docsDirectory);
    return {
        topic: docsRelativePath.slice(0, -MARKDOWN_EXTENSION.length),
        title: metadata.title,
        description: metadata.description,
        relatedCommands: metadata.relatedCommands,
        relatedDocs: metadata.relatedDocs,
        path: relativePath,
        body: frontmatter.body,
        bodyStartLineNumber: frontmatter.bodyStartLineNumber,
        frontmatterLines: frontmatter.lines,
    };
}
function parseDocsMetadata(source, relativePath) {
    const document = (0, yaml_1.parseDocument)(source, { strict: false });
    if (document.errors.length > 0) {
        throw new Error(`Invalid docs frontmatter: ${relativePath}`);
    }
    const parsed = document.toJSON();
    if (!isParsedDocsFrontmatter(parsed)) {
        throw new Error(`Invalid docs frontmatter: ${relativePath}`);
    }
    assertKnownMetadataKeys(parsed, relativePath);
    return {
        title: requireMetadataString(parsed.title, "title", relativePath),
        description: requireMetadataString(parsed.description, "description", relativePath),
        relatedCommands: parseMetadataStringArray(parsed.relatedCommands, "relatedCommands", relativePath),
        relatedDocs: parseMetadataStringArray(parsed.relatedDocs, "relatedDocs", relativePath).map(normalizeDocLookupValue),
    };
}
function isParsedDocsFrontmatter(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function assertKnownMetadataKeys(value, relativePath) {
    for (const key of Object.keys(value)) {
        if (!DOCS_FRONTMATTER_KEYS.has(key)) {
            throw new Error(`Unknown docs frontmatter field "${key}": ${relativePath}`);
        }
    }
}
function requireMetadataString(value, key, relativePath) {
    if (typeof value !== "string" || value.trim().length === 0) {
        throw new Error(`Docs frontmatter ${key} is required: ${relativePath}`);
    }
    return value.trim();
}
function parseMetadataStringArray(value, key, relativePath) {
    if (value === undefined) {
        return [];
    }
    if (!Array.isArray(value)) {
        throw new Error(`Docs frontmatter ${key} must be a string array: ${relativePath}`);
    }
    return value.map((entry) => {
        if (typeof entry !== "string" || entry.trim().length === 0) {
            throw new Error(`Docs frontmatter ${key} must be a string array: ${relativePath}`);
        }
        return entry.trim();
    });
}
function getCanonicalDocsDirectory() {
    return node_path_1.default.join((0, paths_1.getCanonicalSkillDirectory)(), REFERENCES_DIRECTORY_NAME);
}
function validateRelatedDocs(docs) {
    const topics = new Set(docs.map((doc) => doc.topic));
    for (const doc of docs) {
        for (const relatedDoc of doc.relatedDocs) {
            if (!topics.has(relatedDoc)) {
                throw new Error(`Docs frontmatter relatedDocs references unknown topic "${relatedDoc}": ${doc.path}`);
            }
        }
    }
}
function formatRelativePath(filePath, rootDirectory) {
    return node_path_1.default.relative(rootDirectory, filePath).split(node_path_1.default.sep).join("/");
}
function normalizeDocLookupValue(value) {
    const normalizedPath = value
        .split(node_path_1.default.sep)
        .join("/")
        .replace(/^\.?\//u, "");
    const canonicalPrefixIndex = normalizedPath.indexOf(CANONICAL_REFERENCES_PREFIX);
    const referencesRelativePath = canonicalPrefixIndex >= 0
        ? normalizedPath.slice(canonicalPrefixIndex + CANONICAL_REFERENCES_PREFIX.length)
        : normalizedPath.startsWith(REFERENCES_PREFIX)
            ? normalizedPath.slice(REFERENCES_PREFIX.length)
            : normalizedPath;
    return referencesRelativePath.endsWith(MARKDOWN_EXTENSION)
        ? referencesRelativePath.slice(0, -MARKDOWN_EXTENSION.length)
        : referencesRelativePath;
}
function splitMarkdownLines(text) {
    if (text.length === 0) {
        return [];
    }
    const lines = text.split(/\r?\n/u);
    if (lines[lines.length - 1] === "") {
        lines.pop();
    }
    return lines;
}
function buildDocsSearchSourceLines(doc) {
    const frontmatterLines = doc.frontmatterLines.map((text, index) => ({
        lineNumber: index + 2,
        text,
        headingTrail: ["Metadata"],
    }));
    const bodyLines = splitMarkdownLines(doc.body);
    const headingTrails = buildHeadingTrailsByLine(bodyLines);
    return [
        ...frontmatterLines,
        ...bodyLines.map((text, index) => ({
            lineNumber: doc.bodyStartLineNumber + index,
            text,
            headingTrail: headingTrails[index] ?? [],
        })),
    ];
}
function normalizeSearchTerms(query) {
    return tokenizeSearchText(query);
}
function documentMatchesSearchTerms(lines, searchTerms) {
    const tokens = tokenizeSearchText(lines.map((line) => line.text).join("\n"));
    return searchTerms.every((term) => tokens.some((token) => tokenMatchesTerm(token, term)));
}
function lineMatchesAnySearchTerm(line, searchTerms) {
    const tokens = tokenizeSearchText(line);
    return searchTerms.some((term) => tokens.some((token) => tokenMatchesTerm(token, term)));
}
function collectMatchedHeadings(lines, searchTerms) {
    const headings = new Set();
    for (const line of lines) {
        if (line.headingTrail.length > 0 &&
            lineMatchesAnySearchTerm(line.text, searchTerms)) {
            headings.add(line.headingTrail.join(" > "));
        }
    }
    return [...headings];
}
function tokenizeSearchText(text) {
    return text
        .replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2")
        .toLowerCase()
        .split(/[^a-z0-9]+/u)
        .filter((token) => token.length > 0);
}
function tokenMatchesTerm(token, term) {
    return token === term || token === `${term}s` || `${token}s` === term;
}
function buildHeadingTrailsByLine(lines) {
    const headingByLineIndex = new Map(collectMarkdownHeadingEntries(lines).map((heading) => [
        heading.lineIndex,
        heading,
    ]));
    const headingStack = [];
    const headingTrails = [];
    for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
        const heading = headingByLineIndex.get(lineIndex);
        if (heading !== undefined) {
            while (headingStack.length > 0 &&
                (headingStack[headingStack.length - 1]?.level ?? 0) >= heading.level) {
                headingStack.pop();
            }
            headingStack.push(heading);
        }
        headingTrails.push(headingStack.map((entry) => entry.title));
    }
    return headingTrails;
}
function getDocHeadings(doc) {
    const lines = splitMarkdownLines(doc.body);
    return collectMarkdownHeadingEntries(lines).map((heading) => ({
        lineNumber: doc.bodyStartLineNumber + heading.lineIndex,
        level: heading.level,
        text: heading.title,
    }));
}
function collectMarkdownHeadingEntries(lines) {
    const headings = [];
    let insideCodeFence = false;
    for (let index = 0; index < lines.length; index += 1) {
        const line = lines[index] ?? "";
        if (MARKDOWN_FENCE_PATTERN.test(line)) {
            insideCodeFence = !insideCodeFence;
            continue;
        }
        const heading = insideCodeFence ? null : parseMarkdownHeading(line);
        if (heading === null) {
            continue;
        }
        headings.push({
            lineIndex: index,
            level: heading.level,
            title: heading.title,
        });
    }
    return headings;
}
function parseMarkdownHeading(line) {
    const match = MARKDOWN_HEADING_PATTERN.exec(line);
    if (match === null) {
        return null;
    }
    const marker = match[1];
    const title = match[2]?.trim();
    if (!marker || !title) {
        return null;
    }
    return {
        level: marker.length,
        title,
    };
}
function mergeContextWindows(windows) {
    if (windows.length === 0) {
        return [];
    }
    const sortedWindows = [...windows].sort((left, right) => left.start - right.start || left.end - right.end);
    const mergedWindows = [];
    for (const window of sortedWindows) {
        const previous = mergedWindows[mergedWindows.length - 1];
        if (previous && window.start <= previous.end + 1) {
            previous.end = Math.max(previous.end, window.end);
            continue;
        }
        mergedWindows.push({ ...window });
    }
    return mergedWindows;
}
