/**
 * Renderers — three output strategies for OmkEvent consumption.
 *
 * ThemeRenderer: rich terminal output using ThemePalette for semantic colors.
 * NlpRenderer: bilingual (KO/EN) structured NLP output for provider consumption.
 * JsonRenderer: structured JSON output for programmatic consumption.
 *
 * Invariant I-004: provider stdout MUST NOT bypass the router.
 */
import { t } from "../util/i18n.js";
// ─── Fallback ANSI helpers (used when no ThemePalette provided) ─────────────
const ESC = "\x1b[";
const RESET = `${ESC}0m`;
const DIM = `${ESC}2m`;
// opencode-style spinner frames
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
let spinnerIdx = 0;
function spinnerNext() {
    const frame = SPINNER_FRAMES[spinnerIdx % SPINNER_FRAMES.length];
    spinnerIdx++;
    return frame;
}
function fallbackColorize(text, ...codes) {
    return `${codes.join("")}${text}${RESET}`;
}
function progressBar(percent, width = 20) {
    const clamped = Math.max(0, Math.min(100, percent));
    const filled = Math.round((clamped / 100) * width);
    const empty = width - filled;
    const bar = `${"█".repeat(filled)}${"░".repeat(empty)}`;
    return `${bar} ${clamped.toFixed(0)}%`;
}
function timestamp() {
    return new Date().toISOString().slice(11, 19);
}
/** Strip ANSI escape sequences */
function stripAnsi(text) {
    return text.replace(/\x1b\[[0-9;]*m/g, "");
}
// ─── ThemePalette helpers ───────────────────────────────────────────────────
/**
 * Render text with a semantic token from the palette.
 * Falls back to plain text if no palette or palette doesn't support color.
 */
function themed(palette, token, text) {
    if (!palette || !palette.supportsColor)
        return text;
    return palette.render(token, text);
}
/**
 * Render text with dim styling.
 */
function dim(palette, text) {
    if (!palette || !palette.supportsColor)
        return fallbackColorize(text, DIM);
    return palette.render("dim", text);
}
export function createThemeRenderer(write = process.stdout.write.bind(process.stdout), palette) {
    function emit(line) {
        write(line + "\n");
    }
    return {
        renderTurnStarted(intent, provider) {
            // opencode-style: compact one-liner with dim timestamp
            const ts = dim(palette, timestamp());
            const icon = themed(palette, "success", "●");
            const intentColored = themed(palette, "info", intent);
            const providerColored = themed(palette, "agent", provider);
            emit(`  ${icon} ${ts} ${intentColored} → ${providerColored}`);
        },
        renderProgress(message, percent) {
            // opencode-style: spinner + compact message
            const spin = themed(palette, "warning", spinnerNext());
            const bar = percent !== undefined ? ` ${progressBar(percent)}` : "";
            emit(`  ${spin} ${themed(palette, "info", "signal active")} ${dim(palette, "//")} ${message}${bar}`);
        },
        renderMcpStatus(server, status) {
            // opencode-style: inline dot status
            let dot;
            switch (status) {
                case "connected":
                    dot = themed(palette, "success", "●");
                    break;
                case "failed":
                    dot = themed(palette, "error", "✖");
                    break;
                case "disabled":
                    dot = dim(palette, "○");
                    break;
                default: dot = themed(palette, "warning", "◌");
            }
            const srv = themed(palette, "tool", server);
            emit(`    ${dot} ${srv}`);
        },
        renderWarning(message) {
            const icon = themed(palette, "warning", "!");
            emit(`  ${icon} ${themed(palette, "warning", message)}`);
        },
        renderResult(content) {
            // opencode-style: clean output, no tags
            const lines = content.split("\n");
            for (const line of lines) {
                emit(`  ${line}`);
            }
        },
        renderError(message, recoverable) {
            // opencode-style: box overlay for errors
            const label = recoverable ? " Recoverable Error " : " Fatal Error ";
            const w = Math.max(stripAnsi(message).length + 4, 40);
            const top = `${"┌".padEnd(w + 1, "─")}┐`;
            const bot = `${"└".padEnd(w + 1, "─")}┘`;
            const labelLine = `│ ${themed(palette, "error", label)}${" ".repeat(Math.max(0, w - label.length - 1))}│`;
            const msgLine = `│ ${message}${" ".repeat(Math.max(0, w - stripAnsi(message).length - 1))}│`;
            emit("");
            emit(`  ${dim(palette, top)}`);
            emit(`  ${dim(palette, labelLine)}`);
            emit(`  ${dim(palette, msgLine)}`);
            emit(`  ${dim(palette, bot)}`);
            emit("");
        },
        renderTurnFinished(durationMs) {
            const ts = dim(palette, timestamp());
            const icon = themed(palette, "success", "●");
            const dur = durationMs >= 1000
                ? `${(durationMs / 1000).toFixed(1)}s`
                : `${durationMs}ms`;
            emit(`  ${icon} ${ts} ${dim(palette, "turn settled in")} ${themed(palette, "info", dur)}`);
        },
        renderStatusBar(provider, model, intent) {
            // opencode-style: bottom status bar
            const p = themed(palette, "agent", provider);
            const m = themed(palette, "task", model);
            const i = themed(palette, "info", intent);
            const sep = dim(palette, "│");
            emit("");
            emit(`  ${sep} ${p} ${sep} ${m} ${sep} ${i} ${sep}`);
        },
        flush() {
            // Theme renderer flushes on each emit; nothing buffered.
        },
    };
}
/**
 * Bilingual NLP message templates.
 * Uses the existing i18n system (t()) for KO/EN support.
 */
const nlpMessages = {
    turnStarted: (intent, provider) => t("nlp.turnStarted", intent, provider),
    progress: (message) => t("nlp.progress", message),
    progressWithPercent: (message, percent) => t("nlp.progressWithPercent", message, percent),
    mcpStatus: (server, status) => t("nlp.mcpStatus", server, status),
    warning: (message) => t("nlp.warning", message),
    result: (content) => t("nlp.result", content),
    recoverableError: (message) => t("nlp.recoverableError", message),
    fatalError: (message) => t("nlp.fatalError", message),
    turnFinished: (duration) => t("nlp.turnFinished", duration),
    event: (type) => t("nlp.event", type),
};
export function createNlpRenderer() {
    const lines = [];
    function renderEventData(data) {
        switch (data.kind) {
            case "turn_started":
                return nlpMessages.turnStarted(data.intent, data.provider);
            case "progress":
                return data.percent !== undefined
                    ? nlpMessages.progressWithPercent(data.message, data.percent.toFixed(0))
                    : nlpMessages.progress(data.message);
            case "mcp_status":
                return nlpMessages.mcpStatus(data.server, data.status);
            case "warning":
                return nlpMessages.warning(data.message);
            case "result":
                return nlpMessages.result(data.content);
            case "error":
                return data.recoverable
                    ? nlpMessages.recoverableError(data.message)
                    : nlpMessages.fatalError(data.message);
            case "turn_finished": {
                const dur = data.durationMs >= 1000
                    ? `${(data.durationMs / 1000).toFixed(1)}s`
                    : `${data.durationMs}ms`;
                return nlpMessages.turnFinished(dur);
            }
        }
    }
    return {
        render(event) {
            const text = event.data
                ? renderEventData(event.data)
                : nlpMessages.event(event.type);
            lines.push(text);
            return text;
        },
        flush() {
            const output = lines.join("\n");
            lines.length = 0;
            return output;
        },
    };
}
export function createJsonRenderer() {
    const records = [];
    return {
        render(event) {
            const record = {
                type: event.type,
                timestamp: event.timestamp,
                ...(event.turnId !== undefined ? { turnId: event.turnId } : {}),
                ...(event.data !== undefined ? { data: event.data } : {}),
            };
            records.push(record);
            return record;
        },
        flush() {
            const output = [...records];
            records.length = 0;
            return output;
        },
    };
}
