import React, { useMemo } from "react";
import { Box, Text, Static } from "ink";
import { DiffRenderer } from "./DiffRenderer.js";
import { MarkdownRenderer } from "../utils/markdown-renderer.js";
import { useTheme } from "../context/theme-context.js";
import { getRenderManager, isTestResultsData, isWeatherData, isCodeStructureData } from "../../renderers/index.js";
import { ErrorBoundary } from "./ErrorBoundary.js";
import { Divider } from "./EnhancedSpinners.js";
import { ReasoningBlock } from "./ReasoningBlock.js";
import { ToolStreamOutput } from "./ToolStreamOutput.js";
import { logger } from "../../utils/logger.js";
// ============================================================================
// Structured Data Detection & Rendering
// ============================================================================
/**
 * Try to parse content as structured data and render it appropriately
 * Returns null if content is not recognized structured data
 */
function tryRenderStructuredData(content) {
    try {
        const parsed = JSON.parse(content);
        const manager = getRenderManager();
        // Check if it's a known structured data type
        if (isTestResultsData(parsed) || isWeatherData(parsed) || isCodeStructureData(parsed)) {
            try {
                return manager.render(parsed);
            }
            catch (renderError) {
                // Render error - return error message instead of crashing
                logger.error('Error rendering structured data', renderError);
                return '⚠ Error rendering structured output';
            }
        }
        // Check if manager can render it (generic check)
        if (manager.canRender(parsed)) {
            try {
                return manager.render(parsed);
            }
            catch (renderError) {
                // Render error - return error message instead of crashing
                logger.error('Error rendering data', renderError);
                return '⚠ Error rendering output';
            }
        }
    }
    catch (_err) {
        // Intentionally ignored: content is not valid JSON, so it is not structured data
    }
    return null;
}
/**
 * Component to render structured output with proper line handling
 */
