/**
 * Detect which MCP client is invoking nexus-prime.
 *
 * Three tiers (highest-priority first):
 *  1. setMcpClientName()    — from MCP initialize handshake clientInfo.name (most reliable)
 *  2. detectCallerFromEnv() — sync env-var detection, covers ~99% of real clients
 *  3. detectCallerAsync()   — async ps(1) fallback for edge cases (no env vars)
 *
 * The MCP adapter constructor uses tier-2 for immediate init, then the Server's
 * oninitialized callback calls setMcpClientName() (tier-1) once the handshake
 * completes.  detectCallerAsync() falls through only when tier-1 and tier-2 miss.
 */
import { execFile } from 'child_process';
const ENV_MAP = [
    [['CLAUDE_CODE', 'CLAUDE_SESSION_ID', 'CLAUDE_PROJECT_DIR'], 'claude-code'],
    [['CODEX_HOME', 'CODEX_SESSION'], 'codex'],
    [['CURSOR_HOME', 'CURSOR_SESSION'], 'cursor'],
    [['OPENCODE_HOME'], 'opencode'],
    [['WINDSURF_HOME', 'WINDSURF_SESSION'], 'windsurf'],
    [['HERMES_HOME', 'HERMES_SESSION'], 'hermes'],
    [['NANOCLAW_HOME', 'NANOCLAW_SESSION'], 'nanoclaw'],
    [['PICOCLAW_HOME', 'PICOCLAW_SESSION'], 'picoclaw'],
];
// Module-level store for the caller name obtained from the MCP initialize
// handshake.  Takes precedence over env-var and ps fallback once set.
let _mcpHandshakeName;
/**
 * Store the caller identity obtained from the MCP `initialize` handshake
 * `clientInfo.name` field.  Pass an empty string to clear.
 * Called by the MCP adapter's `oninitialized` callback.
 */
export function setMcpClientName(rawName) {
    if (!rawName) {
        _mcpHandshakeName = undefined;
        return;
    }
    const lower = rawName.toLowerCase();
    if (lower.includes('claude')) {
        _mcpHandshakeName = 'claude-code';
        return;
    }
    if (lower.includes('codex')) {
        _mcpHandshakeName = 'codex';
        return;
    }
    if (lower.includes('cursor')) {
        _mcpHandshakeName = 'cursor';
        return;
    }
    if (lower.includes('opencode')) {
        _mcpHandshakeName = 'opencode';
        return;
    }
    if (lower.includes('windsurf')) {
        _mcpHandshakeName = 'windsurf';
        return;
    }
    if (lower.includes('hermes')) {
        _mcpHandshakeName = 'hermes';
        return;
    }
    if (lower.includes('nanoclaw') || lower.includes('nano-claw')) {
        _mcpHandshakeName = 'nanoclaw';
        return;
    }
    if (lower.includes('picoclaw') || lower.includes('pico-claw')) {
        _mcpHandshakeName = 'picoclaw';
        return;
    }
    if (lower.includes('anticlaw') || lower.includes('openclaw')) {
        _mcpHandshakeName = 'openclaw';
        return;
    }
    _mcpHandshakeName = lower || 'mcp';
}
/** Instant env-variable detection. Safe to call in a constructor. */
export function detectCallerFromEnv() {
    for (const [keys, name] of ENV_MAP) {
        if (keys.some((k) => process.env[k]))
            return name;
    }
    if (process.env.MCP_CLIENT_NAME)
        return process.env.MCP_CLIENT_NAME.toLowerCase();
    return 'mcp';
}
let processScanUnavailable = false;
/** Full detection: MCP handshake name → env vars → ps(1) fallback. */
export async function detectCallerAsync() {
    // Tier 1: MCP initialize handshake (most authoritative — set after connect).
    if (_mcpHandshakeName)
        return _mcpHandshakeName;
    // Tier 2: env-var detection.
    const fromEnv = detectCallerFromEnv();
    if (fromEnv !== 'mcp')
        return fromEnv;
    // Tier 3: parent-process scan.
    const ppid = process.ppid;
    if (!ppid || processScanUnavailable)
        return 'mcp';
    try {
        const stdout = await readParentProcessName(ppid);
        const ps = stdout.trim().toLowerCase();
        if (ps.includes('claude'))
            return 'claude-code';
        if (ps.includes('codex'))
            return 'codex';
        if (ps.includes('cursor'))
            return 'cursor';
        if (ps.includes('opencode'))
            return 'opencode';
        if (ps.includes('windsurf'))
            return 'windsurf';
        if (ps.includes('hermes'))
            return 'hermes';
        if (ps.includes('nanoclaw') || ps.includes('nano-claw'))
            return 'nanoclaw';
        if (ps.includes('picoclaw') || ps.includes('pico-claw'))
            return 'picoclaw';
        if (ps.includes('antigravity') || ps.includes('openclaw'))
            return 'openclaw';
    }
    catch {
        processScanUnavailable = true;
    }
    return 'mcp';
}
function readParentProcessName(ppid) {
    return new Promise((resolve, reject) => {
        execFile('ps', ['-p', String(ppid), '-o', 'comm='], {
            encoding: 'utf8',
            timeout: 400,
            windowsHide: true,
        }, (error, stdout) => {
            if (error) {
                reject(error);
                return;
            }
            resolve(stdout);
        });
    });
}
