/**
 * Model catalog inspection command.
 *
 * Registers the hidden `agents models` command for listing models
 * supported by installed agent versions. Extracts model catalogs from
 * each agent's CLI bundle and displays IDs, aliases, and metadata.
 */
import chalk from 'chalk';
import * as fs from 'fs';
import { homeDir } from '../lib/platform/index.js';
import { resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
import { listInstalledVersions, getGlobalDefault, resolveVersion, resolveVersionAlias } from '../lib/installations/versions.js';
import { getModelCatalog, locateModelSource } from '../lib/models.js';
import { resolveTierMap, MODEL_TIERS } from '../lib/model-tiers.js';
import { setTierOverride, clearTierOverride, listTierOverrides } from '../lib/model-tier-overrides.js';
import { getModelPricing } from '../lib/pricing/index.js';
import { terminalWidth, truncateToWidth, stringWidth } from '../lib/session/width.js';
import { wrapJoined } from './inspect.js';
import { setHelpSections } from '../lib/help.js';
import { formatRunDefaultEntry, listRunDefaults, parseRunDefaultSelector, setRunDefault, } from '../lib/run-defaults.js';
const MODEL_CAPABLE_AGENTS = ['claude', 'codex', 'opencode', 'cursor', 'openclaw', 'antigravity', 'kimi', 'grok', 'droid'];
/**
 * Agents that don't necessarily install under ~/.agents/versions (cursor ships
 * via a curl script). For these, fall back to the PATH binary and synthesize
 * a version label from the install path so cache keys stay stable.
 */
const PATH_ONLY_AGENTS = new Set(['cursor']);
/** Derive a version label from the PATH-installed binary location for agents without managed versions. */
function fallbackPathVersion(agent) {
    const src = locateModelSource(agent, 'unresolved');
    if (!src)
        return null;
    let real = src.path;
    try {
        real = fs.realpathSync(src.path);
    }
    catch {
        /* keep symlink path */
    }
    const m = real.match(/\/versions\/([^/]+)\//);
    return m ? m[1] : 'installed';
}
/** Register the `agents models` command + its `tier` override subcommands. */
export function registerModelsCommand(program) {
    const models = program
        .command('models [agentSpec]')
        .description('Show the cost-tier map (cheap|default|best|ultra) for installed harnesses; pin overrides with `tier set`.')
        .option('--all', 'Show the full raw model catalog, not just the tier map')
        .option('--cloud', 'Show per-cloud IDs (Claude only; implies --all)')
        .option('--reasoning', 'Show reasoning levels per model (Codex only; implies --all)')
        .option('--json', 'Output catalog + tiers as JSON')
        .action(async (agentSpec, options) => {
        const targets = await resolveTargets(agentSpec);
        if (targets.length === 0)
            process.exit(1);
        if (options.json) {
            const out = targets.map(({ agent, version }) => ({
                agent,
                version,
                catalog: getModelCatalog(agent, version),
                tiers: resolveTierMap(agent, version),
            }));
            console.log(JSON.stringify(out, null, 2));
            return;
        }
        const all = options.all || options.cloud || options.reasoning;
        let printed = 0;
        for (const { agent, version, isDefault } of targets) {
            if (printed > 0)
                console.log();
            printCatalog(agent, version, isDefault, { ...options, all });
            printed++;
        }
        if (!agentSpec && !all) {
            console.log(chalk.gray('\n  `agents models <agent>` for one harness · `--all` for the full model list'));
            console.log(chalk.gray('  `agents models tier set <agent> <tier> <model>` to override a tier'));
            console.log(chalk.gray('  Permission modes (--mode plan|edit|auto|skip): `agents modes <agent>`'));
        }
    });
    // `models set` — the ergonomic setter for per-agent/version run defaults. It
    // reads and writes the same store as `agents config set run.<agent@version>.*`
    // (agents.yaml -> run.defaults), so the two stay consistent — `set` is just the
    // short front door, nested here because `models` owns model/mode concerns.
    const set = models
        .command('set [selector]')
        .description('Set the default model/mode an agent version uses for `agents run`')
        .option('--model <model>', 'Default model or model alias, forwarded via --model')
        .option('--mode <mode>', "Default mode: plan, edit, auto, skip. 'full' accepted as alias for skip.")
        .action((selector, options) => {
        try {
            const hasFlags = options.model !== undefined || options.mode !== undefined;
            if (!selector) {
                if (hasFlags) {
                    throw new Error('Selector is required when passing --model/--mode. Example: agents models set claude@2.1.220 --model opus-5');
                }
                const entries = listRunDefaults();
                if (entries.length === 0) {
                    console.log(chalk.gray('No agent defaults configured.'));
                    console.log(chalk.gray('Set one with: agents models set claude@2.1.220 --model opus-5'));
                    return;
                }
                console.log(chalk.bold('Agent Defaults\n'));
                for (const entry of entries) {
                    console.log(`  ${formatRunDefaultEntry(entry)}`);
                }
                return;
            }
            if (!hasFlags) {
                const parsed = parseRunDefaultSelector(selector);
                const entry = listRunDefaults().find((e) => e.selector === parsed.selector);
                if (!entry || (!entry.defaults.mode && !entry.defaults.model)) {
                    console.log(chalk.gray(`No default set for ${parsed.selector}.`));
                    console.log(chalk.gray(`Set one with: agents models set ${selector} --model <model>`));
                    return;
                }
                console.log(`  ${formatRunDefaultEntry(entry)}`);
                return;
            }
            const entry = setRunDefault(selector, {
                ...(options.mode !== undefined ? { mode: options.mode } : {}),
                ...(options.model !== undefined ? { model: options.model } : {}),
            });
            console.log(chalk.green('Set default:'));
            console.log(`  ${formatRunDefaultEntry(entry)}`);
        }
        catch (err) {
            console.error(chalk.red(err.message));
            process.exit(1);
        }
    });
    setHelpSections(set, {
        examples: `
      agents models set claude@2.1.220 --model opus-5
      agents models set 'claude:*' --mode auto --model opus
      agents models set claude@2.1.220
      agents models set
    `,
        notes: `
      Selectors use <agent>@<version> or <agent>:<version>; * matches all versions.
      Exact selectors override wildcard selectors field by field.
      Writes the same store as 'agents config set run.<agent@version>.*'. Explicit flags on
      'agents run' always win over configured defaults.
    `,
    });
    // Override subcommands. These WRITE agents.yaml so the user never hand-edits it;
    // resolution is exact `<agent>@<version>` over `<agent>` over the auto guess.
    const tierDeprecation = chalk.yellow('Deprecation: `agents models tier` is replaced by `agents config set run.<agent@version>.tier.<tier>`.');
    const tier = models
        .command('tier')
        .description('Override which model a cost tier resolves to (per harness, or per agent@version).');
    tier
        .command('set <selector> <tier> <model>')
        .description('Pin a tier to a model. selector: <agent> or <agent>@<version> (e.g. kimi, kimi@0.19.2).')
        .action((selector, tierName, model) => {
        try {
            console.warn(tierDeprecation);
            const entry = setTierOverride(selector, tierName, model);
            console.log(chalk.green(`✓ ${entry.selector}  ${chalk.cyan(tierName.toLowerCase())} → ${chalk.bold(model)}`));
        }
        catch (err) {
            console.error(chalk.red(err.message));
            process.exit(1);
        }
    });
    tier
        .command('clear <selector> [tier]')
        .description('Clear one tier (or all tiers) back to the auto guess.')
        .action((selector, tierName) => {
        try {
            console.warn(tierDeprecation);
            const changed = clearTierOverride(selector, tierName);
            console.log(changed ? chalk.green(`✓ cleared ${selector}${tierName ? ` ${tierName}` : ''}`) : chalk.gray('nothing to clear'));
        }
        catch (err) {
            console.error(chalk.red(err.message));
            process.exit(1);
        }
    });
    tier
        .command('list')
        .description('List all configured tier overrides.')
        .action(() => {
        console.warn(tierDeprecation);
        const entries = listTierOverrides();
        if (entries.length === 0) {
            console.log(chalk.gray('No tier overrides — every tier uses the auto guess. Set one with `agents models tier set`.'));
            return;
        }
        for (const e of entries) {
            const parts = Object.entries(e.tiers).map(([t, m]) => `${chalk.cyan(t)}=${m}`);
            console.log(`${chalk.bold(e.selector.padEnd(18))} ${parts.join('  ')}`);
        }
    });
}
/** Resolve the agent spec into one or more (agent, version) pairs to inspect. */
async function resolveTargets(agentSpec) {
    if (!agentSpec) {
        const targets = [];
        for (const agent of MODEL_CAPABLE_AGENTS) {
            let version = getGlobalDefault(agent) || (listInstalledVersions(agent)[0] ?? null);
            if (!version && PATH_ONLY_AGENTS.has(agent)) {
                version = fallbackPathVersion(agent);
            }
            if (version) {
                targets.push({ agent, version, isDefault: true });
            }
            else {
                // Surface the gap instead of silently dropping the agent -- an
                // uninstalled model-capable agent should tell the user how to add it.
                console.error(chalk.gray(`${agentLabel(agent)}: not installed (run 'agents add ${agent}@latest')`));
            }
        }
        if (targets.length === 0) {
            console.error(chalk.yellow('No installed agent versions found. Run `agents add claude@latest` to install one.'));
        }
        return targets;
    }
    const [agentName, versionSpec] = agentSpec.split('@');
    const agent = resolveAgentName(agentName);
    if (!agent) {
        console.error(chalk.red(formatAgentError(agentName, MODEL_CAPABLE_AGENTS)));
        return [];
    }
    if (!MODEL_CAPABLE_AGENTS.includes(agent)) {
        console.error(chalk.yellow(`Model catalog extraction is only supported for: ${MODEL_CAPABLE_AGENTS.join(', ')}`));
        console.error(chalk.gray(`Other agents pass --model through without validation.`));
        return [];
    }
    if (versionSpec === 'all') {
        return listInstalledVersions(agent).map((v) => ({
            agent,
            version: v,
            isDefault: v === getGlobalDefault(agent),
        }));
    }
    const aliasedVersion = resolveVersionAlias(agent, versionSpec);
    let version = aliasedVersion || resolveVersion(agent, process.cwd()) || getGlobalDefault(agent);
    if (!version && PATH_ONLY_AGENTS.has(agent)) {
        version = fallbackPathVersion(agent);
    }
    if (!version) {
        console.error(chalk.red(`No version of ${agent} is installed. Try \`agents add ${agent}@latest\`.`));
        return [];
    }
    return [{ agent, version, isDefault: version === getGlobalDefault(agent) }];
}
/** Print the model catalog for a single agent version with optional cloud/reasoning details. */
function printCatalog(agent, version, isDefault, options) {
    const tag = isDefault ? chalk.gray(' (default)') : '';
    const header = `${agentLabel(agent)} ${chalk.bold(version)}${tag}`;
    console.log(header);
    // Cost tiers first -- the thing an orchestrating agent reads to pick a model.
    printTiers(agent, version);
    const src = locateModelSource(agent, version);
    if (!src) {
        if (agent === 'droid') {
            // Droid has no extractable catalog (no models CLI/API/config); the curated
            // tier map above is the whole surface.
            console.log(chalk.gray('  (Droid has no model list command; tiers are a curated, credit-multiplier map.)'));
            return;
        }
        console.log(chalk.yellow(`  Could not locate model source for ${agent}@${version}.`));
        console.log(chalk.gray(`  Expected the agent's CLI bundle or native binary under ~/.agents/.history/versions/${agent}/${version}/.`));
        return;
    }
    const catalog = getModelCatalog(agent, version);
    if (!catalog || catalog.models.length === 0) {
        console.log(chalk.yellow(`  No models extracted from ${src.kind} at ${src.path}.`));
        return;
    }
    // The tier map above is what an agent reads. Keep the raw catalog behind --all
    // so `agents models` stays a scannable menu instead of a 30-id dump.
    if (!options.all) {
        console.log(chalk.gray(`  ${catalog.models.length} models · \`agents models ${agent} --all\` for the full list`));
        return;
    }
    console.log(chalk.gray(formatModelSourceLine(src.kind, shortPath(src.path))));
    if (Object.keys(catalog.aliases).length > 0) {
        const parts = Object.entries(catalog.aliases).map(([alias, id]) => `${chalk.cyan(alias)}=${id}`);
        for (const line of formatModelAliasLines(parts))
            console.log(line);
    }
    console.log();
    for (const model of catalog.models) {
        const star = model.isDefault ? chalk.green('*') : ' ';
        console.log(formatModelSummaryLine(star, model.id, model.displayName, model.alias));
        if (model.description) {
            const descPrefix = '      ';
            console.log(chalk.gray(descPrefix + truncateToWidth(model.description, Math.max(1, terminalWidth() - stringWidth(descPrefix)))));
        }
        if (options.cloud && model.perCloud) {
            const c = model.perCloud;
            const fields = [];
            if (c.bedrock)
                fields.push(`bedrock=${c.bedrock}`);
            if (c.vertex)
                fields.push(`vertex=${c.vertex}`);
            if (c.foundry)
                fields.push(`foundry=${c.foundry}`);
            if (c.anthropicAws && c.anthropicAws !== c.firstParty)
                fields.push(`anthropicAws=${c.anthropicAws}`);
            if (c.mantle)
                fields.push(`mantle=${c.mantle}`);
            if (fields.length > 0) {
                for (const f of fields)
                    console.log(chalk.gray(`      ${f}`));
            }
        }
        if (options.reasoning && model.reasoningLevels && model.reasoningLevels.length > 0) {
            const levels = model.reasoningLevels.map((l) => l.effort === model.defaultReasoningLevel ? chalk.cyan(`${l.effort}*`) : l.effort);
            console.log(chalk.gray(`      reasoning: `) + levels.join(', '));
        }
    }
}
/** Rough blended $/Mtok label for a model id, or '' when unpriced. */
function priceLabel(id) {
    const p = getModelPricing(id);
    if (!p)
        return chalk.gray('  --');
    const perM = (p.inputPerToken + p.outputPerToken) * 1e6;
    return chalk.gray(`  ~$${perM.toFixed(0)}/Mtok`);
}
/** Print the cheap/default/best/ultra tier map for an (agent, version). */
function printTiers(agent, version) {
    const map = resolveTierMap(agent, version);
    if (!MODEL_TIERS.some((t) => map[t].model))
        return;
    console.log(chalk.gray('  tiers:'));
    for (const t of MODEL_TIERS) {
        const r = map[t];
        if (!r.model)
            continue;
        const eff = r.effort ? chalk.gray(` @${r.effort}`) : '';
        const clamp = r.clampedFrom ? chalk.gray(' (clamped)') : '';
        const over = r.source === 'override' ? chalk.yellow(' [override]') : '';
        console.log(`    ${chalk.cyan(t.padEnd(8))} ${chalk.bold(r.model)}${eff}${priceLabel(r.model)}${clamp}${over}`);
    }
    console.log();
}
/** Abbreviate a path by replacing the home directory with ~. */
function shortPath(p) {
    return p.replace(homeDir(), '~');
}
export function formatModelSourceLine(kind, sourcePath, width = terminalWidth()) {
    const prefix = `  source: ${kind} (`;
    const suffix = ')';
    const room = Math.max(1, width - stringWidth(prefix) - stringWidth(suffix));
    return prefix + truncateToWidth(sourcePath, room) + suffix;
}
export function formatModelAliasLines(parts, width = terminalWidth()) {
    return wrapJoined(chalk.gray('  aliases: '), parts, ', ', width);
}
export function formatModelSummaryLine(star, id, displayName, alias, width = terminalWidth()) {
    const display = displayName && displayName !== id ? ` (${displayName})` : '';
    const aliasTag = alias ? ` [${alias}]` : '';
    const prefix = `  ${star} `;
    const plain = `${id}${display}${aliasTag}`;
    if (stringWidth(plain) > Math.max(1, width - stringWidth(prefix))) {
        return prefix + truncateToWidth(plain, Math.max(1, width - stringWidth(prefix)));
    }
    return prefix + chalk.bold(id) + chalk.gray(display) + chalk.cyan(aliasTag);
}
