'use client';

import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { MessageSquare, Paperclip, Plus, Send, Trash2, X } from 'lucide-react';

import { ChatWorkbench } from '@/components/chat-workbench/ChatWorkbench';
import { Button } from '@/components/ui/button';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Separator } from '@/components/ui/separator';
import {
  cancelDelegation,
  createSession,
  deleteSession,
  getSession,
  getStatus,
  listCommands,
  listSessions,
  sendMessage,
  uploadFile,
  wsManager,
} from '@/lib/api';
import { useChatStore } from '@/lib/store';
import type { ChatMessage, FileAttachment, ProcessWsEvent, SessionUpdatedEvent, SlashCommand, WsEvent } from '@/types';

function scheduleWhenIdle(task: () => void, timeout = 1200): () => void {
  if (typeof window === 'undefined') {
    task();
    return () => {};
  }

  const idleWindow = window as Window &
    typeof globalThis & {
      requestIdleCallback?: (callback: IdleRequestCallback, options?: IdleRequestOptions) => number;
      cancelIdleCallback?: (handle: number) => void;
    };

  if (typeof idleWindow.requestIdleCallback === 'function') {
    const id = idleWindow.requestIdleCallback(() => task(), { timeout });
    return () => idleWindow.cancelIdleCallback?.(id);
  }

  const id = globalThis.setTimeout(task, 250);
  return () => globalThis.clearTimeout(id);
}

function messageFingerprint(msg: ChatMessage): string {
  const attachmentKey = (msg.attachments ?? [])
    .map((a) => `${a.file_id ?? ''}:${a.name}:${a.content_type}:${a.size ?? ''}`)
    .join('|');
  return `${msg.role}::${String(msg.content)}::${attachmentKey}`;
}

function mergeServerWithPendingUsers(serverMessages: ChatMessage[], localMessages: ChatMessage[]): ChatMessage[] {
  const counts = new Map<string, number>();
  for (const message of serverMessages) {
    const key = messageFingerprint(message);
    counts.set(key, (counts.get(key) ?? 0) + 1);
  }

  const pendingUsers: ChatMessage[] = [];
  for (const message of localMessages) {
    const key = messageFingerprint(message);
    const count = counts.get(key) ?? 0;
    if (count > 0) {
      counts.set(key, count - 1);
      continue;
    }
    if (message.role === 'user') {
      pendingUsers.push(message);
    }
  }

  return [...serverMessages, ...pendingUsers];
}

function isProcessEvent(data: WsEvent | Record<string, unknown>): data is ProcessWsEvent {
  const type = typeof data.type === 'string' ? data.type : '';
  return type.startsWith('process_') || type === 'process_cancel_ack';
}

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

