"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLinkDispositionCrudCommands = createLinkDispositionCrudCommands;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const errors_1 = require("../../../lib/errors");
const output_1 = require("../../../lib/output");
const paths_1 = require("../../../lib/paths");
const trace_link_dispositions_1 = require("../../../lib/trace-link-dispositions");
const global_format_1 = require("../global-format");
async function createLinkDispositionCrudCommands() {
    const { Command } = await import("commander");
    const add = new Command("add")
        .description("Create a trace-link lifecycle disposition record.")
        .requiredOption("--status <status>", "Disposition status: retired, deleted, or migrated.")
        .requiredOption("--target <path>", "Original repository-relative trace-link target path.")
        .option("--replacement-target <path>", "Required replacement target path for migrated dispositions.")
        .option("--source-artifact <path>", "Repository-relative artifact where the stale link was found.")
        .option("--trace-field <field>", "Trace field where the stale link was found.")
        .option("--scope <scope>", "Optional discovery scope label.")
        .option("--reason <text>", "Human-readable lifecycle reason.")
        .option("--format <format>", "Output format. Accepted values: text, json.")
        .addHelpText("after", boundaryHelp("Write-capable: creates or updates disposition storage only; does not rewrite planning artifacts or validation findings."))
        .action((_, commandInstance) => {
        const context = resolveContext(commandInstance, "link disposition add command output format");
        const options = commandInstance.opts();
        const document = readStorageDocument(context, { missingAsEmpty: true });
        const now = new Date().toISOString();
        const disposition = (0, trace_link_dispositions_1.createTraceLinkDisposition)({
            status: normalizeStatus(options.status),
            target: requireRepositoryPath(options.target, "target"),
            replacementTarget: normalizeOptionalRepositoryPath(options.replacementTarget, "replacement-target") ?? undefined,
            discovery: normalizeDiscovery(options),
            reason: normalizeOptionalString(options.reason),
            createdAt: now
        });
        if (document.dispositions.some((existing) => existing.id === disposition.id)) {
            throw (0, errors_1.createUsageError)(`Trace-link disposition '${disposition.id}' already exists.`);
        }
        writeStorageDocument(context, {
            schemaVersion: document.schemaVersion,
            dispositions: [...document.dispositions, disposition]
        });
        emitMutation("link disposition add", context, "Added trace-link disposition.", {
            disposition,
            changedPaths: [trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH]
        });
    });
    const list = new Command("list")
        .description("List trace-link lifecycle disposition records without writing files.")
        .option("--status <status>", "Filter by disposition status.")
        .option("--format <format>", "Output format. Accepted values: text, json.")
        .addHelpText("after", boundaryHelp("Read-only: reads disposition storage but does not create storage, rewrite artifacts, or update timestamps."))
        .action((_, commandInstance) => {
        const context = resolveContext(commandInstance, "link disposition list command output format");
        const options = commandInstance.opts();
        const document = readStorageDocument(context, { missingAsEmpty: true });
        const status = options.status === undefined ? null : normalizeStatus(options.status);
        const dispositions = status === null
            ? document.dispositions
            : document.dispositions.filter((disposition) => disposition.status === status);
        emitRead("link disposition list", context, renderListText(dispositions), { dispositions });
    });
    const show = new Command("show")
        .description("Show one trace-link lifecycle disposition record without writing files.")
        .option("--id <id>", "Disposition id.")
        .option("--target <path>", "Original repository-relative trace-link target path.")
        .option("--format <format>", "Output format. Accepted values: text, json.")
        .addHelpText("after", boundaryHelp("Read-only: reads disposition storage but does not create storage, rewrite artifacts, or update timestamps."))
        .action((_, commandInstance) => {
        const context = resolveContext(commandInstance, "link disposition show command output format");
        const document = readStorageDocument(context, { missingAsEmpty: true });
        const disposition = selectOne(document.dispositions, commandInstance.opts());
        emitRead("link disposition show", context, renderDispositionText(disposition), { disposition });
    });
    const update = new Command("update")
        .description("Update one trace-link lifecycle disposition record.")
        .option("--id <id>", "Disposition id.")
        .option("--target <path>", "Original repository-relative trace-link target path.")
        .option("--status <status>", "New disposition status: retired, deleted, or migrated.")
        .option("--replacement-target <path>", "Replacement target path for migrated dispositions.")
        .option("--clear-replacement-target", "Remove the replacement target when changing to retired or deleted.")
        .option("--source-artifact <path>", "Repository-relative artifact where the stale link was found.")
        .option("--trace-field <field>", "Trace field where the stale link was found.")
        .option("--scope <scope>", "Optional discovery scope label.")
        .option("--reason <text>", "Human-readable lifecycle reason.")
        .option("--clear-reason", "Remove the stored reason.")
        .option("--format <format>", "Output format. Accepted values: text, json.")
        .addHelpText("after", boundaryHelp("Write-capable: mutates disposition storage only; does not rewrite planning artifacts or validation findings."))
        .action((_, commandInstance) => {
        const context = resolveContext(commandInstance, "link disposition update command output format");
        const options = commandInstance.opts();
        const document = readStorageDocument(context, { missingAsEmpty: false });
        const existing = selectOne(document.dispositions, options);
        const status = options.status === undefined ? existing.status : normalizeStatus(options.status);
        const replacementTarget = resolveReplacementTarget(existing, options, status);
        const updated = (0, trace_link_dispositions_1.createTraceLinkDisposition)({
            status,
            target: existing.target.normalizedPath,
            replacementTarget,
            discovery: resolveDiscovery(existing, options),
            reason: options.clearReason === true ? undefined : normalizeOptionalString(options.reason) ?? existing.reason,
            createdAt: existing.createdAt,
            updatedAt: new Date().toISOString(),
            applyEvidence: existing.applyEvidence
        });
        const dispositions = document.dispositions.map((disposition) => disposition.id === existing.id ? updated : disposition);
        writeStorageDocument(context, { schemaVersion: document.schemaVersion, dispositions });
        emitMutation("link disposition update", context, "Updated trace-link disposition.", {
            disposition: updated,
            changedPaths: [trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH]
        });
    });
    const remove = new Command("delete")
        .description("Delete one trace-link lifecycle disposition record.")
        .option("--id <id>", "Disposition id.")
        .option("--target <path>", "Original repository-relative trace-link target path.")
        .option("--format <format>", "Output format. Accepted values: text, json.")
        .addHelpText("after", boundaryHelp("Write-capable: removes a disposition storage record only; does not rewrite planning artifacts or validation findings."))
        .action((_, commandInstance) => {
        const context = resolveContext(commandInstance, "link disposition delete command output format");
        const document = readStorageDocument(context, { missingAsEmpty: false });
        const disposition = selectOne(document.dispositions, commandInstance.opts());
        const dispositions = document.dispositions.filter((record) => record.id !== disposition.id);
        writeStorageDocument(context, { schemaVersion: document.schemaVersion, dispositions });
        emitMutation("link disposition delete", context, "Deleted trace-link disposition.", {
            disposition,
            changedPaths: [trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH]
        });
    });
    return [add, list, show, update, remove];
}
function resolveContext(commandInstance, commandBoundary) {
    const requestedFormat = commandInstance.opts().format ?? commandInstance.optsWithGlobals().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 });
    }
    const repositoryRoot = getRootCommand(commandInstance).configRepositoryContext?.root ?? process.cwd();
    return {
        repositoryRoot,
        format,
        storageAbsolutePath: node_path_1.default.join(repositoryRoot, trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH)
    };
}
function readStorageDocument(context, options) {
    if (!node_fs_1.default.existsSync(context.storageAbsolutePath)) {
        if (options.missingAsEmpty) {
            return (0, trace_link_dispositions_1.createEmptyTraceLinkDispositionStorageDocument)();
        }
        throw (0, errors_1.createUsageError)("Trace-link disposition storage does not exist.", [
            `Storage path: ${trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH}`
        ]);
    }
    return (0, trace_link_dispositions_1.parseTraceLinkDispositionStorageDocument)(node_fs_1.default.readFileSync(context.storageAbsolutePath, "utf8"));
}
function writeStorageDocument(context, document) {
    const serialized = (0, trace_link_dispositions_1.serializeTraceLinkDispositionStorageDocument)(document);
    node_fs_1.default.mkdirSync(node_path_1.default.dirname(context.storageAbsolutePath), { recursive: true });
    node_fs_1.default.writeFileSync(context.storageAbsolutePath, serialized, "utf8");
}
function selectOne(dispositions, options) {
    const id = normalizeSelectorId(options);
    const disposition = dispositions.find((record) => record.id === id);
    if (disposition === undefined) {
        throw (0, errors_1.createUsageError)(`Trace-link disposition '${id}' was not found.`);
    }
    return disposition;
}
function normalizeSelectorId(options) {
    if (options.id !== undefined && options.target !== undefined) {
        throw (0, errors_1.createUsageError)("Use either --id or --target, not both.");
    }
    if (options.id !== undefined && options.id.trim() !== "") {
        return options.id;
    }
    if (options.target !== undefined) {
        return (0, trace_link_dispositions_1.createTraceLinkDispositionId)(requireRepositoryPath(options.target, "target"));
    }
    throw (0, errors_1.createUsageError)("Provide --id or --target.");
}
function normalizeStatus(input) {
    if (input !== "retired" && input !== "deleted" && input !== "migrated") {
        throw (0, errors_1.createUsageError)("Disposition status must be one of: retired, deleted, migrated.");
    }
    return input;
}
function requireRepositoryPath(input, fieldName) {
    const normalized = normalizeOptionalRepositoryPath(input, fieldName);
    if (normalized === null) {
        throw (0, errors_1.createUsageError)(`Link disposition ${fieldName} is required.`);
    }
    return normalized;
}
function normalizeOptionalRepositoryPath(input, fieldName) {
    if (input === undefined || input === null || input === "") {
        return null;
    }
    if (typeof input !== "string") {
        throw (0, errors_1.createUsageError)(`Link disposition ${fieldName} must be a repository-relative path.`);
    }
    try {
        return (0, paths_1.parseRepositoryRelativePath)(input).normalizedPath;
    }
    catch (error) {
        throw (0, errors_1.createUsageError)(`Link disposition ${fieldName} must be a repository-relative path.`, [error instanceof Error ? error.message : String(error)]);
    }
}
function normalizeOptionalString(input) {
    if (input === undefined || input === null) {
        return undefined;
    }
    if (typeof input !== "string") {
        throw (0, errors_1.createUsageError)("Link disposition text fields must be strings.");
    }
    const trimmed = input.trim();
    return trimmed === "" ? undefined : input;
}
function normalizeDiscovery(options) {
    const sourceArtifactPath = normalizeOptionalRepositoryPath(options.sourceArtifact, "source-artifact") ?? undefined;
    const traceField = normalizeOptionalString(options.traceField);
    const scope = normalizeOptionalString(options.scope);
    if (sourceArtifactPath === undefined && traceField === undefined && scope === undefined) {
        return undefined;
    }
    return { sourceArtifactPath, traceField, scope };
}
function resolveDiscovery(existing, options) {
    const next = normalizeDiscovery(options);
    return next ?? existing.discovery;
}
function resolveReplacementTarget(existing, options, status) {
    if (status === "migrated") {
        const replacementTarget = normalizeOptionalRepositoryPath(options.replacementTarget, "replacement-target");
        if (replacementTarget !== null) {
            return replacementTarget;
        }
        return existing.status === "migrated" ? existing.replacementTarget.normalizedPath : undefined;
    }
    if (options.replacementTarget !== undefined && options.clearReplacementTarget !== true) {
        throw (0, errors_1.createUsageError)("Replacement targets are only supported for migrated trace-link dispositions.");
    }
    return undefined;
}
function emitMutation(command, context, text, result) {
    (0, output_1.emitSuccess)({
        command,
        format: context.format,
        text: [
            text,
            `Storage path: ${trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH}`,
            `Changed paths: ${result.changedPaths.join(", ")}`
        ].join("\n"),
        data: {
            readOnly: false,
            writeCapable: true,
            localOnly: true,
            storagePath: trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH,
            ...result
        }
    });
}
function emitRead(command, context, text, data) {
    (0, output_1.emitSuccess)({
        command,
        format: context.format,
        text,
        data: {
            readOnly: true,
            writeCapable: false,
            localOnly: true,
            storagePath: trace_link_dispositions_1.TRACE_LINK_DISPOSITION_STORAGE_PATH,
            ...data
        }
    });
}
function renderListText(dispositions) {
    if (dispositions.length === 0) {
        return "No trace-link dispositions found.";
    }
    return [
        "Trace-link dispositions:",
        ...dispositions.map((disposition) => `- ${disposition.id} ${disposition.status} ${disposition.target.normalizedPath}`)
    ].join("\n");
}
function renderDispositionText(disposition) {
    const lines = [
        `Disposition: ${disposition.id}`,
        `Status: ${disposition.status}`,
        `Target: ${disposition.target.normalizedPath}`
    ];
    if (disposition.status === "migrated") {
        lines.push(`Replacement target: ${disposition.replacementTarget.normalizedPath}`);
    }
    if (disposition.reason !== undefined) {
        lines.push(`Reason: ${disposition.reason}`);
    }
    return lines.join("\n");
}
function boundaryHelp(text) {
    return ["", "Boundaries:", `  ${text}`].join("\n");
}
function getRootCommand(commandInstance) {
    let current = commandInstance;
    while (current.parent !== null && current.parent !== undefined) {
        current = current.parent;
    }
    return current;
}
