/**
 * `agents prune cleanup` — destructive cleanup across the install.
 *
 * Cleanup targets:
 *   - Resource orphans: command/skill/hook files inside a version home that no
 *     longer come from any source (deleted from ~/.agents/ but never reconciled
 *     into the version install).
 *   - Version duplicates: older installed versions of an agent that share an
 *     account with a newer installed version of the same agent.
 *   - Trash/session targets are retained as no-op compatibility shims: version
 *     homes and session history are durable and must not be hard-deleted by
 *     agents-cli.
 *   - Runs: routine execution logs, keeping only the last N per job.
 *
 * Sync (additive: copy missing/changed files into version homes) is the job
 * of `agents sync` / `agents refresh` — the shim's launch hook
 * (`agents sync --launch`) is project-scoped only and deliberately skips
 * version-home reconciliation. Pruning, however, is destructive, so it stays
 * explicit.
 *
 * Default scope: each agent's currently-pinned default version for orphan
 * cleanup, plus the standard cross-agent version-dedup pass. Pass `--all`
 * to widen orphan cleanup to every installed version.
 */
import * as fs from 'fs';
import chalk from 'chalk';
import { formatBytes } from '../lib/format.js';
import { confirm } from '@inquirer/prompts';
import { diffVersionCommands, iterCommandsCapableVersions, removeCommandFromVersion, } from '../lib/commands.js';
import { diffVersionSkills, iterSkillsCapableVersions, removeSkillFromVersion, } from '../lib/plugins/skills.js';
import { listUnmanagedHooksInVersionHome, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks/install.js';
import { diffVersionPlugins, iterPluginsCapableVersions, removePluginSkillFromVersion, } from '../lib/plugins/plugins.js';
import { diffVersionSubagents, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
import { getGlobalDefault } from '../lib/installations/versions.js';
import { resolveAgentName, formatAgentError } from '../lib/agents.js';
import { pruneDuplicates } from './view.js';
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
import { getTrashDir } from '../lib/state.js';
import { previewRunsPrune, pruneRuns, countAllRuns } from '../lib/scheduling/routines.js';
const RESOURCE_TYPES = ['commands', 'skills', 'hooks', 'plugins', 'subagents'];
const STATE_TYPES = ['trash', 'sessions', 'runs'];
const ALL_TYPES = [...RESOURCE_TYPES, 'versions', ...STATE_TYPES];
function scopePairs(pairs, all) {
    if (all)
        return pairs;
    return pairs.filter((p) => p.version === getGlobalDefault(p.agent));
}
function collectOrphans(types, all) {
    const groups = [];
    if (types.includes('commands')) {
        for (const { agent, version } of scopePairs(iterCommandsCapableVersions(), all)) {
            const diff = diffVersionCommands(agent, version);
            const removable = [...diff.orphans, ...diff.toRemove];
            if (removable.length > 0) {
                groups.push({ type: 'commands', agent, version, orphans: removable });
            }
        }
    }
    if (types.includes('skills')) {
        for (const { agent, version } of scopePairs(iterSkillsCapableVersions(), all)) {
            const diff = diffVersionSkills(agent, version);
            if (diff.orphans.length > 0) {
                groups.push({ type: 'skills', agent, version, orphans: diff.orphans });
            }
        }
    }
    if (types.includes('hooks')) {
        for (const { agent, version } of scopePairs(iterHooksCapableVersions(), all)) {
            // Orphan hooks = scripts present in the version home but absent from
            // every configured source (user + system + extras) — genuinely dead
            // files, not merely unregistered ones (sync copies helper/test scripts
            // that no manifest entry declares). Same definition the doctor overview
            // reports (PHNX-2693).
            const orphans = listUnmanagedHooksInVersionHome(agent, version);
            if (orphans.length > 0) {
                groups.push({ type: 'hooks', agent, version, orphans });
            }
        }
    }
    if (types.includes('plugins')) {
        for (const { agent, version } of scopePairs(iterPluginsCapableVersions(), all)) {
            const diff = diffVersionPlugins(agent, version);
            if (diff.orphans.length > 0) {
                groups.push({ type: 'plugins', agent, version, orphans: diff.orphans });
            }
        }
    }
    if (types.includes('subagents')) {
        for (const { agent, version } of scopePairs(iterSubagentsCapableVersions(), all)) {
            const diff = diffVersionSubagents(agent, version);
            if (diff.orphans.length > 0) {
                groups.push({ type: 'subagents', agent, version, orphans: diff.orphans });
            }
        }
    }
    return groups;
}
function removeOne(group, name) {
    switch (group.type) {
        case 'commands':
            return removeCommandFromVersion(group.agent, group.version, name);
        case 'skills':
            return removeSkillFromVersion(group.agent, group.version, name);
        case 'hooks':
            return removeHookFromVersion(group.agent, group.version, name);
        case 'plugins':
            return removePluginSkillFromVersion(group.agent, group.version, name);
        case 'subagents':
            return removeSubagentFromVersion(group.agent, group.version, name);
    }
}
function parseTarget(arg) {
    if (!arg) {
        return { resourceTypes: RESOURCE_TYPES, includeVersions: true };
    }
    if (RESOURCE_TYPES.includes(arg)) {
        return { resourceTypes: [arg], includeVersions: false };
    }
    if (STATE_TYPES.includes(arg)) {
        return { resourceTypes: [], includeVersions: false, stateType: arg };
    }
    if (arg === 'versions') {
        return { resourceTypes: [], includeVersions: true };
    }
    // Try treating as an agent name — shortcut for `prune versions <agent>`.
    const agentId = resolveAgentName(arg);
    if (agentId) {
        return { resourceTypes: [], includeVersions: true, versionAgent: agentId };
    }
    console.log(chalk.red(`Unknown prune target: ${arg}`));
    console.log(chalk.gray(`Available types: ${ALL_TYPES.join(', ')}`));
    console.log(chalk.gray(formatAgentError(arg)));
    process.exit(1);
}
async function runTrashPrune(options) {
    const trashDir = getTrashDir();
    if (!fs.existsSync(trashDir)) {
        console.log(chalk.green('Trash is empty.'));
        return;
    }
    if (options.olderThan || options.yes || options.dryRun) {
        console.log(chalk.gray('Trash expiry flags are accepted for compatibility but do not delete data.'));
    }
    console.log(chalk.yellow('Trash is durable. agents-cli does not hard-delete soft-deleted version data.'));
    console.log(chalk.gray('Inspect recoverable versions with: agents trash list'));
    console.log(chalk.gray(`Trash path: ${trashDir}`));
}
async function runSessionsPrune(options) {
    if (options.olderThan || options.yes || options.dryRun) {
        console.log(chalk.gray('Session prune flags are accepted for compatibility but do not delete data.'));
    }
    console.log(chalk.yellow('Session history is durable. agents-cli does not hard-delete session records.'));
    console.log(chalk.gray('Browse sessions with: agents sessions'));
}
async function runRunsPrune(options) {
    const keep = options.keep ? parseInt(options.keep, 10) : 10;
    if (isNaN(keep) || keep < 0) {
        console.log(chalk.red('--keep must be a non-negative integer'));
        process.exit(1);
    }
    const preview = previewRunsPrune(keep);
    const total = countAllRuns();
    if (preview.length === 0) {
        console.log(chalk.green(`All jobs have ${keep} or fewer runs. Nothing to prune.`));
        return;
    }
    console.log(chalk.bold(`Routine runs to prune (keeping last ${keep} per job)\n`));
    const byJob = new Map();
    for (const run of preview) {
        byJob.set(run.jobName, (byJob.get(run.jobName) || 0) + 1);
    }
    for (const [job, count] of byJob) {
        console.log(`  ${chalk.cyan(job)}: ${count} old runs`);
    }
    console.log();
    if (options.dryRun) {
        console.log(chalk.gray(`${preview.length} of ${total} runs would be deleted. Run without --dry-run to delete.`));
        return;
    }
    if (!options.yes) {
        if (!isInteractiveTerminal()) {
            console.log(chalk.yellow('Non-interactive shell: pass -y to confirm, or --dry-run to preview.'));
            process.exit(1);
        }
        let ok = false;
        try {
            ok = await confirm({ message: `Delete ${preview.length} old runs?`, default: false });
        }
        catch (err) {
            if (isPromptCancelled(err)) {
                console.log(chalk.gray('Cancelled'));
                return;
            }
            throw err;
        }
        if (!ok) {
            console.log(chalk.gray('Cancelled'));
            return;
        }
    }
    const { deleted, bytesFreed } = pruneRuns(keep);
    console.log(chalk.green(`Pruned ${deleted} runs (${formatBytes(bytesFreed)}).`));
}
async function runOrphanPrune(resourceTypes, options) {
    const groups = collectOrphans(resourceTypes, options.all === true);
    if (groups.length === 0) {
        console.log(chalk.green('No orphans.'));
        return;
    }
    const total = groups.reduce((n, g) => n + g.orphans.length, 0);
    console.log(chalk.bold('Orphans (in version home, unmanaged by any source)\n'));
    for (const g of groups) {
        const label = `${g.type} · ${g.agent}@${g.version}`;
        console.log(`  ${chalk.cyan(label)}  ${g.orphans.join(', ')}`);
    }
    console.log();
    if (options.dryRun) {
        console.log(chalk.gray(`${total} orphan(s). Run without --dry-run to delete.`));
        return;
    }
    if (!options.yes) {
        if (!isInteractiveTerminal()) {
            console.log(chalk.yellow('Non-interactive shell: pass -y to confirm, or --dry-run to preview.'));
            process.exit(1);
        }
        let ok = false;
        try {
            ok = await confirm({
                message: `Delete ${total} orphan${total === 1 ? '' : 's'}?`,
                default: false,
            });
        }
        catch (err) {
            if (isPromptCancelled(err)) {
                console.log(chalk.gray('Cancelled'));
                return;
            }
            throw err;
        }
        if (!ok) {
            console.log(chalk.gray('Cancelled'));
            return;
        }
    }
    let removed = 0;
    let failures = 0;
    for (const g of groups) {
        for (const name of g.orphans) {
            const r = removeOne(g, name);
            if (r.success) {
                removed++;
            }
            else {
                failures++;
                console.log(chalk.red(`  ! ${g.type} ${g.agent}@${g.version} ${name}: ${r.error}`));
            }
        }
    }
    const summary = `Pruned ${removed} orphan${removed === 1 ? '' : 's'}`;
    console.log(chalk.green(summary) + (failures > 0 ? chalk.red(`, ${failures} failed`) : '') + '.');
}
export function registerPruneCommand(program) {
    const pruneCmd = program.commands.find((cmd) => cmd.name() === 'prune') ?? program
        .command('prune <specs...>')
        .description('Uninstall agent CLI versions. Moves version data to trash for recovery.');
    pruneCmd
        .command('cleanup [target]')
        .description('Remove orphan resources, old versions, or routine runs')
        .option('--all', 'For orphan cleanup: sweep every installed version (default: current default version per agent)')
        .option('--dry-run', 'Show what would be removed without deleting (default for state targets)')
        .option('-y, --yes', 'Skip confirmation prompt')
        .option('--older-than <days>', 'Deprecated for trash/sessions; accepted but no data is deleted')
        .option('--keep <n>', 'For runs: keep the last N runs per job (default: 10)')
        .addHelpText('after', `
Targets:
  (none)     Orphans across commands, skills, hooks + duplicate versions
  commands   Orphan command files only
  skills     Orphan skill directories only
  hooks      Orphan hook scripts only
  versions   Older duplicate version installs only
  <agent>    Older duplicate versions for one agent (e.g. 'claude')
  trash      No-op compatibility target; trash is durable
  sessions   No-op compatibility target; session history is durable
  runs       Routine execution logs, keeping only --keep per job (default 10)

Examples:
  # Full sweep: orphan resources + duplicate versions for current defaults
  agents prune cleanup

  # Preview what a full sweep would remove
  agents prune cleanup --dry-run

  # Just orphan skills
  agents prune cleanup skills

  # Just version dedup
  agents prune cleanup versions

  # Deduplicate versions for one agent only
  agents prune cleanup claude

  # Sweep every installed version's orphans, not only the defaults
  agents prune cleanup --all

  # Show the durable-trash notice
  agents prune cleanup trash --dry-run

  # Show the durable-session notice
  agents prune cleanup sessions --dry-run

  # Preview runs cleanup (keeping last 10)
  agents prune cleanup runs --dry-run

  # Keep only the last 5 runs per job
  agents prune cleanup runs --keep 5 -y

What's an orphan?
  A command, skill, or hook present inside a version home but missing from every
  configured source (project .agents/, central ~/.agents/, and any enabled extra
  repos). Usually leftovers from a resource that was deleted or moved but never
  reconciled into the version install.

Durability:
  Version directories are NEVER hard-deleted by agents-cli. Version prune and
  cleanup move them to ~/.agents/.history/trash/versions/<agent>/<version>/<timestamp>/.
  Session records are also durable; the sessions target remains only as a no-op
  compatibility shim.
`)
        .action(async (target, options) => {
        const parsed = parseTarget(target);
        if (parsed.stateType) {
            switch (parsed.stateType) {
                case 'trash':
                    await runTrashPrune(options);
                    break;
                case 'sessions':
                    await runSessionsPrune(options);
                    break;
                case 'runs':
                    await runRunsPrune(options);
                    break;
            }
            return;
        }
        if (parsed.resourceTypes.length > 0) {
            await runOrphanPrune(parsed.resourceTypes, options);
            if (parsed.includeVersions)
                console.log();
        }
        if (parsed.includeVersions) {
            const versionLabel = parsed.versionAgent ? ` for ${parsed.versionAgent}` : '';
            console.log(chalk.bold(`Duplicate versions${versionLabel}`));
            await pruneDuplicates(parsed.versionAgent, options.yes === true, options.dryRun === true);
        }
    });
}
