﻿"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import MarkdownMessage from "@/components/MarkdownMessage";
import { sendChatMessage } from "@/lib/api";
import {
  parseJsonSafely,
  splitSseEvents,
  type TencentErrorEvent,
  type TencentReplyEvent,
} from "@/lib/tencentSse";
import type { ChatMessage } from "@/types/mindmap";

type TriggerRequest = {
  id: number;
  content: string;
};

type ChatPanelProps = {
  sessionId: string;
  triggerRequest?: TriggerRequest;
  onClose?: () => void;
};

const TYPING_INTERVAL_MS = 24;

export default function ChatPanel({
  sessionId,
  triggerRequest,
  onClose,
}: ChatPanelProps) {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const typingTimerRef = useRef<number | null>(null);
  const pendingCleanupTimerRef = useRef<number | null>(null);
  const abortControllerRef = useRef<AbortController | null>(null);
  const assistantTargetRef = useRef("");
  const assistantDisplayedRef = useRef("");
  const streamCompleteRef = useRef(false);
  const handledTriggerIdRef = useRef<number | null>(null);

  const scrollToBottom = useCallback(() => {
    messagesEndRef.current?.scrollIntoView({
      behavior: loading ? "auto" : "smooth",
      block: "end",
    });
  }, [loading]);

  useEffect(() => {
    scrollToBottom();
  }, [messages, scrollToBottom]);

  const cancelPendingCleanup = useCallback(() => {
    if (pendingCleanupTimerRef.current !== null) {
      window.clearTimeout(pendingCleanupTimerRef.current);
      pendingCleanupTimerRef.current = null;
    }
  }, []);

  const abortInFlightRequest = useCallback(() => {
    abortControllerRef.current?.abort();
    abortControllerRef.current = null;
  }, []);

  const updateAssistantMessage = useCallback((content: string) => {
    setMessages((prev) => {
      const next = [...prev];
      const lastMessage = next[next.length - 1];

      if (!lastMessage || lastMessage.role !== "assistant") {
        next.push({ role: "assistant", content });
        return next;
      }

      next[next.length - 1] = {
        ...lastMessage,
        content,
      };
      return next;
    });
  }, []);

  const stopTypingAnimation = useCallback(() => {
    if (typingTimerRef.current !== null) {
      window.clearInterval(typingTimerRef.current);
      typingTimerRef.current = null;
    }
  }, []);

  const getTypingStep = useCallback((remaining: number) => {
    if (remaining > 160) return 10;
    if (remaining > 80) return 7;
    if (remaining > 36) return 4;
    if (remaining > 12) return 2;
    return 1;
  }, []);

  const ensureTypingAnimation = useCallback(() => {
    if (typingTimerRef.current !== null) {
      return;
    }

    typingTimerRef.current = window.setInterval(() => {
      const current = assistantDisplayedRef.current;
      const target = assistantTargetRef.current;

      if (current === target) {
        if (streamCompleteRef.current) {
          stopTypingAnimation();
        }
        return;
      }

      const remaining = target.length - current.length;
      const nextLength = current.length + getTypingStep(remaining);
      const nextContent = target.slice(0, nextLength);
      assistantDisplayedRef.current = nextContent;
      updateAssistantMessage(nextContent);

      if (nextContent === target && streamCompleteRef.current) {
        stopTypingAnimation();
      }
    }, TYPING_INTERVAL_MS);
  }, [getTypingStep, stopTypingAnimation, updateAssistantMessage]);

  useEffect(() => {
    cancelPendingCleanup();

    return () => {
      pendingCleanupTimerRef.current = window.setTimeout(() => {
        abortInFlightRequest();
        stopTypingAnimation();
        pendingCleanupTimerRef.current = null;
      }, 0);
    };
  }, [abortInFlightRequest, cancelPendingCleanup, stopTypingAnimation]);

  const resetStreamingState = useCallback(() => {
    cancelPendingCleanup();
    abortInFlightRequest();
    stopTypingAnimation();
    assistantTargetRef.current = "";
    assistantDisplayedRef.current = "";
    streamCompleteRef.current = false;
  }, [abortInFlightRequest, cancelPendingCleanup, stopTypingAnimation]);

  const mergeIncomingContent = useCallback((currentTarget: string, incomingContent: string) => {
    if (!currentTarget) {
      return incomingContent;
    }

    if (!incomingContent) {
      return currentTarget;
    }

    if (incomingContent.startsWith(currentTarget)) {
      return incomingContent;
    }

    if (currentTarget.startsWith(incomingContent)) {
      return currentTarget;
    }

    const maxOverlap = Math.min(currentTarget.length, incomingContent.length);
    for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
      if (currentTarget.slice(-overlap) === incomingContent.slice(0, overlap)) {
        return `${currentTarget}${incomingContent.slice(overlap)}`;
      }
    }

    return incomingContent.length > currentTarget.length ? incomingContent : currentTarget;
  }, []);

  const setAssistantTarget = useCallback(
    (incomingContent: string) => {
      assistantTargetRef.current = mergeIncomingContent(
        assistantTargetRef.current,
        incomingContent,
      );
      ensureTypingAnimation();
    },
    [ensureTypingAnimation, mergeIncomingContent],
  );

  const processStreamResponse = useCallback(
    async (response: Response) => {
      const reader = response.body?.getReader();
      if (!reader) {
        throw new Error("No response body");
      }

      const decoder = new TextDecoder("utf-8");
      let buffer = "";

      const handleReplyEvent = (event: TencentReplyEvent | null) => {
        const payload = event?.payload;
        if (!payload || payload.is_from_self || typeof payload.content !== "string") {
          return;
        }

        setAssistantTarget(payload.content);

        if (payload.is_final) {
          streamCompleteRef.current = true;
          ensureTypingAnimation();
        }
      };

      const handleErrorEvent = (event: TencentErrorEvent | null) => {
        const errorMessage =
          event?.error?.message ?? event?.message ?? "Unknown streaming error";
        assistantTargetRef.current = `错误: ${errorMessage}`;
        assistantDisplayedRef.current = assistantTargetRef.current;
        streamCompleteRef.current = true;
        updateAssistantMessage(assistantTargetRef.current);
        stopTypingAnimation();
      };

      const handleSseEvent = (eventName: string, data: string) => {
        const normalizedEventName =
          parseJsonSafely<{ type?: string }>(data)?.type ?? eventName;

        if (normalizedEventName === "reply") {
          handleReplyEvent(parseJsonSafely<TencentReplyEvent>(data));
          return;
        }

        if (normalizedEventName === "error") {
          handleErrorEvent(parseJsonSafely<TencentErrorEvent>(data));
        }
      };

      while (true) {
        const { done, value } = await reader.read();
        if (done) {
          buffer += decoder.decode();
        } else {
          buffer += decoder.decode(value, { stream: true });
        }

        const { events, rest } = splitSseEvents(buffer);
        buffer = rest;

        for (const event of events) {
          handleSseEvent(event.event, event.data);
        }

        if (done) {
          if (buffer.trim()) {
            const { events: tailEvents } = splitSseEvents(`${buffer}\n\n`);
            for (const event of tailEvents) {
              handleSseEvent(event.event, event.data);
            }
          }
          streamCompleteRef.current = true;
          ensureTypingAnimation();
          break;
        }
      }
    },
    [ensureTypingAnimation, setAssistantTarget, stopTypingAnimation, updateAssistantMessage],
  );

  const sendMessage = useCallback(
    async (content: string) => {
      if (!content.trim() || loading) return;

      resetStreamingState();
      const abortController = new AbortController();
      abortControllerRef.current = abortController;
      const userMessage: ChatMessage = { role: "user", content };
      const assistantMessage: ChatMessage = { role: "assistant", content: "" };
      setMessages((prev) => [...prev, userMessage, assistantMessage]);
      setInput("");
      setLoading(true);

      try {
        const response = await sendChatMessage(
          sessionId,
          content,
          "default_visitor",
          abortController.signal,
        );
        await processStreamResponse(response);
      } catch (err) {
        if (err instanceof DOMException && err.name === "AbortError") {
          return;
        }

        const errorMessage = err instanceof Error ? err.message : "请求失败";
        assistantTargetRef.current = `错误: ${errorMessage}`;
        assistantDisplayedRef.current = assistantTargetRef.current;
        streamCompleteRef.current = true;
        updateAssistantMessage(assistantTargetRef.current);
        stopTypingAnimation();
      } finally {
        if (abortControllerRef.current === abortController) {
          abortControllerRef.current = null;
        }
        setLoading(false);
      }
    },
    [loading, processStreamResponse, resetStreamingState, sessionId, stopTypingAnimation, updateAssistantMessage],
  );

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

    if (handledTriggerIdRef.current === triggerRequest.id) {
      return;
    }

    handledTriggerIdRef.current = triggerRequest.id;
    void sendMessage(triggerRequest.content);
  }, [triggerRequest, sendMessage]);

  const handleSend = useCallback(async () => {
    await sendMessage(input);
  }, [input, sendMessage]);

  const handleKeyDown = useCallback(
    (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
      if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        void handleSend();
      }
    },
    [handleSend],
  );

  return (
    <div className="flex h-full min-h-0 w-full flex-col bg-slate-50">
      <div className="flex items-center justify-between border-b border-slate-200 px-4 py-3">
        <h2 className="text-sm font-semibold text-slate-700">AI 对话</h2>
        {onClose ? (
          <button
            type="button"
            onClick={onClose}
            className="rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-sm font-medium text-slate-600 transition-colors hover:border-slate-300 hover:text-slate-900"
          >
            关闭
          </button>
        ) : null}
      </div>

      <div className="flex-1 min-h-0 space-y-3 overflow-y-auto px-4 py-4">
        {messages.length === 0 && (
          <div className="flex h-full items-center justify-center">
            <p className="text-sm text-slate-400">双击思维导图节点开始对话</p>
          </div>
        )}

        {messages.map((msg, idx) => (
          <div
            key={idx}
            className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}
          >
            <div
              className={`max-w-[85%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed ${
                msg.role === "user"
                  ? "bg-blue-600 text-white"
                  : "border border-slate-100 bg-white text-slate-700 shadow-sm"
              }`}
            >
              {msg.role === "assistant" ? (
                msg.content || (loading && idx === messages.length - 1 ? "思考中..." : "") ? (
                  <MarkdownMessage
                    content={msg.content || (loading && idx === messages.length - 1 ? "思考中..." : "")}
                  />
                ) : null
              ) : (
                <div className="whitespace-pre-wrap break-words">{msg.content}</div>
              )}
            </div>
          </div>
        ))}

        <div ref={messagesEndRef} />
      </div>

      <div className="border-t border-slate-200 px-4 py-3">
        <div className="flex items-end gap-2">
          <textarea
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={handleKeyDown}
            placeholder="输入消息..."
            rows={1}
            className="flex-1 resize-none rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm text-slate-700 placeholder-slate-400 outline-none focus:border-blue-400 focus:ring-1 focus:ring-blue-400"
          />
          <button
            onClick={() => void handleSend()}
            disabled={loading || !input.trim()}
            className="rounded-xl bg-blue-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
          >
            发送
          </button>
        </div>
      </div>
    </div>
  );
}
