import { Card, Stack } from "@mantine/core";
import { usePubSub } from "create-pubsub/react";
import {
  type KeyboardEvent,
  useCallback,
  useEffect,
  useRef,
  useState,
} from "react";
import throttle from "throttleit";
import { generateFollowUpQuestion } from "../../modules/followUpQuestions";
import {
  getCurrentSearchRunId,
  saveChatMessageForQuery,
  updateSearchResults,
} from "../../modules/history";
import { handleEnterKeyDown } from "../../modules/keyboard";
import { addLogEntry } from "../../modules/logEntries";
import {
  chatGenerationStatePubSub,
  chatInputPubSub,
  followUpQuestionPubSub,
  getSettings,
  imageSearchResultsPubSub,
  queryPubSub,
  settingsPubSub,
  suppressNextFollowUpPubSub,
  textSearchResultsPubSub,
  updateImageSearchResults,
  updateLlmTextSearchResults,
  updateTextSearchResults,
} from "../../modules/pubSub";
import { generateRelatedSearchQuery } from "../../modules/relatedSearchQuery";
import { searchImages, searchText } from "../../modules/search";
import { generateChatResponse } from "../../modules/textGeneration";
import type { ChatMessage } from "../../modules/types";
import ChatHeader from "./ChatHeader";
import ChatInputArea from "./ChatInputArea";
import MessageList from "./MessageList";

interface ChatInterfaceProps {
  initialQuery?: string;
  initialResponse?: string;
  initialMessages?: ChatMessage[];
  suppressInitialFollowUp?: boolean;
}