export default function ChatPage() {
  const {
    sessionId,
    messages,
    isLoading,
    isThinking,
    sessions,
    processRuns,
    processEvents,
    processArtifacts,
    selectedRunId,
    setSessionId,
    setMessages,
    addMessage,
    setIsLoading,
    setSessions,
    clearMessages,
    setWsStatus,
    setIsThinking,
    setNanobotReady,
    resetProcessState,
    ingestProcessEvent,
    setSelectedRunId,
  } = useChatStore();

  const [input, setInput] = useState('');
  const [commands, setCommands] = useState<SlashCommand[]>([]);
  const [showCommandPicker, setShowCommandPicker] = useState(false);
  const [pickerIndex, setPickerIndex] = useState(0);
  const [pendingFiles, setPendingFiles] = useState<Array<{ file: File; id?: string; progress: number; error?: string }>>([]);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const messageViewportRef = useRef<HTMLDivElement>(null);
  const textareaRef = useRef<HTMLTextAreaElement>(null);
  const pickerRef = useRef<HTMLDivElement>(null);
  const fileInputRef = useRef<HTMLInputElement>(null);
  const loadSessionReqSeq = useRef(0);
  const commandsLoadedRef = useRef(false);
  const refreshSessionOnReconnectRef = useRef(false);
  const hasConnectedRef = useRef(false);
  const statusCheckCleanupRef = useRef<(() => void) | null>(null);
  const statusCheckInFlightRef = useRef(false);
  const shouldSnapToLatestRef = useRef(true);

  const filteredCommands = useMemo(() => {
    if (!input.startsWith('/') || input.includes(' ')) return [];
    const filter = input.slice(1).toLowerCase();
    return commands.filter(
      (command) => command.name.startsWith(filter) || (filter === '' ? true : command.name.includes(filter))
    );
  }, [commands, input]);

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

  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 = await getSession(key);
      if (reqSeq !== loadSessionReqSeq.current) return;
      if (useChatStore.getState().sessionId !== key) return;
      const nextMessages = waitingForReply
        ? mergeServerWithPendingUsers(detail.messages, localSnapshot)
        : detail.messages;
      setMessages(nextMessages);
      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;
    }
  }, [setIsLoading, setIsThinking, setMessages]);

  const loadCommands = useCallback(async () => {
    if (commandsLoadedRef.current) return;
    commandsLoadedRef.current = true;
    try {
      const nextCommands = await listCommands();
      setCommands(nextCommands);
    } catch {
      commandsLoadedRef.current = false;
    }
  }, []);

  const scheduleStatusCheck = useCallback(() => {
    if (statusCheckInFlightRef.current) return;

    statusCheckCleanupRef.current?.();
    statusCheckCleanupRef.current = scheduleWhenIdle(async () => {
      statusCheckInFlightRef.current = true;
      try {
        await getStatus();
        setNanobotReady(true);
      } catch {
        setNanobotReady(false);
      } finally {
        statusCheckInFlightRef.current = false;
      }
    });
  }, [setNanobotReady]);

  useEffect(() => {
    if (input.startsWith('/') && !input.includes(' ')) {
      void loadCommands();
    }
  }, [input, loadCommands]);

  useEffect(() => {
    setShowCommandPicker(filteredCommands.length > 0);
    setPickerIndex(0);
  }, [filteredCommands]);

  useEffect(() => {
    loadSessions();
  }, [loadSessions]);

  useEffect(() => {
    clearMessages();
    setIsLoading(false);
    setIsThinking(false);
    resetProcessState();
    const wsSessionId = sessionId.startsWith('web:') ? sessionId.slice(4) : sessionId;
    wsManager.connect(wsSessionId);
    loadSessionMessages(sessionId);
  }, [clearMessages, loadSessionMessages, resetProcessState, sessionId, setIsLoading, setIsThinking]);

  useEffect(() => {
    const unsubStatus = wsManager.onStatusChange(async (status) => {
      setWsStatus(status);
      if (status === 'connected') {
        if (hasConnectedRef.current && refreshSessionOnReconnectRef.current) {
          refreshSessionOnReconnectRef.current = false;
          void loadSessionMessages(useChatStore.getState().sessionId);
        }
        hasConnectedRef.current = true;
        scheduleStatusCheck();
      } else {
        if (status === 'disconnected' && hasConnectedRef.current) {
          refreshSessionOnReconnectRef.current = true;
        }
        statusCheckCleanupRef.current?.();
        statusCheckCleanupRef.current = null;
        setNanobotReady(null);
      }
    });

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

      if (isProcessEvent(data)) {
        ingestProcessEvent(data);
        return;
      }

      if (data.type === 'status' && data.status === 'thinking') {
        setIsThinking(true);
      } else if (data.type === 'message' && data.role === 'assistant') {
        setIsThinking(false);
        setIsLoading(false);
        addMessage({
          role: 'assistant',
          content: typeof data.content === 'string' ? data.content : '',
          timestamp: new Date().toISOString(),
          attachments: Array.isArray(data.attachments) ? data.attachments : undefined,
        });
        loadSessions();
      }
    });

    return () => {
      statusCheckCleanupRef.current?.();
      statusCheckCleanupRef.current = null;
      unsubStatus();
      unsubMessage();
    };
  }, [addMessage, ingestProcessEvent, loadSessionMessages, loadSessions, scheduleStatusCheck, setIsLoading, setIsThinking, setNanobotReady, setWsStatus]);

  useEffect(() => {
    if (!isLoading && !isThinking) {
      return;
    }
    const timer = setInterval(() => {
      loadSessionMessages(useChatStore.getState().sessionId);
    }, 1500);
    return () => clearInterval(timer);
  }, [isLoading, isThinking, loadSessionMessages]);

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

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

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

    scrollMessagesToLatest(shouldSnapToLatestRef.current ? 'auto' : 'smooth');
    shouldSnapToLatestRef.current = false;
  }, [isThinking, messages, processEvents, scrollMessagesToLatest]);

  useEffect(() => {
    if (!showCommandPicker || !pickerRef.current) return;
    const item = pickerRef.current.children[pickerIndex] as HTMLElement | undefined;
    item?.scrollIntoView({ block: 'nearest' });
  }, [pickerIndex, showCommandPicker]);

  const selectCommand = useCallback((command: SlashCommand) => {
    setInput(command.argument_hint ? `/${command.name} ` : `/${command.name}`);
    setShowCommandPicker(false);
    textareaRef.current?.focus();
  }, []);

  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,
    }));

    setInput('');
    setPendingFiles([]);
    setShowCommandPicker(false);

    const msgContent = text || '（仅附件）';
    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 };
      if (attachments.length > 0) {
        wsPayload.attachments = attachments;
      }
      wsManager.sendRaw(wsPayload);
    } else {
      try {
        const result = await sendMessage(msgContent, sessionId, attachments.length > 0 ? attachments : undefined);
        setIsThinking(false);
        setIsLoading(false);
        if (result.response) {
          if (useChatStore.getState().sessionId !== sessionId) {
            await loadSessions();
            return;
          }
          addMessage({
            role: 'assistant',
            content: result.response,
            timestamp: new Date().toISOString(),
          });
          loadSessions();
        } else {
          await loadSessionMessages(sessionId);
          loadSessions();
        }
      } catch {
        setIsThinking(false);
        setIsLoading(false);
        if (useChatStore.getState().sessionId !== sessionId) {
          return;
        }
        addMessage({
          role: 'assistant',
          content: '发送失败，请检查后端服务是否正在运行。',
          timestamp: new Date().toISOString(),
        });
      }
    }
  }, [addMessage, input, isLoading, loadSessionMessages, loadSessions, pendingFiles, sessionId, setIsLoading, setIsThinking]);

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (showCommandPicker && filteredCommands.length > 0) {
      if (e.key === 'ArrowUp') {
        e.preventDefault();
        setPickerIndex((i) => (i <= 0 ? filteredCommands.length - 1 : i - 1));
        return;
      }
      if (e.key === 'ArrowDown') {
        e.preventDefault();
        setPickerIndex((i) => (i >= filteredCommands.length - 1 ? 0 : i + 1));
        return;
      }
      if (e.key === 'Tab' || (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing)) {
        e.preventDefault();
        selectCommand(filteredCommands[pickerIndex]);
        return;
      }
      if (e.key === 'Escape') {
        e.preventDefault();
        setShowCommandPicker(false);
        return;
      }
    }

    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: '文件过大（最大 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 || '上传失败' } : item)));
      }
    }
  }, [sessionId]);

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

  const handleDeleteSession = async (key: string, e: React.MouseEvent) => {
    e.stopPropagation();
    try {
      await deleteSession(key);
      if (key === sessionId) {
        setSessionId('web:default');
        clearMessages();
        resetProcessState();
      }
      loadSessions();
    } catch {
      // ignore transient errors
    }
  };

  const handleSelectSession = (key: string) => {
    setSessionId(key);
  };

  const handleCancelRun = async (runId: string) => {
    try {
      await cancelDelegation(runId);
    } catch (err: any) {
      addMessage({
        role: 'assistant',
        content: `取消任务 ${runId} 失败：${err.message || '未知错误'}`,
        timestamp: new Date().toISOString(),
      });
    }
  };

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

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

  return (
    <div className="flex h-[calc(100vh-3.5rem)] bg-background">
      <div className="w-64 border-r border-border flex flex-col bg-card">
        <div className="p-3">
          <Button onClick={handleNewSession} variant="outline" className="w-full justify-start gap-2" size="sm">
            <Plus className="w-4 h-4" />
            新对话
          </Button>
        </div>
        <Separator />
        <ScrollArea className="flex-1">
          <div className="p-2 space-y-1">
            {sessions.length === 0 && (
              <p className="text-xs text-muted-foreground px-2 py-4 text-center">暂无对话记录</p>
            )}
            {sessions.map((session) => (
              <div
                key={session.key}
                onClick={() => handleSelectSession(session.key)}
                className={`group flex items-center justify-between px-2 py-1.5 rounded-md cursor-pointer text-sm ${
                  session.key === sessionId
                    ? 'bg-accent text-accent-foreground'
                    : 'text-muted-foreground hover:bg-accent/50'
                }`}
              >
                <div className="flex items-center gap-2 truncate">
                  <MessageSquare className="w-3.5 h-3.5 flex-shrink-0" />
                  <span className="truncate">{formatSessionName(session.key)}</span>
                </div>
                <button
                  onClick={(event) => handleDeleteSession(session.key, event)}
                  className="opacity-0 group-hover:opacity-100 p-0.5 hover:text-destructive transition-opacity"
                >
                  <Trash2 className="w-3.5 h-3.5" />
                </button>
              </div>
            ))}
          </div>
        </ScrollArea>
      </div>

      <div className="flex-1 flex flex-col min-w-0">
        <div className="flex-1 min-h-0">
          <ChatWorkbench
            messages={messages}
            isThinking={isThinking || (isLoading && messages[messages.length - 1]?.role === 'user')}
            messagesEndRef={messagesEndRef}
            messageViewportRef={messageViewportRef}
            processRuns={processRuns}
            processEvents={processEvents}
            processArtifacts={processArtifacts}
            selectedRunId={selectedRunId}
            onSelectRun={setSelectedRunId}
            onCancelRun={handleCancelRun}
          />
        </div>

        <div className="border-t border-border p-4 bg-background/95 backdrop-blur">
          <div className="max-w-5xl mx-auto">
            {pendingFiles.length > 0 && (
              <div className="mb-2 space-y-1">
                {pendingFiles.map((item, index) => (
                  <div key={`${item.file.name}:${index}`} className="flex items-center gap-2 px-3 py-1.5 bg-muted rounded-md 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-green-500 text-xs">就绪</span>
                    )}
                    <button onClick={() => removePendingFile(item.file)} className="text-muted-foreground hover:text-foreground">
                      <X className="w-3.5 h-3.5" />
                    </button>
                  </div>
                ))}
              </div>
            )}

            <div className="relative flex gap-2">
              {showCommandPicker && filteredCommands.length > 0 && (
                <div
                  ref={pickerRef}
                  className="absolute bottom-full left-0 right-10 mb-2 bg-popover border border-border rounded-lg shadow-lg overflow-y-auto max-h-60 z-50"
                >
                  {filteredCommands.map((command, index) => (
                    <button
                      key={command.name}
                      className={`w-full text-left px-3 py-2 flex items-center gap-2 text-sm transition-colors ${
                        index === pickerIndex
                          ? 'bg-accent text-accent-foreground'
                          : 'hover:bg-accent/50 text-foreground'
                      }`}
                      onMouseDown={(event) => {
                        event.preventDefault();
                        selectCommand(command);
                      }}
                      onMouseEnter={() => setPickerIndex(index)}
                    >
                      <span className="font-mono font-semibold text-primary shrink-0">/{command.name}</span>
                      {command.argument_hint && (
                        <span className="text-muted-foreground text-xs shrink-0">{command.argument_hint}</span>
                      )}
                      <span className="text-muted-foreground text-xs truncate ml-auto">{command.description}</span>
                      {command.plugin_name !== 'builtin' && (
                        <span className={`text-xs px-1 rounded shrink-0 ${command.plugin_name === 'skill' ? 'bg-blue-500/10 text-blue-500' : 'bg-muted'}`}>
                          {command.plugin_name === 'skill' ? '技能' : command.plugin_name}
                        </span>
                      )}
                    </button>
                  ))}
                </div>
              )}

              <input ref={fileInputRef} type="file" multiple className="hidden" onChange={handleFileSelect} />

              <Button
                onClick={() => fileInputRef.current?.click()}
                variant="ghost"
                size="icon"
                className="h-10 w-10 flex-shrink-0"
                title="添加附件"
              >
                <Paperclip className="w-4 h-4" />
              </Button>

              <textarea
                ref={textareaRef}
                value={input}
                onChange={(e) => setInput(e.target.value)}
                onKeyDown={handleKeyDown}
                placeholder="输入消息或 / 呼出命令…（回车发送，Shift+回车换行）"
                rows={1}
                className="flex-1 resize-none rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
                style={{ minHeight: '40px', maxHeight: '200px' }}
                onInput={(e) => {
                  const target = e.target as HTMLTextAreaElement;
                  target.style.height = 'auto';
                  target.style.height = `${Math.min(target.scrollHeight, 200)}px`;
                }}
              />

              <Button
                onClick={handleSend}
                disabled={(!input.trim() && pendingFiles.filter((item) => item.id && !item.error).length === 0) || isLoading}
                size="icon"
                className="h-10 w-10 flex-shrink-0"
              >
                <Send className="w-4 h-4" />
              </Button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
