#!/usr/bin/env node
// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * Memory Delete CLI (WU-1284)
 *
 * Delete or archive memory nodes using soft-delete pattern.
 * Respects append-only pattern by marking nodes with metadata.status=deleted.
 *
 * Features:
 * - Delete by node ID(s)
 * - Bulk delete via --tag filter
 * - Bulk delete via --older-than filter
 * - Dry-run preview mode
 *
 * Usage:
 *   pnpm mem:delete <node-id>                 # Delete single node
 *   pnpm mem:delete <id1> <id2>               # Delete multiple nodes
 *   pnpm mem:delete --tag obsolete            # Delete by tag
 *   pnpm mem:delete --older-than 30d          # Delete old nodes
 *   pnpm mem:delete --tag old --older-than 7d # Combined filters
 *   pnpm mem:delete <id> --dry-run            # Preview only
 *
 * @see {@link packages/@lumenflow/memory/src/mem-delete-core.ts} - Core logic
 * @see {@link packages/@lumenflow/memory/__tests__/mem-delete.test.ts} - Tests
 */
import fs from 'node:fs/promises';
import path from 'node:path';
import { deleteMemoryNodes } from '@lumenflow/memory/mem-delete-core';
import { createWUParser } from '@lumenflow/core/arg-parser';
import { EXIT_CODES, LUMENFLOW_PATHS } from '@lumenflow/core/wu-constants';
import { runCLI } from './cli-entry-point.js';
/**
 * Log prefix for mem:delete output
 */
const LOG_PREFIX = '[mem:delete]';
/**
 * Tool name for audit logging
 */
const TOOL_NAME = 'mem:delete';
/**
 * CLI argument options specific to mem:delete
 */
const CLI_OPTIONS = {
    dryRun: {
        name: 'dryRun',
        flags: '--dry-run',
        description: 'Preview deletion without making changes',
    },
    id: {
        name: 'id',
        flags: '--id <nodeId...>',
        description: 'Node ID to delete (mem-XXXX; repeatable). Also accepted as positional args.',
    },
    tag: {
        name: 'tag',
        flags: '--tag <tag>',
        description: 'Delete all nodes matching this tag',
    },
    olderThan: {
        name: 'olderThan',
        flags: '--older-than <duration>',
        description: 'Delete nodes older than duration (e.g., 30d, 7d, 24h, 2w)',
    },
    json: {
        name: 'json',
        flags: '--json',
        description: 'Output as JSON',
    },
    quiet: {
        name: 'quiet',
        flags: '-q, --quiet',
        description: 'Suppress output except errors',
    },
    baseDir: {
        name: 'baseDir',
        flags: '-b, --base-dir <path>',
        description: 'Base directory (defaults to current directory)',
    },
};
/**
 * Write audit log entry for tool execution
 *
 * @param baseDir - Base directory
 * @param entry - Audit log entry
 */
async function writeAuditLog(baseDir, entry) {
    try {
        const logPath = path.join(baseDir, LUMENFLOW_PATHS.AUDIT_LOG);
        const logDir = path.dirname(logPath);
        await fs.mkdir(logDir, { recursive: true });
        const line = `${JSON.stringify(entry)}\n`;
        await fs.appendFile(logPath, line, 'utf-8');
    }
    catch {
        // Audit logging is non-fatal - silently ignore errors
    }
}
/**
 * Parse CLI arguments and extract node IDs from positional args
 *
 * @returns Parsed args and node IDs
 */
function parseArguments() {
    const args = createWUParser({
        name: 'mem-delete',
        description: 'Delete memory nodes (soft delete via metadata.status=deleted)',
        options: [
            CLI_OPTIONS.dryRun,
            CLI_OPTIONS.id,
            CLI_OPTIONS.tag,
            CLI_OPTIONS.olderThan,
            CLI_OPTIONS.json,
            CLI_OPTIONS.quiet,
            CLI_OPTIONS.baseDir,
        ],
        required: [],
        allowPositionalId: true,
    });
    const positional = [];
    const argv = process.argv.slice(2).filter((arg) => arg !== '--');
    for (const arg of argv) {
        if (arg.startsWith('-')) {
            continue;
        }
        const prevArg = argv[argv.indexOf(arg) - 1];
        if (prevArg &&
            prevArg.startsWith('-') &&
            !prevArg.startsWith('--dry-run') &&
            !prevArg.startsWith('--json') &&
            !prevArg.startsWith('-q')) {
            continue;
        }
        positional.push(arg);
    }
    const rawIdFlag = args.id;
    const idFlags = Array.isArray(rawIdFlag)
        ? rawIdFlag
        : typeof rawIdFlag === 'string'
            ? [rawIdFlag]
            : [];
    const nodeIds = collectNodeIds({ positional, idFlags });
    return { args, nodeIds };
}
export function collectNodeIds(input) {
    const seen = new Set();
    const out = [];
    const push = (candidate) => {
        if (!candidate.startsWith('mem-'))
            return;
        if (seen.has(candidate))
            return;
        seen.add(candidate);
        out.push(candidate);
    };
    for (const p of input.positional)
        push(p);
    for (const f of input.idFlags)
        push(f);
    return out;
}
/**
 * Display limit for ID lists
 */
