import { formatRawAssistantErrorForUi } from "../agents/pi-embedded-helpers.js";
import { stripInboundMetadata } from "../auto-reply/reply/strip-inbound-meta.js";
import { stripAnsi } from "../terminal/ansi.js";
import { formatTokenCount } from "../utils/usage-format.js";
const REPLACEMENT_CHAR_RE = /\uFFFD/g;
const MAX_TOKEN_CHARS = 32;
const LONG_TOKEN_RE = /\S{33,}/g;
const LONG_TOKEN_TEST_RE = /\S{33,}/;
const BINARY_LINE_REPLACEMENT_THRESHOLD = 12;
function hasControlChars(text) {
    for (const char of text) {
        const code = char.charCodeAt(0);
        const isAsciiControl = code <= 0x1f && code !== 0x09 && code !== 0x0a && code !== 0x0d;
        const isC1Control = code >= 0x7f && code <= 0x9f;
        if (isAsciiControl || isC1Control) {
            return true;
        }
    }
    return false;
}
function stripControlChars(text) {
    if (!hasControlChars(text)) {
        return text;
    }
    let sanitized = "";
    for (const char of text) {
        const code = char.charCodeAt(0);
        const isAsciiControl = code <= 0x1f && code !== 0x09 && code !== 0x0a && code !== 0x0d;
        const isC1Control = code >= 0x7f && code <= 0x9f;
        if (!isAsciiControl && !isC1Control) {
            sanitized += char;
        }
    }
    return sanitized;
}
function chunkToken(token, maxChars) {
    if (token.length <= maxChars) {
        return [token];
    }
    const chunks = [];
    for (let i = 0; i < token.length; i += maxChars) {
        chunks.push(token.slice(i, i + maxChars));
    }
    return chunks;
}
function redactBinaryLikeLine(line) {
    const replacementCount = (line.match(REPLACEMENT_CHAR_RE) || []).length;
    if (replacementCount >= BINARY_LINE_REPLACEMENT_THRESHOLD &&
        replacementCount * 2 >= line.length) {
        return "[binary data omitted]";
    }
    return line;
}
export function sanitizeRenderableText(text) {
    if (!text) {
        return text;
    }
    const hasAnsi = text.includes("\u001b");
    const hasReplacementChars = text.includes("\uFFFD");
    const hasLongTokens = LONG_TOKEN_TEST_RE.test(text);
    const hasControls = hasControlChars(text);
    if (!hasAnsi && !hasReplacementChars && !hasLongTokens && !hasControls) {
        return text;
    }
    const withoutAnsi = hasAnsi ? stripAnsi(text) : text;
    const withoutControlChars = hasControls ? stripControlChars(withoutAnsi) : withoutAnsi;
    const redacted = hasReplacementChars
        ? withoutControlChars
            .split("\n")
            .map((line) => redactBinaryLikeLine(line))
            .join("\n")
        : withoutControlChars;
    return LONG_TOKEN_TEST_RE.test(redacted)
        ? redacted.replace(LONG_TOKEN_RE, (token) => chunkToken(token, MAX_TOKEN_CHARS).join(" "))
        : redacted;
}
export function resolveFinalAssistantText(params) {
    const finalText = params.finalText ?? "";
    if (finalText.trim()) {
        return finalText;
    }
    const streamedText = params.streamedText ?? "";
    if (streamedText.trim()) {
        return streamedText;
    }
    return "(no output)";
}
export function composeThinkingAndContent(params) {
    const thinkingText = params.thinkingText?.trim() ?? "";
    const contentText = params.contentText?.trim() ?? "";
    const parts = [];
    if (params.showThinking && thinkingText) {
        parts.push(`[thinking]\n${thinkingText}`);
    }
    if (contentText) {
        parts.push(contentText);
    }
    return parts.join("\n\n").trim();
}
/**
 * Extract ONLY thinking blocks from message content.
 * Model-agnostic: returns empty string if no thinking blocks exist.
 */
export function extractThinkingFromMessage(message) {
    if (!message || typeof message !== "object") {
        return "";
    }
    const record = message;
    const content = record.content;
    if (typeof content === "string") {
        return "";
    }
    if (!Array.isArray(content)) {
        return "";
    }
    const parts = [];
    for (const block of content) {
        if (!block || typeof block !== "object") {
            continue;
        }
        const rec = block;
        if (rec.type === "thinking" && typeof rec.thinking === "string") {
            parts.push(sanitizeRenderableText(rec.thinking));
        }
    }
    return parts.join("\n").trim();
}
/**
 * Extract ONLY text content blocks from message (excludes thinking).
 * Model-agnostic: works for any model with text content blocks.
 */
