"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLinkMaterializeCommand = createLinkMaterializeCommand;
const node_fs_1 = __importDefault(require("node:fs"));
const errors_1 = require("../../../lib/errors");
const markdown_link_materialization_1 = require("../../../lib/planning/markdown-link-materialization");
const output_1 = require("../../../lib/output");
const paths_1 = require("../../../lib/paths");
const validation_links_check_1 = require("../validation-links-check");
const global_format_1 = require("../global-format");
const MATERIALIZE_NO_WRITE_BOUNDARY = "Dry-run previews Markdown body-link materialization without writing files. Write execution is explicit, selected-artifact or selected-directory scoped, and separate from validation links-check.";
async function createLinkMaterializeCommand() {
    const { Command } = await import("commander");
    const command = new Command("materialize");
    command
        .description("Preview or materialize validated local Markdown body references as clickable links.")
        .option("--scope <scope>", "Materialization scope. Currently only 'planning' is supported.", "planning")
        .option("--artifact <path>", "Repository-relative Markdown artifact file or directory path. Required for write execution.")
        .option("--dry-run", "Preview candidates without writing files. Repository-wide execution is dry-run only.")
        .option("--write", "Apply materialization changes to selected artifact file(s).")
        .option("--format <format>", "Output format. Accepted values: text, json.")
        .addHelpText("after", [
        "",
        "Boundaries:",
        "  Evidence source: uses validation links-check discovery and resolution evidence; validation links-check remains read-only and writeCapable: false.",
        "  Dry-run: previews repository-wide, selected-artifact, or selected-directory candidates without writing files.",
        "  Write execution: requires --artifact and mutates only selected Markdown artifact files under the selected artifact or directory when safe body-reference candidates exist.",
        "  Skips malformed, missing, outside-boundary, unsupported external, already-linked, inline-code, fenced-code, duplicate, ambiguous, or otherwise unsafe candidates.",
        "  Rollback: revert the reported changed artifact paths from version control."
    ].join("\n"))
        .action((_, commandInstance) => {
        const options = commandInstance.opts();
        const requestedFormat = options.format ?? (0, global_format_1.getGlobalFormat)(commandInstance);
        const format = (0, output_1.resolveOutputFormat)(requestedFormat);
        if (format === null) {
            throw (0, errors_1.createUnsupportedOutputFormatError)({
                received: requestedFormat,
                commandBoundary: "link materialize command output format"
            });
        }
        if (options.scope !== "planning") {
            throw (0, errors_1.createUsageError)("Link materialize currently supports only the 'planning' scope.", [
                `Received: ${String(options.scope)}`
            ]);
        }
        const artifactPath = normalizeOptionalArtifactPath(options.artifact);
        const write = options.write === true;
        const dryRun = !write || options.dryRun === true;
        if (write && options.dryRun === true) {
            throw (0, errors_1.createUsageError)("Use either --dry-run or --write for link materialize, not both.", [
                "Dry-run is the default when --write is omitted."
            ]);
        }
        if (write && artifactPath === null) {
            throw (0, errors_1.createUsageError)("Link materialize write execution requires --artifact.", [
                "Repository-wide materialization is preview-only; use --dry-run or select one artifact with --artifact."
            ]);
        }
        const repositoryRoot = getRootCommand(commandInstance).configRepositoryContext?.root ?? process.cwd();
        const validation = (0, validation_links_check_1.runValidationLinksCheckReport)({
            repositoryRoot,
            artifactPath
        });
        const report = buildMaterializeReport({
            repositoryRoot,
            artifactPath,
            dryRun,
            write,
            validation
        });
        const { writePlans, ...outputReport } = report;
        if (write && outputReport.changedArtifactPaths.length > 0) {
            for (const writePlan of writePlans) {
                node_fs_1.default.writeFileSync((0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, writePlan.path), writePlan.markdownText, "utf8");
            }
        }
        if (validation.findings.some((finding) => finding.severity === "error")) {
            getRootCommand(commandInstance).runtimeExitCode = errors_1.RuntimeExitCode.ERROR;
        }
        (0, output_1.emitSuccess)({
            command: "link materialize",
            format,
            text: renderLinkMaterializeReport(outputReport),
            data: outputReport,
            findings: validation.findings
        });
    });
    return command;
}
function buildMaterializeReport(options) {
    const resolvedCandidates = collectResolvedCandidates(options.validation);
    const materialization = runMaterialization({
        repositoryRoot: options.repositoryRoot,
        candidates: resolvedCandidates
    });
    const candidates = materialization.replacements;
    const skippedCandidates = [];
    for (const evidence of options.validation.resolution.valueResolutions) {
        if (evidence.valueShape !== "body-reference-list") {
            continue;
        }
        if (evidence.resolution.status === "resolved-local-target") {
            continue;
        }
        skippedCandidates.push({
            sourceArtifactPath: evidence.sourceArtifactPath,
            field: evidence.field,
            originalValue: evidence.originalValue,
            reason: normalizeSkipReason(evidence.resolution.status),
            message: evidence.resolution.message
        });
    }
    skippedCandidates.push(...materialization.skippedCandidates.map((skipped) => ({
        sourceArtifactPath: skipped.sourceArtifactPath,
        field: skipped.field,
        originalValue: skipped.value,
        reason: skipped.reason,
        message: skipped.message
    })));
    const previewChangedArtifactPaths = materialization.changedArtifactPaths;
    const previewChangedPaths = previewChangedArtifactPaths;
    const changedArtifactPaths = options.write ? materialization.changedArtifactPaths : [];
    const changedPaths = changedArtifactPaths;
    return {
        repositoryRoot: options.repositoryRoot,
        scope: "planning",
        artifact: options.artifactPath,
        artifactInputKind: classifyArtifactInputKind(options.repositoryRoot, options.artifactPath),
        readOnly: options.dryRun,
        writeCapable: true,
        dryRun: options.dryRun,
        localOnly: true,
        candidateCount: candidates.length,
        previewChangedPaths,
        previewChangedArtifactPaths,
        changedPaths,
        changedArtifactPaths,
        skippedCandidateCount: skippedCandidates.length,
        skippedCandidates,
        candidates,
        rollbackGuidance: {
            summary: options.dryRun
                ? "Dry-run only; no rollback is required because no files were written."
                : changedArtifactPaths.length === 0
                    ? "No files were changed; no rollback is required."
                    : "To rollback, revert the reported changed artifact paths from version control.",
            changedArtifactPaths
        },
        validationEvidence: {
            command: "validation links-check",
            readOnly: true,
            writeCapable: false,
            checkedArtifactCount: options.validation.discovery.summary.artifactCount,
            checkedRelationshipCount: options.validation.resolution.summary.valueResolutionCount +
                options.validation.resolution.summary.hierarchyCheckCount +
                options.validation.resolution.summary.pairingCheckCount
        },
        writePlans: options.write ? materialization.writePlans : []
    };
}
function collectResolvedCandidates(validation) {
    const normalizedByKey = new Map();
    for (const evidence of validation.resolution.valueResolutions) {
        if (evidence.valueShape === "body-reference-list" &&
            evidence.resolution.status === "resolved-local-target" &&
            evidence.resolution.normalizedPath !== undefined) {
            normalizedByKey.set(candidateKey(evidence.sourceArtifactPath, evidence.field, evidence.originalValue), evidence.resolution.normalizedPath);
        }
    }
    return validation.discovery.artifacts.flatMap((artifact) => artifact.traceFields.flatMap((field) => {
        if (field.valueShape !== "body-reference-list" || field.bodyReferenceOccurrences === undefined) {
            return [];
        }
        return field.bodyReferenceOccurrences.flatMap((occurrence) => {
            const normalizedPath = normalizedByKey.get(candidateKey(artifact.path, field.field, occurrence.value));
            if (normalizedPath === undefined) {
                return [];
            }
            return [{
                    sourceArtifactPath: artifact.path,
                    field: field.field,
                    value: occurrence.value,
                    normalizedPath,
                    occurrence
                }];
        });
    }));
}
function runMaterialization(options) {
    const candidatesByArtifact = new Map();
    for (const candidate of options.candidates) {
        const existing = candidatesByArtifact.get(candidate.sourceArtifactPath) ?? [];
        existing.push(candidate);
        candidatesByArtifact.set(candidate.sourceArtifactPath, existing);
    }
    const replacements = [];
    const skippedCandidates = [];
    const changedArtifactPaths = [];
    const writePlans = [];
    for (const [artifactPath, candidates] of candidatesByArtifact) {
        const absolutePath = (0, paths_1.resolveRepositoryRelativePath)(options.repositoryRoot, artifactPath);
        const markdownText = node_fs_1.default.readFileSync(absolutePath, "utf8");
        const result = (0, markdown_link_materialization_1.materializeMarkdownLinks)({ markdownText, candidates });
        replacements.push(...result.replacements.map((replacement) => ({
            sourceArtifactPath: replacement.sourceArtifactPath,
            field: replacement.field,
            originalValue: replacement.value,
            normalizedPath: replacement.normalizedPath,
            status: "candidate"
        })));
        skippedCandidates.push(...result.skippedCandidates);
        if (result.changed) {
            changedArtifactPaths.push(artifactPath);
            writePlans.push({
                path: artifactPath,
                markdownText: result.markdownText
            });
        }
    }
    return {
        replacements,
        skippedCandidates,
        changedArtifactPaths,
        writePlans
    };
}
function candidateKey(sourceArtifactPath, field, value) {
    return `${sourceArtifactPath}\u0000${field}\u0000${value}`;
}
function renderLinkMaterializeReport(report) {
    return [
        "Markdown link materialization",
        "",
        `Scope: ${report.scope}`,
        `Artifact: ${report.artifact ?? "(repository preview)"}`,
        `Artifact input: ${report.artifactInputKind}`,
        `Dry-run: ${report.dryRun ? "yes" : "no"}`,
        `Write-capable: ${report.writeCapable ? "yes" : "no"}`,
        `Candidate count: ${report.candidateCount}`,
        `Skipped candidate count: ${report.skippedCandidateCount}`,
        `Preview changed paths: ${report.previewChangedPaths.length === 0 ? "none" : report.previewChangedPaths.join(", ")}`,
        `Changed paths: ${report.changedPaths.length === 0 ? "none" : report.changedPaths.join(", ")}`,
        `Rollback: ${report.rollbackGuidance.summary}`,
        "",
        `Evidence: ${report.validationEvidence.command} (read-only, writeCapable: false)`,
        MATERIALIZE_NO_WRITE_BOUNDARY
    ].join("\n");
}
function normalizeOptionalArtifactPath(input) {
    if (input === undefined || input === null || input === "") {
        return null;
    }
    if (typeof input !== "string") {
        throw (0, errors_1.createUsageError)("Link materialize artifact path must be repository-relative.", [
            `Received: ${String(input)}`
        ]);
    }
    try {
        return (0, paths_1.parseRepositoryRelativePath)(input).normalizedPath;
    }
    catch (error) {
        if (error instanceof RangeError) {
            throw (0, errors_1.createUsageError)("Link materialize artifact path must be repository-relative.", [
                error.message
            ]);
        }
        throw error;
    }
}
function classifyArtifactInputKind(repositoryRoot, artifactPath) {
    if (artifactPath === null) {
        return "repository";
    }
    try {
        return node_fs_1.default.existsSync((0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, artifactPath)) &&
            node_fs_1.default.statSync((0, paths_1.resolveRepositoryRelativePath)(repositoryRoot, artifactPath)).isDirectory()
            ? "directory"
            : "file";
    }
    catch {
        return "file";
    }
}
function normalizeSkipReason(status) {
    if (status === "missing-local-target") {
        return "missing-local-target";
    }
    if (status === "malformed-local-link") {
        return "malformed-local-link";
    }
    if (status === "outside-traversal-boundary") {
        return "outside-traversal-boundary";
    }
    if (status === "unsupported-external-reference") {
        return "unsupported-external-reference";
    }
    return "unresolved";
}
function getRootCommand(commandInstance) {
    let current = commandInstance;
    while (current.parent !== null && current.parent !== undefined) {
        current = current.parent;
    }
    return current;
}
