import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useSize } from "react-use"
import { useTranslation, Trans } from "react-i18next"
import deepEqual from "fast-deep-equal"
import { type SearchResult } from "./hooks/useChatSearch"
import { VSCodeBadge, VSCodeProgressRing } from "@vscode/webview-ui-toolkit/react"

import type {
	ClineMessage,
	FollowUpData,
	SuggestionItem,
	ClineApiReqInfo,
	ClineAskUseMcpServer,
	ClineSayTool,
	MultipleChoiceData,
	MultipleChoiceResponse,
} from "@roo-code/types"

import { Mode } from "@roo/modes"

import { COMMAND_OUTPUT_STRING } from "@roo/combineCommandSequences"
import { safeJsonParse } from "@roo/core"

import { useCopyToClipboard } from "@src/utils/clipboard"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { findMatchingResourceOrTemplate } from "@src/utils/mcp"
import { vscode } from "@src/utils/vscode"
import { formatPathTooltip } from "@src/utils/formatPathTooltip"
// import { getLanguageFromPath } from "@src/utils/getLanguageFromPath"
import { StandardTooltip } from "@src/components/ui"

import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock"
import UpdateTodoListToolBlock from "./UpdateTodoListToolBlock"
import { TodoChangeDisplay } from "./TodoChangeDisplay"
import CodeAccordion from "../common/CodeAccordion"
import MarkdownBlock from "../common/MarkdownBlock"
import { ReasoningBlock } from "./ReasoningBlock"
import Thumbnails from "../common/Thumbnails"
import ImageBlock from "../common/ImageBlock"
import ErrorRow from "./ErrorRow"
import WarningRow from "./WarningRow"

import McpResourceRow from "../mcp/McpResourceRow"

