'use client';

import Link from 'next/link';
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { Brain, Menu, Plus, Send, Trash2, X } from 'lucide-react';

import { ChatWorkbench } from '@/components/chat-workbench/ChatWorkbench';
import { CurrentSessionProgressSidebar } from '@/components/chat-workbench/CurrentSessionProgressSidebar';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { ScrollArea } from '@/components/ui/scroll-area';
import {
  archiveSession,
  createSession,
  getActiveTask,
  getBackendTask,
  getSession,
  getSessionProcess,
  listSessions,
  promptLocaleForAppLocale,
  sendMessage,
  submitChatFeedback,
  uploadFile,
  wsManager,
} from '@/lib/api';
import {
  getSessionRefreshIntervalMs,
  mergeServerWithPendingUsers,
  shouldDisplayChatMessage,
  shouldMergePendingUsers,
} from '@/lib/chat-messages';
import { pickAppText } from '@/lib/i18n/core';
import { useAppI18n } from '@/lib/i18n/provider';
import { useChatStore } from '@/lib/store';
import { buildTaskTimelineView } from '@/lib/task-timeline-view';
import type { ActiveTask, BackendTask, ChatMessage, FileAttachment, SessionUpdatedEvent, WsEvent } from '@/types';

function isSessionUpdatedEvent(data: WsEvent | Record<string, unknown>): data is SessionUpdatedEvent {
  return data.type === 'session_updated' && typeof data.session_id === 'string';
}

function activeTaskStatusLabel(status: string, locale: string) {
  if (status === 'needs_revision') return pickAppText(locale, '待修改', 'Needs revision');
  if (status === 'awaiting_acceptance') return pickAppText(locale, '待验收', 'Awaiting acceptance');
  if (status === 'running') return pickAppText(locale, '进行中', 'Running');
  return pickAppText(locale, '进行中', 'Active');
}

const THINKING_MODE_STORAGE_KEY = 'beaver_chat_thinking_enabled';

function loadThinkingModePreference(): boolean {
  if (typeof window === 'undefined') {
    return false;
  }
  const stored = window.localStorage.getItem(THINKING_MODE_STORAGE_KEY);
  return stored == null ? false : stored !== 'false';
}

function isDocumentHidden(): boolean {
  return typeof document !== 'undefined' && document.visibilityState === 'hidden';
}

