"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 ?? commandInstance);
}
