import { execFile } from 'node:child_process';
import { readFile as defaultReadFile } from 'node:fs/promises';
import { join } from 'node:path';
import { promisify } from 'node:util';
import { buildCommandContext, } from '../../app/command-context.js';
import { readGlobalOptions } from '../shared/shared.utils.js';
import { classifyModelFamily, } from '../../providers/identity/family.js';
import { Command } from 'commander';
const execFileAsync = promisify(execFile);
async function runCursorAgent(args, options) {
    try {
        const { stdout, stderr } = await execFileAsync('cursor-agent', args, {
            cwd: options.cwd,
            env: options.env,
            maxBuffer: 1024 * 1024,
            timeout: 10_000,
        });
        return {
            ok: true,
            stdout: String(stdout),
            stderr: String(stderr),
        };
    }
    catch (error) {
        const failure = error;
        return {
            ok: false,
            stdout: String(failure.stdout ?? ''),
            stderr: String(failure.stderr ?? failure.message),
        };
    }
}
const DEFAULT_DEPENDENCIES = {
    buildCommandContext,
    runCursorAgent,
    readFile: async (path, encoding) => defaultReadFile(path, encoding),
    env: process.env,
};
function apiKeyArgs(env) {
    const apiKey = env.CURSOR_API_KEY;
    // p01 live verification showed the argv flag can bypass a locked login
    // keychain for catalog probes; keep using it even though argv is visible to
    // local process inspection for the short-lived helper process.
    return typeof apiKey === 'string' && apiKey.length > 0
        ? ['--api-key', apiKey]
        : [];
}
function buildModelsArgs(env) {
    return [...apiKeyArgs(env), 'models'];
}
function buildListModelsArgs(env) {
    return [...apiKeyArgs(env), '--list-models'];
}
function buildInitEventArgs(env) {
    return [
        ...apiKeyArgs(env),
        '-p',
        '--mode',
        'ask',
        '--trust',
        '--output-format',
        'stream-json',
        'ok',
    ];
}
function stripMarkers(displayName) {
    return displayName.replace(/\s+\((current|default)\)/gi, '').trim();
}
function parseCatalog(stdout) {
    const entries = [];
    for (const line of stdout.split(/\r?\n/)) {
        const match = line.match(/^(\S+)\s+-\s+(.+)$/);
        if (!match) {
            continue;
        }
        const [, slug, display] = match;
        if (!slug || !display) {
            continue;
        }
        entries.push({
            slug,
            displayName: stripMarkers(display),
            current: /\(current\)/i.test(display),
        });
    }
    return entries;
}
function mergeCatalogEntries(left, right) {
    const bySlug = new Map();
    for (const entry of [...left, ...right]) {
        const existing = bySlug.get(entry.slug);
        bySlug.set(entry.slug, {
            ...entry,
            current: entry.current || existing?.current === true,
        });
    }
    return [...bySlug.values()];
}
function currentCatalogEntry(entries) {
    return entries.find((entry) => entry.current);
}
function isSingleToken(value) {
    return /^\S+$/.test(value);
}
function mapThroughCatalog(value, catalog) {
    const normalized = value.trim();
    const match = catalog.find((entry) => entry.slug === normalized || entry.displayName === normalized);
    if (match) {
        return match.slug;
    }
    if (catalog.length > 0) {
        return undefined;
    }
    return isSingleToken(normalized) ? normalized : undefined;
}
function target(value, source, provenance, rawValue) {
    return {
        value,
        source,
        provenance,
        family: classifyModelFamily({ value }),
        ...(rawValue && rawValue !== value ? { rawValue } : {}),
    };
}
function unknown(source = 'unknown', rawValue) {
    return target('unknown', source, source === 'unknown' ? 'unknown' : 'inferred', rawValue);
}
function parseInitEventModel(stdout) {
    for (const line of stdout.split(/\r?\n/)) {
        const trimmed = line.trim();
        if (trimmed.length === 0) {
            continue;
        }
        try {
            const parsed = JSON.parse(trimmed);
            if (typeof parsed.model === 'string' && parsed.model.trim().length > 0) {
                return parsed.model.trim();
            }
        }
        catch {
            continue;
        }
    }
    return undefined;
}
function readConfigModel(raw) {
    const parsed = JSON.parse(raw);
    return typeof parsed.model === 'string' && parsed.model.trim().length > 0
        ? parsed.model.trim()
        : undefined;
}
export async function probeCursorCurrentTarget(options) {
    const dependencies = {
        runCursorAgent,
        readFile: async (path, encoding) => defaultReadFile(path, encoding),
        env: process.env,
        ...options.dependencies,
    };
    const env = dependencies.env ?? process.env;
    const runOptions = { cwd: options.cwd, env };
    let catalog = [];
    const modelsResult = await dependencies.runCursorAgent(buildModelsArgs(env), runOptions);
    if (modelsResult.ok) {
        catalog = mergeCatalogEntries(catalog, parseCatalog(modelsResult.stdout));
        const current = currentCatalogEntry(catalog);
        if (current) {
            return target(current.slug, 'cursor-agent models', 'inferred');
        }
    }
    const listResult = await dependencies.runCursorAgent(buildListModelsArgs(env), runOptions);
    if (listResult.ok) {
        const listEntries = parseCatalog(listResult.stdout);
        catalog = mergeCatalogEntries(catalog, listEntries);
        const current = currentCatalogEntry(listEntries);
        if (current) {
            return target(current.slug, '--list-models', 'inferred');
        }
    }
    const initResult = await dependencies.runCursorAgent(buildInitEventArgs(env), runOptions);
    if (initResult.ok) {
        const rawModel = parseInitEventModel(initResult.stdout);
        if (rawModel) {
            const mapped = mapThroughCatalog(rawModel, catalog);
            return mapped
                ? target(mapped, 'init-event', 'inferred', rawModel)
                : unknown('init-event', rawModel);
        }
    }
    try {
        const rawConfig = await dependencies.readFile(join(options.home, '.cursor', 'cli-config.json'), 'utf8');
        const rawModel = readConfigModel(rawConfig);
        if (rawModel) {
            const mapped = mapThroughCatalog(rawModel, catalog);
            return mapped
                ? target(mapped, 'cli-config', 'inferred', rawModel)
                : unknown('cli-config', rawModel);
        }
    }
    catch {
        return unknown();
    }
    return unknown();
}
function emitResult(context, result) {
    if (context.json) {
        context.logger.json(result);
        return;
    }
    context.logger.info(`${result.value} source=${result.source} provenance=${result.provenance}`);
}
export function createCursorCurrentTargetCommand(overrides = {}) {
    const dependencies = {
        ...DEFAULT_DEPENDENCIES,
        ...overrides,
    };
    return new Command('cursor-current-target')
        .description('Probe the current Cursor model target')
        .option('--json', 'Output JSON')
        .action(async (_options, command) => {
        const context = dependencies.buildCommandContext(readGlobalOptions(command));
        const result = await probeCursorCurrentTarget({
            cwd: context.cwd,
            home: context.home,
            dependencies,
        });
        emitResult(context, result);
    });
}