export default function ChatPage() {
  const { locale } = useAppI18n();
  const {
    sessionId,
    messages,
    isLoading,
    isThinking,
    sessions,
    processRuns,
    processEvents,
    processArtifacts,
    selectedRunId,
    setSessionId,
    setMessages,
    addMessage,
    setInputDraft,
    getInputDraft,
    clearInputDraft,
    setIsLoading,
    clearMessages,
    setIsThinking,
    setSelectedRunId,
    setSessionProcess,
    updateMessageFeedback,
  } = useChatStore();

  const [input, setInput] = useState(() => useChatStore.getState().getInputDraft(useChatStore.getState().sessionId));
  const [thinkingModeEnabled, setThinkingModeEnabled] = useState(loadThinkingModePreference);
  const [pendingFiles, setPendingFiles] = useState<Array<{ file: File; id?: string; progress: number; error?: string }>>([]);
  const [activeTask, setActiveTask] = useState<ActiveTask | null>(null);
  const [activeTaskDetail, setActiveTaskDetail] = useState<BackendTask | null>(null);
  const [revisionTargetRunId, setRevisionTargetRunId] = useState<string | null>(null);
  const [documentHidden, setDocumentHidden] = useState(isDocumentHidden);
  const [sessionDrawerOpen, setSessionDrawerOpen] = useState(false);
  const [archiveTargetSessionId, setArchiveTargetSessionId] = useState<string | null>(null);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const messageViewportRef = useRef<HTMLDivElement>(null);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const loadSessionReqSeq = useRef(0);
  const loadActiveTaskReqSeq = useRef(0);
  const loadedSessionIdRef = useRef<string | null>(null);
  const refreshSessionOnReconnectRef = useRef(false);
  const hasConnectedRef = useRef(false);
  const shouldSnapToLatestRef = useRef(true);
  const wsStatus = useChatStore((state) => state.wsStatus);

  const sessionProcessRuns = useMemo(
    () => processRuns.filter((run) => run.session_id === sessionId),
    [processRuns, sessionId]
  );

  const sessionRunIds = useMemo(
    () => new Set(sessionProcessRuns.map((run) => run.run_id)),
    [sessionProcessRuns]
  );

  const sessionProcessEvents = useMemo(
    () => processEvents.filter((event) => sessionRunIds.has(event.run_id)),
    [processEvents, sessionRunIds]
  );

  const sessionProcessArtifacts = useMemo(
    () => processArtifacts.filter((artifact) => sessionRunIds.has(artifact.run_id)),
    [processArtifacts, sessionRunIds]
  );

  const selectedSessionRunId = selectedRunId && sessionRunIds.has(selectedRunId) ? selectedRunId : null;
  const activeTaskTimelineView = useMemo(
    () =>
      buildTaskTimelineView({
        task: activeTaskDetail,
        liveRuns: processRuns,
        liveEvents: processEvents,
        liveArtifacts: processArtifacts,
        locale,
      }),
    [activeTaskDetail, locale, processArtifacts, processEvents, processRuns]
  );

  const loadSessions = useCallback(async () => {
    try {
      const list = await listSessions();
      useChatStore.getState().setSessions(list);
    } catch {
      // backend may be offline during first render
    }
  }, []);

  const loadActiveTask = useCallback(async (key: string) => {
    const reqSeq = ++loadActiveTaskReqSeq.current;
    try {
      const nextActiveTask = await getActiveTask(key);
      if (reqSeq !== loadActiveTaskReqSeq.current || useChatStore.getState().sessionId !== key) return;
      setActiveTask(nextActiveTask);
      if (!nextActiveTask) {
        setActiveTaskDetail(null);
        return;
      }
      setActiveTaskDetail((current) => (current?.task_id === nextActiveTask.task_id ? current : null));
      try {
        const detail = await getBackendTask(nextActiveTask.task_id);
        if (reqSeq !== loadActiveTaskReqSeq.current || useChatStore.getState().sessionId !== key) return;
        if (detail.is_open === false) {
          setActiveTask(null);
          setActiveTaskDetail(null);
          return;
        }
        setActiveTaskDetail(detail);
      } catch {
        if (reqSeq === loadActiveTaskReqSeq.current && useChatStore.getState().sessionId === key) {
          setActiveTaskDetail(null);
        }
      }
    } catch {
      if (reqSeq === loadActiveTaskReqSeq.current && useChatStore.getState().sessionId === key) {
        setActiveTask(null);
        setActiveTaskDetail(null);
      }
    }
  }, []);

  const loadSessionMessages = useCallback(async (key: string) => {
    const reqSeq = ++loadSessionReqSeq.current;
    const localSnapshot = useChatStore.getState().messages;
    const waitingForReply = useChatStore.getState().isLoading || useChatStore.getState().isThinking;
    try {
      const [detail, process] = await Promise.all([
        getSession(key),
        getSessionProcess(key).catch(() => null),
      ]);
      if (reqSeq !== loadSessionReqSeq.current) return;
      if (useChatStore.getState().sessionId !== key) return;
      if (process) {
        setSessionProcess(key, process);
      }
      void loadActiveTask(key);
      const displayMessages = detail.messages.filter(shouldDisplayChatMessage);
      const shouldMergePending = shouldMergePendingUsers(displayMessages, localSnapshot, waitingForReply);
      const nextMessages = shouldMergePending
        ? mergeServerWithPendingUsers(displayMessages, localSnapshot)
        : displayMessages;
      setMessages(nextMessages);
      shouldSnapToLatestRef.current = true;
      const last = nextMessages[nextMessages.length - 1];
      if (last?.role === 'assistant') {
        setIsThinking(false);
        setIsLoading(false);
      }
    } catch {
      if (reqSeq !== loadSessionReqSeq.current) return;
      if (useChatStore.getState().sessionId !== key) return;
    }
  }, [loadActiveTask, setIsLoading, setIsThinking, setMessages, setSessionProcess]);

  useEffect(() => {
    const didSwitchSession = loadedSessionIdRef.current !== null && loadedSessionIdRef.current !== sessionId;
    loadedSessionIdRef.current = sessionId;
    if (didSwitchSession) {
      clearMessages();
      setIsLoading(false);
      setIsThinking(false);
    }
    setActiveTask(null);
    setActiveTaskDetail(null);
    setRevisionTargetRunId(null);
    setInput(useChatStore.getState().getInputDraft(sessionId));
    void loadSessionMessages(sessionId);
    void loadActiveTask(sessionId);
  }, [clearMessages, loadActiveTask, loadSessionMessages, sessionId, setIsLoading, setIsThinking]);

  useEffect(() => {
    if (wsStatus === 'connected') {
      if (hasConnectedRef.current && refreshSessionOnReconnectRef.current) {
        refreshSessionOnReconnectRef.current = false;
        void loadSessionMessages(useChatStore.getState().sessionId);
      }
      hasConnectedRef.current = true;
      return;
    }

    if (wsStatus === 'disconnected' && hasConnectedRef.current) {
      refreshSessionOnReconnectRef.current = true;
    }
  }, [loadSessionMessages, wsStatus]);

  useEffect(() => {
    const unsubMessage = wsManager.onMessage((data) => {
      if (isSessionUpdatedEvent(data)) {
        void loadSessions();
        if (data.session_id === useChatStore.getState().sessionId) {
          void loadSessionMessages(data.session_id);
        }
        return;
      }

      if (data.type === 'status' && data.status === 'thinking') {
        setIsThinking(true);
      } else if (data.type === 'message' && data.role === 'assistant') {
        setIsThinking(false);
        setIsLoading(false);
        const rawEvidenceStatus = data.evidence_status ?? data.metadata?.evidence_status;
        const evidenceStatus = rawEvidenceStatus === 'recorded' ? 'recorded' : undefined;
        const assistantMessage = {
          role: 'assistant',
          content: typeof data.content === 'string' ? data.content : '',
          timestamp: new Date().toISOString(),
          attachments: Array.isArray(data.attachments) ? data.attachments : undefined,
          run_id: typeof data.run_id === 'string' ? data.run_id : undefined,
          task_id: data.task_id ?? data.metadata?.task_id ?? null,
          task_status: data.task_status ?? data.metadata?.task_status ?? null,
          evidence_status: evidenceStatus,
        } as const;
        if (shouldDisplayChatMessage(assistantMessage)) {
          addMessage(assistantMessage);
        }
        void loadSessionMessages(typeof data.session_id === 'string' ? data.session_id : useChatStore.getState().sessionId);
        void loadActiveTask(typeof data.session_id === 'string' ? data.session_id : useChatStore.getState().sessionId);
        loadSessions();
      }
    });

    return () => {
      unsubMessage();
    };
  }, [addMessage, loadActiveTask, loadSessionMessages, loadSessions, setIsLoading, setIsThinking]);

  useEffect(() => {
    const intervalMs = getSessionRefreshIntervalMs({ isLoading, isThinking, documentHidden });
    if (intervalMs == null) {
      return;
    }
    const timer = setInterval(() => {
      const currentSessionId = useChatStore.getState().sessionId;
      void loadSessionMessages(currentSessionId);
      void loadSessions();
    }, intervalMs);
    return () => clearInterval(timer);
  }, [documentHidden, isLoading, isThinking, loadSessionMessages, loadSessions]);

  useEffect(() => {
    if (typeof document === 'undefined') {
      return;
    }
    const updateVisibility = () => setDocumentHidden(isDocumentHidden());
    document.addEventListener('visibilitychange', updateVisibility);
    return () => document.removeEventListener('visibilitychange', updateVisibility);
  }, []);

  const scrollMessagesToLatest = useCallback((behavior: ScrollBehavior) => {
    const viewport = messageViewportRef.current;
    if (!viewport) return;
    messagesEndRef.current?.scrollIntoView({ block: 'end', behavior });
    viewport.scrollTo({ top: viewport.scrollHeight, behavior });
  }, []);

  const scheduleScrollToLatest = useCallback((behavior: ScrollBehavior) => {
    if (typeof window === 'undefined') {
      scrollMessagesToLatest(behavior);
      return;
    }
    window.requestAnimationFrame(() => {
      window.requestAnimationFrame(() => {
        scrollMessagesToLatest(behavior);
      });
    });
  }, [scrollMessagesToLatest]);

  useEffect(() => {
    shouldSnapToLatestRef.current = true;
    setSessionDrawerOpen(false);
  }, [sessionId]);

  useLayoutEffect(() => {
    if (messages.length === 0 && !isThinking && sessionProcessEvents.length === 0) {
      return;
    }

    scheduleScrollToLatest(shouldSnapToLatestRef.current ? 'auto' : 'smooth');
    shouldSnapToLatestRef.current = false;
  }, [isThinking, messages.length, scheduleScrollToLatest, sessionProcessEvents.length]);

  const handleSend = useCallback(async () => {
    const text = input.trim();
    if ((!text && pendingFiles.length === 0) || isLoading) return;

    const readyFiles = pendingFiles.filter((p) => p.id && !p.error);
    const attachments: FileAttachment[] = readyFiles.map((item) => ({
      file_id: item.id!,
      name: item.file.name,
      content_type: item.file.type || 'application/octet-stream',
      size: item.file.size,
    }));

    const msgContent = text || pickAppText(locale, '（仅附件）', '(Attachments only)');

    if (revisionTargetRunId && text) {
      setIsLoading(true);
      setIsThinking(false);
      updateMessageFeedback(revisionTargetRunId, 'revise');
      try {
        await submitChatFeedback({
          sessionId,
          runId: revisionTargetRunId,
          feedbackType: 'revise',
          comment: msgContent,
        });
      } catch (err: any) {
        setIsThinking(false);
        setIsLoading(false);
        updateMessageFeedback(revisionTargetRunId, undefined, err?.message || pickAppText(locale, '反馈提交失败', 'Feedback failed'));
        return;
      } finally {
        setRevisionTargetRunId(null);
      }
    } else {
      setRevisionTargetRunId(null);
    }

    setInput('');
    clearInputDraft(sessionId);
    setPendingFiles([]);
    addMessage({
      role: 'user',
      content: msgContent,
      timestamp: new Date().toISOString(),
      attachments: attachments.length > 0 ? attachments : undefined,
    });
    setIsLoading(true);
    setIsThinking(false);

    if (wsManager.getStatus() === 'connected') {
      const wsPayload: Record<string, unknown> = {
        type: 'message',
        content: msgContent,
        thinking_enabled: thinkingModeEnabled,
        prompt_locale: promptLocaleForAppLocale(locale),
      };
      if (attachments.length > 0) {
        wsPayload.attachments = attachments;
      }
      wsManager.sendRaw(wsPayload);
    } else {
      try {
        const result = await sendMessage(msgContent, sessionId, attachments.length > 0 ? attachments : undefined, {
          thinkingEnabled: thinkingModeEnabled,
        });
        setIsThinking(false);
        setIsLoading(false);
        if (result.response) {
          if (useChatStore.getState().sessionId !== sessionId) {
            await loadSessions();
            return;
          }
          const assistantMessage = {
            role: 'assistant',
            content: result.response,
            timestamp: new Date().toISOString(),
            run_id: result.run_id,
            task_id: result.task_id,
            task_status: result.task_status,
            evidence_status: result.evidence_status === 'recorded' ? 'recorded' : undefined,
          } as const;
          if (shouldDisplayChatMessage(assistantMessage)) {
            addMessage(assistantMessage);
          }
          void getSessionProcess(sessionId).then((process) => setSessionProcess(sessionId, process)).catch(() => null);
          void loadActiveTask(sessionId);
          loadSessions();
        } else {
          await loadSessionMessages(sessionId);
          void loadActiveTask(sessionId);
          loadSessions();
        }
      } catch {
        setIsThinking(false);
        setIsLoading(false);
        if (useChatStore.getState().sessionId !== sessionId) {
          return;
        }
        addMessage({
          role: 'assistant',
          content: pickAppText(locale, '发送失败，请检查后端服务是否正在运行。', 'Send failed. Please check whether the backend service is running.'),
          timestamp: new Date().toISOString(),
        });
      }
    }
  }, [addMessage, clearInputDraft, input, isLoading, loadActiveTask, loadSessionMessages, loadSessions, locale, pendingFiles, revisionTargetRunId, sessionId, setIsLoading, setIsThinking, setSessionProcess, thinkingModeEnabled, updateMessageFeedback]);

  const handleFeedback = useCallback(async (runId: string, feedbackType: 'accept' | 'revise' | 'abandon', comment?: string) => {
    updateMessageFeedback(runId, feedbackType);
    try {
      await submitChatFeedback({
        sessionId,
        runId,
        feedbackType,
        comment,
      });
      void loadSessionMessages(sessionId);
      void getSessionProcess(sessionId).then((process) => setSessionProcess(sessionId, process)).catch(() => null);
      void loadActiveTask(sessionId);
      void loadSessions();
    } catch (err: any) {
      updateMessageFeedback(runId, undefined, err?.message || pickAppText(locale, '反馈提交失败', 'Feedback failed'));
    }
  }, [loadActiveTask, loadSessionMessages, loadSessions, locale, sessionId, setSessionProcess, updateMessageFeedback]);

  const handleRequestRevision = useCallback((runId: string) => {
    setRevisionTargetRunId(runId);
    textareaRef.current?.focus();
  }, []);

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
      e.preventDefault();
      handleSend();
    }
  };

  const handleFileSelect = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files || []);
    if (!files.length) return;
    e.target.value = '';

    for (const file of files) {
      if (file.size > 50 * 1024 * 1024) {
        setPendingFiles((prev) => [...prev, { file, progress: 0, error: pickAppText(locale, '文件过大（最大 50MB）', 'File is too large (max 50MB)') }]);
        continue;
      }

      setPendingFiles((prev) => [...prev, { file, progress: 0 }]);
      try {
        const result = await uploadFile(file, sessionId, (pct) => {
          setPendingFiles((prev) => prev.map((item) => (item.file === file ? { ...item, progress: pct } : item)));
        });
        setPendingFiles((prev) => prev.map((item) => (item.file === file ? { ...item, id: result.file_id, progress: 100 } : item)));
      } catch (err: any) {
        setPendingFiles((prev) => prev.map((item) => (item.file === file ? { ...item, error: err.message || pickAppText(locale, '上传失败', 'Upload failed') } : item)));
      }
    }
  }, [locale, sessionId]);

  const handleNewSession = async () => {
    const id = `web:${Date.now()}`;
    setSessionId(id);
    setSelectedRunId(null);
    setActiveTask(null);
    setActiveTaskDetail(null);
    setRevisionTargetRunId(null);
    clearInputDraft(id);
    setInput('');
    clearMessages();
    useChatStore.getState().resetProcessState();
    try {
      await createSession(id);
    } catch {
      // ignore transient create failures; first message can still create the session server-side
    }
    void loadSessions();
  };

  const handleArchiveSession = async (key: string) => {
    try {
      await archiveSession(key);
      setArchiveTargetSessionId(null);
      useChatStore.getState().setSessions(useChatStore.getState().sessions.filter((session) => session.key !== key));
      if (key === sessionId) {
        setSessionId('web:default');
        setActiveTask(null);
        setActiveTaskDetail(null);
        setRevisionTargetRunId(null);
        clearInputDraft(key);
        setInput(useChatStore.getState().getInputDraft('web:default'));
        clearMessages();
        useChatStore.getState().resetProcessState();
      }
      void loadSessions();
    } catch {
      addMessage({
        role: 'assistant',
        content: pickAppText(locale, '归档会话失败，请稍后重试。', 'Failed to archive the session. Please try again later.'),
        timestamp: new Date().toISOString(),
      });
    }
  };

  const handleSelectSession = (key: string) => {
    setSelectedRunId(null);
    setActiveTask(null);
    setActiveTaskDetail(null);
    setRevisionTargetRunId(null);
    setInput(useChatStore.getState().getInputDraft(key));
    setSessionId(key);
    setSessionDrawerOpen(false);
  };

  const removePendingFile = useCallback((file: File) => {
    setPendingFiles((prev) => prev.filter((item) => item.file !== file));
  }, []);

  const toggleThinkingMode = useCallback(() => {
    setThinkingModeEnabled((current) => {
      const next = !current;
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(THINKING_MODE_STORAGE_KEY, String(next));
      }
      return next;
    });
  }, []);

  const formatSessionName = (key: string) => {
    if (key.startsWith('web:')) {
      const id = key.slice(4);
      if (id === 'default') return pickAppText(locale, '默认', 'Default');
      const numeric = Number(id);
      if (!Number.isNaN(numeric)) {
        return new Date(numeric).toLocaleDateString(locale, {
          month: 'short',
          day: 'numeric',
          hour: '2-digit',
          minute: '2-digit',
        });
      }
      return id;
    }
    return key;
  };

  const archiveTargetSessionName = archiveTargetSessionId ? formatSessionName(archiveTargetSessionId) : '';

  const renderSessionSidebar = (variant: 'desktop' | 'drawer') => (
    <>
      <div className="px-5 pb-5 pt-6">
        <button
          type="button"
          onClick={() => {
            setSessionDrawerOpen(false);
            void handleNewSession();
          }}
          className="flex h-11 w-full items-center justify-center gap-2 rounded-full bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-[#342E2B]"
        >
          <Plus className="h-4 w-4" />
          {pickAppText(locale, '新对话', 'New chat')}
        </button>
      </div>
      <ScrollArea className="flex-1">
        <div className="space-y-3 px-3 pb-6">
          <div className="px-3 pb-2 text-[14px] text-muted-foreground">{pickAppText(locale, '最近对话', 'Recent chats')}</div>
          {sessions.length === 0 && (
            <p className="px-3 py-4 text-sm text-muted-foreground">{pickAppText(locale, '暂无对话记录', 'No chat history yet')}</p>
          )}
          {sessions.map((session) => {
            const sessionName = formatSessionName(session.key);
            const isCurrent = session.key === sessionId;

            return (
              <div
                key={`${variant}:${session.key}`}
                className={`group flex items-center gap-1 rounded-xl px-2 py-1 text-[15px] transition-colors ${
                  isCurrent
                    ? 'bg-[#EFEEED] text-foreground'
                    : 'text-foreground hover:bg-[#EFEEED]/70 focus-within:bg-[#EFEEED]/70'
                }`}
              >
                <button
                  type="button"
                  onClick={() => handleSelectSession(session.key)}
                  className="flex h-11 min-w-0 flex-1 items-center rounded-lg px-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring"
                  aria-current={isCurrent ? 'true' : undefined}
                >
                  <span className="truncate">{sessionName}</span>
                </button>
                <button
                  type="button"
                  onClick={() => setArchiveTargetSessionId(session.key)}
                  className="flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-muted-foreground opacity-100 transition-colors hover:bg-white hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
                  title={pickAppText(locale, '归档会话', 'Archive session')}
                  aria-label={pickAppText(locale, `归档会话 ${sessionName}`, `Archive session ${sessionName}`)}
                >
                  <Trash2 className="h-4 w-4" />
                </button>
              </div>
            );
          })}
        </div>
      </ScrollArea>
    </>
  );

  return (
    <div className="relative flex h-[calc(100dvh-4rem)] overflow-hidden bg-background">
      <aside className="hidden w-[280px] shrink-0 flex-col border-r border-[#E6E1DE] bg-[#F7F6F5] md:flex">
        {renderSessionSidebar('desktop')}
      </aside>

      {sessionDrawerOpen && (
        <div className="fixed inset-x-0 bottom-0 top-16 z-40 md:hidden">
          <button
            type="button"
            className="absolute inset-0 bg-black/30"
            aria-label={pickAppText(locale, '关闭最近对话', 'Close recent chats')}
            onClick={() => setSessionDrawerOpen(false)}
          />
          <aside className="absolute bottom-0 left-0 top-0 flex w-[min(86vw,320px)] flex-col border-r border-[#E6E1DE] bg-[#F7F6F5] shadow-2xl">
            {renderSessionSidebar('drawer')}
          </aside>
        </div>
      )}

      <div className="flex-1 flex flex-col min-w-0">
        <div className="flex min-h-14 items-center gap-2 border-b border-[#E6E1DE] bg-[#F7F6F5] px-3 md:hidden">
          <button
            type="button"
            onClick={() => setSessionDrawerOpen(true)}
            className="flex h-11 w-11 items-center justify-center rounded-full border border-[#E6E1DE] bg-white text-[#1D1715]"
            aria-label={pickAppText(locale, '打开最近对话', 'Open recent chats')}
          >
            <Menu className="h-5 w-5" />
          </button>
          <div className="min-w-0 text-sm font-medium text-foreground">
            <span className="block truncate">{formatSessionName(sessionId)}</span>
          </div>
        </div>
        <div className="flex-1 min-h-0">
          <ChatWorkbench
            messages={messages}
            isThinking={isThinking || (isLoading && messages[messages.length - 1]?.role === 'user')}
            messagesEndRef={messagesEndRef}
            messageViewportRef={messageViewportRef}
            processRuns={sessionProcessRuns}
            processEvents={sessionProcessEvents}
            processArtifacts={sessionProcessArtifacts}
            selectedRunId={selectedSessionRunId}
            onSelectRun={(runId) => setSelectedRunId(selectedSessionRunId === runId ? null : runId)}
            onFeedback={handleFeedback}
            onRequestRevision={handleRequestRevision}
          />
        </div>

        <div className="bg-background px-3 pb-4 pt-3 sm:px-5 sm:pb-6 md:px-8 md:pb-8 md:pt-4">
          <div className="mx-auto max-w-5xl">
            {(activeTask || revisionTargetRunId) && (
              <div className="mb-2 flex">
                {activeTask ? (
                  <Link
                    href={`/tasks/${encodeURIComponent(activeTask.task_id)}`}
                    className="inline-flex h-11 max-w-full items-center gap-2 rounded-full border border-[#D8D2CE] bg-[#F7F6F5] px-3 text-xs text-foreground transition-colors hover:bg-[#EFEEED]"
                    title={activeTask.description}
                  >
                    <span className="shrink-0 text-muted-foreground">
                      {revisionTargetRunId ? pickAppText(locale, '修改任务', 'Revising task') : pickAppText(locale, '当前任务', 'Current task')}:
                    </span>
                    <span className="truncate font-medium">{activeTask.short_title}</span>
                    <span className="shrink-0 rounded-full bg-white px-2 py-0.5 text-[11px] text-muted-foreground">
                      {revisionTargetRunId ? pickAppText(locale, '待输入修改要求', 'Awaiting revision') : activeTaskStatusLabel(activeTask.status, locale)}
                    </span>
                  </Link>
                ) : null}
              </div>
            )}
            {pendingFiles.length > 0 && (
              <div className="mb-2 space-y-1">
                {pendingFiles.map((item, index) => (
                  <div key={`${item.file.name}:${index}`} className="flex min-h-11 items-center gap-2 rounded-md bg-muted px-3 py-1.5 text-sm">
                    <span className="truncate flex-1">
                      {item.file.name}{' '}
                      <span className="text-muted-foreground">({(item.file.size / 1024).toFixed(0)}KB)</span>
                    </span>
                    {item.error ? (
                      <span className="text-destructive text-xs">{item.error}</span>
                    ) : item.progress < 100 ? (
                      <div className="w-20 h-1.5 bg-muted-foreground/20 rounded-full overflow-hidden">
                        <div className="h-full bg-primary rounded-full transition-all" style={{ width: `${item.progress}%` }} />
                      </div>
                    ) : (
                      <span className="text-[#657162] text-xs">{pickAppText(locale, '就绪', 'Ready')}</span>
                    )}
                    <button
                      type="button"
                      onClick={() => removePendingFile(item.file)}
                      className="flex h-11 w-11 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground"
                      aria-label={pickAppText(locale, `移除附件 ${item.file.name}`, `Remove attachment ${item.file.name}`)}
                    >
                      <X className="h-4 w-4" />
                    </button>
                  </div>
                ))}
              </div>
            )}

            <div className="relative rounded-[28px] border border-[#E6E1DE] bg-white p-4 shadow-[0_8px_24px_rgba(0,0,0,0.08)]">
              <input ref={fileInputRef} type="file" multiple className="hidden" onChange={handleFileSelect} />
              <label htmlFor="chat-composer" className="sr-only">
                {revisionTargetRunId
                  ? pickAppText(locale, '修改要求', 'Revision request')
                  : pickAppText(locale, '消息内容', 'Message content')}
              </label>

              <textarea
                id="chat-composer"
                ref={textareaRef}
                value={input}
                onChange={(e) => {
                  setInput(e.target.value);
                  setInputDraft(sessionId, e.target.value);
                }}
                onKeyDown={handleKeyDown}
                placeholder={
                  revisionTargetRunId
                    ? pickAppText(locale, '请输入修改要求', 'Describe the requested changes')
                    : pickAppText(locale, '今天想聊什么？', 'What would you like to talk about today?')
                }
                rows={1}
                className="block w-full resize-none border-0 bg-transparent px-1 pb-8 pt-1 text-[16px] leading-7 placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:px-2 sm:text-[17px]"
                style={{ minHeight: '72px', maxHeight: '200px' }}
                onInput={(e) => {
                  const target = e.target as HTMLTextAreaElement;
                  target.style.height = 'auto';
                  target.style.height = `${Math.min(target.scrollHeight, 200)}px`;
                }}
              />

              <div className="flex items-center justify-between">
                <div className="flex items-center gap-2 text-[15px] text-muted-foreground sm:gap-5">
                  <button
                    type="button"
                    onClick={() => fileInputRef.current?.click()}
                    className="inline-flex h-11 w-11 items-center justify-center rounded-full text-foreground transition-colors hover:bg-[#F7F5F4] hover:text-muted-foreground"
                    title={pickAppText(locale, '添加附件', 'Add attachment')}
                    aria-label={pickAppText(locale, '添加附件', 'Add attachment')}
                  >
                    <Plus className="h-5 w-5" />
                  </button>
                  <button
                    type="button"
                    onClick={toggleThinkingMode}
                    className={`inline-flex h-11 items-center gap-2 rounded-full border px-3 text-sm transition-colors ${
                      thinkingModeEnabled
                        ? 'border-primary/40 bg-[#F1EFEE] text-foreground'
                        : 'border-[#E6E1DE] bg-white text-muted-foreground hover:text-foreground'
                    }`}
                    title={
                      thinkingModeEnabled
                        ? pickAppText(locale, '思考模式已开启', 'Thinking mode is on')
                        : pickAppText(locale, '思考模式已关闭', 'Thinking mode is off')
                    }
                    aria-pressed={thinkingModeEnabled}
                  >
                    <Brain className="h-4 w-4" />
                    {pickAppText(locale, '思考', 'Think')}
                  </button>
                </div>
                <button
                  type="button"
                  onClick={handleSend}
                  disabled={(!input.trim() && pendingFiles.filter((item) => item.id && !item.error).length === 0) || isLoading}
                  className="flex h-12 w-12 items-center justify-center rounded-full bg-[#85817E] text-white transition-colors hover:bg-primary disabled:opacity-40"
                  aria-label={pickAppText(locale, '发送', 'Send')}
                >
                  <Send className="h-5 w-5" />
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>

      <Dialog open={Boolean(archiveTargetSessionId)} onOpenChange={(open) => !open && setArchiveTargetSessionId(null)}>
        <DialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-md">
          <DialogHeader>
            <DialogTitle>{pickAppText(locale, '归档此会话？', 'Archive this chat?')}</DialogTitle>
            <DialogDescription>
              {pickAppText(
                locale,
                archiveTargetSessionName ? `会话「${archiveTargetSessionName}」会从最近对话中移除。` : '此会话会从最近对话中移除。',
                archiveTargetSessionName ? `Chat "${archiveTargetSessionName}" will be removed from recent chats.` : 'This chat will be removed from recent chats.'
              )}
            </DialogDescription>
          </DialogHeader>
          <DialogFooter className="gap-2 sm:gap-2">
            <button
              type="button"
              onClick={() => setArchiveTargetSessionId(null)}
              className="h-11 rounded-md border border-border px-4 text-sm text-muted-foreground hover:bg-accent"
            >
              {pickAppText(locale, '取消', 'Cancel')}
            </button>
            <button
              type="button"
              onClick={() => archiveTargetSessionId && void handleArchiveSession(archiveTargetSessionId)}
              className="h-11 rounded-md bg-destructive px-4 text-sm font-medium text-destructive-foreground hover:bg-destructive/90"
            >
              {pickAppText(locale, '确认归档', 'Confirm archive')}
            </button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {activeTaskDetail ? (
        <CurrentSessionProgressSidebar
          cards={activeTaskTimelineView?.cards ?? []}
          isLive={Boolean(activeTaskDetail.is_open && wsStatus === 'connected')}
        />
      ) : null}
    </div>
  );
}