export default function ChatInterface({
  initialQuery,
  initialResponse,
  initialMessages,
  suppressInitialFollowUp,
}: ChatInterfaceProps) {
  const initialMessagesArray =
    initialMessages &&
    initialMessages.length > 0 &&
    initialQuery &&
    initialResponse
      ? [
          { role: "user" as const, content: initialQuery },
          { role: "assistant" as const, content: initialResponse },
          ...initialMessages,
        ]
      : initialMessages || [];

  const [messages, setMessages] = useState<ChatMessage[]>(initialMessagesArray);
  const [input, setInput] = usePubSub(chatInputPubSub);
  const [generationState, setGenerationState] = usePubSub(
    chatGenerationStatePubSub,
  );
  const [, setFollowUpQuestion] = usePubSub(followUpQuestionPubSub);
  const [textSearchResults] = usePubSub(textSearchResultsPubSub);
  const [imageSearchResults] = usePubSub(imageSearchResultsPubSub);
  const [currentQuery] = usePubSub(queryPubSub);
  const [suppressNextFollowUp] = usePubSub(suppressNextFollowUpPubSub);
  const [previousFollowUpQuestions, setPreviousFollowUpQuestions] = useState<
    string[]
  >([]);
  const [settings] = usePubSub(settingsPubSub);
  const [streamedResponse, setStreamedResponse] = useState("");
  const hasInitialized = useRef(false);
  const prevInitialMessagesRef = useRef<ChatMessage[] | undefined>(undefined);
  const updateStreamedResponse = useCallback(
    throttle((response: string) => {
      setStreamedResponse(response);
    }, 1000 / 12),
    [],
  );

  const regenerateFollowUpQuestion = useCallback(
    async (currentQuery: string, currentResponse: string) => {
      if (suppressNextFollowUp) return;
      if (!currentResponse || !currentQuery.trim()) return;

      try {
        setGenerationState({
          isGeneratingResponse: false,
          isGeneratingFollowUpQuestion: true,
        });

        const newQuestion = await generateFollowUpQuestion({
          topic: currentQuery,
          currentContent: currentResponse,
          previousQuestions: previousFollowUpQuestions,
        });

        setPreviousFollowUpQuestions((prev) =>
          [...prev, newQuestion].slice(-5),
        );
        setFollowUpQuestion(newQuestion);
        setGenerationState({
          isGeneratingResponse: false,
          isGeneratingFollowUpQuestion: false,
        });
      } catch (_) {
        setFollowUpQuestion("");
        setGenerationState({
          isGeneratingResponse: false,
          isGeneratingFollowUpQuestion: false,
        });
      }
    },
    [
      setFollowUpQuestion,
      setGenerationState,
      previousFollowUpQuestions,
      suppressNextFollowUp,
    ],
  );

  useEffect(() => {
    const messagesChanged =
      !prevInitialMessagesRef.current ||
      JSON.stringify(prevInitialMessagesRef.current) !==
        JSON.stringify(initialMessages);

    if (!messagesChanged) return;

    prevInitialMessagesRef.current = initialMessages;

    const newInitialMessagesArray =
      initialMessages &&
      initialMessages.length > 0 &&
      initialQuery &&
      initialResponse
        ? [
            { role: "user" as const, content: initialQuery },
            { role: "assistant" as const, content: initialResponse },
            ...initialMessages,
          ]
        : initialMessages || [];

    if (newInitialMessagesArray.length > 0) {
      setMessages(newInitialMessagesArray);
    } else if (initialQuery && initialResponse) {
      setMessages([
        { role: "user", content: initialQuery },
        { role: "assistant", content: initialResponse },
      ]);
    }
  }, [initialQuery, initialResponse, initialMessages]);

  useEffect(() => {
    if (suppressNextFollowUp) {
      hasInitialized.current = true;
      return;
    }
    if (suppressInitialFollowUp) return;
    if (hasInitialized.current) return;

    if (initialMessages && initialMessages.length > 0) {
      const lastAssistant = messages
        .filter((m) => m.role === "assistant")
        .pop();
      const lastUser = messages.filter((m) => m.role === "user").pop();
      if (lastUser && lastAssistant) {
        regenerateFollowUpQuestion(lastUser.content, lastAssistant.content);
        hasInitialized.current = true;
      }
    } else if (messages.length >= 2 && initialQuery && initialResponse) {
      regenerateFollowUpQuestion(initialQuery, initialResponse);
      hasInitialized.current = true;
    }
  }, [
    initialQuery,
    initialResponse,
    initialMessages,
    messages,
    regenerateFollowUpQuestion,
    suppressInitialFollowUp,
    suppressNextFollowUp,
  ]);

  useEffect(() => {
    return () => {
      setFollowUpQuestion("");
      setPreviousFollowUpQuestions([]);
    };
  }, [setFollowUpQuestion]);

  const handleEditMessage = useCallback(
    (absoluteIndex: number) => {
      const target = messages[absoluteIndex];
      if (!target || target.role !== "user") return;
      setInput(target.content);
      setMessages(messages.slice(0, absoluteIndex));
      setFollowUpQuestion("");
    },
    [messages, setInput, setFollowUpQuestion],
  );

  const handleRegenerateResponse = useCallback(async () => {
    if (
      generationState.isGeneratingResponse ||
      messages.length < 3 ||
      messages[messages.length - 1].role !== "assistant"
    )
      return;

    const history = messages.slice(0, -1);
    const lastUser = history[history.length - 1];

    setMessages(history);
    setGenerationState({ ...generationState, isGeneratingResponse: true });
    setFollowUpQuestion("");
    setStreamedResponse("");

    try {
      const finalResponse = await generateChatResponse(
        history,
        updateStreamedResponse,
      );

      setMessages((prev) => [
        ...prev,
        { role: "assistant", content: finalResponse },
      ]);

      addLogEntry("AI response re-generated");

      if (lastUser?.role === "user") {
        await regenerateFollowUpQuestion(lastUser.content, finalResponse);
      }
    } catch (error) {
      addLogEntry(`Error re-generating response: ${error}`);
    } finally {
      setGenerationState({ ...generationState, isGeneratingResponse: false });
    }
  }, [
    generationState,
    messages,
    regenerateFollowUpQuestion,
    setFollowUpQuestion,
    setGenerationState,
    updateStreamedResponse,
  ]);

  const handleSend = useCallback(
    async (textToSend?: string) => {
      const currentInput = textToSend ?? input;
      if (currentInput.trim() === "" || generationState.isGeneratingResponse)
        return;

      const userMessage: ChatMessage = { role: "user", content: currentInput };
      const newMessages: ChatMessage[] = [...messages, userMessage];

      setMessages(newMessages);
      if (!textToSend) setInput("");
      setGenerationState({
        ...generationState,
        isGeneratingResponse: true,
      });
      setFollowUpQuestion("");
      setStreamedResponse("");

      try {
        const relatedQuery = await generateRelatedSearchQuery([...newMessages]);
        const searchQuery = relatedQuery || currentInput;

        if (settings.enableTextSearch) {
          const freshResults = await searchText(
            searchQuery,
            settings.searchResultsLimit,
          );

          if (freshResults.length > 0) {
            const existingUrls = new Set(
              textSearchResults.map(([, , url]) => url),
            );

            const uniqueFreshResults = freshResults.filter(
              ([, , url]) => !existingUrls.has(url),
            );

            updateLlmTextSearchResults(
              freshResults.slice(0, getSettings().searchResultsToConsider),
            );

            if (uniqueFreshResults.length > 0) {
              const updatedResults = [
                ...textSearchResults,
                ...uniqueFreshResults,
              ];
              updateTextSearchResults(updatedResults);

              updateSearchResults(getCurrentSearchRunId(), {
                textResults: {
                  type: "text",
                  items: updatedResults.map(([title, snippet, url]) => ({
                    title,
                    url,
                    snippet,
                  })),
                },
              });
            }
          }
        }

        if (settings.enableImageSearch) {
          searchImages(searchQuery, settings.searchResultsLimit)
            .then((imageResults) => {
              if (imageResults.length > 0) {
                const existingUrls = new Set(
                  imageSearchResults.map(([, url]) => url),
                );

                const uniqueFreshResults = imageResults.filter(
                  ([, url]) => !existingUrls.has(url),
                );

                if (uniqueFreshResults.length > 0) {
                  const updatedImageResults = [
                    ...uniqueFreshResults,
                    ...imageSearchResults,
                  ];
                  updateImageSearchResults(updatedImageResults);

                  updateSearchResults(getCurrentSearchRunId(), {
                    imageResults: {
                      type: "image",
                      items: updatedImageResults.map(
                        ([title, url, thumbnailUrl, sourceUrl]) => ({
                          title,
                          url,
                          thumbnailUrl,
                          sourceUrl,
                        }),
                      ),
                    },
                  });
                }
              }
            })
            .catch((error) => {
              addLogEntry(`Error in follow-up image search: ${error}`);
            });
        }
      } catch (error) {
        addLogEntry(`Error in follow-up search: ${error}`);
      }

      try {
        const finalResponse = await generateChatResponse(
          newMessages,
          updateStreamedResponse,
        );

        setMessages((prevMessages) => [
          ...prevMessages,
          { role: "assistant", content: finalResponse },
        ]);

        addLogEntry("AI response completed");

        await saveChatMessageForQuery(currentQuery, "user", currentInput);
        await saveChatMessageForQuery(currentQuery, "assistant", finalResponse);

        await regenerateFollowUpQuestion(currentInput, finalResponse);
      } catch (error) {
        addLogEntry(`Error in chat response: ${error}`);
        setMessages((prevMessages) => [
          ...prevMessages,
          {
            role: "assistant",
            content:
              "Sorry, I encountered an error while generating a response.",
          },
        ]);
      } finally {
        setGenerationState({
          ...generationState,
          isGeneratingResponse: false,
        });
      }
    },
    [
      generationState,
      messages,
      settings,
      input,
      regenerateFollowUpQuestion,
      setFollowUpQuestion,
      setGenerationState,
      setInput,
      updateStreamedResponse,
      currentQuery,
      textSearchResults,
      imageSearchResults,
    ],
  );

  const handleKeyDown = useCallback(
    (event: KeyboardEvent<HTMLTextAreaElement>) => {
      handleEnterKeyDown(event, settings, handleSend);
    },
    [settings, handleSend],
  );

  return (
    <Card withBorder shadow="sm" radius="md">
      <Card.Section withBorder inheritPadding py="xs">
        <ChatHeader messages={messages} />
      </Card.Section>
      <Stack gap="md" pt="md">
        <MessageList
          messages={
            generationState.isGeneratingResponse
              ? [...messages, { role: "assistant", content: streamedResponse }]
              : messages
          }
          onEditMessage={handleEditMessage}
          onRegenerate={handleRegenerateResponse}
          isGenerating={generationState.isGeneratingResponse}
        />
        <ChatInputArea onKeyDown={handleKeyDown} handleSend={handleSend} />
      </Stack>
    </Card>
  );
}
