/**
 * Headless UI Handling — auto-response, progress formatting, and supervised stdin
 *
 * Handles extension UI requests (auto-responding in headless mode),
 * formats progress events for stderr output, and reads orchestrator
 * commands from stdin in supervised mode.
 */
// R6: hoisted regex — was recreated on every formatProgress call
const PROGRESS_IMPORTANT_RE = /^(committed:|verification gate:|milestone|blocked:)/i;
import { attachJsonlLineReader } from '@nerf/pi-coding-agent';
// ---------------------------------------------------------------------------
// ANSI Color Helpers
// ---------------------------------------------------------------------------
const _c = {
    reset: '\x1b[0m',
    bold: '\x1b[1m',
    dim: '\x1b[2m',
    italic: '\x1b[3m',
    red: '\x1b[31m',
    green: '\x1b[32m',
    yellow: '\x1b[33m',
    cyan: '\x1b[36m',
    gray: '\x1b[90m',
};
/** Build a no-op color map (all codes empty). */
function noColor() {
    const nc = {};
    for (const k of Object.keys(_c))
        nc[k] = '';
    return nc;
}
const colorsDisabled = !!process.env['NO_COLOR'] || !process.stderr.isTTY;
const c = colorsDisabled ? noColor() : _c;
// ---------------------------------------------------------------------------
// Tool-Arg Summarizer
// ---------------------------------------------------------------------------
/**
 * Produce a short human-readable summary of tool arguments.
 * Returns a string like "path/to/file.ts" or "grep pattern *.ts" — never the
 * full JSON blob.
 */
