"use client"

import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { useTranslations } from "next-intl"
import { MessageInput } from "@/components/chat/message-input"
import type { AgentType, PromptDraft, SessionStats } from "@/lib/types"
import { useFolderContext } from "@/contexts/folder-context"
import { useTabContext } from "@/contexts/tab-context"
import { useSessionStats } from "@/contexts/session-stats-context"
import { useAcpActions } from "@/contexts/acp-connections-context"
import { useConnectionLifecycle } from "@/hooks/use-connection-lifecycle"
import type { AdaptedMessage } from "@/lib/adapters/ai-elements-adapter"
import {
  adaptLiveMessageFromAcp,
  adaptMessageTurns,
} from "@/lib/adapters/ai-elements-adapter"
import {
  buildUserMessageTextPartsFromDraft,
  extractUserImagesFromDraft,
  extractUserResourcesFromDraft,
  getPromptDraftDisplayText,
} from "@/lib/prompt-draft"
import {
  buildPlanKey,
  extractLatestPlanEntriesFromMessages,
} from "@/lib/agent-plan"
import {
  buildConversationDraftStorageKey,
  buildNewConversationDraftStorageKey,
  moveMessageInputDraft,
} from "@/lib/message-input-draft"
import {
  createConversation,
  getFolderConversation,
  openSettingsWindow,
  updateConversationStatus,
  updateConversationExternalId,
} from "@/lib/tauri"
import { disposeTauriListener } from "@/lib/tauri-listener"
import { AgentSelector } from "@/components/chat/agent-selector"
import { LiveMessageBlock } from "@/components/chat/live-message-block"
import { AgentPlanOverlay } from "@/components/chat/agent-plan-overlay"
import { LiveTurnStats } from "@/components/message/live-turn-stats"
import { TurnStats } from "@/components/message/turn-stats"
import { UserResourceLinks } from "@/components/message/user-resource-links"
import { UserImageAttachments } from "@/components/message/user-image-attachments"
import { ConversationShell } from "@/components/chat/conversation-shell"
import {
  MessageThread,
  MessageThreadContent,
} from "@/components/ai-elements/message-thread"
import { Message, MessageContent } from "@/components/ai-elements/message"
import { ContentPartsRenderer } from "@/components/message/content-parts-renderer"

const ACP_AGENTS_UPDATED_EVENT = "app://acp-agents-updated"

interface WelcomeInputPanelProps {
  defaultAgentType?: AgentType
  workingDir?: string
  tabId?: string
  isActive?: boolean
}

interface AgentsUpdatedEventPayload {
  reason?: string
  agent_type?: AgentType | null
}

function normalizeErrorMessage(error: unknown): string {
  if (error instanceof Error) return error.message
  return String(error)
}

function isExpectedAutoLinkError(error: unknown): boolean {
  if (!error || typeof error !== "object") return false
  return (error as { alerted?: unknown }).alerted === true
}

function buildInlineAutoConnectErrorMessage(
  raw: string,
  options: {
    fallback: string
    append: (message: string) => string
    alreadyContainsPath: (message: string) => boolean
  }
): string {
  const normalized = raw.trim().replace(/[。.!?，,；;：:]+$/u, "")
  if (!normalized) return options.fallback
  if (options.alreadyContainsPath(normalized)) return normalized
  return options.append(normalized)
}