const DISPLAY_LIMITS = {
    DELETED_IDS: 10,
    SKIPPED_IDS: 5,
};
/**
 * Print a list of IDs with limit
 */
function printIdList(ids, limit, label) {
    console.log('');
    console.log(label);
    const displayIds = ids.slice(0, limit);
    for (const id of displayIds) {
        console.log(`  - ${id}`);
    }
    if (ids.length > limit) {
        console.log(`  ... and ${ids.length - limit} more`);
    }
}
/**
 * Print errors list
 */
function printErrors(errors) {
    if (errors.length === 0)
        return;
    console.log('');
    console.log('Errors:');
    for (const error of errors) {
        console.log(`  - ${error}`);
    }
}
/**
 * Print deletion result to console
 *
 * @param result - Deletion result
 * @param quiet - Suppress verbose output
 */
function printResult(result, quiet) {
    const statusMsg = result.dryRun
        ? `${LOG_PREFIX} Dry-run: Would delete ${result.deletedCount} node(s)`
        : `${LOG_PREFIX} Deletion complete`;
    console.log(statusMsg);
    if (quiet) {
        console.log(`${result.deletedCount} deleted, ${result.skippedIds.length} skipped`);
        return;
    }
    console.log('');
    console.log('Summary:');
    console.log(`  Deleted:  ${result.deletedCount} node(s)`);
    console.log(`  Skipped:  ${result.skippedIds.length} node(s)`);
    if (result.deletedIds.length > 0) {
        const label = result.dryRun ? 'Would Delete:' : 'Deleted:';
        printIdList(result.deletedIds, DISPLAY_LIMITS.DELETED_IDS, label);
    }
    if (result.skippedIds.length > 0) {
        printIdList(result.skippedIds, DISPLAY_LIMITS.SKIPPED_IDS, 'Skipped (already deleted):');
    }
    printErrors(result.errors);
    if (result.dryRun) {
        console.log('');
        console.log('To execute, run without --dry-run');
    }
}
/**
 * Build delete options from CLI arguments
 */
function buildDeleteOptions(args, nodeIds) {
    const options = {
        dryRun: args.dryRun,
    };
    if (nodeIds.length > 0) {
        options.nodeIds = nodeIds;
    }
    if (args.tag) {
        options.tag = args.tag;
    }
    if (args.olderThan) {
        options.olderThan = args.olderThan;
    }
    return options;
}
/**
 * Print usage help and exit with error
 */
function printUsageAndExit() {
    console.error(`${LOG_PREFIX} Error: At least one filter is required`);
    console.error('');
    console.error('Usage:');
    console.error('  pnpm mem:delete <node-id>          Delete single node by ID (positional)');
    console.error('  pnpm mem:delete <id1> <id2>        Delete multiple nodes (positional)');
    console.error('  pnpm mem:delete --id mem-XXXX      Delete by ID (explicit flag, repeatable)');
    console.error('  pnpm mem:delete --tag <tag>        Delete all nodes with tag');
    console.error('  pnpm mem:delete --older-than 30d   Delete nodes older than 30 days');
    console.error('  pnpm mem:delete --dry-run          Preview deletion');
    process.exit(EXIT_CODES.ERROR);
}
/**
 * Main CLI entry point
 */
export async function main() {
    const { args, nodeIds } = parseArguments();
    const baseDir = args.baseDir || process.cwd();
    const startedAt = new Date().toISOString();
    const startTime = Date.now();
    const deleteOptions = buildDeleteOptions(args, nodeIds);
    // Check if UnsafeAny filter is provided
    if (!deleteOptions.nodeIds && !deleteOptions.tag && !deleteOptions.olderThan) {
        printUsageAndExit();
    }
    let result = null;
    let error = null;
    try {
        result = await deleteMemoryNodes(baseDir, deleteOptions);
    }
    catch (err) {
        error = err instanceof Error ? err.message : String(err);
    }
    const durationMs = Date.now() - startTime;
    await writeAuditLog(baseDir, {
        tool: TOOL_NAME,
        status: error ? 'failed' : 'success',
        startedAt,
        completedAt: new Date().toISOString(),
        durationMs,
        input: { baseDir, nodeIds, tag: args.tag, olderThan: args.olderThan, dryRun: args.dryRun },
        output: result
            ? {
                success: result.success,
                deletedCount: result.deletedCount,
                deletedIds: result.deletedIds,
                skippedCount: result.skippedIds.length,
                dryRun: result.dryRun,
            }
            : null,
        error: error ? { message: error } : null,
    });
    if (error) {
        console.error(`${LOG_PREFIX} Error: ${error}`);
        process.exit(EXIT_CODES.ERROR);
    }
    if (!result) {
        console.error(`${LOG_PREFIX} Error: No result from delete operation`);
        process.exit(EXIT_CODES.ERROR);
    }
    if (args.json) {
        console.log(JSON.stringify(result, null, 2));
        process.exit(result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
    }
    printResult(result, args.quiet);
    process.exit(result.success ? EXIT_CODES.SUCCESS : EXIT_CODES.ERROR);
}
// WU-1537: Use import.meta.main + runCLI for consistent EPIPE and error handling
if (import.meta.main) {
    void runCLI(main);
}
//# sourceMappingURL=mem-delete.js.map