export function summarizeToolArgs(toolName, toolInput) {
    const name = String(toolName ?? '');
    const input = (toolInput && typeof toolInput === 'object') ? toolInput : {};
    // Helper: extract file path from either 'path' or 'file_path' (tools use both)
    const filePath = () => shortPath(input.path ?? input.file_path) || '';
    switch (name) {
        case 'Read':
        case 'read':
            return filePath();
        case 'Write':
        case 'write':
            return filePath();
        case 'Edit':
        case 'edit':
            return filePath();
        case 'hashline_edit':
            return filePath();
        case 'Bash':
        case 'bash': {
            const cmd = String(input.command ?? '');
            return cmd.length > 80 ? cmd.slice(0, 77) + '...' : cmd;
        }
        case 'async_bash': {
            const cmd = String(input.command ?? '');
            return cmd.length > 80 ? cmd.slice(0, 77) + '...' : cmd;
        }
        case 'await_job': {
            const jobs = input.jobs;
            if (Array.isArray(jobs) && jobs.length > 0)
                return jobs.join(', ');
            return '';
        }
        case 'cancel_job':
            return String(input.job_id ?? '');
        case 'Glob':
        case 'glob':
            return String(input.pattern ?? '');
        case 'find': {
            const pat = String(input.pattern ?? '');
            const p = shortPath(input.path);
            return p ? `${pat} in ${p}` : pat;
        }
        case 'Grep':
        case 'grep':
        case 'Search':
        case 'search': {
            const pat = String(input.pattern ?? '');
            const g = input.glob ? ` ${input.glob}` : '';
            return `${pat}${g}`;
        }
        case 'ls':
            return shortPath(input.path) || '';
        case 'lsp': {
            const action = String(input.action ?? '');
            const file = shortPath(input.file);
            const sym = input.symbol ? ` ${input.symbol}` : '';
            return file ? `${action} ${file}${sym}` : action;
        }
        case 'Task':
        case 'task': {
            const desc = String(input.description ?? input.prompt ?? '');
            return desc.length > 60 ? desc.slice(0, 57) + '...' : desc;
        }
        case 'subagent': {
            const agent = String(input.agent ?? '');
            const t = String(input.task ?? '');
            const summary = t.length > 50 ? t.slice(0, 47) + '...' : t;
            return agent ? `${agent}: ${summary}` : summary;
        }
        case 'browser_navigate':
            return String(input.url ?? '');
        default: {
            // NERF tools: show milestone/slice/task IDs when present
            if (name.startsWith('nerf_')) {
                return summarizeNerfTool(name, input);
            }
            // Fallback: show first string-valued key up to 60 chars
            for (const v of Object.values(input)) {
                if (typeof v === 'string' && v.length > 0) {
                    return v.length > 60 ? v.slice(0, 57) + '...' : v;
                }
            }
            return '';
        }
    }
}
/** Summarize NERF extension tool args into a compact identifier string. */
function summarizeNerfTool(name, input) {
    const parts = [];
    if (input.milestoneId)
        parts.push(String(input.milestoneId));
    if (input.sliceId)
        parts.push(String(input.sliceId));
    if (input.taskId)
        parts.push(String(input.taskId));
    if (parts.length > 0) {
        const id = parts.join('/');
        // For completion tools, add the one-liner if present
        if (name.includes('complete') && typeof input.oneLiner === 'string') {
            const ol = input.oneLiner.length > 50 ? input.oneLiner.slice(0, 47) + '...' : input.oneLiner;
            return `${id} ${ol}`;
        }
        return id;
    }
    // Fallback for NERF tools without IDs (e.g. nerf_decision_save)
    if (input.decision) {
        const d = String(input.decision);
        return d.length > 60 ? d.slice(0, 57) + '...' : d;
    }
    return '';
}
function shortPath(p) {
    if (typeof p !== 'string')
        return '';
    // Strip common CWD prefix to save space
    const cwd = process.cwd();
    if (p.startsWith(cwd + '/'))
        return p.slice(cwd.length + 1);
    // Strip /Users/*/Developer/ prefix
    return p.replace(/^\/Users\/[^/]+\/Developer\//, '');
}
// ---------------------------------------------------------------------------
// Format Duration
// ---------------------------------------------------------------------------
function formatDuration(ms) {
    if (ms < 1000)
        return `${ms}ms`;
    const s = (ms / 1000).toFixed(1);
    return `${s}s`;
}
// ---------------------------------------------------------------------------
// Extension UI Auto-Responder
// ---------------------------------------------------------------------------
export function handleExtensionUIRequest(event, client) {
    const { id, method } = event;
    switch (method) {
        case 'select': {
            // Lock-guard prompts list "View status" first, but headless needs "Force start"
            // to proceed. Detect by title and pick the force option.
            const title = String(event.title ?? '');
            let selected = event.options?.[0] ?? '';
            if (title.includes('Auto-mode is running') && event.options) {
                const forceOption = event.options.find(o => o.toLowerCase().includes('force start'));
                if (forceOption)
                    selected = forceOption;
            }
            client.sendUIResponse(id, { value: selected });
            break;
        }
        case 'confirm':
            client.sendUIResponse(id, { confirmed: true });
            break;
        case 'input':
            client.sendUIResponse(id, { value: '' });
            break;
        case 'editor':
            client.sendUIResponse(id, { value: event.prefill ?? '' });
            break;
        case 'notify':
        case 'setStatus':
        case 'setWidget':
        case 'setTitle':
        case 'set_editor_text':
            client.sendUIResponse(id, { value: '' });
            break;
        default:
            process.stderr.write(`[headless] Warning: unknown extension_ui_request method "${method}", cancelling\n`);
            client.sendUIResponse(id, { cancelled: true });
            break;
    }
}
// ---------------------------------------------------------------------------
// Progress Formatter
// ---------------------------------------------------------------------------
export function formatProgress(event, ctx) {
    const type = String(event.type ?? '');
    // Emit accumulated thinking preview before tool calls
    if (ctx.thinkingPreview) {
        // thinkingPreview is handled by the caller in headless.ts — it prepends
        // the thinking line before the current event's line. We return the thinking
        // line as a prefix joined with newline.
    }
    switch (type) {
        case 'tool_execution_start': {
            if (!ctx.verbose)
                return null;
            const name = String(event.toolName ?? 'unknown');
            const args = summarizeToolArgs(event.toolName, event.args);
            const argStr = args ? ` ${c.dim}${args}${c.reset}` : '';
            return `  ${c.dim}[tool]${c.reset}    ${name}${argStr}`;
        }
        case 'tool_execution_end': {
            if (!ctx.verbose)
                return null;
            const name = String(event.toolName ?? 'unknown');
            const durationStr = ctx.toolDuration != null ? ` ${c.dim}${formatDuration(ctx.toolDuration)}${c.reset}` : '';
            if (ctx.isError) {
                return `  ${c.red}[tool]    ${name} error${c.reset}${durationStr}`;
            }
            return `  ${c.dim}[tool]    ${name} done${c.reset}${durationStr}`;
        }
        case 'agent_start':
            return `${c.dim}[agent]   Session started${c.reset}`;
        case 'agent_end': {
            let line = `${c.dim}[agent]   Session ended${c.reset}`;
            if (ctx.lastCost) {
                const cost = `$${ctx.lastCost.costUsd.toFixed(4)}`;
                const tokens = `${ctx.lastCost.inputTokens + ctx.lastCost.outputTokens} tokens`;
                line += ` ${c.dim}(${cost}, ${tokens})${c.reset}`;
            }
            return line;
        }
        case 'extension_ui_request': {
            const method = String(event.method ?? '');
            if (method === 'notify') {
                const msg = String(event.message ?? '');
                if (!msg)
                    return null;
                // Bold important notifications
                const isImportant = PROGRESS_IMPORTANT_RE.test(msg);
                return isImportant
                    ? `${c.bold}[nerf]     ${msg}${c.reset}`
                    : `[nerf]     ${msg}`;
            }
            if (method === 'setStatus') {
                // Parse statusKey for phase transitions
                const statusKey = String(event.statusKey ?? '');
                const msg = String(event.message ?? '');
                if (!statusKey && !msg)
                    return null; // suppress empty status lines
                // Show meaningful phase transitions
                if (statusKey) {
                    const label = parsePhaseLabel(statusKey, msg);
                    if (label)
                        return `${c.cyan}[phase]   ${label}${c.reset}`;
                }
                // Fallback: show message if non-empty
                if (msg)
                    return `${c.cyan}[phase]   ${msg}${c.reset}`;
                return null;
            }
            return null;
        }
        default:
            return null;
    }
}
/**
 * Format a thinking preview line from accumulated LLM text deltas.
 * Used as a fallback when streaming is not enabled — shows a truncated one-liner.
 */
export function formatThinkingLine(text) {
    const trimmed = text.replace(/\s+/g, ' ').trim();
    const truncated = trimmed.length > 120 ? trimmed.slice(0, 117) + '...' : trimmed;
    return `${c.dim}${c.italic}[thinking] ${truncated}${c.reset}`;
}
// ---------------------------------------------------------------------------
// Streaming Text / Thinking Formatters
// ---------------------------------------------------------------------------
/**
 * Format a text_start marker — printed once when the assistant begins a text block.
 */
export function formatTextStart() {
    return `${c.dim}[text]${c.reset}`;
}
/**
 * Format a text_end marker — printed after the last text_delta.
 */
export function formatTextEnd() {
    return ''; // empty — newline handled by caller
}
/**
 * Format a thinking_start marker.
 */
export function formatThinkingStart() {
    return `${c.dim}${c.italic}[thinking]${c.reset}`;
}
/**
 * Format a thinking_end marker.
 */
export function formatThinkingEnd() {
    return ''; // empty — newline handled by caller
}
/**
 * Format a cost line (used for periodic cost updates in verbose mode).
 */
export function formatCostLine(costUsd, inputTokens, outputTokens) {
    return `${c.dim}[cost]    $${costUsd.toFixed(4)} (${inputTokens + outputTokens} tokens)${c.reset}`;
}
// ---------------------------------------------------------------------------
// Phase Label Parser
// ---------------------------------------------------------------------------
/**
 * Parse a statusKey into a human-readable phase label.
 * statusKey format varies but common patterns:
 *   "milestone:M1", "slice:S1.1", "task:T1.1.1", "phase:discuss", etc.
 */
function parsePhaseLabel(statusKey, message) {
    // Direct phase/milestone/slice/task keys
    const parts = statusKey.split(':');
    if (parts.length >= 2) {
        const [kind, value] = parts;
        switch (kind.toLowerCase()) {
            case 'milestone':
                return `Milestone ${value}${message ? ' -- ' + message : ''}`;
            case 'slice':
                return `Slice ${value}${message ? ' -- ' + message : ''}`;
            case 'task':
                return `Task ${value}${message ? ' -- ' + message : ''}`;
            case 'phase':
                return `Phase: ${value}${message ? ' -- ' + message : ''}`;
            default:
                return `${kind}: ${value}${message ? ' -- ' + message : ''}`;
        }
    }
    // Single-word status keys with a message
    if (message)
        return `${statusKey}: ${message}`;
    return statusKey || null;
}
// ---------------------------------------------------------------------------
// Supervised Stdin Reader
// ---------------------------------------------------------------------------
export function startSupervisedStdinReader(client, onResponse) {
    return attachJsonlLineReader(process.stdin, (line) => {
        let msg;
        try {
            msg = JSON.parse(line);
        }
        catch {
            process.stderr.write(`[headless] Warning: invalid JSON from orchestrator stdin, skipping\n`);
            return;
        }
        const type = String(msg.type ?? '');
        switch (type) {
            case 'extension_ui_response': {
                const id = String(msg.id ?? '');
                const value = msg.value !== undefined ? String(msg.value) : undefined;
                const confirmed = typeof msg.confirmed === 'boolean' ? msg.confirmed : undefined;
                const cancelled = typeof msg.cancelled === 'boolean' ? msg.cancelled : undefined;
                client.sendUIResponse(id, { value, confirmed, cancelled });
                if (id) {
                    onResponse(id);
                }
                break;
            }
            case 'prompt':
                client.prompt(String(msg.message ?? ''));
                break;
            case 'steer':
                client.steer(String(msg.message ?? ''));
                break;
            case 'follow_up':
                client.followUp(String(msg.message ?? ''));
                break;
            default:
                process.stderr.write(`[headless] Warning: unknown message type "${type}" from orchestrator stdin\n`);
                break;
        }
    });
}
