/**
 * HandlerEnvelope — unified success/error shape for all MCP tool handlers.
 *
 * Lifted out of runHandler.ts so any module (middleware, handlers, tests,
 * dashboard routes) can import the type without pulling in the full
 * runHandler runtime.  runHandler.ts re-exports these for back-compat.
 */
// ─── Pretty-print helpers ─────────────────────────────────────────────────────
const DASH_PORT = process.env.NEXUS_DASHBOARD_PORT ?? '3377';
/** Render an array of objects as an aligned ASCII table (max 20 rows × 6 cols). */
function renderTable(rows, maxRows = 20) {
    if (rows.length === 0)
        return '(empty)';
    const cols = Object.keys(rows[0]).slice(0, 6);
    const display = rows.slice(0, maxRows);
    const widths = cols.map(col => Math.max(col.length, ...display.map(r => String(r[col] ?? '').length)));
    const sep = '─'.repeat(widths.reduce((a, w) => a + w + 3, 1));
    const header = '│ ' + cols.map((c, i) => c.toUpperCase().padEnd(widths[i])).join(' │ ') + ' │';
    const dataRows = display.map(r => '│ ' + cols.map((c, i) => String(r[c] ?? '').slice(0, widths[i]).padEnd(widths[i])).join(' │ ') + ' │');
    const truncNote = rows.length > maxRows ? `\n  … ${rows.length - maxRows} more rows` : '';
    return [sep, header, sep, ...dataRows, sep].join('\n') + truncNote;
}
/** Build a compact timing + savings footer line for tool responses. */
function buildMcpFooter(meta) {
    if (process.env.NEXUS_MCP_RESPONSE_FOOTER !== '1')
        return '';
    const ms = meta.durationMs;
    const timing = ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
    return `\n\n─── nexus-prime · ${meta.toolName} · ${timing} · dashboard: http://localhost:${DASH_PORT}/#runtime ───`;
}
/** Format a plain object as key: value rows. */
function renderKV(obj, indent = '  ') {
    return Object.entries(obj)
        .filter(([, v]) => v !== null && v !== undefined && v !== '' && !Array.isArray(v))
        .map(([k, v]) => `${indent}${k.padEnd(22)} ${String(v).slice(0, 120)}`)
        .join('\n');
}
/** Detect if value is an array of objects suitable for table rendering. */
function isTableData(val) {
    return Array.isArray(val) && val.length > 0 && typeof val[0] === 'object' && val[0] !== null;
}
// ─── Serialisation helper ─────────────────────────────────────────────────────
/**
 * Convert a HandlerEnvelope into the MCP tool-call response shape.
 *
 * On success:
 *  - If data is already a ToolResult (`{ content: Array<...> }`), appended footer + pass-through.
 *  - If data is an array of objects, rendered as ASCII table.
 *  - If data is a plain object, rendered as key-value rows.
 *  - If data is a string, returned as-is with footer appended.
 *
 * On error, a compact human-readable error block is produced.
 */
export function envelopeToMcpResponse(envelope) {
    const footer = buildMcpFooter(envelope.meta);
    if (envelope.ok) {
        const data = envelope.data;
        // Pass-through: handler already returned a ToolResult-shaped object.
        if (data !== null &&
            data !== undefined &&
            typeof data === 'object' &&
            Array.isArray(data.content)) {
            const result = data;
            // Append footer to last text block
            const last = result.content[result.content.length - 1];
            if (last?.type === 'text') {
                last.text += footer;
            }
            return result;
        }
        let text;
        if (typeof data === 'string') {
            text = data + footer;
        }
        else if (isTableData(data)) {
            // Array of objects → ASCII table
            const noun = envelope.meta.toolName.replace('nexus_list_', '').replace('nexus_', '');
            text = `⚡ ${envelope.meta.toolName} · ${data.length} ${noun}\n\n${renderTable(data)}\n${footer}`;
        }
        else if (data !== null && data !== undefined && typeof data === 'object') {
            // Plain object → key-value rows (compact, no JSON noise)
            const obj = data;
            // Check for a nested array payload (e.g. { skills: [...] })
            const arrayKey = Object.keys(obj).find(k => isTableData(obj[k]));
            if (arrayKey) {
                const rows = obj[arrayKey];
                const rest = Object.entries(obj).filter(([k]) => k !== arrayKey);
                const kvSection = rest.length > 0 ? renderKV(Object.fromEntries(rest)) + '\n\n' : '';
                text = `⚡ ${envelope.meta.toolName} · ${rows.length} ${arrayKey}\n\n${kvSection}${renderTable(rows)}${footer}`;
            }
            else {
                // Flat KV display — avoids raw JSON blobs
                const kvText = renderKV(obj);
                text = `⚡ ${envelope.meta.toolName}\n\n${kvText || JSON.stringify(data, null, 2)}${footer}`;
            }
        }
        else {
            text = String(data ?? '') + footer;
        }
        return { content: [{ type: 'text', text }] };
    }
    // Error: compact human-readable format
    const err = envelope.error;
    const errText = [
        `✗ ${envelope.meta.toolName} failed · ${err.code}`,
        `  ${err.message}`,
        err.hint ? `  hint: ${err.hint}` : '',
        err.retriable ? '  retriable: yes' : '',
        `  duration: ${envelope.meta.durationMs}ms · attempts: ${envelope.meta.attempts}`,
        `  dashboard: http://localhost:${DASH_PORT}/#runtime`,
    ].filter(Boolean).join('\n');
    return { content: [{ type: 'text', text: errText }] };
}