export function WelcomeInputPanel({
  defaultAgentType,
  workingDir,
  tabId,
  isActive = true,
}: WelcomeInputPanelProps) {
  const t = useTranslations("Folder.chat.welcomeInputPanel")
  const tabT = useTranslations("Folder.tabContext")
  const sharedT = useTranslations("Folder.chat.shared")
  const fallbackContextId = useMemo(() => crypto.randomUUID(), [])
  const contextKey = tabId ?? `new-${fallbackContextId}`

  const { folderId, refreshConversations } = useFolderContext()
  const { promoteNewConversationTab, linkTabConversation } = useTabContext()
  const { setSessionStats } = useSessionStats()
  const { migrateContextKey } = useAcpActions()
  const latestSessionStatsRef = useRef<SessionStats | null>(null)
  const isActiveRef = useRef(isActive)
  const statsRefreshSeqRef = useRef(0)

  useEffect(() => {
    isActiveRef.current = isActive
  }, [isActive])

  // Reset or restore token stats when tab becomes active
  useEffect(() => {
    if (isActive) {
      setSessionStats(latestSessionStatsRef.current)
    }
  }, [isActive, setSessionStats])

  const applySessionStats = useCallback(
    (stats: SessionStats | null) => {
      latestSessionStatsRef.current = stats
      if (isActiveRef.current) {
        setSessionStats(stats)
      }
    },
    [setSessionStats]
  )

  const hasTokenStats = useCallback((stats: SessionStats | null): boolean => {
    if (!stats) return false
    return (
      stats.total_usage !== null ||
      stats.total_tokens != null ||
      stats.context_window_used_tokens != null ||
      stats.context_window_max_tokens != null
    )
  }, [])

  const hasAssistantUsage = useCallback(
    (messages: AdaptedMessage[]): boolean => {
      for (let i = messages.length - 1; i >= 0; i -= 1) {
        const message = messages[i]
        if (message.role !== "assistant") continue
        return message.usage != null
      }
      return false
    },
    []
  )

  const refreshConversationFromDb = useCallback(
    async (expectedTurnCount?: number) => {
      const conversationId = dbConvIdRef.current
      if (!conversationId) return

      const refreshSeq = ++statsRefreshSeqRef.current
      const maxAttempts = 10
      const retryDelayMs = 400
      let latestMessages: AdaptedMessage[] | null = null
      let latestStats: SessionStats | null = null

      for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
        if (refreshSeq !== statsRefreshSeqRef.current) return

        try {
          const detail = await getFolderConversation(conversationId)
          if (refreshSeq !== statsRefreshSeqRef.current) return

          const messages = adaptMessageTurns(detail.turns, {
            attachedResources: sharedT("attachedResources"),
            toolCallFailed: sharedT("toolCallFailed"),
          })
          const stats = detail.session_stats ?? null
          latestMessages = messages
          latestStats = stats

          const hasExpectedTurns =
            expectedTurnCount == null ||
            detail.turns.length >= expectedTurnCount
          const canShowTurnTokenStats = hasAssistantUsage(messages)
          const canShowSessionTokenStats = hasTokenStats(stats)
          if (
            hasExpectedTurns &&
            (canShowTurnTokenStats || canShowSessionTokenStats)
          ) {
            setHistory(messages)
            if (canShowSessionTokenStats) {
              applySessionStats(stats)
            }
            return
          }
        } catch {
          // Ignore transient read failures while session file is syncing.
        }

        if (attempt < maxAttempts - 1) {
          await new Promise((resolve) => setTimeout(resolve, retryDelayMs))
        }
      }

      if (refreshSeq !== statsRefreshSeqRef.current) return
      if (latestMessages) {
        setHistory(latestMessages)
      }
      if (latestStats && hasTokenStats(latestStats)) {
        applySessionStats(latestStats)
      }
    },
    [applySessionStats, hasAssistantUsage, hasTokenStats, sharedT]
  )

  useEffect(() => {
    return () => {
      statsRefreshSeqRef.current += 1
    }
  }, [])

  const [phase, setPhase] = useState<"welcome" | "conversation">("welcome")
  const [selectedAgent, setSelectedAgent] = useState<AgentType>(
    defaultAgentType ?? "codex"
  )
  const [history, setHistory] = useState<AdaptedMessage[]>([])
  const historyRef = useRef<AdaptedMessage[]>([])
  useEffect(() => {
    historyRef.current = history
  }, [history])
  const historicalPlanEntries = useMemo(
    () => extractLatestPlanEntriesFromMessages(history),
    [history]
  )
  const historicalPlanKey = useMemo(
    () => buildPlanKey(historicalPlanEntries),
    [historicalPlanEntries]
  )
  const [modeId, setModeId] = useState<string | null>(null)
  const [dbConversationId, setDbConversationId] = useState<number | null>(null)
  const [agentsLoaded, setAgentsLoaded] = useState(false)
  const [usableAgentCount, setUsableAgentCount] = useState(0)
  const [agentConnectError, setAgentConnectError] = useState<string | null>(
    null
  )
  const canAutoConnect = agentsLoaded && usableAgentCount > 0
  const pendingPromptRef = useRef<{
    draft: PromptDraft
    modeId: string | null
  } | null>(null)
  const newConversationDraftStorageKey = useMemo(
    () =>
      buildNewConversationDraftStorageKey({
        folderId,
      }),
    [folderId]
  )
  const activeDraftStorageKey = useMemo(() => {
    if (dbConversationId != null) {
      return buildConversationDraftStorageKey(selectedAgent, dbConversationId)
    }
    return newConversationDraftStorageKey
  }, [dbConversationId, newConversationDraftStorageKey, selectedAgent])

  // DB persistence state
  const dbConvIdRef = useRef<number | null>(null)
  const statusUpdatedRef = useRef(false)
  const tabPromotedRef = useRef(false)
  const tabIdRef = useRef(tabId)
  const selectedAgentRef = useRef(selectedAgent)
  const convTitleRef = useRef<string | null>(null)
  useEffect(() => {
    tabIdRef.current = tabId
  }, [tabId])
  useEffect(() => {
    selectedAgentRef.current = selectedAgent
  }, [selectedAgent])

  const {
    conn,
    modeLoading,
    configOptionsLoading,
    autoConnectError,
    handleFocus,
    handleSend: lifecycleSend,
    handleSetConfigOption,
    handleCancel,
    handleRespondPermission,
  } = useConnectionLifecycle({
    contextKey,
    agentType: selectedAgent,
    isActive: isActive && canAutoConnect,
    workingDir,
  })

  // Destructure stable callback + volatile status separately.
  // conn.connect is stable (depends only on actions + contextKey).
  // conn.status changes on state transitions (~5/turn), NOT on every
  // streaming delta (hundreds/sec) — much cheaper than depending on `conn`.
  const {
    status: connStatus,
    connect: connConnect,
    disconnect: connDisconnect,
    sessionId: connSessionId,
  } = conn
  const isConnecting =
    connStatus === "connecting" || connStatus === "downloading"
  const connectionModes = useMemo(
    () => conn.modes?.available_modes ?? [],
    [conn.modes?.available_modes]
  )
  const connectionConfigOptions = useMemo(
    () => conn.configOptions ?? [],
    [conn.configOptions]
  )
  const connectionCommands = useMemo(
    () => conn.availableCommands ?? [],
    [conn.availableCommands]
  )
  const selectedModeId = useMemo(() => {
    if (connectionModes.length === 0) return null
    if (modeId && connectionModes.some((mode) => mode.id === modeId)) {
      return modeId
    }
    return conn.modes?.current_mode_id ?? connectionModes[0]?.id ?? null
  }, [conn.modes?.current_mode_id, connectionModes, modeId])

  // Persist the agent-assigned session ID as external_id once both
  // the DB conversation ID and the ACP session ID are available.
  const externalIdSavedRef = useRef(false)
  const sessionIdRef = useRef<string | null>(null)
  const refreshingCurrentAgentRef = useRef(false)
  const agentStatusRefreshTimerRef = useRef<ReturnType<
    typeof setTimeout
  > | null>(null)
  const phaseRef = useRef(phase)
  const workingDirRef = useRef(workingDir)
  const connStatusRef = useRef(connStatus)
  const isConnectingRef = useRef(false)
  const connConnectRef = useRef(connConnect)
  const connDisconnectRef = useRef(connDisconnect)
  useEffect(() => {
    if (connSessionId) {
      sessionIdRef.current = connSessionId
    }
  }, [connSessionId])
  useEffect(() => {
    phaseRef.current = phase
  }, [phase])
  useEffect(() => {
    workingDirRef.current = workingDir
  }, [workingDir])
  useEffect(() => {
    connStatusRef.current = connStatus
  }, [connStatus])
  useEffect(() => {
    isConnectingRef.current = isConnecting
  }, [isConnecting])
  useEffect(() => {
    connConnectRef.current = connConnect
  }, [connConnect])
  useEffect(() => {
    connDisconnectRef.current = connDisconnect
  }, [connDisconnect])

  const trySaveExternalId = useCallback(() => {
    if (
      externalIdSavedRef.current ||
      !dbConvIdRef.current ||
      !sessionIdRef.current
    )
      return
    externalIdSavedRef.current = true
    updateConversationExternalId(
      dbConvIdRef.current,
      sessionIdRef.current
    ).catch((e: unknown) =>
      console.error("[WelcomePanel] update external_id:", e)
    )
  }, [])

  // Trigger when session ID arrives from ACP
  useEffect(() => {
    if (connSessionId) trySaveExternalId()
  }, [connSessionId, trySaveExternalId])

  useEffect(() => {
    let cancelled = false
    let unlisten: (() => void) | null = null

    const syncCurrentAgentStatus = async () => {
      if (cancelled) return
      if (phaseRef.current !== "welcome") return
      const currentWorkingDir = workingDirRef.current
      if (!currentWorkingDir) return
      if (refreshingCurrentAgentRef.current) return
      const currentConnStatus = connStatusRef.current
      if (currentConnStatus === "prompting" || isConnectingRef.current) return

      refreshingCurrentAgentRef.current = true
      try {
        setAgentConnectError(null)
        if (currentConnStatus === "connected") {
          await connDisconnectRef.current()
        }
        await connConnectRef.current(
          selectedAgentRef.current,
          currentWorkingDir,
          undefined,
          {
            source: "auto_link",
          }
        )
        if (!cancelled) {
          setAgentConnectError(null)
        }
      } catch (error) {
        if (!cancelled) {
          setAgentConnectError(normalizeErrorMessage(error))
        }
        if (!isExpectedAutoLinkError(error)) {
          console.error("[WelcomePanel] refresh current agent status:", error)
        }
      } finally {
        refreshingCurrentAgentRef.current = false
      }
    }

    void import("@tauri-apps/api/event")
      .then(({ listen }) =>
        listen<AgentsUpdatedEventPayload>(ACP_AGENTS_UPDATED_EVENT, (event) => {
          if (cancelled) return
          if (event.payload?.reason === "agent_reordered") return
          const changedAgentType = event.payload?.agent_type
          if (
            changedAgentType &&
            changedAgentType !== selectedAgentRef.current
          ) {
            return
          }
          if (agentStatusRefreshTimerRef.current) {
            clearTimeout(agentStatusRefreshTimerRef.current)
          }
          agentStatusRefreshTimerRef.current = setTimeout(() => {
            void syncCurrentAgentStatus()
          }, 120)
        })
      )
      .then((dispose) => {
        if (cancelled) {
          disposeTauriListener(dispose, "WelcomeInputPanel.agentsUpdated")
          return
        }
        unlisten = dispose
      })
      .catch(() => {
        // Ignore when non-tauri runtime.
      })

    return () => {
      cancelled = true
      if (agentStatusRefreshTimerRef.current) {
        clearTimeout(agentStatusRefreshTimerRef.current)
        agentStatusRefreshTimerRef.current = null
      }
      disposeTauriListener(unlisten, "WelcomeInputPanel.agentsUpdated")
    }
  }, [])

  const prevStatusRef = useRef(connStatus)

  // Accumulate history when prompting completes
  useEffect(() => {
    const prev = prevStatusRef.current
    prevStatusRef.current = connStatus

    if (prev === "prompting" && connStatus !== "prompting") {
      if (conn.liveMessage && conn.liveMessage.content.length > 0) {
        const adapted = adaptLiveMessageFromAcp(conn.liveMessage, {
          isLiveStreaming: false,
          toolCallFailedText: sharedT("toolCallFailed"),
          planUpdatedText: sharedT("planUpdated"),
        })

        setHistory((h) => [...h, adapted])
      }
      // Agent turn ended — mark as pending_review unless it's a terminal state
      if (
        dbConvIdRef.current &&
        connStatus !== "disconnected" &&
        connStatus !== "error"
      ) {
        updateConversationStatus(dbConvIdRef.current, "pending_review")
          .then(() => refreshConversations())
          .catch((e: unknown) =>
            console.error("[WelcomePanel] update status:", e)
          )
      }

      void refreshConversationFromDb(
        historyRef.current.length + (conn.liveMessage ? 1 : 0)
      )
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps -- conn.liveMessage, lifecycleSend intentionally omitted: effect only fires on status transitions
  }, [connStatus, refreshConversations, refreshConversationFromDb, sharedT])

  // When connection becomes "connected" and we have a pending prompt, send it
  useEffect(() => {
    if (connStatus === "connected" && pendingPromptRef.current) {
      const pending = pendingPromptRef.current
      pendingPromptRef.current = null
      lifecycleSend(pending.draft, pending.modeId)
    }
  }, [connStatus, lifecycleSend])

  // Promote tab helper — call once when conversation ends or component unmounts
  const promoteTab = useCallback(() => {
    if (tabPromotedRef.current || !dbConvIdRef.current) return
    tabPromotedRef.current = true
    const tid = tabIdRef.current
    const convId = dbConvIdRef.current
    const agent = selectedAgentRef.current
    const title = convTitleRef.current || tabT("untitledConversation")
    const canonicalContextKey = `conv-${agent}-${convId}`

    // Keep in-flight stream/state attached when this new-conversation view
    // is closed and later reopened as a canonical conversation tab.
    migrateContextKey(contextKey, canonicalContextKey)

    if (tid) {
      promoteNewConversationTab(tid, convId, agent, title)
    }
    refreshConversations()
  }, [
    promoteNewConversationTab,
    refreshConversations,
    migrateContextKey,
    contextKey,
    tabT,
  ])

  // Update conversation status on disconnect/error + promote tab
  useEffect(() => {
    if (!dbConvIdRef.current || statusUpdatedRef.current) return
    if (connStatus === "disconnected") {
      statusUpdatedRef.current = true
      updateConversationStatus(dbConvIdRef.current, "completed").catch((e) =>
        console.error("[WelcomePanel] update status:", e)
      )
      promoteTab()
    } else if (connStatus === "error") {
      statusUpdatedRef.current = true
      updateConversationStatus(dbConvIdRef.current, "cancelled").catch((e) =>
        console.error("[WelcomePanel] update status:", e)
      )
      promoteTab()
    }
  }, [connStatus, promoteTab])

  // Promote tab on unmount if not yet promoted (e.g. user closes tab)
  useEffect(() => {
    return () => {
      promoteTab()
    }
  }, [promoteTab])

  const handleAgentSelect = useCallback(
    (agentType: AgentType) => {
      if (agentType === selectedAgent) return
      setSelectedAgent(agentType)
      setModeId(null)
      setAgentConnectError(null)
      connDisconnect()
        .catch((e) => console.error("[WelcomePanel] disconnect old agent:", e))
        .finally(() => {
          connConnect(agentType, workingDir, undefined, {
            source: "auto_link",
          })
            .then(() => {
              setAgentConnectError(null)
            })
            .catch((e) => {
              setAgentConnectError(normalizeErrorMessage(e))
              if (!isExpectedAutoLinkError(e)) {
                console.error("[WelcomePanel] switch agent:", e)
              }
            })
        })
    },
    [selectedAgent, connConnect, connDisconnect, workingDir]
  )

  // Welcome phase: submit first message.
  const handleWelcomeSend = useCallback(
    (draft: PromptDraft, selectedModeId?: string | null) => {
      const displayText = getPromptDraftDisplayText(
        draft,
        sharedT("attachedResources")
      )
      const userMsg: AdaptedMessage = {
        id: crypto.randomUUID(),
        role: "user",
        content: buildUserMessageTextPartsFromDraft(
          draft,
          sharedT("attachedResources")
        ),
        userImages: extractUserImagesFromDraft(draft),
        userResources: extractUserResourcesFromDraft(draft),
        timestamp: new Date().toISOString(),
      }
      setHistory([userMsg])
      setPhase("conversation")
      applySessionStats(null)
      statsRefreshSeqRef.current += 1

      // If already connected, send directly; otherwise queue for when connected
      if (connStatus === "connected") {
        lifecycleSend(draft, selectedModeId)
      } else {
        pendingPromptRef.current = {
          draft,
          modeId: selectedModeId ?? null,
        }
        // Ensure connection is being established
        if (
          !connStatus ||
          connStatus === "disconnected" ||
          connStatus === "error"
        ) {
          connConnect(selectedAgent, workingDir, undefined, {
            source: "auto_link",
          }).catch((e) => {
            setAgentConnectError(normalizeErrorMessage(e))
          })
        }
      }

      // DB persistence: create conversation
      const title = displayText.slice(0, 80)
      convTitleRef.current = title
      createConversation(folderId, selectedAgent, title)
        .then((convId) => {
          dbConvIdRef.current = convId
          setDbConversationId(convId)
          moveMessageInputDraft(
            newConversationDraftStorageKey,
            buildConversationDraftStorageKey(selectedAgent, convId)
          )
          // Link tab to DB conversation so status dot updates and tab is persisted
          if (tabIdRef.current) {
            linkTabConversation(tabIdRef.current, convId, selectedAgent, title)
          }
          // If ACP session ID already arrived, save external_id now
          trySaveExternalId()
          refreshConversations()
        })
        .catch((e: unknown) =>
          console.error("[WelcomePanel] create conversation:", e)
        )
    },
    [
      selectedAgent,
      workingDir,
      connStatus,
      connConnect,
      lifecycleSend,
      folderId,
      refreshConversations,
      linkTabConversation,
      trySaveExternalId,
      applySessionStats,
      newConversationDraftStorageKey,
      sharedT,
    ]
  )

  // Conversation phase: prepend user message to history before sending
  const handleSendWithHistory = useCallback(
    (draft: PromptDraft, selectedModeId?: string | null) => {
      const userMsg: AdaptedMessage = {
        id: crypto.randomUUID(),
        role: "user",
        content: buildUserMessageTextPartsFromDraft(
          draft,
          sharedT("attachedResources")
        ),
        userImages: extractUserImagesFromDraft(draft),
        userResources: extractUserResourcesFromDraft(draft),
        timestamp: new Date().toISOString(),
      }
      setHistory((h) => [...h, userMsg])
      lifecycleSend(draft, selectedModeId)

      // Update status
      if (dbConvIdRef.current) {
        updateConversationStatus(dbConvIdRef.current, "in_progress")
          .then(() => refreshConversations())
          .catch((e: unknown) =>
            console.error("[WelcomePanel] update status:", e)
          )
        statusUpdatedRef.current = false
      }
    },
    [lifecycleSend, refreshConversations, sharedT]
  )

  const handleOpenAgentsSettings = useCallback(() => {
    openSettingsWindow("agents", { agentType: selectedAgent }).catch((err) => {
      console.error("[WelcomePanel] failed to open settings window:", err)
    })
  }, [selectedAgent])

  const buildAutoConnectErrorMessage = useCallback(
    (raw: string) =>
      buildInlineAutoConnectErrorMessage(raw, {
        fallback: t("autoConnectFallback"),
        append: (message) =>
          t("autoConnectAppend", {
            message,
            path: t("agentsSettingsPath"),
          }),
        alreadyContainsPath: (message) =>
          [t("agentsSettingsPath"), "Settings > Agents"].some((path) =>
            message.includes(path)
          ),
      }),
    [t]
  )

  // Track live message visibility across turn completion.
  // Hooks must be called before any conditional returns.
  const prevConnStatusForLiveRef = useRef(connStatus)
  const showLiveTransitionRef = useRef(false)
  const prevHistoryLenRef = useRef(history.length)

  if (connStatus === "prompting") {
    showLiveTransitionRef.current = false
  } else if (prevConnStatusForLiveRef.current === "prompting") {
    showLiveTransitionRef.current = true
  }
  prevConnStatusForLiveRef.current = connStatus

  // Once the effect adds the adapted message to history, hide the live block.
  if (
    history.length > prevHistoryLenRef.current &&
    showLiveTransitionRef.current
  ) {
    showLiveTransitionRef.current = false
  }
  prevHistoryLenRef.current = history.length

  // ── Welcome phase ──
  if (phase === "welcome") {
    return (
      <div className="flex flex-col items-center justify-center h-full px-4">
        <div className="w-full max-w-2xl space-y-6">
          <AgentSelector
            defaultAgentType={selectedAgent}
            onSelect={handleAgentSelect}
            onAgentsLoaded={(agents) => {
              setAgentsLoaded(true)
              setUsableAgentCount(
                agents.filter((agent) => agent.enabled && agent.available)
                  .length
              )
            }}
            onOpenAgentsSettings={handleOpenAgentsSettings}
            disabled={isConnecting}
          />

          {autoConnectError || agentConnectError ? (
            <button
              type="button"
              onClick={handleOpenAgentsSettings}
              className="w-full cursor-pointer rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-center text-xs text-destructive transition-colors hover:bg-destructive/10"
            >
              {(() => {
                const inlineMessage = buildAutoConnectErrorMessage(
                  autoConnectError ?? agentConnectError ?? ""
                )
                return (
                  <div
                    className="overflow-hidden text-ellipsis whitespace-nowrap text-center"
                    title={inlineMessage}
                  >
                    {inlineMessage}
                  </div>
                )
              })()}
            </button>
          ) : null}

          <MessageInput
            key={newConversationDraftStorageKey}
            onSend={handleWelcomeSend}
            promptCapabilities={conn.promptCapabilities}
            defaultPath={workingDir}
            placeholder={
              agentsLoaded && usableAgentCount === 0
                ? t("enableAgentFirstPlaceholder")
                : t("askAnythingPlaceholder")
            }
            autoFocus
            attachmentTabId={tabId ?? null}
            modes={connectionModes}
            configOptions={connectionConfigOptions}
            modeLoading={modeLoading}
            configOptionsLoading={configOptionsLoading}
            selectedModeId={selectedModeId}
            onModeChange={setModeId}
            onConfigOptionChange={handleSetConfigOption}
            availableCommands={connectionCommands}
            disabled={!canAutoConnect || isConnecting}
            className="min-h-28 max-h-60"
            draftStorageKey={newConversationDraftStorageKey}
          />
        </div>
      </div>
    )
  }

  // ── Conversation phase ──

  const showLive = Boolean(
    conn.liveMessage &&
    (connStatus === "prompting" ||
      (conn.liveMessage.content.length > 0 && showLiveTransitionRef.current))
  )

  return (
    <ConversationShell
      status={connStatus}
      promptCapabilities={conn.promptCapabilities}
      defaultPath={workingDir}
      error={conn.error}
      pendingPermission={conn.pendingPermission}
      onFocus={handleFocus}
      onSend={handleSendWithHistory}
      onCancel={handleCancel}
      onRespondPermission={handleRespondPermission}
      modes={connectionModes}
      configOptions={connectionConfigOptions}
      modeLoading={modeLoading}
      configOptionsLoading={configOptionsLoading}
      selectedModeId={selectedModeId}
      onModeChange={setModeId}
      onConfigOptionChange={handleSetConfigOption}
      availableCommands={connectionCommands}
      attachmentTabId={tabId ?? null}
      draftStorageKey={activeDraftStorageKey}
    >
      <div className="relative flex flex-col h-full">
        <MessageThread className="flex-1 min-h-0">
          <MessageThreadContent className="p-4 max-w-3xl mx-auto">
            {history.map((msg) => (
              <div key={msg.id}>
                <Message from={msg.role === "tool" ? "assistant" : msg.role}>
                  <MessageContent>
                    <ContentPartsRenderer parts={msg.content} role={msg.role} />
                  </MessageContent>
                  {msg.role === "user" && msg.userImages?.length ? (
                    <UserImageAttachments
                      images={msg.userImages}
                      className="self-end"
                    />
                  ) : null}
                  {msg.role === "user" && msg.userResources?.length ? (
                    <UserResourceLinks
                      resources={msg.userResources}
                      className="self-end"
                    />
                  ) : null}
                </Message>
                {msg.role === "assistant" && (
                  <TurnStats
                    usage={msg.usage}
                    duration_ms={msg.duration_ms}
                    model={msg.model}
                  />
                )}
              </div>
            ))}
            {showLive && <LiveMessageBlock message={conn.liveMessage!} />}
          </MessageThreadContent>
        </MessageThread>
        {showLive && <LiveTurnStats message={conn.liveMessage!} />}
        <AgentPlanOverlay
          message={conn.liveMessage}
          entries={historicalPlanEntries}
          planKey={historicalPlanKey}
        />
      </div>
    </ConversationShell>
  )
}
