"use client";

import { useEffect, useRef, useState } from "react";

import { ArrowDown, CircleDot, KeyRound, LoaderCircle, SendHorizontal, Sparkles } from "lucide-react";
import { motion } from "motion/react";

import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/sonner";
import {
  agentChatMessageStoragePrefix,
  agentChatStateEventName,
  markAgentChatAsSeen,
  markAgentInboxAsSeen,
  mergeAgentChatMessagesForRehydration,
  normalizeAgentChatMessagesForDisplay,
  readAgentChatMessages,
  writeAgentChatMessages,
  type AgentChatMessage
} from "@/components/mission-control/agent-chat-storage";
import {
  getAgentChatRunSnapshot,
  sendAgentChatMessage,
  type AgentChatRunSnapshot
} from "@/components/mission-control/agent-chat-runner";
import {
  resolveAgentChatAuthAction,
  resolveAgentChatGatewayRepairAction,
  type AgentChatGatewayRepairAction
} from "@/lib/openclaw/chat-auth-actions";
import { formatAgentDisplayName } from "@/lib/openclaw/presenters";
import type { MissionControlSnapshot, AgentRecord } from "@/lib/agentos/contracts";
import { cn } from "@/lib/utils";

type ChatMessage = AgentChatMessage;

function formatChatTime(timestamp: number) {
  if (!Number.isFinite(timestamp)) {
    return "now";
  }

  return new Intl.DateTimeFormat(undefined, {
    hour: "2-digit",
    minute: "2-digit"
  }).format(new Date(timestamp));
}

function TypingDots({ surfaceTheme }: { surfaceTheme: "dark" | "light" }) {
  return (
    <span className="inline-flex items-center gap-[3px] align-middle">
      {[0, 1, 2].map((index) => (
        <motion.span
          key={index}
          animate={{ opacity: [0.35, 1, 0.35], y: [0, -1, 0] }}
          transition={{ duration: 1.1, repeat: Infinity, delay: index * 0.14, ease: "easeInOut" }}
          className={cn("h-1.5 w-1.5 rounded-full", surfaceTheme === "light" ? "bg-[#8f7263]" : "bg-cyan-300")}
        />
      ))}
    </span>
  );
}

function AssistantBubbleHeader({
  agentLabel,
  statusLabel,
  timestamp,
  surfaceTheme
}: {
  agentLabel: string;
  statusLabel: string | null;
  timestamp: number;
  surfaceTheme: "dark" | "light";
}) {
  return (
    <div
      className={cn(
        "flex min-w-0 items-center justify-between gap-3 text-[9px] uppercase tracking-[0.24em]",
        surfaceTheme === "light" ? "text-[#8b7262]" : "text-slate-400"
      )}
    >
      <span className="min-w-0 truncate">{agentLabel} · {formatChatTime(timestamp)}</span>
      {statusLabel ? (
        <span className="inline-flex shrink-0 items-center gap-1.5">
          <span>{statusLabel}</span>
          <TypingDots surfaceTheme={surfaceTheme} />
        </span>
      ) : null}
    </div>
  );
}

function AssistantThinkingActivity({
  statusMessage,
  expanded,
  onToggle,
  surfaceTheme
}: {
  statusMessage: string | null;
  expanded: boolean;
  onToggle: () => void;
  surfaceTheme: "dark" | "light";
}) {
  const previewLines = resolveAssistantThinkingPreview(statusMessage);
  const detailLines = resolveAssistantThinkingDetails(statusMessage);

  return (
    <div
      className={cn(
        "mt-2 overflow-hidden rounded-[14px] border px-3 py-2",
        surfaceTheme === "light"
          ? "border-[#e7d8cc] bg-[#fff7f1]/70"
          : "border-cyan-300/10 bg-slate-950/24"
      )}
    >
      <div className="space-y-1.5">
        {previewLines.map((line, index) => (
          <motion.div
            key={line}
            animate={{ opacity: [0.48, 0.92, 0.48] }}
            transition={{ duration: 1.8, repeat: Infinity, delay: index * 0.18, ease: "easeInOut" }}
            className={cn(
              "h-3.5 rounded-full",
              surfaceTheme === "light"
                ? "bg-[linear-gradient(90deg,rgba(139,114,98,0.16),rgba(139,114,98,0.34),rgba(139,114,98,0.14))]"
                : "bg-[linear-gradient(90deg,rgba(125,211,252,0.10),rgba(125,211,252,0.27),rgba(125,211,252,0.08))]",
              index === 0 ? "w-[72%]" : "w-[54%]"
            )}
          >
            <span
              className={cn(
                "block truncate px-2 text-[10px] leading-3.5",
                surfaceTheme === "light" ? "text-[#7d6556]/78" : "text-slate-300/72"
              )}
            >
              {line}
            </span>
          </motion.div>
        ))}
      </div>

      <div className="mt-2 flex justify-end">
        <button
          type="button"
          onClick={onToggle}
          className={cn(
            "text-[8px] uppercase tracking-[0.18em] transition hover:opacity-80",
            surfaceTheme === "light" ? "text-[#8b7262]" : "text-cyan-200/70"
          )}
        >
          {expanded ? "Hide details" : "Show details"}
        </button>
      </div>

      {expanded ? (
        <motion.ul
          initial={{ opacity: 0, y: -3 }}
          animate={{ opacity: 1, y: 0 }}
          className={cn(
            "mt-2 space-y-1 border-t pt-2 text-[11px] leading-4",
            surfaceTheme === "light" ? "border-[#e7d8cc] text-[#7d6556]" : "border-white/[0.07] text-slate-400"
          )}
        >
          {detailLines.map((line) => (
            <li key={line} className="flex gap-2">
              <span
                className={cn(
                  "mt-[7px] h-1 w-1 shrink-0 rounded-full",
                  surfaceTheme === "light" ? "bg-[#b28f78]" : "bg-cyan-300/70"
                )}
              />
              <span>{line}</span>
            </li>
          ))}
        </motion.ul>
      ) : null}
    </div>
  );
}