export function extractContentFromMessage(message) {
    if (!message || typeof message !== "object") {
        return "";
    }
    const record = message;
    const content = record.content;
    if (typeof content === "string") {
        return sanitizeRenderableText(content).trim();
    }
    // Check for error BEFORE returning empty for non-array content
    if (!Array.isArray(content)) {
        const stopReason = typeof record.stopReason === "string" ? record.stopReason : "";
        if (stopReason === "error") {
            const errorMessage = typeof record.errorMessage === "string" ? record.errorMessage : "";
            return formatRawAssistantErrorForUi(errorMessage);
        }
        return "";
    }
    const parts = [];
    for (const block of content) {
        if (!block || typeof block !== "object") {
            continue;
        }
        const rec = block;
        if (rec.type === "text" && typeof rec.text === "string") {
            parts.push(sanitizeRenderableText(rec.text));
        }
    }
    // If no text blocks found, check for error
    if (parts.length === 0) {
        const stopReason = typeof record.stopReason === "string" ? record.stopReason : "";
        if (stopReason === "error") {
            const errorMessage = typeof record.errorMessage === "string" ? record.errorMessage : "";
            return formatRawAssistantErrorForUi(errorMessage);
        }
    }
    return parts.join("\n").trim();
}
function extractTextBlocks(content, opts) {
    if (typeof content === "string") {
        return sanitizeRenderableText(content).trim();
    }
    if (!Array.isArray(content)) {
        return "";
    }
    const thinkingParts = [];
    const textParts = [];
    for (const block of content) {
        if (!block || typeof block !== "object") {
            continue;
        }
        const record = block;
        if (record.type === "text" && typeof record.text === "string") {
            textParts.push(sanitizeRenderableText(record.text));
        }
        if (opts?.includeThinking &&
            record.type === "thinking" &&
            typeof record.thinking === "string") {
            thinkingParts.push(sanitizeRenderableText(record.thinking));
        }
    }
    return composeThinkingAndContent({
        thinkingText: thinkingParts.join("\n").trim(),
        contentText: textParts.join("\n").trim(),
        showThinking: opts?.includeThinking ?? false,
    });
}
export function extractTextFromMessage(message, opts) {
    if (!message || typeof message !== "object") {
        return "";
    }
    const record = message;
    const text = extractTextBlocks(record.content, opts);
    if (text) {
        if (record.role === "user") {
            return stripInboundMetadata(text);
        }
        return text;
    }
    const stopReason = typeof record.stopReason === "string" ? record.stopReason : "";
    if (stopReason !== "error") {
        return "";
    }
    const errorMessage = typeof record.errorMessage === "string" ? record.errorMessage : "";
    return formatRawAssistantErrorForUi(errorMessage);
}
export function isCommandMessage(message) {
    if (!message || typeof message !== "object") {
        return false;
    }
    return message.command === true;
}
export function formatTokens(total, context) {
    if (total == null && context == null) {
        return "tokens ?";
    }
    const totalLabel = total == null ? "?" : formatTokenCount(total);
    if (context == null) {
        return `tokens ${totalLabel}`;
    }
    const pct = typeof total === "number" && context > 0
        ? Math.min(999, Math.round((total / context) * 100))
        : null;
    return `tokens ${totalLabel}/${formatTokenCount(context)}${pct !== null ? ` (${pct}%)` : ""}`;
}
export function formatContextUsageLine(params) {
    const totalLabel = typeof params.total === "number" ? formatTokenCount(params.total) : "?";
    const ctxLabel = typeof params.context === "number" ? formatTokenCount(params.context) : "?";
    const pct = typeof params.percent === "number" ? Math.min(999, Math.round(params.percent)) : null;
    const remainingLabel = typeof params.remaining === "number" ? `${formatTokenCount(params.remaining)} left` : null;
    const pctLabel = pct !== null ? `${pct}%` : null;
    const extra = [remainingLabel, pctLabel].filter(Boolean).join(", ");
    return `tokens ${totalLabel}/${ctxLabel}${extra ? ` (${extra})` : ""}`;
}
export function asString(value, fallback = "") {
    if (typeof value === "string") {
        return value;
    }
    if (typeof value === "number" || typeof value === "boolean") {
        return String(value);
    }
    return fallback;
}
