"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.discoverValidationLinksArtifacts = discoverValidationLinksArtifacts;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const output_1 = require("../output");
const paths_1 = require("../paths");
const planning_document_utils_1 = require("./planning-document-utils");
const planning_artifact_document_1 = require("./planning-artifact-document");
const SPEC_INDEX_PATH = ".day-shift/specs/index.json";
function discoverValidationLinksArtifacts(options) {
    const artifactPath = options.artifactPath ?? null;
    const planningRootPath = normalizeDiscoveryRootPath(options.planningRootPath, ".day-shift/planning");
    const specIndexPath = normalizeDiscoveryRootPath(options.specIndexPath, SPEC_INDEX_PATH);
    const discoveryArtifacts = artifactPath === null
        ? discoverRepositoryArtifacts(options.repositoryRoot, {
            planningRootPath,
            specIndexPath
        })
        : discoverFocusedArtifact(options.repositoryRoot, artifactPath);
    const evidence = discoveryArtifacts
        .filter((artifact) => artifact.parseStatus !== "parsed")
        .map((artifact) => createDiscoveryEvidence(artifact));
    return {
        mode: artifactPath === null ? "repository" : "artifact",
        repositoryRoot: options.repositoryRoot,
        artifact: artifactPath,
        traversalBoundaries: output_1.TRACE_LINK_TRAVERSAL_BOUNDARIES,
        artifacts: discoveryArtifacts,
        evidence,
        summary: summarizeDiscovery(discoveryArtifacts, evidence)
    };
}
function discoverRepositoryArtifacts(repositoryRoot, roots) {
    const artifactPaths = [
        ...collectMarkdownArtifactPaths(repositoryRoot, ".day-shift/specs"),
        roots.specIndexPath,
        ...collectMarkdownArtifactPaths(repositoryRoot, roots.planningRootPath)
    ];
    return artifactPaths.flatMap((artifactPath) => discoverArtifactPath(repositoryRoot, artifactPath));
}
function discoverFocusedArtifact(repositoryRoot, artifactPath) {
    if (!isInsideTraversalBoundary(artifactPath)) {
        return discoverArtifactPath(repositoryRoot, artifactPath);
    }
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, artifactPath);
    try {
        if (node_fs_1.default.existsSync(absolutePath) && node_fs_1.default.statSync(absolutePath).isDirectory()) {
            return collectMarkdownArtifactPaths(repositoryRoot, artifactPath)
                .flatMap((candidatePath) => discoverArtifactPath(repositoryRoot, candidatePath));
        }
    }
    catch (error) {
        return [
            createUnsupportedArtifact({
                path: artifactPath,
                parseStatus: "unreadable",
                parseMessage: error instanceof Error ? error.message : "Artifact directory could not be read."
            })
        ];
    }
    return discoverArtifactPath(repositoryRoot, artifactPath);
}
function discoverArtifactPath(repositoryRoot, artifactPath) {
    if (!isInsideTraversalBoundary(artifactPath)) {
        return [
            createUnsupportedArtifact({
                path: artifactPath,
                parseStatus: "outside-coverage",
                parseMessage: "Artifact path is outside declared links-check traversal boundaries."
            })
        ];
    }
    const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, artifactPath);
    if (!node_fs_1.default.existsSync(absolutePath)) {
        return [
            createUnsupportedArtifact({
                path: artifactPath,
                parseStatus: "missing",
                parseMessage: "Artifact path does not exist locally."
            })
        ];
    }
    if (artifactPath === SPEC_INDEX_PATH || artifactPath.endsWith("/index.json")) {
        return discoverSpecIndexEntries(absolutePath, artifactPath);
    }
    if (!artifactPath.endsWith(".md")) {
        return [
            createUnsupportedArtifact({
                path: artifactPath,
                parseStatus: "unsupported",
                parseMessage: "Artifact extension is outside links-check coverage."
            })
        ];
    }
    return [discoverMarkdownArtifact(repositoryRoot, absolutePath, artifactPath)];
}
function discoverMarkdownArtifact(repositoryRoot, absolutePath, artifactPath) {
    let markdownText;
    try {
        markdownText = node_fs_1.default.readFileSync(absolutePath, "utf8");
    }
    catch (error) {
        return createUnsupportedArtifact({
            path: artifactPath,
            parseStatus: "unreadable",
            parseMessage: error instanceof Error ? error.message : "Artifact could not be read."
        });
    }
    const document = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(markdownText);
    if (!document.hasFrontMatter) {
        return createUnsupportedArtifact({
            path: artifactPath,
            parseStatus: "malformed",
            parseMessage: "Markdown artifact is missing closed YAML front matter."
        });
    }
    const artifactType = document.frontMatterFields.artifact_type?.trim() ?? null;
    const coverage = findCoverageByArtifactType(artifactType);
    if (coverage === null) {
        return createUnsupportedArtifact({
            path: artifactPath,
            artifactType,
            parseStatus: "unsupported",
            parseMessage: `Artifact type '${artifactType ?? "(missing)"}' is outside links-check coverage.`
        });
    }
    const planningDepth = document.frontMatterFields.planning_depth?.trim() ?? null;
    return {
        path: artifactPath,
        family: coverage.family,
        artifactType,
        parseStatus: "parsed",
        parseMessage: "Artifact parsed with declared trace-field coverage.",
        coverageSource: "traceable-artifact-coverage",
        traceFields: coverage.traceFields
            .filter((field) => isTraceFieldApplicable({
            artifactType,
            planningDepth,
            field
        }))
            .map((field) => discoverTraceField(field, document.frontMatterLines, markdownText, repositoryRoot))
    };
}
function isTraceFieldApplicable(options) {
    if (options.planningDepth === "lightweight" &&
        (options.artifactType === "task_definition" ||
            options.artifactType === "implementation_summary") &&
        (options.field.field === "upstream.phase_overview" ||
            options.field.field === "upstream.milestone_overview")) {
        return false;
    }
    return true;
}
function discoverSpecIndexEntries(indexAbsolutePath, specIndexPath) {
    let indexText;
    try {
        indexText = node_fs_1.default.readFileSync(indexAbsolutePath, "utf8");
    }
    catch (error) {
        return [
            createUnsupportedArtifact({
                path: specIndexPath,
                parseStatus: "unreadable",
                parseMessage: error instanceof Error ? error.message : "Spec index could not be read."
            })
        ];
    }
    let parsedIndex;
    try {
        parsedIndex = JSON.parse(indexText);
    }
    catch (error) {
        return [
            createUnsupportedArtifact({
                path: specIndexPath,
                parseStatus: "malformed",
                parseMessage: error instanceof Error ? error.message : "Spec index JSON is malformed."
            })
        ];
    }
    if (!isRecord(parsedIndex) || !Array.isArray(parsedIndex.specs)) {
        return [
            createUnsupportedArtifact({
                path: specIndexPath,
                parseStatus: "malformed",
                parseMessage: "Spec index JSON must contain a specs array."
            })
        ];
    }
    const coverage = findCoverageByFamily("spec_index_entry");
    if (coverage === null) {
        return [];
    }
    return parsedIndex.specs.map((entry, index) => {
        if (!isRecord(entry)) {
            return createUnsupportedArtifact({
                path: `${specIndexPath}#specs[${index}]`,
                parseStatus: "malformed",
                parseMessage: "Spec index entry must be a JSON object."
            });
        }
        return {
            path: `${specIndexPath}#specs[${index}]`,
            family: "spec_index_entry",
            artifactType: null,
            parseStatus: "parsed",
            parseMessage: "Spec index entry parsed with declared trace-field coverage.",
            coverageSource: "traceable-artifact-coverage",
            traceFields: coverage.traceFields.map((field) => discoverSpecIndexTraceField(field, entry))
        };
    });
}
function normalizeDiscoveryRootPath(input, fallback) {
    if (typeof input !== "string") {
        return fallback;
    }
    const trimmed = input.trim();
    return trimmed === "" ? fallback : (0, paths_1.parseRepositoryRelativePath)(trimmed).normalizedPath;
}
function discoverTraceField(field, frontMatterLines, markdownText, repositoryRoot) {
    if (field.source === "body-section") {
        const bodyReferenceOccurrences = readBodySectionReferenceOccurrences(markdownText, field.field, repositoryRoot);
        const values = uniqueValues(bodyReferenceOccurrences.map((occurrence) => occurrence.value));
        return {
            field: field.field,
            source: field.source,
            requirement: field.requirement,
            valueShape: field.valueShape,
            relationship: field.relationship,
            notes: field.notes,
            present: values.length > 0,
            values,
            bodyReferenceOccurrences
        };
    }
    const values = readFrontMatterPathValues(frontMatterLines, field.field);
    return {
        field: field.field,
        source: field.source,
        requirement: field.requirement,
        valueShape: field.valueShape,
        relationship: field.relationship,
        notes: field.notes,
        present: values.length > 0,
        values
    };
}
function discoverSpecIndexTraceField(field, entry) {
    const values = readObjectPathValues(entry, field.field);
    return {
        field: field.field,
        source: field.source,
        requirement: field.requirement,
        valueShape: field.valueShape,
        relationship: field.relationship,
        notes: field.notes,
        present: values.length > 0,
        values
    };
}
function readFrontMatterPathValues(frontMatterLines, fieldPath) {
    const parts = fieldPath.split(".");
    const value = readNestedFrontMatterValue(frontMatterLines, parts);
    if (value === undefined || value === null || value === "") {
        return [];
    }
    if (Array.isArray(value)) {
        return value;
    }
    return [value];
}
function readNestedFrontMatterValue(frontMatterLines, parts) {
    if (parts.length === 1) {
        return (0, planning_document_utils_1.readTopLevelField)(frontMatterLines, parts[0]);
    }
    if (parts[0] === "upstream" && parts[1] === "specs") {
        return readFrontMatterObjectList(frontMatterLines, "upstream", "specs");
    }
    if (parts[0] === "upstream" && parts.length === 2) {
        return readNestedScalar(frontMatterLines, "upstream", parts[1]);
    }
    if (parts[0] === "source" && parts.length === 2) {
        return readNestedScalar(frontMatterLines, "source", parts[1]);
    }
    return undefined;
}
function readNestedScalar(frontMatterLines, parentKey, childKey) {
    let inParent = false;
    for (const line of frontMatterLines) {
        if (line === `${parentKey}:`) {
            inParent = true;
            continue;
        }
        if (!inParent) {
            continue;
        }
        if (/^[^\s]/.test(line)) {
            break;
        }
        const match = line.match(/^\s{2}([A-Za-z0-9_]+):\s*(.*)$/);
        if (match !== null && match[1] === childKey) {
            return (0, planning_document_utils_1.parseScalar)(match[2]);
        }
    }
    return undefined;
}
function readFrontMatterObjectList(frontMatterLines, parentKey, listKey) {
    let inParent = false;
    let inList = false;
    const values = [];
    let current = null;
    for (const line of frontMatterLines) {
        if (line === `${parentKey}:`) {
            inParent = true;
            continue;
        }
        if (!inParent) {
            continue;
        }
        if (/^[^\s]/.test(line)) {
            break;
        }
        if (line.match(new RegExp(`^\\s{2}${listKey}:\\s*$`)) !== null) {
            inList = true;
            continue;
        }
        if (!inList) {
            continue;
        }
        const itemStartMatch = line.match(/^\s{4}-\s+([A-Za-z0-9_]+):\s*(.*)$/);
        if (itemStartMatch !== null) {
            current = { [itemStartMatch[1]]: (0, planning_document_utils_1.parseScalar)(itemStartMatch[2]) };
            values.push(current);
            continue;
        }
        const itemFieldMatch = line.match(/^\s{6}([A-Za-z0-9_]+):\s*(.*)$/);
        if (itemFieldMatch !== null && current !== null) {
            current[itemFieldMatch[1]] = (0, planning_document_utils_1.parseScalar)(itemFieldMatch[2]);
        }
    }
    return values;
}
function readBodySectionReferences(markdownText, fieldPath, repositoryRoot) {
    return uniqueValues(readBodySectionReferenceOccurrences(markdownText, fieldPath, repositoryRoot).map((occurrence) => occurrence.value));
}
function readBodySectionReferenceOccurrences(markdownText, fieldPath, repositoryRoot) {
    const sectionName = fieldPath.replace(/^body\./, "");
    const section = extractSectionTextWithOffset(markdownText, sectionName);
    const sectionText = section.text;
    if (sectionText.trim() === "") {
        return [];
    }
    const references = [];
    if (isDeclaredChildSectionField(fieldPath)) {
        return readDeclaredChildReferenceOccurrences(markdownText, section, sectionName);
    }
    const referencePattern = /(?<!`)`([^`\n]+)`(?!`)|(?:^|[\s(])([^\s`()\[\]<>]+\/[^\s`()\[\]<>]*[A-Za-z0-9_~/#@!$&'*+=%-])/gm;
    let match;
    while ((match = referencePattern.exec(sectionText)) !== null) {
        const value = (match[1] ?? match[2] ?? "").trim();
        if (value === "" || !isPathLikeRepositoryReference(value, repositoryRoot)) {
            continue;
        }
        const format = match[1] === undefined ? "plain" : "backticked";
        const matchedText = format === "backticked" ? `\`${match[1] ?? ""}\`` : value;
        const relativeStart = format === "backticked"
            ? match.index + match[0].indexOf("`")
            : match.index + match[0].lastIndexOf(value);
        const startOffset = section.startOffset + relativeStart;
        const endOffset = startOffset + matchedText.length;
        const location = offsetToLineColumn(markdownText, startOffset);
        references.push({
            value,
            rawText: matchedText,
            sectionName,
            format,
            startOffset,
            endOffset,
            line: location.line,
            column: location.column,
            occurrenceIndex: references.length
        });
    }
    return references;
}
function isPathLikeRepositoryReference(value, repositoryRoot) {
    if (/\s/u.test(value)) {
        return false;
    }
    try {
        const parsed = (0, paths_1.parseRepositoryRelativePath)(value);
        if (parsed.segments.length <= 1) {
            return false;
        }
        const firstSegment = parsed.segments[0];
        if (isKnownTraceRootSegment(firstSegment)) {
            return true;
        }
        return node_fs_1.default.existsSync(node_path_1.default.join(repositoryRoot, firstSegment));
    }
    catch {
        return false;
    }
}
function isKnownTraceRootSegment(segment) {
    return segment === ".day-shift" ||
        segment === "src" ||
        segment === "tests" ||
        segment === "tests_v2";
}
function readDeclaredChildReferenceOccurrences(markdownText, section, sectionName) {
    const references = [];
    const referencePattern = /^(\s*-\s*)`([^`\n]+)`/gm;
    let match;
    while ((match = referencePattern.exec(section.text)) !== null) {
        const value = (match[2] ?? "").trim();
        if (value === "") {
            continue;
        }
        const rawText = `\`${match[2] ?? ""}\``;
        const relativeStart = match.index + (match[1]?.length ?? 0);
        const startOffset = section.startOffset + relativeStart;
        const endOffset = startOffset + rawText.length;
        const location = offsetToLineColumn(markdownText, startOffset);
        references.push({
            value,
            rawText,
            sectionName,
            format: "backticked",
            startOffset,
            endOffset,
            line: location.line,
            column: location.column,
            occurrenceIndex: references.length
        });
    }
    return references;
}
function isDeclaredChildSectionField(fieldPath) {
    return fieldPath === "body.Phases" ||
        fieldPath === "body.Milestones" ||
        fieldPath === "body.Tasks";
}
function readObjectPathValues(record, fieldPath) {
    const value = fieldPath.split(".").reduce((current, key) => {
        if (!isRecord(current)) {
            return undefined;
        }
        return current[key];
    }, record);
    if (value === undefined || value === null || value === "") {
        return [];
    }
    return [value];
}
function collectMarkdownArtifactPaths(repositoryRoot, traversalRoot) {
    const absoluteRoot = (0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, traversalRoot);
    if (!node_fs_1.default.existsSync(absoluteRoot)) {
        return [];
    }
    const paths = [];
    const stack = [absoluteRoot];
    while (stack.length > 0) {
        const currentPath = stack.pop();
        if (currentPath === undefined) {
            continue;
        }
        let stats;
        try {
            stats = node_fs_1.default.statSync(currentPath);
        }
        catch {
            continue;
        }
        if (stats.isFile()) {
            if (currentPath.endsWith(".md")) {
                paths.push((0, planning_document_utils_1.toRepositoryLikePath)(node_path_1.default.relative(repositoryRoot, currentPath)));
            }
            continue;
        }
        if (!stats.isDirectory()) {
            continue;
        }
        for (const child of node_fs_1.default.readdirSync(currentPath)) {
            stack.push(node_path_1.default.join(currentPath, child));
        }
    }
    return paths.sort((left, right) => left.localeCompare(right));
}
function findCoverageByArtifactType(artifactType) {
    if (artifactType === null) {
        return null;
    }
    return output_1.TRACEABLE_ARTIFACT_COVERAGE.find((entry) => "artifactType" in entry && entry.artifactType === artifactType) ?? null;
}
function findCoverageByFamily(family) {
    return output_1.TRACEABLE_ARTIFACT_COVERAGE.find((entry) => entry.family === family) ?? null;
}
function createUnsupportedArtifact(options) {
    return {
        path: options.path,
        family: "unsupported",
        artifactType: options.artifactType ?? null,
        parseStatus: options.parseStatus,
        parseMessage: options.parseMessage,
        coverageSource: "outside-coverage",
        traceFields: []
    };
}
function createDiscoveryEvidence(artifact) {
    return {
        path: artifact.path,
        status: artifact.parseStatus,
        message: artifact.parseMessage,
        family: artifact.family === "unsupported" ? null : artifact.family,
        artifactType: artifact.artifactType,
        noWriteRequired: true
    };
}
function summarizeDiscovery(artifacts, evidence) {
    return {
        artifactCount: artifacts.length,
        evidenceCount: evidence.length,
        parsedCount: countByStatus(artifacts, "parsed"),
        unsupportedCount: countByStatus(artifacts, "unsupported"),
        malformedCount: countByStatus(artifacts, "malformed"),
        unreadableCount: countByStatus(artifacts, "unreadable"),
        missingCount: countByStatus(artifacts, "missing"),
        outsideCoverageCount: countByStatus(artifacts, "outside-coverage")
    };
}
function countByStatus(artifacts, status) {
    return artifacts.filter((artifact) => artifact.parseStatus === status).length;
}
function uniqueValues(values) {
    return [...new Set(values)];
}
function extractSectionTextWithOffset(markdownText, sectionName) {
    const section = (0, planning_artifact_document_1.parsePlanningArtifactDocument)(markdownText).sections.find((candidate) => candidate.heading === sectionName);
    if (section === undefined) {
        return {
            text: "",
            startOffset: 0
        };
    }
    return {
        text: section.contentText,
        startOffset: section.contentRange.start.offset
    };
}
function offsetToLineColumn(markdownText, offset) {
    const prefix = markdownText.slice(0, offset);
    const lines = prefix.split(/\r?\n/);
    return {
        line: lines.length,
        column: lines[lines.length - 1].length + 1
    };
}
function isInsideTraversalBoundary(artifactPath) {
    let normalizedPath;
    try {
        normalizedPath = (0, paths_1.parseRepositoryRelativePath)(artifactPath).normalizedPath;
    }
    catch {
        return false;
    }
    return output_1.TRACE_LINK_TRAVERSAL_BOUNDARIES.some((boundary) => {
        if (normalizedPath === boundary) {
            return true;
        }
        if (boundary.endsWith(".json")) {
            return false;
        }
        return normalizedPath.startsWith(`${boundary}/`);
    });
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