function AgentChatSessionHeader({
  agent,
  agentLabel,
  agentWorkLabel,
  isRunning,
  surfaceTheme
}: {
  agent: AgentRecord;
  agentLabel: string;
  agentWorkLabel: string;
  isRunning: boolean;
  surfaceTheme: "dark" | "light";
}) {
  const statusLabel = isRunning ? "Replying" : agent.status;

  return (
    <div
      className={cn(
        "rounded-[14px] border px-3 py-2.5 shadow-[0_8px_22px_rgba(0,0,0,0.08)] backdrop-blur-xl",
        surfaceTheme === "light"
          ? "border-[#e3d4c8] bg-[#fffaf6]/94"
          : "border-white/[0.08] bg-slate-950/82"
      )}
    >
      <div className="flex items-center justify-between gap-2">
        <div className="min-w-0">
          <p className={cn("text-[9px] font-semibold uppercase tracking-[0.2em]", surfaceTheme === "light" ? "text-[#8b7262]" : "text-slate-400")}>
            Direct agent session
          </p>
          <p className={cn("mt-1 truncate text-[11px] font-semibold", surfaceTheme === "light" ? "text-[#3f2f24]" : "text-slate-100")}>
            {agentLabel}
          </p>
        </div>
        <span
          className={cn(
            "inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2 py-1 text-[9px] font-semibold uppercase tracking-[0.14em]",
            isRunning
              ? surfaceTheme === "light"
                ? "border-emerald-300 bg-emerald-50 text-emerald-800"
                : "border-emerald-300/25 bg-emerald-300/10 text-emerald-100"
              : surfaceTheme === "light"
                ? "border-[#e3d4c8] bg-white text-[#765b4b]"
                : "border-white/[0.08] bg-white/[0.04] text-slate-300"
          )}
        >
          <span className={cn("h-1.5 w-1.5 rounded-full", isRunning ? "bg-emerald-400 animate-pulse" : surfaceTheme === "light" ? "bg-[#b28f78]" : "bg-slate-500")} />
          {statusLabel}
        </span>
      </div>
      <div className={cn("mt-2 flex flex-wrap gap-x-3 gap-y-1 text-[9px]", surfaceTheme === "light" ? "text-[#806657]" : "text-slate-400")}>
        <span className="truncate">Model · {agent.modelId || "not reported"}</span>
        <span>Sessions · {agent.sessionCount}</span>
        <span className="min-w-0 truncate">Working on · {agentWorkLabel}</span>
      </div>
    </div>
  );
}