import { Mention } from "./Mention"
import { CheckpointSaved } from "./checkpoints/CheckpointSaved"
import { FollowUpSuggest } from "./FollowUpSuggest"
import { MultipleChoiceForm } from "./MultipleChoiceForm"
import { BatchFilePermission } from "./BatchFilePermission"
import { BatchDiffApproval } from "./BatchDiffApproval"
import { ProgressIndicator } from "./ProgressIndicator"
import { Markdown } from "./Markdown"
import { CollapsibleMarkdownBlock } from "./CollapsibleMarkdownBlock"
import { CommandExecution } from "./CommandExecution"
import { CommandExecutionError } from "./CommandExecutionError"
import { AutoApprovedRequestLimitWarning } from "./AutoApprovedRequestLimitWarning"
import { InProgressRow, CondensationResultRow, CondensationErrorRow, TruncationResultRow } from "./context-management"
import CodebaseSearchResultsDisplay from "./CodebaseSearchResultsDisplay"
import { appendImages } from "@src/utils/imageUtils"
import { McpExecution } from "./McpExecution"
import { ChatTextArea } from "./ChatTextArea"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import { useSelectedModel } from "../ui/hooks/useSelectedModel"
import HighlightedPlainText from "../common/HighlightedPlainText"
import {
	ClipboardCheck,
	ClipboardCopy,
	Eye,
	FileDiff,
	ListTree,
	User,
	Trash2,
	MessageCircleQuestionMark,
	SquareArrowOutUpRight,
	FileCode2,
	PocketKnife,
	FolderTree,
	TerminalSquare,
	MessageCircle,
	Repeat2,
	RotateCcw,
	Split,
	ArrowRight,
	Check,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { getJumpLine } from "@/utils/path-mentions"
import { useCostrictUserInfo } from "@/hooks/useCostrictUserInfo"
import { format } from "date-fns"
import { PathTooltip } from "../ui/PathTooltip"
import { RandomLoadingMessage, RandomLoadingMessageLanguage } from "@/components/chat/RandomLoadingMessage"
import { OpenMarkdownPreviewButton } from "./OpenMarkdownPreviewButton"

// Helper function to get previous todos before a specific message
function getPreviousTodos(messages: ClineMessage[], currentMessageTs: number): any[] {
	// Find the previous updateTodoList message before the current one
	const previousUpdateIndex = messages
		.slice()
		.reverse()
		.findIndex((msg) => {
			if (msg.ts >= currentMessageTs) return false
			if (msg.type === "ask" && msg.ask === "tool") {
				try {
					const tool = JSON.parse(msg.text || "{}")
					return tool.tool === "updateTodoList"
				} catch {
					return false
				}
			}
			return false
		})

	if (previousUpdateIndex !== -1) {
		const previousMessage = messages.slice().reverse()[previousUpdateIndex]
		try {
			const tool = JSON.parse(previousMessage.text || "{}")
			return tool.todos || []
		} catch {
			return []
		}
	}

	// If no previous updateTodoList message, return empty array
	return []
}

interface ChatRowProps {
	message: ClineMessage
	lastModifiedMessage?: ClineMessage
	isExpanded: boolean
	isLast: boolean
	isStreaming: boolean
	onToggleExpand: (ts: number) => void
	onHeightChange: (isTaller: boolean) => void
	onSuggestionClick?: (suggestion: SuggestionItem, event?: React.MouseEvent) => void
	onMultipleChoiceSubmit?: (response: MultipleChoiceResponse) => void
	onBatchFileResponse?: (response: { [key: string]: boolean }) => void
	onFollowUpUnmount?: () => void
	onMultipleChoiceUnmount?: () => void
	onCommandStop?: () => void
	isFollowUpAnswered?: boolean
	isMultipleChoiceAnswered?: boolean
	isFollowUpAutoApprovalPaused?: boolean
	editable?: boolean
	shouldHighlight?: boolean
	searchResults?: SearchResult[]
	searchQuery?: string
	hasCheckpoint?: boolean
	onJumpToPreviousCheckpoint?: () => void
}

interface ChatRowContentProps extends Omit<ChatRowProps, "onHeightChange" | "searchResults" | "searchQuery"> {
	searchResults?: SearchResult[]
	searchQuery?: string
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface ChatRowContentProps extends Omit<ChatRowProps, "onHeightChange"> {}

const ChatRow = memo(
	(props: ChatRowProps) => {
		const { isLast, onHeightChange, message } = props
		// Store the previous height to compare with the current height
		// This allows us to detect changes without causing re-renders
		const prevHeightRef = useRef(0)

		const [chatrow, { height }] = useSize(
			<div
				className={`px-[15px] py-2.5 transition-all duration-300 ease-in-out ${
					props.shouldHighlight
						? "bg-vscode-editor-findMatchHighlightBackground border-l-4 border-vscode-editor-findMatchBorder shadow-sm"
						: ""
				}`}>
				<ChatRowContent {...props} />
			</div>,
		)

		useEffect(() => {
			const isHeightValid = height !== 0 && height !== Infinity
			// used for partials, command output, etc.
			// NOTE: it's important we don't distinguish between partial or complete here since our scroll effects in chatview need to handle height change during partial -> complete
			const isInitialRender = prevHeightRef.current === 0 // prevents scrolling when new element is added since we already scroll for that
			// height starts off at Infinity
			if (isLast && isHeightValid && height !== prevHeightRef.current) {
				if (!isInitialRender) {
					onHeightChange(height > prevHeightRef.current)
				}
				prevHeightRef.current = height
			}
		}, [height, isLast, onHeightChange, message])

		// we cannot return null as virtuoso does not support it, so we use a separate visibleMessages array to filter out messages that should not be rendered
		return chatrow
	},
	// memo does shallow comparison of props, so we need to do deep comparison of arrays/objects whose properties might change
	deepEqual,
)

export default ChatRow

export const ChatRowContent = ({
	message,
	lastModifiedMessage,
	isExpanded,
	isLast,
	isStreaming,
	onToggleExpand,
	onSuggestionClick,
	onMultipleChoiceSubmit,
	onFollowUpUnmount,
	//costrict: plumb multiple_choice auto-approval cancellation callback through ChatRowContent
	onMultipleChoiceUnmount,
	onCommandStop,
	onBatchFileResponse,
	isFollowUpAnswered,
	isMultipleChoiceAnswered,
	// editable,
	searchQuery,
	isFollowUpAutoApprovalPaused,
	onJumpToPreviousCheckpoint,
}: ChatRowContentProps) => {
	const { t, i18n } = useTranslation()

	const {
		mcpServers,
		alwaysAllowMcp,
		currentCheckpoint,
		mode,
		apiConfiguration,
		clineMessages,
		showSpeedInfo,
		language,
		collapseMarkdownWithoutScroll,
		enableCheckpoints,
		currentTaskItem,
	} = useExtensionState()
	const { logoPic, userInfo } = useCostrictUserInfo(apiConfiguration?.costrictAccessToken)
	const { info: model } = useSelectedModel(apiConfiguration)
	const [showCopySuccess, setShowCopySuccess] = useState(false)
	const [isEditing, setIsEditing] = useState(false)
	const [editedContent, setEditedContent] = useState("")
	const [isFeedbackExpanded, setIsFeedbackExpanded] = useState(false)
	const [editMode, setEditMode] = useState<Mode>(mode || "code")
	const [editImages, setEditImages] = useState<string[]>([])
	const { copyWithFeedback } = useCopyToClipboard()
	// const userEditRef = useRef<HTMLDivElement>(null)
	const collapseWithoutScrollEnabled = collapseMarkdownWithoutScroll ?? true
	const deleteMessageTs = useMemo(
		() => (clineMessages.findIndex((m) => m.ts === message.ts) > 1 ? message.ts : -1),
		[clineMessages, message.ts],
	)
	// Handle message events for image selection during edit mode
	useEffect(() => {
		const handleMessage = (event: MessageEvent) => {
			const msg = event.data
			if (msg.type === "selectedImages" && msg.context === "edit" && msg.messageTs === message.ts && isEditing) {
				setEditImages((prevImages) => appendImages(prevImages, msg.images, MAX_IMAGES_PER_MESSAGE))
			}
		}

		window.addEventListener("message", handleMessage)
		return () => window.removeEventListener("message", handleMessage)
	}, [isEditing, message.ts])

	// Memoized callback to prevent re-renders caused by inline arrow functions.
	const handleToggleExpand = useCallback(() => {
		onToggleExpand(message.ts)
	}, [onToggleExpand, message.ts])

	// Handle edit button click
	const handleEditClick = useCallback(() => {
		setIsEditing(true)
		setEditedContent(message.text || "")
		setEditImages(message.images || [])
		setEditMode(mode || "code")
		// Edit mode is now handled entirely in the frontend
		// No need to notify the backend
	}, [message.text, message.images, mode])

	// Handle cancel edit
	const handleCancelEdit = useCallback(() => {
		setIsEditing(false)
		setEditedContent(message.text || "")
		setEditImages(message.images || [])
		setEditMode(mode || "code")
	}, [message.text, message.images, mode])

	// Handle save edit
	const handleSaveEdit = useCallback(() => {
		setIsEditing(false)
		// Send edited message to backend
		vscode.postMessage({
			type: "submitEditedMessage",
			value: message.ts,
			editedMessageContent: editedContent,
			images: editImages,
		})
	}, [message.ts, editedContent, editImages])

	// Handle image selection for editing
	const handleSelectImages = useCallback(() => {
		vscode.postMessage({ type: "selectImages", context: "edit", messageTs: message.ts })
	}, [message.ts])

	// Extract timing data and calculate performance metrics in the component
	const [
		cost,
		apiReqCancelReason,
		apiReqStreamingFailedMessage,
		selectedLLM,
		selectReason,
		isAuto,
		originModelId,
		isFallbackActive,
		firstTokenLatency,
		tokensPerSecond,
		totalDuration,
	] = useMemo(() => {
		if (message.text !== null && message.text !== undefined && message.say === "api_req_started") {
			const info = safeJsonParse<ClineApiReqInfo>(message.text)

			let calculatedFirstTokenLatency: number | undefined
			let calculatedTokensPerSecond: number | undefined
			let calculatedTotalDuration: number | undefined

			if (
				info?.requestIdTimestamp &&
				info?.responseIdTimestamp &&
				info?.responseEndTimestamp &&
				info?.completionTokens
			) {
				const generationTimeMs = info.responseEndTimestamp - info.responseIdTimestamp
				const safeGenerationTimeMs = generationTimeMs > 0 ? generationTimeMs : Infinity

				calculatedFirstTokenLatency = Number(
					((info.responseIdTimestamp - info.requestIdTimestamp) / 1000).toFixed(1),
				)
				calculatedTotalDuration = Number(
					((info.responseEndTimestamp - info.requestIdTimestamp) / 1000).toFixed(1),
				)
				calculatedTokensPerSecond =
					safeGenerationTimeMs !== Infinity
						? Number(((info.completionTokens / safeGenerationTimeMs) * 1000).toFixed(1))
						: 0
			}

			return [
				info?.cost,
				info?.cancelReason,
				info?.streamingFailedMessage,
				info?.selectedLLM,
				info?.selectReason,
				info?.isAuto,
				info?.originModelId,
				info?.isFallbackActive,
				calculatedFirstTokenLatency,
				calculatedTokensPerSecond,
				calculatedTotalDuration,
			]
		}

		return []
	}, [message.text, message.say])

	const multipleChoiceData = useMemo(() => {
		if (message.type === "ask" && message.ask === "multiple_choice" && !message.partial) {
			const data = safeJsonParse<MultipleChoiceData>(message.text)
			// Costrict: Merge saved user response for display on reload
			if (data && message.userResponse) {
				data.userResponse = message.userResponse as MultipleChoiceResponse
			}
			return data
		}
		return null
	}, [message.type, message.ask, message.partial, message.text, message.userResponse])
	// When resuming task, last won't be api_req_failed but a resume_task
	// message, so api_req_started will show loading spinner. That's why we just
	// remove the last api_req_started that failed without streaming anything.
	const apiRequestFailedMessage =
		isLast && lastModifiedMessage?.ask === "api_req_failed" // if request is retried then the latest message is a api_req_retried
			? lastModifiedMessage?.text
			: undefined

	const isCommandExecuting =
		isLast && lastModifiedMessage?.ask === "command" && lastModifiedMessage?.text?.includes(COMMAND_OUTPUT_STRING)

	const isMcpServerResponding = isLast && lastModifiedMessage?.say === "mcp_server_request_started"

	const type = message.type === "ask" ? message.ask : message.say

	const normalColor = "var(--vscode-foreground)"
	const errorColor = "var(--vscode-errorForeground)"
	const successColor = "var(--vscode-charts-green)"
	const linkColor = "var(--vscode-textLink-foreground)"
	const cancelledColor = "var(--vscode-descriptionForeground)"
	const getIconSpan = ({
		iconName,
		color,
		width = 16,
		height = 16,
	}: {
		iconName: string
		color: string
		width?: number
		height?: number
	}) => (
		<div
			style={{
				width,
				height,
				display: "flex",
				alignItems: "center",
				justifyContent: "center",
			}}>
			<span
				className={`codicon codicon-${iconName}`}
				style={{ color, fontSize: width, marginBottom: "-1.5px" }}
			/>
		</div>
	)
	const [icon, title] = useMemo(() => {
		switch (type) {
			case "error":
			case "mistake_limit_reached":
				return [null, null] // These will be handled by ErrorRow component
			case "command":
				return [
					isCommandExecuting ? (
						<ProgressIndicator />
					) : (
						<TerminalSquare className="size-4" aria-label="Terminal icon" />
					),
					<span style={{ color: normalColor, fontWeight: "bold" }}>
						{t("chat:commandExecution.running")}
					</span>,
				]
			case "use_mcp_server":
				const mcpServerUse = safeJsonParse<ClineAskUseMcpServer>(message.text)
				if (mcpServerUse === undefined) {
					return [null, null]
				}
				return [
					isMcpServerResponding ? (
						<ProgressIndicator />
					) : (
						<span
							className="codicon codicon-server"
							style={{ color: normalColor, marginBottom: "-1.5px" }}></span>
					),
					<span style={{ color: normalColor, fontWeight: "bold" }}>
						{mcpServerUse.type === "use_mcp_tool"
							? t("chat:mcp.wantsToUseTool", { serverName: mcpServerUse.serverName })
							: t("chat:mcp.wantsToAccessResource", { serverName: mcpServerUse.serverName })}
					</span>,
				]
			case "completion_result":
				const isLoading = isLast && isStreaming
				return [
					isLoading ? (
						<ProgressIndicator />
					) : (
						<span
							className="codicon codicon-check"
							style={{ color: successColor, marginBottom: "-1.5px" }}></span>
					),
					isLoading ? (
						<span style={{ color: successColor, fontWeight: "bold" }}>
							<RandomLoadingMessage language={language as RandomLoadingMessageLanguage} />
						</span>
					) : (
						<span style={{ color: successColor, fontWeight: "bold" }}>{t("chat:taskCompleted")}</span>
					),
				]
			case "api_req_rate_limit_wait":
				return []
			case "api_req_retry_delayed":
				return []
			case "api_req_started":
				return [
					apiReqCancelReason !== null && apiReqCancelReason !== undefined ? (
						apiReqCancelReason === "user_cancelled" ? (
							getIconSpan({ iconName: "error", color: cancelledColor })
						) : (
							getIconSpan({ iconName: "error", color: errorColor })
						)
					) : cost !== null && cost !== undefined ? (
						getIconSpan({ iconName: "arrow-swap", color: normalColor })
					) : apiRequestFailedMessage ? (
						getIconSpan({ iconName: "error", color: errorColor })
					) : isLast && isStreaming ? (
						<ProgressIndicator />
					) : (
						getIconSpan({ iconName: "arrow-swap", color: errorColor })
					),
					apiReqCancelReason !== null && apiReqCancelReason !== undefined ? (
						apiReqCancelReason === "user_cancelled" ? (
							<span style={{ color: normalColor, fontWeight: "bold" }}>
								{t("chat:apiRequest.cancelled")}{" "}
								{message.ts ? format(new Date(message.ts), "yyyy-MM-dd HH:mm:ss") : ""}
							</span>
						) : (
							<span style={{ color: errorColor, fontWeight: "bold" }}>
								{t("chat:apiRequest.streamingFailed")}{" "}
								{message.ts ? format(new Date(message.ts), "yyyy-MM-dd HH:mm:ss") : ""}
							</span>
						)
					) : cost !== null && cost !== undefined ? (
						<span style={{ color: normalColor }}>
							{t("chat:apiRequest.title")}{" "}
							{message.ts ? format(new Date(message.ts), "yyyy-MM-dd HH:mm:ss") : ""}
						</span>
					) : apiRequestFailedMessage ? (
						<span style={{ color: errorColor }}>
							{t("chat:apiRequest.failed")}{" "}
							{message.ts ? format(new Date(message.ts), "yyyy-MM-dd HH:mm:ss") : ""}
						</span>
					) : (
						<span style={{ color: normalColor }}>{t("chat:apiRequest.streaming")}</span>
					),
				]
			case "followup":
				return [
					<MessageCircleQuestionMark className="w-4 shrink-0" aria-label="Question icon" />,
					<span style={{ color: normalColor, fontWeight: "bold" }}>{t("chat:questions.hasQuestion")}</span>,
				]
			case "multiple_choice": {
				const isLoading = isLast && isStreaming
				if (!isLoading && multipleChoiceData) {
					return [
						<MessageCircleQuestionMark
							className="w-4 shrink-0"
							aria-label="Multiple choice question icon"
						/>,
						<span style={{ color: normalColor, fontWeight: "bold" }}>
							{t("chat:multipleChoice.headerTitle")}
						</span>,
					]
				}

				return [
					<VSCodeProgressRing className="size-4" />,
					<span className="text-sm">{t("chat:multipleChoice.loading")}</span>,
				]
			}
			default:
				return [null, null]
		}
	}, [
		type,
		isCommandExecuting,
		t,
		message.text,
		message.ts,
		isMcpServerResponding,
		isLast,
		isStreaming,
		language,
		apiReqCancelReason,
		cost,
		apiRequestFailedMessage,
		multipleChoiceData,
	])

	const headerStyle: React.CSSProperties = {
		display: "flex",
		alignItems: "center",
		gap: "10px",
		cursor: "default",
		marginBottom: "10px",
		wordBreak: "break-word",
	}

	const tool = useMemo(
		() => (message.ask === "tool" ? safeJsonParse<ClineSayTool>(message.text) : null),
		[message.ask, message.text],
	)

	// Unified diff content (provided by backend when relevant)
	const unifiedDiff = useMemo(() => {
		if (!tool) return undefined
		return (tool.content ?? tool.diff) as string | undefined
	}, [tool])

	const onJumpToCreatedFile = useMemo(() => {
		if (!tool || tool.tool !== "newFileCreated" || !tool.path) {
			return undefined
		}

		return () => vscode.postMessage({ type: "openFile", text: "./" + tool.path })
	}, [tool])

	const followUpData = useMemo(() => {
		if (message.type === "ask" && message.ask === "followup" && !message.partial) {
			return safeJsonParse<FollowUpData>(message.text)
		}
		return null
	}, [message.type, message.ask, message.partial, message.text])


	if (tool) {
		const toolIcon = (name: string) => (
			<span
				className={`codicon codicon-${name}`}
				style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}></span>
		)

		switch (tool.tool as string) {
			case "editedExistingFile":
			case "appliedDiff":
			case "newFileCreated":
			case "searchAndReplace":
			case "search_and_replace":
			case "search_replace":
			case "edit":
			case "edit_file":
			case "apply_patch":
			case "apply_diff":
				// Check if this is a batch diff request
				if (message.type === "ask" && tool.batchDiffs && Array.isArray(tool.batchDiffs)) {
					return (
						<>
							<div style={headerStyle}>
								<FileDiff className="w-4 shrink-0" aria-label="Batch diff icon" />
								<span style={{ fontWeight: "bold" }}>
									{t("chat:fileOperations.wantsToApplyBatchChanges")}
								</span>
							</div>
							<BatchDiffApproval files={tool.batchDiffs} ts={message.ts} />
						</>
					)
				}

				// Regular single file diff
				return (
					<>
						<div style={headerStyle}>
							{message.partial && isLast && isStreaming ? null : tool.isProtected ? (
								<span
									className="codicon codicon-lock"
									style={{ color: "var(--vscode-editorWarning-foreground)", marginBottom: "-1.5px" }}
								/>
							) : (
								toolIcon("diff")
							)}
							{message.partial && isLast && isStreaming ? (
								<span style={{ fontWeight: "bold" }}>
									<RandomLoadingMessage language={language as RandomLoadingMessageLanguage} />
								</span>
							) : (
								<span style={{ fontWeight: "bold" }}>
									{tool.isProtected
										? t("chat:fileOperations.wantsToEditProtected")
										: tool.isOutsideWorkspace
											? t("chat:fileOperations.wantsToEditOutsideWorkspace")
											: t("chat:fileOperations.wantsToEdit")}
								</span>
							)}
						</div>
						<div className="pl-6">
							<CodeAccordion
								path={tool.path}
								code={unifiedDiff ?? tool.content ?? tool.diff ?? ""}
								language="diff"
								progressStatus={message.progressStatus}
								isLoading={message.partial && isLast && isStreaming}
								isExpanded={isExpanded}
								onToggleExpand={handleToggleExpand}
								onJumpToFile={onJumpToCreatedFile}
								diffStats={tool.diffStats}
							/>
						</div>
					</>
				)
			case "insertContent":
				return (
					<>
						<div style={headerStyle}>
							{message.partial && isLast && isStreaming ? null : tool.isProtected ? (
								<span
									className="codicon codicon-lock"
									style={{ color: "var(--vscode-editorWarning-foreground)", marginBottom: "-1.5px" }}
								/>
							) : (
								toolIcon("insert")
							)}
							{message.partial && isLast && isStreaming ? (
								<span style={{ fontWeight: "bold" }}>
									<RandomLoadingMessage language={language as RandomLoadingMessageLanguage} />
								</span>
							) : (
								<span style={{ fontWeight: "bold" }}>
									{tool.isProtected
										? t("chat:fileOperations.wantsToEditProtected")
										: tool.isOutsideWorkspace
											? t("chat:fileOperations.wantsToEditOutsideWorkspace")
											: tool.lineNumber === 0
												? t("chat:fileOperations.wantsToInsertAtEnd")
												: t("chat:fileOperations.wantsToInsertWithLineNumber", {
														lineNumber: tool.lineNumber,
													})}
								</span>
							)}
						</div>
						<div className="pl-6">
							<CodeAccordion
								path={tool.path}
								code={unifiedDiff ?? tool.diff}
								language="diff"
								progressStatus={message.progressStatus}
								isLoading={message.partial && isLast && isStreaming}
								isExpanded={isExpanded}
								onToggleExpand={handleToggleExpand}
								diffStats={tool.diffStats}
							/>
						</div>
					</>
				)
			case "codebaseSearch": {
				return (
					<div style={headerStyle}>
						{toolIcon("search")}
						<span style={{ fontWeight: "bold" }}>
							{tool.path ? (
								<Trans
									i18nKey="chat:codebaseSearch.wantsToSearchWithPath"
									components={{ code: <code></code> }}
									values={{ query: tool.query, path: tool.path }}
								/>
							) : (
								<Trans
									i18nKey="chat:codebaseSearch.wantsToSearch"
									components={{ code: <code></code> }}
									values={{ query: tool.query }}
								/>
							)}
						</span>
					</div>
				)
			}
			case "updateTodoList" as any: {
				const todos = (tool as any).todos || []
				// Get previous todos from the latest todos in the task context
				const previousTodos = getPreviousTodos(clineMessages, message.ts)

				return <TodoChangeDisplay previousTodos={previousTodos} newTodos={todos} />
			}
			case "readFile":
				// Check if this is a batch file permission request
				const isBatchRequest = message.type === "ask" && tool.batchFiles && Array.isArray(tool.batchFiles)

				if (isBatchRequest) {
					return (
						<>
							<div style={headerStyle}>
								<Eye className="w-4 shrink-0" aria-label="View files icon" />
								<span style={{ fontWeight: "bold" }}>
									{t("chat:fileOperations.wantsToReadMultiple")}
								</span>
							</div>
							<BatchFilePermission
								files={tool.batchFiles || []}
								onPermissionResponse={(response) => {
									onBatchFileResponse?.(response)
								}}
								ts={message?.ts}
							/>
						</>
					)
				}

				// Regular single file read request
				return (
					<>
						<div style={headerStyle}>
							<FileCode2 className="w-4 shrink-0" aria-label="Read file icon" />
							<span style={{ fontWeight: "bold" }}>
								{message.type === "ask"
									? tool.isOutsideWorkspace
										? t("chat:fileOperations.wantsToReadOutsideWorkspace")
										: tool.additionalFileCount && tool.additionalFileCount > 0
											? t("chat:fileOperations.wantsToReadAndXMore", {
													count: tool.additionalFileCount,
												})
											: t("chat:fileOperations.wantsToRead")
									: t("chat:fileOperations.didRead")}
							</span>
						</div>
						<div className="pl-6">
							<ToolUseBlock>
								<ToolUseBlockHeader
									className="group"
									onClick={() =>
										vscode.postMessage({
											type: "openFile",
											text: tool.content,
											values: {
												line: tool.startLine ? tool.startLine : getJumpLine(tool)[0] || 0,
											},
										})
									}>
									{tool.path?.startsWith(".") && <span>.</span>}
									<PathTooltip content={formatPathTooltip(tool.path, tool.reason)}>
										<span className="whitespace-nowrap overflow-hidden text-ellipsis text-left mr-2 rtl">
											{formatPathTooltip(tool.path, tool.reason)}
										</span>
									</PathTooltip>
									<div style={{ flexGrow: 1 }}></div>
									<SquareArrowOutUpRight
										className="w-4 shrink-0 codicon codicon-link-external opacity-0 group-hover:opacity-100 transition-opacity"
										style={{ fontSize: 13.5, margin: "1px 0" }}
									/>
								</ToolUseBlockHeader>
							</ToolUseBlock>
						</div>
					</>
				)
			case "skill": {
				const skillInfo = tool
				return (
					<>
						<div style={headerStyle}>
							{toolIcon("book")}
							<span style={{ fontWeight: "bold" }}>
								{message.type === "ask" ? t("chat:skill.wantsToLoad") : t("chat:skill.didLoad")}
							</span>
						</div>
						<div
							style={{
								marginTop: "4px",
								backgroundColor: "var(--vscode-editor-background)",
								border: "1px solid var(--vscode-editorGroup-border)",
								borderRadius: "4px",
								overflow: "hidden",
								cursor: "pointer",
							}}
							onClick={handleToggleExpand}>
							<ToolUseBlockHeader
								className="group"
								style={{
									display: "flex",
									alignItems: "center",
									justifyContent: "space-between",
									padding: "10px 12px",
								}}>
								<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
									<span style={{ fontWeight: "500", fontSize: "var(--vscode-font-size)" }}>
										{skillInfo.skill}
									</span>
									{skillInfo.source && (
										<VSCodeBadge style={{ fontSize: "calc(var(--vscode-font-size) - 2px)" }}>
											{skillInfo.source}
										</VSCodeBadge>
									)}
								</div>
								<span
									className={`codicon codicon-chevron-${isExpanded ? "up" : "down"} opacity-0 group-hover:opacity-100 transition-opacity duration-200`}></span>
							</ToolUseBlockHeader>
							{isExpanded && (skillInfo.args || skillInfo.description) && (
								<div
									style={{
										padding: "12px 16px",
										borderTop: "1px solid var(--vscode-editorGroup-border)",
										display: "flex",
										flexDirection: "column",
										gap: "8px",
									}}>
									{skillInfo.description && (
										<div style={{ color: "var(--vscode-descriptionForeground)" }}>
											{skillInfo.description}
										</div>
									)}
									{skillInfo.args && (
										<div>
											<span style={{ fontWeight: "500" }}>Arguments: </span>
											<span style={{ color: "var(--vscode-descriptionForeground)" }}>
												{skillInfo.args}
											</span>
										</div>
									)}
								</div>
							)}
						</div>
					</>
				)
			}
			case "listFilesTopLevel":
				return (
					<>
						<div style={headerStyle}>
							<ListTree className="w-4 shrink-0" aria-label="List files icon" />
							<span style={{ fontWeight: "bold" }}>
								{message.type === "ask"
									? tool.isOutsideWorkspace
										? t("chat:directoryOperations.wantsToViewTopLevelOutsideWorkspace")
										: t("chat:directoryOperations.wantsToViewTopLevel")
									: tool.isOutsideWorkspace
										? t("chat:directoryOperations.didViewTopLevelOutsideWorkspace")
										: t("chat:directoryOperations.didViewTopLevel")}
							</span>
						</div>
						<div className="pl-6">
							<CodeAccordion
								path={tool.path}
								code={tool.content}
								language="shell-session"
								isExpanded={isExpanded}
								onToggleExpand={handleToggleExpand}
							/>
						</div>
					</>
				)
			case "listFilesRecursive":
				return (
					<>
						<div style={headerStyle}>
							<FolderTree className="w-4 shrink-0" aria-label="Folder tree icon" />
							<span style={{ fontWeight: "bold" }}>
								{message.type === "ask"
									? tool.isOutsideWorkspace
										? t("chat:directoryOperations.wantsToViewRecursiveOutsideWorkspace")
										: t("chat:directoryOperations.wantsToViewRecursive")
									: tool.isOutsideWorkspace
										? t("chat:directoryOperations.didViewRecursiveOutsideWorkspace")
										: t("chat:directoryOperations.didViewRecursive")}
							</span>
						</div>
						<div className="pl-6">
							<CodeAccordion
								path={tool.path}
								code={tool.content}
								language="shellsession"
								isExpanded={isExpanded}
								onToggleExpand={handleToggleExpand}
							/>
						</div>
					</>
				)
			case "searchFiles":
				return (
					<>
						<div style={headerStyle}>
							{toolIcon("search")}
							<span style={{ fontWeight: "bold" }}>
								{message.type === "ask" ? (
									<Trans
										i18nKey={
											tool.isOutsideWorkspace
												? "chat:directoryOperations.wantsToSearchOutsideWorkspace"
												: "chat:directoryOperations.wantsToSearch"
										}
										components={{ code: <code className="font-medium">{tool.regex}</code> }}
										values={{ regex: tool.regex }}
									/>
								) : (
									<Trans
										i18nKey={
											tool.isOutsideWorkspace
												? "chat:directoryOperations.didSearchOutsideWorkspace"
												: "chat:directoryOperations.didSearch"
										}
										components={{ code: <code className="font-medium">{tool.regex}</code> }}
										values={{ regex: tool.regex }}
									/>
								)}
							</span>
						</div>
						<div className="pl-6">
							<CodeAccordion
								path={tool.path! + (tool.filePattern ? `/(${tool.filePattern})` : "")}
								code={tool.content}
								language="shellsession"
								isExpanded={isExpanded}
								onToggleExpand={handleToggleExpand}
							/>
						</div>
					</>
				)
			case "switchMode":
				return (
					<>
						<div style={headerStyle}>
							<PocketKnife className="w-4 shrink-0" aria-label="Switch mode icon" />
							<span style={{ fontWeight: "bold" }}>
								{message.type === "ask" ? (
									<>
										{tool.reason ? (
											<Trans
												i18nKey="chat:modes.wantsToSwitchWithReason"
												components={{ code: <code className="font-medium">{tool.mode}</code> }}
												values={{ mode: tool.mode, reason: tool.reason }}
											/>
										) : (
											<Trans
												i18nKey="chat:modes.wantsToSwitch"
												components={{ code: <code className="font-medium">{tool.mode}</code> }}
												values={{ mode: tool.mode }}
											/>
										)}
									</>
								) : (
									<>
										{tool.reason ? (
											<Trans
												i18nKey="chat:modes.didSwitchWithReason"
												components={{ code: <code className="font-medium">{tool.mode}</code> }}
												values={{ mode: tool.mode, reason: tool.reason }}
											/>
										) : (
											<Trans
												i18nKey="chat:modes.didSwitch"
												components={{ code: <code className="font-medium">{tool.mode}</code> }}
												values={{ mode: tool.mode }}
											/>
										)}
									</>
								)}
							</span>
						</div>
					</>
				)
			case "newTask":
				// Find all newTask messages to determine which child task ID corresponds to this message
				const newTaskMessages = clineMessages.filter((msg) => {
					if (msg.type === "ask" && msg.ask === "tool") {
						const t = safeJsonParse<ClineSayTool>(msg.text)
						return t?.tool === "newTask"
					}
					return false
				})
				const thisNewTaskIndex = newTaskMessages.findIndex((msg) => msg.ts === message.ts)
				const childIds = currentTaskItem?.childIds || []

				// Only get the child task ID if this newTask has been approved (has a corresponding entry in childIds)
				// This prevents showing a link to a previous task when the current newTask is still awaiting approval
				// Note: We don't use delegatedToId here because it persists after child tasks complete and would
				// incorrectly point to the previous task when a new newTask is awaiting approval
				const childTaskId =
					thisNewTaskIndex >= 0 && thisNewTaskIndex < childIds.length ? childIds[thisNewTaskIndex] : undefined

				// Check if the next message is a subtask_result - if so, don't show the button
				// since the result is displayed right after this message
				const currentMessageIndex = clineMessages.findIndex((msg) => msg.ts === message.ts)
				const nextMessage = currentMessageIndex >= 0 ? clineMessages[currentMessageIndex + 1] : undefined
				const isFollowedBySubtaskResult = nextMessage?.type === "say" && nextMessage?.say === "subtask_result"

				return (
					<>
						<div style={headerStyle}>
							<Split className="size-4" />
							<span style={{ fontWeight: "bold" }}>
								<Trans
									i18nKey="chat:subtasks.wantsToCreate"
									components={{ code: <code>{tool.mode}</code> }}
									values={{ mode: tool.mode }}
								/>
							</span>
						</div>
						<div>
							{childTaskId && !isFollowedBySubtaskResult && (
								<button
									style={{ color: linkColor }}
									className="cursor-pointer flex gap-1 items-center mt-2 text-vscode-descriptionForeground hover:text-vscode-descriptionForeground hover:underline font-normal"
									onClick={() => vscode.postMessage({ type: "showTaskWithId", text: childTaskId })}>
									{t("chat:subtasks.goToSubtask")}
									<ArrowRight className="size-3" />
								</button>
							)}
						</div>
						<div className="border-l border-muted-foreground/80 ml-2 pl-4 pb-1">
							<CollapsibleMarkdownBlock
								markdown={tool.content}
								collapseWithoutScroll={collapseWithoutScrollEnabled}
							/>
						</div>
					</>
				)
			case "finishTask":
				return (
					<>
						<div style={headerStyle}>
							{toolIcon("check-all")}
							<span style={{ fontWeight: "bold" }}>{t("chat:subtasks.wantsToFinish")}</span>
						</div>
						<div className="text-muted-foreground pl-6">
							<MarkdownBlock markdown={t("chat:subtasks.completionInstructions")} />
						</div>
					</>
				)
			case "runSlashCommand": {
				const slashCommandInfo = tool
				return (
					<>
						<div style={headerStyle}>
							{toolIcon("play")}
							<span style={{ fontWeight: "bold" }}>
								{message.type === "ask"
									? t("chat:slashCommand.wantsToRun")
									: t("chat:slashCommand.didRun")}
							</span>
						</div>
						<div
							style={{
								marginTop: "4px",
								backgroundColor: "var(--vscode-editor-background)",
								border: "1px solid var(--vscode-editorGroup-border)",
								borderRadius: "4px",
								overflow: "hidden",
								cursor: "pointer",
							}}
							onClick={handleToggleExpand}>
							<ToolUseBlockHeader
								className="group"
								style={{
									display: "flex",
									alignItems: "center",
									justifyContent: "space-between",
									padding: "10px 12px",
								}}>
								<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
									<span style={{ fontWeight: "500", fontSize: "var(--vscode-font-size)" }}>
										/{slashCommandInfo.command}
									</span>
									{slashCommandInfo.source && (
										<VSCodeBadge style={{ fontSize: "calc(var(--vscode-font-size) - 2px)" }}>
											{slashCommandInfo.source}
										</VSCodeBadge>
									)}
								</div>
								<span
									className={`codicon codicon-chevron-${isExpanded ? "up" : "down"} opacity-0 group-hover:opacity-100 transition-opacity duration-200`}></span>
							</ToolUseBlockHeader>
							{isExpanded && (slashCommandInfo.args || slashCommandInfo.description) && (
								<div
									style={{
										padding: "12px 16px",
										borderTop: "1px solid var(--vscode-editorGroup-border)",
										display: "flex",
										flexDirection: "column",
										gap: "8px",
									}}>
									{slashCommandInfo.args && (
										<div>
											<span style={{ fontWeight: "500" }}>Arguments: </span>
											<span style={{ color: "var(--vscode-descriptionForeground)" }}>
												{slashCommandInfo.args}
											</span>
										</div>
									)}
									{slashCommandInfo.description && (
										<div style={{ color: "var(--vscode-descriptionForeground)" }}>
											{slashCommandInfo.description}
										</div>
									)}
								</div>
							)}
						</div>
					</>
				)
			}
			case "generateImage":
				return (
					<>
						<div style={headerStyle}>
							{tool.isProtected ? (
								<span
									className="codicon codicon-lock"
									style={{ color: "var(--vscode-editorWarning-foreground)", marginBottom: "-1.5px" }}
								/>
							) : (
								toolIcon("file-media")
							)}
							<span style={{ fontWeight: "bold" }}>
								{message.type === "ask"
									? tool.isProtected
										? t("chat:fileOperations.wantsToGenerateImageProtected")
										: tool.isOutsideWorkspace
											? t("chat:fileOperations.wantsToGenerateImageOutsideWorkspace")
											: t("chat:fileOperations.wantsToGenerateImage")
									: t("chat:fileOperations.didGenerateImage")}
							</span>
						</div>
						{message.type === "ask" && (
							<div className="pl-6">
								<ToolUseBlock>
									<div className="p-2">
										<div className="mb-2 break-words">{tool.content}</div>
										<div className="flex items-center gap-1 text-xs text-vscode-descriptionForeground">
											{tool.path}
										</div>
									</div>
								</ToolUseBlock>
							</div>
						)}
					</>
				)
			default:
				return null
		}
	}

	switch (message.type) {
		case "say":
			switch (message.say) {
				// case "auto_switch_model":
				// 	return (
				// 		<ErrorRow
				// 			deleteMessageTs={deleteMessageTs}
				// 			type="auto_switch_model"
				// 			message={message.text || ""}
				// 			expandable={true}
				// 			isLast={isLast}
				// 		/>
				// 	)
				case "diff_error":
					return (
						<ErrorRow
							deleteMessageTs={deleteMessageTs}
							type="diff_error"
							message={message.text || ""}
							expandable={true}
							showCopyButton={true}
						/>
					)
				case "subtask_result":
					// Get the child task ID that produced this result
					const completedChildTaskId = currentTaskItem?.completedByChildId
					return (
						<div className="border-l border-muted-foreground/80 ml-2 pl-4 pt-2 pb-1 -mt-5">
							<div style={headerStyle}>
								<span style={{ fontWeight: "bold", color: successColor }}>
									{t("chat:subtasks.resultContent")}
								</span>
								<Check className="size-3" />
								{(completedChildTaskId || message.subtaskId) && (
									<button
										style={{ color: linkColor }}
										className="cursor-pointer flex gap-1 items-center text-vscode-descriptionForeground hover:text-vscode-descriptionForeground hover:underline font-normal"
										onClick={() =>
											vscode.postMessage({
												type: "showTaskWithId",
												text: message.subtaskId || completedChildTaskId,
											})
										}>
										{t("chat:subtasks.goToSubtask")}
										<ArrowRight className="size-3" />
									</button>
								)}
								{(completedChildTaskId || message.subtaskId) &&
									!(window as any).isJetbrainsPlatform && (
										<>
											{"or"}
											<button
												style={{ color: linkColor }}
												className="cursor-pointer flex gap-1 items-center text-vscode-descriptionForeground hover:text-vscode-descriptionForeground hover:underline font-normal"
												onClick={(e) => {
													e.stopPropagation()
													vscode.postMessage({
														type: "showTaskWithIdInNewTab",
														text: message.subtaskId || completedChildTaskId,
													})
												}}>
												{t("worktrees:openInNewWindow")}
												<ArrowRight className="size-3" />
											</button>
										</>
									)}
							</div>
							<CollapsibleMarkdownBlock
								markdown={message.text}
								collapseWithoutScroll={collapseWithoutScrollEnabled}
							/>
						</div>
					)
				case "reasoning":
					const reasoningText = message?.text || ""
					if (!reasoningText?.trim()) return null
					return (
						<ReasoningBlock
							content={reasoningText}
							ts={message.ts}
							isStreaming={isStreaming}
							isLast={isLast}
						/>
					)
				case "api_req_started": {
					// Determine if the API request is in progress
					const isApiRequestInProgress =
						apiReqCancelReason === undefined && apiRequestFailedMessage === undefined && cost === undefined
					const showApiFetchErrorIcon =
						((cost === null || cost === undefined) && apiRequestFailedMessage) ||
						apiReqStreamingFailedMessage

					return (
						<>
							{/* header Info */}
							<div
								className={`group text-sm transition-opacity`}
								style={{
									...headerStyle,
									marginBottom:
										((cost === null || cost === undefined) && apiRequestFailedMessage) ||
										apiReqStreamingFailedMessage
											? 10
											: 0,
									justifyContent: "space-between",
								}}>
								<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
									{!apiRequestFailedMessage &&
									!apiReqStreamingFailedMessage &&
									isLast &&
									isStreaming ? (
										<ProgressIndicator />
									) : (
										getIconSpan({ iconName: "arrow-swap", color: normalColor })
									)}
									{title}
									{(selectedLLM || originModelId) && !selectReason && (
										<div className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg">
											{isFallbackActive
												? originModelId
												: isAuto
													? t("chat:autoMode.selectedLLM", { selectedLLM })
													: originModelId}
											{isFallbackActive && (
												<span
													title={`${t("chat:autoMode.fallbackModelTooltip")} (${isAuto ? t("chat:autoMode.selectedLLM", { selectedLLM }) : originModelId})`}
													style={{ display: "inline-flex" }}>
													&nbsp;
													{getIconSpan({
														iconName: "warning",
														color: normalColor,
														width: 12,
														height: 12,
													})}
												</span>
											)}
										</div>
									)}
								</div>
								<div
									className="text-xs text-vscode-dropdown-foreground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
									style={{ opacity: cost !== null && cost !== undefined && cost > 0 ? 1 : 0 }}>
									${Number(cost || 0)?.toFixed(4)}
								</div>
								{!isApiRequestInProgress && deleteMessageTs > -1 && (
									<StandardTooltip
										content={
											enableCheckpoints
												? t("common:confirmation.deleteMessageOrRollback")
												: t("common:confirmation.deleteMessage")
										}>
										<RotateCcw
											className={`size-4 mt-[3px] cursor-pointer 
											${isStreaming ? "opacity-10" : isApiRequestInProgress ? "opacity-100" : "opacity-60 hover:opacity-100"}`}
											style={{
												color: "rgba(0, 188, 255, 1)",
											}}
											onClick={(e) => {
												if (isStreaming) return
												e.preventDefault()
												e.stopPropagation()
												vscode.postMessage({ type: "deleteMessage", value: deleteMessageTs })
											}}
										/>
									</StandardTooltip>
								)}
							</div>
							{/* speed Info */}
							{(selectReason || firstTokenLatency !== undefined || tokensPerSecond !== undefined) && (
								<div className="mt-2 flex items-center flex-wrap gap-2">
									{selectReason && (
										<div
											className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
											title="Selection Reason">
											{t("chat:autoMode.selectReason", { selectReason })}
										</div>
									)}
									{showSpeedInfo && firstTokenLatency !== undefined && (
										<div
											className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
											title={t("chat:performance.firstToken")}>
											{t("chat:performance.firstToken")}: {firstTokenLatency}s
										</div>
									)}
									{showSpeedInfo && totalDuration !== undefined && (
										<div
											className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
											title={t("chat:performance.totalDuration")}>
											{t("chat:performance.totalDuration")}: {totalDuration}s
										</div>
									)}
									{showSpeedInfo && tokensPerSecond !== undefined && (
										<div
											className="text-xs text-vscode-descriptionForeground border-vscode-dropdown-border/50 border px-1.5 py-0.5 rounded-lg"
											title={t("chat:performance.tokensPerSecond", { time: tokensPerSecond })}>
											{t("chat:performance.tokensPerSecond", { time: tokensPerSecond })}
										</div>
									)}
								</div>
							)}
							{/* content */}
							{showApiFetchErrorIcon ? (
								<ErrorRow
									deleteMessageTs={deleteMessageTs}
									type="api_failure"
									message={apiRequestFailedMessage || apiReqStreamingFailedMessage || ""}
									docsURL={
										apiRequestFailedMessage?.toLowerCase().includes("powershell")
											? "https://github.com/cline/cline/wiki/TroubleShooting-%E2%80%90-%22PowerShell-is-not-recognized-as-an-internal-or-external-command%22"
											: undefined
									}
									errorDetails={apiReqStreamingFailedMessage}
								/>
							) : null}
						</>
					)
				}
				case "api_req_retry_delayed": {
					let body = t(`chat:apiRequest.failed`)
					let retryInfo, rawError, code, docsURL
					docsURL = "costrict://settings?provider=claude-code"

					// Check if message is countdown-only (empty or just countdown format like "↻ 25s...")
					const isCountdownOnly = message.text && /^↻ \d+s\.\.\.$/.test(message.text)
					const isEmptyMessage = !message.text || message.text.trim() === ""

					// If message is empty, don't show anything (error already shown in api_req_started)
					if (isEmptyMessage) {
						return null
					}

					if (!!message.text && !isCountdownOnly) {
						// Check for Claude Code authentication error first
						if (message.text.includes("Not authenticated with Claude Code")) {
							body = t("chat:apiRequest.errorMessage.claudeCodeNotAuthenticated")
							docsURL = "costrict://settings?provider=claude-code"
						} else {
							// Try to show richer error message for that code, if available
							const potentialCode = parseInt(message.text.substring(0, 3))
							if (!isNaN(potentialCode) && potentialCode >= 400) {
								code = potentialCode
								const stringForError = `chat:apiRequest.errorMessage.${code}`
								if (i18n.exists(stringForError)) {
									body = t(stringForError)
									// Fill this out in upcoming PRs
									// Do not remove this
									// switch(code) {
									// 	case ERROR_CODE:
									// 		docsURL = ???
									// 		break;
									// }
								} else {
									body = t("chat:apiRequest.errorMessage.unknown")
									docsURL =
										"mailto:zgsm@sangfor.com.cn?subject=Unknown API Error&body=[Please include full error details]"
								}
							} else if (message.text.indexOf("Connection error") === 0) {
								body = t("chat:apiRequest.errorMessage.connection")
							} else if (message.text.includes("RequestID:")) {
								body = message.text.replace(/<retry_timer>(.*?)<\/retry_timer>/, "")?.trim()
							} else {
								// Non-HTTP-status-code error message - store full text as errorDetails
								body = t("chat:apiRequest.errorMessage.unknown")
								docsURL =
									"mailto:zgsm@sangfor.com.cn?subject=Unknown API Error&body=[Please include full error details]"
							}
						}

						// This isn't pretty, but since the retry logic happens at a lower level
						// and the message object is just a flat string, we need to extract the
						// retry information using this "tag" as a convention
						const retryTimerMatch = message.text.match(/<retry_timer>(.*?)<\/retry_timer>/)
						const retryTimer = retryTimerMatch && retryTimerMatch[1] ? parseInt(retryTimerMatch[1], 10) : 0
						rawError = message.text.replace(/<retry_timer>(.*?)<\/retry_timer>/, "")?.trim()
						retryInfo = retryTimer > 0 && (
							<p
								className={cn(
									"mt-2 font-light text-xs  text-vscode-descriptionForeground cursor-default flex items-center gap-1 transition-all duration-1000",
									retryTimer === 0 ? "opacity-0 max-h-0" : "max-h-2 opacity-100",
								)}>
								<Repeat2 className="size-3" strokeWidth={1.5} />
								<span>{retryTimer}s</span>
							</p>
						)
					}

					return (
						<ErrorRow
							deleteMessageTs={deleteMessageTs}
							type="api_req_retry_delayed"
							code={code}
							message={body}
							docsURL={docsURL}
							errorDetails={rawError}
							additionalContent={retryInfo}
						/>
					)
				}
				case "api_req_rate_limit_wait": {
					const isWaiting = message.partial === true

					const waitSeconds = (() => {
						if (!message.text) return undefined
						try {
							const data = JSON.parse(message.text)
							return typeof data.seconds === "number" ? data.seconds : undefined
						} catch {
							return undefined
						}
					})()

					return isWaiting && waitSeconds !== undefined && isLast ? (
						<div
							className={`group text-sm transition-opacity opacity-100`}
							style={{
								...headerStyle,
								marginBottom: 0,
								justifyContent: "space-between",
							}}>
							<div style={{ display: "flex", alignItems: "center", gap: "10px", flexGrow: 1 }}>
								<ProgressIndicator />
								<span style={{ color: normalColor }}>{t("chat:apiRequest.rateLimitWait")}</span>
							</div>
							<span className="text-xs font-light text-vscode-descriptionForeground">{waitSeconds}s</span>
						</div>
					) : null
				}
				case "api_req_finished":
					return null // we should never see this message type
				case "text": {
					const resultText = `${message?.text ?? ""}`
					const loadingMessage = isLast && message.partial && isStreaming
					if (!resultText?.trim()) {
						return <div className="ml-2 mb-0 pl-4 pb-1">{t("chat:emptyCompletionResult")}</div>
					}
					return (
						<div className="group">
							{loadingMessage ? (
								<div className="group text-sm transition-opacity opacity-100" style={headerStyle}>
									<ProgressIndicator />
									<span style={{ color: normalColor }}>
										<RandomLoadingMessage language={language as RandomLoadingMessageLanguage} />
									</span>
								</div>
							) : (
								<div style={headerStyle}>
									<MessageCircle className="w-4 shrink-0" aria-label="Speech bubble icon" />
									<span style={{ fontWeight: "bold" }}>{t("chat:text.rooSaid")}</span>
									{message.ts ? format(new Date(message.ts), "yyyy-MM-dd HH:mm:ss") : ""}
									<div style={{ flexGrow: 1 }} />
									<OpenMarkdownPreviewButton markdown={message.text} />
								</div>
							)}
							<div className="pl-6">
								<Markdown
									collapseWithoutScroll={collapseWithoutScrollEnabled}
									markdown={HighlightedPlainText({
										message: message || {},
										query: searchQuery,
										flag: t("settings:experimental.CHAT_SEARCH.placeholder"),
									})}
									partial={message.partial}
								/>
								{message.images && message.images.length > 0 && (
									<div style={{ marginTop: "10px" }}>
										{message.images.map((image, index) => (
											<ImageBlock key={index} imageData={image} />
										))}
									</div>
								)}
							</div>
						</div>
					)
				}
				case "user_feedback":
					return (
						<div className="group">
							<div style={headerStyle}>
								{logoPic ? (
									<img
										src={logoPic}
										title={userInfo?.name || t("chat:feedback.defaultUserName")}
										alt={userInfo?.name || t("chat:feedback.defaultUserName")}
										className="w-6 h-6 rounded-full object-cover"
									/>
								) : (
									<User className="w-4 shrink-0" aria-label="User icon" />
								)}
								<span style={{ fontWeight: "bold" }}>
									{t("chat:feedback.youSaid", {
										username: userInfo?.name || t("chat:feedback.defaultUserName"),
									})}
								</span>
								{message.ts ? format(new Date(message.ts), "yyyy-MM-dd HH:mm:ss") : ""}
							</div>
							<div
								className={cn(
									"ml-6 border rounded-sm whitespace-pre-wrap",
									isEditing ? "" : "overflow-hidden",
									isEditing
										? "bg-vscode-editor-background text-vscode-editor-foreground"
										: "cursor-text p-1 bg-vscode-editor-foreground/50 text-vscode-editor-background",
								)}>
								{isEditing ? (
									<div className="flex flex-col gap-2">
										<ChatTextArea
											inputValue={editedContent}
											setInputValue={setEditedContent}
											sendingDisabled={false}
											selectApiConfigDisabled={true}
											placeholderText={t("chat:editMessage.placeholder")}
											selectedImages={editImages}
											setSelectedImages={setEditImages}
											onSend={handleSaveEdit}
											onSelectImages={handleSelectImages}
											shouldDisableImages={!model?.supportsImages}
											mode={editMode}
											setMode={setEditMode}
											modeShortcutText=""
											isEditMode={true}
											isStreaming={isStreaming}
											onCancel={handleCancelEdit}
										/>
									</div>
								) : (
									<div className="flex justify-between cursor-pointer">
										<div
											className="grow px-2 py-1 wrap-anywhere rounded-lg transition-colors"
											onClick={(e) => {
												e.stopPropagation()
												if (!isStreaming) {
													handleEditClick()
												}
											}}>
											{message.text && message.text.length > 200 ? (
												<>
													<div
														className="wrap-anywhere"
														title={t("chat:queuedMessages.clickToEdit")}
														style={{
															wordBreak: "break-word",
															overflowWrap: "anywhere",
															maxHeight: !isFeedbackExpanded ? "120px" : "none",
															overflow: !isFeedbackExpanded ? "hidden" : "auto",
															position: "relative",
														}}>
														<Mention text={message.text} withShadow />
													</div>
													<div className={"flex gap-2 pr-1 justify-end"}>
														{!isFeedbackExpanded && (
															<button
																className="text-vscode-textLink-foreground hover:underline mt-1 relative -right-12"
																onClick={(e) => {
																	e.stopPropagation()
																	setIsFeedbackExpanded(true)
																}}>
																{t("chat:markdown.expandPrompt")}
															</button>
														)}
														{isFeedbackExpanded && (
															<button
																className="text-vscode-textLink-foreground hover:underline mt-1 relative -right-12"
																onClick={(e) => {
																	e.stopPropagation()
																	setIsFeedbackExpanded(false)
																}}>
																{t("chat:markdown.collapsePrompt")}
															</button>
														)}
													</div>
												</>
											) : (
												<Mention text={message.text} withShadow />
											)}
										</div>
										<div className="flex gap-2 pr-1">
											<div
												className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
												style={{ visibility: isStreaming ? "hidden" : "visible" }}
												title={t("history:copyPrompt")}
												onClick={(e) => {
													e.stopPropagation()
													// handleEditClick()
													copyWithFeedback(message.text || "").then((success) => {
														if (success) {
															// Show checkmark
															setShowCopySuccess(true)

															// Reset after a brief delay
															setTimeout(() => {
																setShowCopySuccess(false)
															}, 1000)
														}
													})
												}}>
												{showCopySuccess ? (
													<ClipboardCheck className="w-4" aria-label="Copy message icon" />
												) : (
													<ClipboardCopy className="w-4" aria-label="Copy message icon" />
												)}
											</div>
											<div
												className="cursor-pointer shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
												style={{ visibility: isStreaming ? "hidden" : "visible" }}
												title={t("common:confirmation.deleteMessage")}
												onClick={(e) => {
													e.stopPropagation()
													vscode.postMessage({
														type: "deleteMessage",
														value: deleteMessageTs,
													})
												}}>
												<Trash2 className="w-4 shrink-0" aria-label="Delete message icon" />
											</div>
										</div>
									</div>
								)}
								{!isEditing && message.images && message.images.length > 0 && (
									<Thumbnails images={message.images} style={{ marginTop: "8px" }} />
								)}
							</div>
						</div>
					)
				case "user_feedback_diff":
					const tool = safeJsonParse<ClineSayTool>(message.text)
					return (
						<div style={{ marginTop: -10, width: "100%" }}>
							<CodeAccordion
								code={tool?.diff}
								language="diff"
								isFeedback={true}
								isExpanded={isExpanded}
								onToggleExpand={handleToggleExpand}
							/>
						</div>
					)
				case "error":
					// Check if this is a model response error based on marker strings from backend
					const isNoToolsUsedError = message.text === "MODEL_NO_TOOLS_USED"
					const isNoAssistantMessagesError = message.text === "MODEL_NO_ASSISTANT_MESSAGES"

					if (
						(isNoAssistantMessagesError || isNoToolsUsedError) &&
						apiConfiguration.apiProvider === "costrict"
					) {
						return <div className="ml-2 pl-4 pb-1">{t("chat:emptyCompletionResult")}</div>
					}
					if (isNoToolsUsedError) {
						return (
							<ErrorRow
								deleteMessageTs={deleteMessageTs}
								type="error"
								title={t("chat:modelResponseIncomplete")}
								message={t("chat:modelResponseErrors.noToolsUsed")}
								errorDetails={t("chat:modelResponseErrors.noToolsUsedDetails")}
							/>
						)
					}

					if (isNoAssistantMessagesError) {
						return (
							<ErrorRow
								deleteMessageTs={deleteMessageTs}
								type="error"
								title={t("chat:modelResponseIncomplete")}
								message={t("chat:modelResponseErrors.noAssistantMessages")}
								errorDetails={t("chat:modelResponseErrors.noAssistantMessagesDetails")}
							/>
						)
					}

					// Fallback for generic errors
					return (
						<ErrorRow
							deleteMessageTs={deleteMessageTs}
							type="error"
							message={message.text || t("chat:error")}
							errorDetails={message.text}
							isLast={isLast}
						/>
					)
				case "completion_result":
					const resultText = `${message?.text ?? ""}`
					if (!resultText.trim()) {
						return <div className="ml-2 pl-4 pb-1">{t("chat:emptyCompletionResult")}</div>
					}
					return (
						<div className="group">
							<div style={headerStyle}>
								{icon}
								{title}
								<div style={{ flexGrow: 1 }} />
								<OpenMarkdownPreviewButton markdown={resultText} />
							</div>
							<div className="border-l border-green-600/30 ml-2 pl-4 pb-1">
								<Markdown
									collapseWithoutScroll={collapseWithoutScrollEnabled}
									markdown={HighlightedPlainText({
										message: message || {},
										query: searchQuery,
										flag: t("settings:experimental.CHAT_SEARCH.placeholder"),
									})}
									partial={message.partial}
								/>
							</div>
						</div>
					)
				case "shell_integration_warning":
					return <CommandExecutionError />
				case "checkpoint_saved":
					return (
						<CheckpointSaved
							ts={message.ts!}
							isLast={isLast}
							commitHash={message.text!}
							currentHash={currentCheckpoint}
							checkpoint={message.checkpoint}
							onJumpToPreviousCheckpoint={onJumpToPreviousCheckpoint}
						/>
					)
				case "condense_context":
					// In-progress state
					if (message.partial) {
						return <InProgressRow eventType="condense_context" />
					}
					// Completed state
					if (message.contextCondense) {
						return <CondensationResultRow data={message.contextCondense} />
					}
					return null
				case "condense_context_error":
					// return (
					// 	<ErrorRow
					//      deleteMessageTs={deleteMessageTs}
					// 		type="error"
					// 		apiConfiguration={apiConfiguration}
					// 		title={t("chat:contextCondense.errorHeader")}
					// 		message={message.text || ""}
					// 	/>
					// )
					return <CondensationErrorRow errorText={message.text} />
				case "sliding_window_truncation":
					// In-progress state
					if (message.partial) {
						return <InProgressRow eventType="sliding_window_truncation" />
					}
					// Completed state
					if (message.contextTruncation) {
						return <TruncationResultRow data={message.contextTruncation} />
					}
					return null
				case "codebase_search_result":
					let parsed: {
						content: {
							query: string
							results: Array<{
								filePath: string
								score: number
								startLine: number
								endLine: number
								codeChunk: string
							}>
						}
					} | null = null

					try {
						if (message.text) {
							parsed = JSON.parse(message.text)
						}
					} catch (error) {
						console.error("Failed to parse codebaseSearch content:", error)
					}

					if (parsed && !parsed?.content) {
						console.error("Invalid codebaseSearch content structure:", parsed.content)
						return <div>Error displaying search results.</div>
					}

					const { results = [] } = parsed?.content || {}

					return <CodebaseSearchResultsDisplay results={results} />
				case "user_edit_todos":
					return <UpdateTodoListToolBlock userEdited onChange={() => {}} />
				case "tool" as any:
					// Handle say tool messages
					const sayTool = safeJsonParse<ClineSayTool>(message.text)
					if (!sayTool) return null

					switch (sayTool.tool) {
						case "runSlashCommand": {
							const slashCommandInfo = sayTool
							return (
								<>
									<div style={headerStyle}>
										<span
											className="codicon codicon-terminal-cmd"
											style={{
												color: "var(--vscode-foreground)",
												marginBottom: "-1.5px",
											}}></span>
										<span style={{ fontWeight: "bold" }}>{t("chat:slashCommand.didRun")}</span>
									</div>
									<div className="pl-6">
										<ToolUseBlock>
											<ToolUseBlockHeader
												style={{
													display: "flex",
													flexDirection: "column",
													alignItems: "flex-start",
													gap: "4px",
													padding: "10px 12px",
												}}>
												<div
													style={{
														display: "flex",
														alignItems: "center",
														gap: "8px",
														width: "100%",
													}}>
													<span
														style={{
															fontWeight: "500",
															fontSize: "var(--vscode-font-size)",
														}}>
														/{slashCommandInfo.command}
													</span>
													{slashCommandInfo.args && (
														<span
															style={{
																color: "var(--vscode-descriptionForeground)",
																fontSize: "var(--vscode-font-size)",
															}}>
															{slashCommandInfo.args}
														</span>
													)}
												</div>
												{slashCommandInfo.description && (
													<div
														style={{
															color: "var(--vscode-descriptionForeground)",
															fontSize: "calc(var(--vscode-font-size) - 1px)",
														}}>
														{slashCommandInfo.description}
													</div>
												)}
												{slashCommandInfo.source && (
													<div style={{ display: "flex", alignItems: "center", gap: "4px" }}>
														<VSCodeBadge
															style={{ fontSize: "calc(var(--vscode-font-size) - 2px)" }}>
															{slashCommandInfo.source}
														</VSCodeBadge>
													</div>
												)}
											</ToolUseBlockHeader>
										</ToolUseBlock>
									</div>
								</>
							)
						}
						case "readCommandOutput": {
							const formatBytes = (bytes: number) => {
								if (bytes < 1024) return `${bytes} B`
								if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
								return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
							}

							// Determine if this is a search operation
							const isSearch = sayTool.searchPattern !== undefined

							let infoText = ""
							if (isSearch) {
								// Search mode: show pattern and match count
								const matchText =
									sayTool.matchCount !== undefined
										? sayTool.matchCount === 1
											? "1 match"
											: `${sayTool.matchCount} matches`
										: ""
								infoText = `search: "${sayTool.searchPattern}"${matchText ? ` • ${matchText}` : ""}`
							} else if (
								sayTool.readStart !== undefined &&
								sayTool.readEnd !== undefined &&
								sayTool.totalBytes !== undefined
							) {
								// Read mode: show byte range
								infoText = `${formatBytes(sayTool.readStart)} - ${formatBytes(sayTool.readEnd)} of ${formatBytes(sayTool.totalBytes)}`
							} else if (sayTool.totalBytes !== undefined) {
								infoText = formatBytes(sayTool.totalBytes)
							}

							return (
								<div style={headerStyle}>
									<FileCode2 className="w-4 shrink-0" aria-label="Read command output icon" />
									<span style={{ fontWeight: "bold" }}>{t("chat:readCommandOutput.title")}</span>
									{infoText && (
										<span
											className="text-xs ml-1"
											style={{ color: "var(--vscode-descriptionForeground)" }}>
											({infoText})
										</span>
									)}
								</div>
							)
						}
						default:
							return null
					}
				case "image":
					// Parse the JSON to get imageUri and imagePath
					const imageInfo = safeJsonParse<{ imageUri: string; imagePath: string }>(message.text || "{}")
					if (!imageInfo) {
						return null
					}
					return (
						<div style={{ marginTop: "10px" }}>
							<ImageBlock imageUri={imageInfo.imageUri} imagePath={imageInfo.imagePath} />
						</div>
					)
				case "too_many_tools_warning": {
					const warningData = safeJsonParse<{
						toolCount: number
						serverCount: number
						threshold: number
					}>(message.text || "{}")
					if (!warningData) return null
					const toolsPart = t("chat:tooManyTools.toolsPart", { count: warningData.toolCount })
					const serversPart = t("chat:tooManyTools.serversPart", { count: warningData.serverCount })
					return (
						<WarningRow
							title={t("chat:tooManyTools.title")}
							message={t("chat:tooManyTools.messageTemplate", {
								tools: toolsPart,
								servers: serversPart,
								threshold: warningData.threshold,
							})}
							actionText={t("chat:tooManyTools.openMcpSettings")}
							onAction={() =>
								window.postMessage(
									{ type: "action", action: "settingsButtonClicked", values: { section: "mcp" } },
									"*",
								)
							}
						/>
					)
				}
				default: {
					const resultText = `${message?.text ?? ""}`
					if (!resultText.trim()) {
						return <div className="ml-2 pl-4 pb-1">{t("chat:emptyCompletionResult")}</div>
					}
					return (
						<>
							{title && (
								<div style={headerStyle}>
									{icon}
									{title}
								</div>
							)}
							<div style={{ paddingTop: 10 }}>
								<Markdown
									collapseWithoutScroll={collapseWithoutScrollEnabled}
									markdown={HighlightedPlainText({
										message: message || {},
										query: searchQuery,
										flag: t("settings:experimental.CHAT_SEARCH.placeholder"),
									})}
									partial={message.partial}
								/>
							</div>
						</>
					)
				}
			}
		case "ask":
			switch (message.ask) {
				case "mistake_limit_reached":
					return (
						<ErrorRow
							deleteMessageTs={deleteMessageTs}
							type="mistake_limit"
							message={message.text || ""}
							errorDetails={message.text}
							isLast={isLast}
						/>
					)
				case "command":
					if (!message?.text?.trim()) {
						return <div className="ml-2 pl-4 pb-1">{t("chat:emptyCompletionResult")}</div>
					}
					return (
						<CommandExecution
							executionId={message.ts.toString()}
							text={message.text}
							icon={icon}
							onCommandStop={onCommandStop}
							title={title}
						/>
					)
				case "use_mcp_server":
					// Parse the message text to get the MCP server request
					const messageJson = safeJsonParse<any>(message.text, {})

					// Extract the response field if it exists
					const { response, ...mcpServerRequest } = messageJson

					// Create the useMcpServer object with the response field
					const useMcpServer: ClineAskUseMcpServer = {
						...mcpServerRequest,
						response,
					}

					if (!useMcpServer) {
						return null
					}

					const server = mcpServers.find((server) => server.name === useMcpServer.serverName)

					return (
						<>
							<div style={headerStyle}>
								{icon}
								{title}
							</div>
							<div className="w-full bg-vscode-editor-background border border-vscode-border rounded-xs p-2 mt-2">
								{useMcpServer.type === "access_mcp_resource" && (
									<McpResourceRow
										item={{
											// Use the matched resource/template details, with fallbacks
											...(findMatchingResourceOrTemplate(
												useMcpServer.uri || "",
												server?.resources,
												server?.resourceTemplates,
											) || {
												name: "",
												mimeType: "",
												description: "",
											}),
											// Always use the actual URI from the request
											uri: useMcpServer.uri || "",
										}}
									/>
								)}
								{useMcpServer.type === "use_mcp_tool" && (
									<McpExecution
										executionId={message.ts.toString()}
										text={useMcpServer.arguments !== "{}" ? useMcpServer.arguments : undefined}
										serverName={useMcpServer.serverName}
										toolName={useMcpServer.toolName}
										isArguments={true}
										server={server}
										useMcpServer={useMcpServer}
										alwaysAllowMcp={alwaysAllowMcp}
									/>
								)}
							</div>
						</>
					)
				case "completion_result":
					const resultText = `${message?.text ?? ""}`
					if (!resultText.trim()) {
						return <div className="ml-2 pl-4 pb-1">{t("chat:emptyCompletionResult")}</div>
					}
					if (resultText) {
						return (
							<div className="group">
								<div style={headerStyle}>
									{icon}
									{title}
									<div style={{ flexGrow: 1 }} />
									<OpenMarkdownPreviewButton markdown={resultText} />
								</div>
								<div style={{ color: "var(--vscode-charts-green)", paddingTop: 10 }}>
									<Markdown
										collapseWithoutScroll={collapseWithoutScrollEnabled}
										markdown={HighlightedPlainText({
											message: message || {},
											query: searchQuery,
											flag: t("settings:experimental.CHAT_SEARCH.placeholder"),
										})}
										partial={message.partial}
									/>
								</div>
							</div>
						)
					} else {
						return null // Don't render anything when we get a completion_result ask without text
					}
				case "followup": {
					const resultText = `${message?.text ?? ""}`
					if (!resultText.trim()) {
						return <div className="ml-2 pl-4 pb-1">{t("chat:emptyCompletionResult")}</div>
					}
					return (
						<>
							{title && (
								<div style={headerStyle}>
									{icon}
									{title}
								</div>
							)}
							<div className="flex flex-col gap-2 ml-6">
								<Markdown markdown={message.partial === true ? resultText : followUpData?.question} />
								<FollowUpSuggest
									suggestions={followUpData?.suggest}
									onSuggestionClick={onSuggestionClick}
									ts={message?.ts}
									onCancelAutoApproval={onFollowUpUnmount}
									isAnswered={isFollowUpAnswered}
									isFollowUpAutoApprovalPaused={isFollowUpAutoApprovalPaused}
								/>
							</div>
						</>
					)
				}
				case "multiple_choice": {
					const resultText = `${message?.text ?? ""}`
					if (!resultText.trim()) {
						return <div className="ml-2 pl-4 pb-1">{t("chat:emptyCompletionResult")}</div>
					}
					return (
						<>
							{title && (
								<div style={headerStyle}>
									{icon}
									{title}
								</div>
							)}
							<div className="flex flex-col gap-2 ml-6">
								{message.partial && isStreaming ? (
									<div className="flex items-center gap-2 py-2 text-vscode-descriptionForeground">
										<VSCodeProgressRing className="size-4" />
										<span className="text-sm">{t("chat:multipleChoice.loading")}</span>
									</div>
								) : (
									multipleChoiceData &&
									onMultipleChoiceSubmit && (
										<MultipleChoiceForm
											data={multipleChoiceData}
											onSubmit={onMultipleChoiceSubmit}
											isAnswered={isMultipleChoiceAnswered}
											onCancelAutoApproval={onMultipleChoiceUnmount}
										/>
									)
								)}
							</div>
						</>
					)
				}
				case "auto_approval_max_req_reached": {
					return <AutoApprovedRequestLimitWarning message={message} />
				}
				default:
					return null
			}
	}
}