function StructuredContent({ content, color }) {
    try {
        const lines = content.split('\n');
        return (React.createElement(Box, { flexDirection: "column" }, lines.map((line, idx) => (React.createElement(Text, { key: idx, color: color }, line)))));
    }
    catch (_err) {
        // Intentionally ignored: content rendering may fail for malformed data, show error UI
        return (React.createElement(Text, { color: "yellow" }, "\u26A0 Error rendering structured content"));
    }
}
// Memoized ChatEntry component to prevent unnecessary re-renders
const MemoizedChatEntry = React.memo(({ entry, index, colors, avatars }) => {
    const renderDiff = (diffContent, filename) => {
        return (React.createElement(ErrorBoundary, { fallback: React.createElement(Text, { color: "yellow" }, "\u26A0 Error rendering diff (content too large or invalid format)"), showDetails: false },
            React.createElement(DiffRenderer, { diffContent: diffContent, filename: filename, terminalWidth: 80 })));
    };
    const renderFileContent = (content) => {
        try {
            const lines = content.split("\n");
            // Calculate minimum indentation like DiffRenderer does
            let baseIndentation = Infinity;
            for (const line of lines) {
                if (line.trim() === "")
                    continue;
                const firstCharIndex = line.search(/\S/);
                const currentIndent = firstCharIndex === -1 ? 0 : firstCharIndex;
                baseIndentation = Math.min(baseIndentation, currentIndent);
            }
            if (!isFinite(baseIndentation)) {
                baseIndentation = 0;
            }
            return lines.map((line, index) => {
                const displayContent = line.substring(baseIndentation);
                return (React.createElement(Text, { key: index, color: "gray" }, displayContent));
            });
        }
        catch (_err) {
            // Intentionally ignored: file content rendering can fail for large or binary files
            return (React.createElement(Text, { color: "yellow" }, "\u26A0 Error rendering file content (file too large or invalid format)"));
        }
    };
    switch (entry.type) {
        case "user":
            return (React.createElement(Box, { key: index, flexDirection: "column", marginTop: 1 },
                React.createElement(Divider, { color: colors.border, title: "You" }),
                React.createElement(Box, { paddingLeft: 1 },
                    React.createElement(Text, { color: colors.userMessage },
                        avatars.user,
                        " ",
                        entry.content))));
        case "assistant":
            return (React.createElement(Box, { key: index, flexDirection: "column", marginTop: 1 },
                React.createElement(Divider, { color: colors.border, title: "Code Buddy" }),
                React.createElement(Box, { flexDirection: "row", alignItems: "flex-start", paddingLeft: 1 },
                    React.createElement(Text, { color: colors.assistantMessage },
                        avatars.assistant,
                        " "),
                    React.createElement(Box, { flexDirection: "column", flexGrow: 1 },
                        entry.toolCalls ? (
                        // If there are tool calls, just show plain text
                        React.createElement(Text, { color: colors.assistantMessage }, entry.content.trim())) : (
                        // If no tool calls, render as markdown
                        // Pass isStreaming to handle incomplete tables properly
                        React.createElement(MarkdownRenderer, { content: entry.content.trim(), isStreaming: entry.isStreaming })),
                        entry.isStreaming && React.createElement(Text, { color: colors.info }, "\u2588")))));
        case "reasoning":
            return (React.createElement(Box, { key: index, flexDirection: "column" },
                React.createElement(ReasoningBlock, { content: entry.content, isStreaming: entry.isStreaming })));
        case "plan_progress":
            return (React.createElement(Box, { key: index, flexDirection: "column", marginTop: 1 },
                React.createElement(Box, { paddingLeft: 1 },
                    React.createElement(Text, { color: colors.info },
                        avatars.tool,
                        " Plan: ",
                        entry.content))));
        case "tool_call":
        case "tool_result":
            const getToolActionName = (toolName) => {
                // Handle MCP tools with mcp__servername__toolname format
                if (toolName.startsWith("mcp__")) {
                    const parts = toolName.split("__");
                    if (parts.length >= 3) {
                        const serverName = parts[1];
                        const actualToolName = parts.slice(2).join("__");
                        return `${serverName.charAt(0).toUpperCase() + serverName.slice(1)}(${actualToolName.replace(/_/g, " ")})`;
                    }
                }
                switch (toolName) {
                    case "view_file":
                        return "Read";
                    case "str_replace_editor":
                        return "Update";
                    case "create_file":
                        return "Create";
                    case "bash":
                        return "Bash";
                    case "search":
                        return "Search";
                    case "create_todo_list":
                        return "Created Todo";
                    case "update_todo_list":
                        return "Updated Todo";
                    default:
                        return "Tool";
                }
            };
            const toolName = entry.toolCall?.function?.name || "unknown";
            const actionName = getToolActionName(toolName);
            const getFilePath = (toolCall) => {
                if (toolCall?.function?.arguments) {
                    try {
                        const args = JSON.parse(toolCall.function.arguments);
                        if (toolCall.function.name === "search") {
                            return args.query;
                        }
                        return args.path || args.file_path || args.command || "";
                    }
                    catch (_err) {
                        // Intentionally ignored: tool call arguments may not be valid JSON
                        return "";
                    }
                }
                return "";
            };
            const filePath = getFilePath(entry.toolCall);
            const isExecuting = entry.type === "tool_call" || !entry.toolResult;
            // Format JSON content for better readability
            const formatToolContent = (content, toolName) => {
                try {
                    // First, try to render as structured data
                    const structuredOutput = tryRenderStructuredData(content);
                    if (structuredOutput) {
                        return { text: structuredOutput, isStructured: true };
                    }
                    if (toolName.startsWith("mcp__")) {
                        try {
                            // Try to parse as JSON and format it
                            const parsed = JSON.parse(content);
                            if (Array.isArray(parsed)) {
                                // For arrays, show a summary instead of full JSON
                                return { text: `Found ${parsed.length} items`, isStructured: false };
                            }
                            else if (typeof parsed === 'object') {
                                // For objects, show a formatted version
                                return { text: JSON.stringify(parsed, null, 2), isStructured: false };
                            }
                        }
                        catch (_err) {
                            // Intentionally ignored: content is not valid JSON, return as plain text
                            return { text: content, isStructured: false };
                        }
                    }
                    return { text: content, isStructured: false };
                }
                catch (error) {
                    // Fallback for any unexpected errors
                    logger.error('Error formatting tool content', error);
                    return { text: '⚠ Error formatting tool output', isStructured: false };
                }
            };
            const shouldShowDiff = entry.toolCall?.function?.name === "str_replace_editor" &&
                entry.toolResult?.success &&
                entry.content.includes("Updated") &&
                entry.content.includes("---") &&
                entry.content.includes("+++");
            const shouldShowFileContent = (entry.toolCall?.function?.name === "view_file" ||
                entry.toolCall?.function?.name === "create_file") &&
                entry.toolResult?.success &&
                !shouldShowDiff;
            return (React.createElement(Box, { key: index, flexDirection: "column", marginTop: 1 },
                React.createElement(Box, null,
                    React.createElement(Text, { color: colors.toolCall }, avatars.tool),
                    React.createElement(Text, { color: colors.text },
                        " ",
                        filePath ? `${actionName}(${filePath})` : actionName)),
                React.createElement(Box, { marginLeft: 2, flexDirection: "column" }, isExecuting && entry.isStreaming && entry.content !== 'Executing...' ? (React.createElement(ToolStreamOutput, { output: entry.content, toolName: toolName, isStreaming: true })) : isExecuting ? (React.createElement(Text, { color: colors.info }, "\u23BF Executing...")) : shouldShowFileContent ? (React.createElement(Box, { flexDirection: "column" },
                    React.createElement(Text, { color: colors.toolResult }, "\u23BF File contents:"),
                    React.createElement(Box, { marginLeft: 2, flexDirection: "column" }, renderFileContent(entry.content)))) : shouldShowDiff ? (
                // For diff results, show only the summary line, not the raw content
                React.createElement(Text, { color: colors.toolResult },
                    "\u23BF ",
                    entry.content.split("\n")[0])) : (() => {
                    const formatted = formatToolContent(entry.content, toolName);
                    if (formatted.isStructured) {
                        return (React.createElement(ErrorBoundary, { fallback: React.createElement(Text, { color: "yellow" }, "\u23BF \u26A0 Error displaying structured output"), showDetails: false },
                            React.createElement(Box, { flexDirection: "column" },
                                React.createElement(Text, { color: colors.toolResult }, "\u23BF Structured output:"),
                                React.createElement(Box, { marginLeft: 2 },
                                    React.createElement(StructuredContent, { content: formatted.text })))));
                    }
                    return React.createElement(Text, { color: colors.toolResult },
                        "\u23BF ",
                        formatted.text);
                })()),
                shouldShowDiff && !isExecuting && (React.createElement(Box, { marginLeft: 4, flexDirection: "column" }, renderDiff(entry.content, filePath)))));
        default:
            return null;
    }
});
MemoizedChatEntry.displayName = "MemoizedChatEntry";
export function ChatHistory({ entries, isConfirmationActive = false, maxMessages = 50, }) {
    const { colors, avatars } = useTheme();
    // Filter out tool_call entries with "Executing..." when confirmation is active
    // Apply windowing to prevent performance issues with very long conversations
    const filteredEntries = useMemo(() => {
        const filtered = isConfirmationActive
            ? entries.filter((entry) => !(entry.type === "tool_call" && entry.content === "Executing..."))
            : entries;
        // Windowing: Only show the most recent N messages
        // This prevents performance degradation in very long conversations
        const windowSize = Math.max(10, Math.min(maxMessages, 100)); // Clamp between 10-100
        if (filtered.length > windowSize) {
            // Show truncation indicator for long conversations
            const truncatedCount = filtered.length - windowSize;
            logger.debug(`[ChatHistory] Showing ${windowSize} most recent messages (${truncatedCount} older messages hidden)`);
        }
        return filtered.slice(-windowSize);
    }, [entries, isConfirmationActive, maxMessages]);
    // Calculate truncation info
    const truncatedCount = useMemo(() => {
        const totalFiltered = isConfirmationActive
            ? entries.filter((entry) => !(entry.type === "tool_call" && entry.content === "Executing...")).length
            : entries.length;
        return Math.max(0, totalFiltered - filteredEntries.length);
    }, [entries, filteredEntries, isConfirmationActive]);
    // Separate completed (static) entries from streaming (dynamic) entries
    // Static entries use Ink's Static component - they render once and never re-render
    // This dramatically reduces flickering during streaming
    const { staticEntries, dynamicEntries } = useMemo(() => {
        const staticList = [];
        const dynamicList = [];
        filteredEntries.forEach((entry, index) => {
            // Create a stable unique key based on timestamp and content hash
            const uniqueKey = `${entry.timestamp.getTime()}-${index}`;
            const entryWithKey = { ...entry, uniqueKey };
            // Entry is dynamic if it's streaming or is a tool_call being executed
            const isDynamic = entry.isStreaming ||
                (entry.type === "tool_call" && entry.content === "Executing...");
            if (isDynamic) {
                dynamicList.push(entryWithKey);
            }
            else {
                staticList.push(entryWithKey);
            }
        });
        return { staticEntries: staticList, dynamicEntries: dynamicList };
    }, [filteredEntries]);
    return (React.createElement(Box, { flexDirection: "column" },
        truncatedCount > 0 && (React.createElement(Box, { marginBottom: 1 },
            React.createElement(Text, { color: "gray", dimColor: true },
                "\u00B7\u00B7\u00B7 ",
                truncatedCount,
                " older message",
                truncatedCount !== 1 ? 's' : '',
                " hidden (showing most recent ",
                filteredEntries.length,
                ")"))),
        React.createElement(Static, { items: staticEntries }, (entry) => (React.createElement(MemoizedChatEntry, { key: entry.uniqueKey, entry: entry, index: 0, colors: colors, avatars: avatars }))),
        dynamicEntries.map((entry, index) => (React.createElement(MemoizedChatEntry, { key: entry.uniqueKey, entry: entry, index: index, colors: colors, avatars: avatars })))));
}
//# sourceMappingURL=ChatHistory.js.map