function AgentChatWelcome({
  agentLabel,
  surfaceTheme,
  onPrompt
}: {
  agentLabel: string;
  surfaceTheme: "dark" | "light";
  onPrompt: (text: string) => void;
}) {
  const prompts = [
    { label: "Get a status update", text: "Summarize your current work, progress, and next step." },
    { label: "Ask about blockers", text: "What is blocking you right now, if anything?" }
  ];

  return (
    <div
      className={cn(
        "rounded-[16px] border px-3.5 py-4",
        surfaceTheme === "light"
          ? "border-[#e3d4c8] bg-[#fffaf6]"
          : "border-white/[0.08] bg-white/[0.035]"
      )}
    >
      <div className="flex items-start gap-2.5">
        <div className={cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-[11px] border", surfaceTheme === "light" ? "border-[#e3d4c8] bg-[#fff2e8] text-[#8c6550]" : "border-cyan-200/15 bg-cyan-300/10 text-cyan-100")}>
          <Sparkles className="h-4 w-4" />
        </div>
        <div className="min-w-0">
          <p className={cn("text-[12px] font-semibold", surfaceTheme === "light" ? "text-[#3f2f24]" : "text-slate-100")}>
            Start a direct conversation with {agentLabel}
          </p>
          <p className={cn("mt-1 text-[11px] leading-5", surfaceTheme === "light" ? "text-[#806657]" : "text-slate-400")}>
            Use this channel for guidance, decisions, and status checks. Task continuation stays in the task workflow.
          </p>
        </div>
      </div>
      <div className="mt-3 flex flex-wrap gap-1.5">
        {prompts.map((prompt) => (
          <button
            key={prompt.label}
            type="button"
            onClick={() => onPrompt(prompt.text)}
            className={cn(
              "rounded-full border px-2.5 py-1.5 text-[10px] font-semibold transition",
              surfaceTheme === "light"
                ? "border-[#e3d4c8] bg-white text-[#705345] hover:border-[#cda98f] hover:bg-[#fff7f1]"
                : "border-white/[0.09] bg-white/[0.04] text-slate-300 hover:border-cyan-200/20 hover:text-cyan-50"
            )}
          >
            {prompt.label}
          </button>
        ))}
      </div>
    </div>
  );
}

export function AgentChatDrawer({
  agent,
  snapshot,
  surfaceTheme,
  isVisible,
  onRefresh,
  onSnapshotChange,
  onConnectModelProvider
}: {
  agent: AgentRecord;
  snapshot: MissionControlSnapshot;
  surfaceTheme: "dark" | "light";
  isVisible: boolean;
  onRefresh?: () => Promise<void>;
  onSnapshotChange?: (updater: (snapshot: MissionControlSnapshot) => MissionControlSnapshot) => void;
  onConnectModelProvider?: (provider: string) => void;
}) {
  const [draft, setDraft] = useState("");
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [runSnapshot, setRunSnapshot] = useState<AgentChatRunSnapshot>(() => getAgentChatRunSnapshot(agent.id));
  const [revealingAssistantId, setRevealingAssistantId] = useState<string | null>(null);
  const [revealedAssistantTextById, setRevealedAssistantTextById] = useState<Record<string, string>>({});
  const [expandedThinkingById, setExpandedThinkingById] = useState<Record<string, boolean>>({});
  const [repairingGatewayMessageId, setRepairingGatewayMessageId] = useState<string | null>(null);
  const [isNearBottom, setIsNearBottom] = useState(true);
  const [hasUnreadBelow, setHasUnreadBelow] = useState(false);
  const listRef = useRef<HTMLDivElement | null>(null);
  const textareaRef = useRef<HTMLTextAreaElement | null>(null);
  const isVisibleRef = useRef(isVisible);
  const isNearBottomRef = useRef(true);
  const rehydratedAgentRef = useRef<string | null>(null);
  const agentLabel = formatAgentDisplayName(agent);
  const inboxItems = snapshot.agentInbox.filter((item) => item.agentId === agent.id);
  const agentWorkLabel = agent.currentAction?.trim() || "current work";
  const quickPrompts = [
    { label: "Status", text: "Summarize your current work, progress, and next step." },
    { label: "Blockers", text: "What is blocking you right now, if anything?" },
    { label: "Priorities", text: "What needs my attention or decision next?" }
  ];

  const scrollToLatest = () => {
    const list = listRef.current;
    if (!list) {
      return;
    }

    list.scrollTo({ top: list.scrollHeight, behavior: "smooth" });
    isNearBottomRef.current = true;
    setIsNearBottom(true);
    setHasUnreadBelow(false);
  };

  const handleTimelineScroll = () => {
    const list = listRef.current;
    if (!list) {
      return;
    }

    const nextIsNearBottom = list.scrollHeight - list.scrollTop - list.clientHeight < 48;
    isNearBottomRef.current = nextIsNearBottom;
    setIsNearBottom(nextIsNearBottom);

    if (nextIsNearBottom) {
      setHasUnreadBelow(false);
    }
  };

  const applyQuickPrompt = (text: string) => {
    setDraft(text);
    requestAnimationFrame(() => textareaRef.current?.focus());
  };

  useEffect(() => {
    isVisibleRef.current = isVisible;
  }, [isVisible]);

  useEffect(() => {
    const syncAgentChatState = () => {
      const nextRunSnapshot = getAgentChatRunSnapshot(agent.id);

      setRunSnapshot(nextRunSnapshot);
      setMessages(readVisibleAgentChatMessages(agent.id, nextRunSnapshot));
    };

    syncAgentChatState();
    setDraft("");
    setRevealingAssistantId(null);
    setRevealedAssistantTextById({});
    setExpandedThinkingById({});
    isNearBottomRef.current = true;
    setIsNearBottom(true);
    setHasUnreadBelow(false);

    const handleChatStateChange = (event: Event) => {
      const detail = (event as CustomEvent<{ agentId?: string }>).detail;

      if (!detail || detail.agentId === agent.id) {
        syncAgentChatState();
      }
    };

    const handleStorage = (event: StorageEvent) => {
      if (!event.key || !event.key.startsWith(agentChatMessageStoragePrefix)) {
        return;
      }

      syncAgentChatState();
    };

    window.addEventListener(agentChatStateEventName, handleChatStateChange as EventListener);
    window.addEventListener("storage", handleStorage);

    return () => {
      window.removeEventListener(agentChatStateEventName, handleChatStateChange as EventListener);
      window.removeEventListener("storage", handleStorage);
    };
  }, [agent.id]);

  useEffect(() => {
    if (runSnapshot.assistantMessageId) {
      setRevealingAssistantId(runSnapshot.assistantMessageId);
    }
  }, [runSnapshot.assistantMessageId]);

  useEffect(() => {
    if (!isVisible || runSnapshot.isRunning || rehydratedAgentRef.current === agent.id) {
      return;
    }

    rehydratedAgentRef.current = agent.id;
    let cancelled = false;

    void (async () => {
      try {
        const response = await fetch(`/api/agents/${encodeURIComponent(agent.id)}/chat`, {
          method: "GET",
          cache: "no-store"
        });

        if (!response.ok) {
          return;
        }

        const payload = (await response.json().catch(() => null)) as {
          messages?: AgentChatMessage[];
        } | null;

        if (cancelled || !Array.isArray(payload?.messages) || payload.messages.length === 0) {
          return;
        }

        const currentMessages = readAgentChatMessages(agent.id);
        const mergedMessages = mergeAgentChatMessagesForRehydration(currentMessages, payload.messages);

        if (agentChatMessagesEqual(currentMessages, mergedMessages)) {
          return;
        }

        writeAgentChatMessages(agent.id, mergedMessages);
      } catch {
        // Rehydration is best-effort; local chat cache remains usable when OpenClaw history is unavailable.
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [agent.id, isVisible, runSnapshot.isRunning]);

  useEffect(() => {
    const assistantId = runSnapshot.assistantMessageId ?? revealingAssistantId;
    if (!assistantId) {
      return;
    }

    const assistantMessage = messages.find((entry) => entry.id === assistantId && entry.role === "assistant");
    const targetText = assistantMessage?.text ?? "";
    if (!targetText.trim()) {
      return;
    }

    const revealedText = revealedAssistantTextById[assistantId] ?? "";
    if (revealedText === targetText) {
      return;
    }

    const timer = window.setTimeout(() => {
      setRevealedAssistantTextById((current) => {
        const currentText = current[assistantId] ?? "";
        const nextText = revealNextAssistantText(targetText, currentText);

        if (nextText === currentText) {
          return current;
        }

        return {
          ...current,
          [assistantId]: nextText
        };
      });
    }, 42);

    return () => window.clearTimeout(timer);
  }, [messages, revealedAssistantTextById, revealingAssistantId, runSnapshot.assistantMessageId]);

  useEffect(() => {
    if (!isVisible) {
      return;
    }

    const frame = requestAnimationFrame(() => {
      if (isVisibleRef.current) {
        textareaRef.current?.focus();
      }
    });

    return () => cancelAnimationFrame(frame);
  }, [agent.id, isVisible]);

  useEffect(() => {
    if (!isVisible) {
      return;
    }

    markAgentChatAsSeen(agent.id, messages);
    markAgentInboxAsSeen(agent.id, inboxItems);
  }, [agent.id, messages, inboxItems, isVisible]);

  useEffect(() => {
    if (!isVisible || !listRef.current) {
      return;
    }

    if (isNearBottomRef.current) {
      listRef.current.scrollTop = listRef.current.scrollHeight;
      return;
    }

    if (messages.length > 0 || inboxItems.length > 0) {
      setHasUnreadBelow(true);
    }
  }, [agent.id, inboxItems.length, isVisible, messages]);

  const canSend = Boolean(draft.trim()) && !runSnapshot.isRunning;
  const streamingAssistantId = runSnapshot.assistantMessageId;

  const hasConversation = messages.length > 0 || inboxItems.length > 0;

  const send = async () => {
    const text = draft.trim();
    if (!text || runSnapshot.isRunning) return;

    setDraft("");
    scrollToLatest();

    try {
      await sendAgentChatMessage({
        agentId: agent.id,
        agentName: agentLabel,
        text,
        onRefresh,
        onSnapshotChange,
        onError: (message) => {
          toast.error("Chat message failed.", { description: message });
        }
      });
    } finally {
      if (isVisibleRef.current) {
        requestAnimationFrame(() => textareaRef.current?.focus());
      }
    }
  };

  const repairGatewayAccessAndRetry = async (
    messageId: string,
    text: string,
    action: AgentChatGatewayRepairAction
  ) => {
    const retryText = text.trim();
    if (!retryText || repairingGatewayMessageId || runSnapshot.isRunning) {
      return;
    }

    setRepairingGatewayMessageId(messageId);

    try {
      const response = await fetch("/api/settings/gateway", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({ action: action.apiAction })
      });
      const result = (await response.json().catch(() => null)) as {
        authStatus?: {
          native?: {
            ok?: boolean;
            issue?: string | null;
          };
        };
        error?: string;
      } | null;

      if (!response.ok) {
        throw new Error(result?.error || "Gateway access could not be repaired.");
      }

      if (result?.authStatus?.native && result.authStatus.native.ok === false) {
        throw new Error(result.authStatus.native.issue || "Gateway access still needs attention.");
      }

      toast.success(`${action.label} repaired.`, {
        description: "Retrying the chat message."
      });
      await onRefresh?.().catch(() => undefined);
      setRepairingGatewayMessageId(null);
      await sendAgentChatMessage({
        agentId: agent.id,
        agentName: agentLabel,
        text: retryText,
        onRefresh,
        onSnapshotChange,
        onError: (message) => {
          toast.error("Chat message failed.", { description: message });
        }
      });
    } catch (error) {
      toast.error("Gateway repair failed.", {
        description: error instanceof Error ? error.message : "Unable to repair Gateway access."
      });
    } finally {
      setRepairingGatewayMessageId(null);
      if (isVisibleRef.current) {
        requestAnimationFrame(() => textareaRef.current?.focus());
      }
    }
  };

  return (
    <div
      className={cn(
        "relative flex h-full min-h-0 flex-col overflow-hidden",
        surfaceTheme === "light" ? "text-[#4a382c]" : "text-slate-200"
      )}
    >
      <div
        ref={listRef}
        onScroll={handleTimelineScroll}
        className={cn(
          "mission-scroll min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1",
          surfaceTheme === "light" ? "text-[#4a382c]" : "text-slate-200"
        )}
      >
        <div className="sticky top-0 z-10 mb-3">
          <AgentChatSessionHeader
            agent={agent}
            agentLabel={agentLabel}
            agentWorkLabel={agentWorkLabel}
            isRunning={runSnapshot.isRunning}
            surfaceTheme={surfaceTheme}
          />
        </div>
        <div className="space-y-2.5 pb-1">
          {!hasConversation ? (
            <AgentChatWelcome
              agentLabel={agentLabel}
              surfaceTheme={surfaceTheme}
              onPrompt={applyQuickPrompt}
            />
          ) : null}
          {inboxItems.map((item) => (
            <AgentInboxItemBubble
              key={item.id}
              item={item}
              surfaceTheme={surfaceTheme}
            />
          ))}
          {messages.map((entry) => {
            const isUser = entry.role === "user";
            const isSystem = entry.role === "system";
            const isAssistant = entry.role === "assistant";
            const isActiveAssistant =
              isAssistant && entry.id === streamingAssistantId && runSnapshot.isRunning;
            const isPendingAssistant = isActiveAssistant && !entry.text.trim();
            const revealedAssistantText = isAssistant ? revealedAssistantTextById[entry.id] : undefined;
            const visibleAssistantText = revealedAssistantText ?? entry.text;
            const isRevealingAssistant =
              isAssistant && Boolean(revealedAssistantText) && visibleAssistantText !== entry.text;
            const showAssistantActivity = isActiveAssistant || isRevealingAssistant;
            const assistantActivityLabel = isPendingAssistant ? "Thinking" : showAssistantActivity ? "Replying" : null;
            const isPendingUser = entry.role === "user" && entry.id === runSnapshot.userMessageId && runSnapshot.isRunning;
            const showInlineStatus = entry.status === "sending" && isPendingUser;
            const errorMessage = entry.errorMessage?.trim();
            const assistantDiagnosticText =
              isAssistant && !isPendingAssistant && !isActiveAssistant ? visibleAssistantText.trim() : "";
            const authActionMessage = errorMessage || assistantDiagnosticText;
            const gatewayRepairAction = errorMessage ? resolveAgentChatGatewayRepairAction(errorMessage) : null;
            const authAction =
              authActionMessage && !gatewayRepairAction ? resolveAgentChatAuthAction(authActionMessage, agent.modelId) : null;
            const showAssistantRecoveryAction =
              !isPendingAssistant && entry.status !== "error" && isAssistant && Boolean(authAction && onConnectModelProvider);

            return (
              <div key={entry.id} className={cn("flex", isUser ? "justify-end" : "justify-start")}>
                <div
                  className={cn(
                    "min-w-0 max-w-[92%] rounded-[18px] border px-3 py-2 text-[13px] leading-5 shadow-[0_14px_34px_rgba(0,0,0,0.14)]",
                    isPendingUser && "opacity-85",
                    isSystem
                      ? surfaceTheme === "light"
                        ? "border-[#e3d4c8] bg-[#fffaf6] text-[#6c5647]"
                        : "border-white/[0.08] bg-white/[0.03] text-slate-400"
                      : isUser
                        ? surfaceTheme === "light"
                          ? "border-[#e3d4c8] bg-[#fff3f6] text-[#4a382c]"
                          : "border-white/[0.08] bg-[linear-gradient(180deg,rgba(255,255,255,0.07),rgba(255,255,255,0.03))] text-slate-100"
                        : surfaceTheme === "light"
                          ? "border-[#e3d4c8] bg-[#fffaf6] text-[#4a382c]"
                          : "border-cyan-300/12 bg-[linear-gradient(180deg,rgba(34,211,238,0.10),rgba(59,130,246,0.06))] text-slate-100"
                  )}
                >
                  {isPendingAssistant ? (
                    <>
                      <AssistantBubbleHeader
                        agentLabel={agentLabel}
                        statusLabel={assistantActivityLabel}
                        timestamp={entry.createdAt}
                        surfaceTheme={surfaceTheme}
                      />
                      <AssistantThinkingActivity
                        statusMessage={runSnapshot.statusMessage}
                        expanded={Boolean(expandedThinkingById[entry.id])}
                        onToggle={() =>
                          setExpandedThinkingById((current) => ({
                            ...current,
                            [entry.id]: !current[entry.id]
                          }))
                        }
                        surfaceTheme={surfaceTheme}
                      />
                    </>
                  ) : (
                    <>
                      {isAssistant ? (
                        <AssistantBubbleHeader
                          agentLabel={agentLabel}
                          statusLabel={assistantActivityLabel}
                          timestamp={entry.createdAt}
                          surfaceTheme={surfaceTheme}
                        />
                      ) : (
                        <p
                          className={cn(
                            "text-[9px] font-semibold uppercase tracking-[0.18em]",
                            surfaceTheme === "light" ? "text-[#8b7262]" : "text-slate-400"
                          )}
                        >
                          {isUser ? "You" : "Direct session"} · {formatChatTime(entry.createdAt)}
                        </p>
                      )}
                      <p className={cn("whitespace-pre-wrap break-words [overflow-wrap:anywhere]", isAssistant && "mt-1.5")}>
                        {isAssistant ? visibleAssistantText : entry.text}
                      </p>
                      {showAssistantActivity ? (
                        <motion.span
                          aria-hidden="true"
                          animate={{ opacity: [0.2, 1, 0.2] }}
                          transition={{ duration: 0.9, repeat: Infinity, ease: "easeInOut" }}
                          className="ml-0.5 inline-block h-[1em] w-[1px] translate-y-[2px] bg-current"
                        />
                      ) : null}
                    </>
                  )}
                  {!isPendingAssistant && showInlineStatus ? (
                    <p
                      className={cn(
                        "mt-1.5 text-[10px] uppercase tracking-[0.18em]",
                        surfaceTheme === "light" ? "text-[#8b7262]" : "text-slate-500"
                      )}
                    >
                      {isUser ? "Sending…" : "Drafting…"}
                    </p>
                  ) : !isPendingAssistant && entry.status === "error" ? (
                    <div className="mt-1.5 space-y-1">
                      <p className="text-[10px] uppercase tracking-[0.18em] text-rose-300">
                        Failed to send
                      </p>
                      {errorMessage ? (
                        <p
                          className={cn(
                            "text-[11px] leading-4 [overflow-wrap:anywhere]",
                            surfaceTheme === "light" ? "text-rose-700" : "text-rose-200"
                          )}
                        >
                          {errorMessage}
                        </p>
                      ) : null}
                      {authAction && onConnectModelProvider ? (
                        <Button
                          type="button"
                          variant="secondary"
                          size="sm"
                          onClick={() => onConnectModelProvider(authAction.provider)}
                          className={cn(
                            "mt-1 h-8 rounded-full px-3 text-[11px]",
                            surfaceTheme === "light"
                              ? "border-rose-200 bg-white text-rose-800 hover:bg-rose-50"
                              : "border-rose-300/20 bg-rose-300/10 text-rose-100 hover:bg-rose-300/16"
                          )}
                        >
                          <KeyRound className="mr-1.5 h-3.5 w-3.5" />
                          Connect {authAction.label}
                        </Button>
                      ) : null}
                      {gatewayRepairAction ? (
                        <Button
                          type="button"
                          variant="secondary"
                          size="sm"
                          onClick={() => void repairGatewayAccessAndRetry(entry.id, entry.text, gatewayRepairAction)}
                          disabled={Boolean(repairingGatewayMessageId) || runSnapshot.isRunning}
                          title={gatewayRepairAction.detail}
                          className={cn(
                            "mt-1 h-8 rounded-full px-3 text-[11px]",
                            surfaceTheme === "light"
                              ? "border-emerald-200 bg-white text-emerald-800 hover:bg-emerald-50"
                              : "border-emerald-300/20 bg-emerald-300/10 text-emerald-100 hover:bg-emerald-300/16"
                          )}
                        >
                          {repairingGatewayMessageId === entry.id ? (
                            <LoaderCircle className="mr-1.5 h-3.5 w-3.5 animate-spin" />
                          ) : (
                            <KeyRound className="mr-1.5 h-3.5 w-3.5" />
                          )}
                          {gatewayRepairAction.cta}
                        </Button>
                      ) : null}
                    </div>
                  ) : showAssistantRecoveryAction && authAction && onConnectModelProvider ? (
                    <div className="mt-2">
                      <Button
                        type="button"
                        variant="secondary"
                        size="sm"
                        onClick={() => onConnectModelProvider(authAction.provider)}
                        className={cn(
                          "h-8 rounded-full px-3 text-[11px]",
                          surfaceTheme === "light"
                            ? "border-rose-200 bg-white text-rose-800 hover:bg-rose-50"
                            : "border-rose-300/20 bg-rose-300/10 text-rose-100 hover:bg-rose-300/16"
                        )}
                      >
                        <KeyRound className="mr-1.5 h-3.5 w-3.5" />
                        Connect {authAction.label}
                      </Button>
                    </div>
                  ) : null}
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {hasUnreadBelow && !isNearBottom ? (
        <button
          type="button"
          onClick={scrollToLatest}
          className={cn(
            "absolute bottom-[142px] left-1/2 z-20 inline-flex -translate-x-1/2 items-center gap-1.5 rounded-full border px-3 py-1.5 text-[10px] font-semibold shadow-lg transition",
            surfaceTheme === "light"
              ? "border-[#d8bead] bg-[#fffaf6] text-[#5c4031] hover:bg-white"
              : "border-cyan-200/20 bg-slate-900 text-cyan-50 hover:bg-slate-800"
          )}
        >
          <ArrowDown className="h-3.5 w-3.5" />
          New reply
        </button>
      ) : null}

      <div
        className={cn(
          "mt-2 shrink-0 rounded-[18px] border p-3",
          surfaceTheme === "light"
            ? "border-[#e3d4c8] bg-[#fffaf6]"
            : "border-white/[0.08] bg-[linear-gradient(180deg,rgba(11,18,32,0.86),rgba(8,13,24,0.82))]"
        )}
        onPointerDown={(event) => {
          const target = event.target as HTMLElement | null;
          if (!target || target.closest("textarea") || target.closest("button")) return;
          textareaRef.current?.focus();
        }}
      >
        <div className="mb-2 flex flex-wrap items-center justify-between gap-2 px-1">
          <div className="flex min-w-0 items-center gap-1.5">
            <CircleDot className={cn("h-3.5 w-3.5 shrink-0", runSnapshot.isRunning ? "text-emerald-400" : surfaceTheme === "light" ? "text-[#9c745f]" : "text-slate-500")} />
            <span className={cn("truncate text-[9px] font-semibold uppercase tracking-[0.18em]", surfaceTheme === "light" ? "text-[#806657]" : "text-slate-400")}>
              Direct session · {runSnapshot.isRunning ? "replying" : "ready"}
            </span>
          </div>
          <div className="flex flex-wrap justify-end gap-1">
            {quickPrompts.map((prompt) => (
              <button
                key={prompt.label}
                type="button"
                disabled={runSnapshot.isRunning}
                onClick={() => applyQuickPrompt(prompt.text)}
                className={cn(
                  "rounded-full border px-2 py-1 text-[9px] font-semibold transition disabled:cursor-not-allowed disabled:opacity-45",
                  surfaceTheme === "light"
                    ? "border-[#e3d4c8] bg-white/70 text-[#765b4b] hover:border-[#cda98f] hover:bg-white"
                    : "border-white/[0.09] bg-white/[0.04] text-slate-300 hover:border-cyan-200/20 hover:text-cyan-50"
                )}
              >
                {prompt.label}
              </button>
            ))}
          </div>
        </div>
        <Textarea
          ref={textareaRef}
          value={draft}
          onChange={(event) => setDraft(event.target.value)}
          onKeyDown={async (event) => {
            if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
              event.preventDefault();
              await send();
            }
          }}
          placeholder={`Ask ${agentLabel} about ${agentWorkLabel}…`}
          className={cn(
            "min-h-[60px] cursor-text resize-none border-0 bg-transparent px-3.5 py-2.5 text-[13px] leading-[1.5] shadow-none focus-visible:ring-0 focus-visible:ring-offset-0",
            surfaceTheme === "light"
              ? "text-[#3f2f24] placeholder:text-[#8f7664]"
              : "text-white placeholder:text-slate-500"
          )}
        />

        <div className="mt-1.5 flex items-center justify-between gap-2 px-1">
          <p className={cn("text-[9px] leading-4", surfaceTheme === "light" ? "text-[#8b7262]" : "text-slate-500")}>
            Enter to send · Shift+Enter for newline
          </p>
          <Button
            disabled={!canSend}
            className={cn(
              "h-8 rounded-full px-3 shadow-none",
              surfaceTheme === "light"
                ? "bg-[#4a382c] text-[#fffaf6] hover:bg-[#3f2f24]"
                : "bg-white text-slate-950 hover:bg-white/92"
            )}
            onClick={send}
          >
            {runSnapshot.isRunning ? (
              <LoaderCircle className="mr-[5px] h-[13px] w-[13px] animate-spin" />
            ) : (
              <SendHorizontal className="mr-[5px] h-[13px] w-[13px]" />
            )}
            Send
          </Button>
        </div>
      </div>
    </div>
  );
}

function readVisibleAgentChatMessages(agentId: string, runSnapshot: AgentChatRunSnapshot): ChatMessage[] {
  return normalizeAgentChatMessagesForDisplay(readAgentChatMessages(agentId), runSnapshot);
}

function AgentInboxItemBubble({
  item,
  surfaceTheme
}: {
  item: MissionControlSnapshot["agentInbox"][number];
  surfaceTheme: "dark" | "light";
}) {
  const sourceLabel = item.sourceAgentName || item.sourceAgentId || "OpenClaw";
  const provenanceLabel = item.provenance === "openclaw-task" ? "OpenClaw task" : "OpenClaw runtime";
  const reference = item.taskId || item.runtimeId || item.sessionId || item.runId || null;

  return (
    <div className="flex justify-start">
      <div
        className={cn(
          "min-w-0 max-w-[92%] rounded-[18px] border px-3 py-2 text-[13px] leading-5 shadow-[0_14px_34px_rgba(0,0,0,0.14)]",
          surfaceTheme === "light"
            ? "border-emerald-200 bg-emerald-50 text-[#304238]"
            : "border-emerald-300/18 bg-[linear-gradient(180deg,rgba(16,185,129,0.12),rgba(6,95,70,0.08))] text-emerald-50"
        )}
      >
        <div className="flex flex-wrap items-center gap-1.5">
          <span
            className={cn(
              "rounded-full border px-2 py-0.5 text-[9px] uppercase tracking-[0.18em]",
              surfaceTheme === "light"
                ? "border-emerald-300 bg-white/70 text-emerald-800"
                : "border-emerald-300/25 bg-emerald-300/10 text-emerald-100"
            )}
          >
            Handoff result
          </span>
          <span className={cn("text-[10px]", surfaceTheme === "light" ? "text-emerald-800/70" : "text-emerald-100/70")}>
            {sourceLabel} · {provenanceLabel}
          </span>
        </div>
        <p className="mt-1.5 text-[12px] font-medium">{item.title}</p>
        <p className="mt-1 whitespace-pre-wrap break-words text-[12px] leading-5 [overflow-wrap:anywhere]">
          {item.summary}
        </p>
        {reference ? (
          <p className={cn("mt-1.5 font-mono text-[9px]", surfaceTheme === "light" ? "text-emerald-900/55" : "text-emerald-100/45")}>
            {reference}
          </p>
        ) : null}
      </div>
    </div>
  );
}

function agentChatMessagesEqual(left: readonly AgentChatMessage[], right: readonly AgentChatMessage[]) {
  if (left.length !== right.length) {
    return false;
  }

  return left.every((message, index) => {
    const other = right[index];

    return Boolean(
      other &&
        message.id === other.id &&
        message.role === other.role &&
        message.text === other.text &&
        message.createdAt === other.createdAt &&
        message.status === other.status &&
        message.errorMessage === other.errorMessage &&
        message.runId === other.runId
    );
  });
}

function resolveAssistantThinkingHint(statusMessage: string | null) {
  const normalizedStatus = statusMessage?.toLowerCase() ?? "";

  if (normalizedStatus.includes("final confirmation")) {
    return "Waiting for OpenClaw to confirm the turn is complete.";
  }

  if (normalizedStatus.includes("still working") || normalizedStatus.includes("start the reply")) {
    return "OpenClaw is still running the direct chat turn.";
  }

  if (normalizedStatus.includes("finalizing") || normalizedStatus.includes("drafting")) {
    return "Shaping the reply before it appears here.";
  }

  if (normalizedStatus.includes("thinking")) {
    return "Checking recent context before answering.";
  }

  return "Reading your message and preparing a reply.";
}

function resolveAssistantThinkingPreview(statusMessage: string | null) {
  const hint = resolveAssistantThinkingHint(statusMessage);

  if (hint.includes("Shaping")) {
    return ["Shaping the reply", "Preparing the final wording"];
  }

  if (hint.includes("confirm")) {
    return ["Reply signal received", "Waiting for final confirmation"];
  }

  if (hint.includes("still running")) {
    return ["OpenClaw is still working", "Waiting for reply text"];
  }

  if (hint.includes("Checking")) {
    return ["Reading your message", "Checking recent context"];
  }

  return ["Reading your message", "Preparing a reply"];
}

function resolveAssistantThinkingDetails(statusMessage: string | null) {
  const normalizedStatus = statusMessage?.toLowerCase() ?? "";

  if (normalizedStatus.includes("final confirmation")) {
    return [
      "AgentOS has not received the final turn-complete confirmation from OpenClaw yet.",
      "If reply text already arrived, AgentOS is waiting to verify that the turn was saved.",
      "If this later fails, refresh state or ask the agent for a summary of any completed work."
    ];
  }

  if (normalizedStatus.includes("still working") || normalizedStatus.includes("start the reply")) {
    return [
      "OpenClaw accepted the direct chat turn and is still working.",
      "AgentOS has not received assistant reply text yet.",
      "Long waits usually mean the runtime is busy, the model is slow, or the Gateway has not exposed the reply."
    ];
  }

  if (normalizedStatus.includes("finalizing") || normalizedStatus.includes("drafting")) {
    return [
      "The agent has enough context to answer.",
      "It is tightening the response before showing it here.",
      "Raw reasoning stays hidden; only the final reply is saved."
    ];
  }

  if (normalizedStatus.includes("thinking")) {
    return [
      "The agent is reading the direct message.",
      "It is checking recent chat and workspace context.",
      "It will replace this activity card with the final reply."
    ];
  }

  return [
    "The message was sent to the selected agent.",
    "AgentOS is waiting for the first response signal.",
    "This activity is temporary and is not saved to chat history."
  ];
}

function revealNextAssistantText(targetText: string, currentText: string) {
  const safeCurrent = targetText.startsWith(currentText) ? currentText : "";
  const remainingText = targetText.slice(safeCurrent.length);
  const nextWord = remainingText.match(/^\s*\S+\s*/)?.[0];

  if (!nextWord) {
    return targetText;
  }

  return targetText.slice(0, safeCurrent.length + nextWord.length);
}
