import { Button, Card, Slider } from "@theme";
import { useChatSettings } from "@utils/context/chatSettings/useChatSettings.ts";
import { formatBytes } from "@utils/format.ts";
import { CONVERSATION_STARTERS, MODELS } from "@utils/models.ts";
import { TOOLS } from "@utils/tools.ts";
import { RotateCcw, Settings } from "lucide-react";
import {
  useEffect,
  useMemo,
  useRef,
  useState,
  useSyncExternalStore,
} from "react";

import TextGeneration from "../textGeneration/TextGeneration.ts";
import type {
  ChatMessageAssistant,
  ChatMessageUser,
} from "../textGeneration/types.ts";
import cn from "../utils/classnames.ts";
import ChatForm from "./ChatForm.tsx";
import Message from "./Message.tsx";

enum State {
  IDLE,
  INITIALIZING,
  READY,
  GENERATING,
}

export default function Chat({ className = "" }: { className?: string }) {
  const { openSettingsModal, settings, downloadedModels } = useChatSettings();
  const initializedModelKey = useRef<string>(null);
  const [downloadProgress, setDownloadProgress] = useState<number>(0);
  const [state, setState] = useState<State>(State.IDLE);
  const settingsKey = useRef<string>(null);
  const [starterCategory, setStarterCategory] = useState<number>(0);
  const messagesEndRef = useRef<HTMLDivElement>(null);

  const generator = useMemo(() => new TextGeneration(), []);
  const getSnapshot = () => generator.chatMessages;
  const messages = useSyncExternalStore(
    generator.onChatMessageUpdate,
    getSnapshot
  );

  useEffect(() => {
    const newSettingsKey = JSON.stringify(settings);
    if (!settings || settingsKey.current === newSettingsKey) return;
    settingsKey.current = newSettingsKey;
    initializeConversation();
  }, [settings]);

  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
  }, [messages]);

  if (!settings) return;

  const modelDownloaded = downloadedModels.includes(settings.modelKey);

  const initializeConversation = () =>
    generator.initializeConversation(
      TOOLS.filter((tool) => settings.tools.includes(tool.name)),
      settings.temperature,
      settings.enableThinking,
      settings.systemPrompt
    );

  const initializeModel = async () => {
    setState(State.INITIALIZING);
    setDownloadProgress(0);
    await generator.initializeModel(settings.modelKey, (percentage) =>
      setDownloadProgress(percentage)
    );
    initializedModelKey.current = settings.modelKey;
    setState(State.READY);
  };

  const generate = async (prompt: string) => {
    if (initializedModelKey.current !== settings.modelKey) {
      await initializeModel();
    }
    setState(State.GENERATING);
    await generator.runAgent(prompt);

    setState(State.READY);
  };

  const model = MODELS[settings.modelKey];
  const ready: boolean = state === State.READY || modelDownloaded;

  const conversationMessages = messages.filter(({ role }) => role !== "system");

  return (
    <>
      <div
        className={cn(
          className,
          "mx-auto min-h-screen w-full max-w-4xl px-4 pt-24 pb-36"
        )}
      >
        {state === State.IDLE && !modelDownloaded ? (
          <Card className="mt-26 flex max-w-[500px] flex-col gap-4 self-center justify-self-center">
            <p>
              You are about to load <b>{model.title}</b>.<br />
              Once downloaded, the model ({formatBytes(model.size)}) will be
              cached and reused when you revisit the page.
            </p>
            <p>
              Everything runs directly in your browser using 🤗 Transformers.js
              and ONNX Runtime Web, meaning your conversations aren't sent to a
              server. You can even disconnect from the internet after the model
              has loaded!
            </p>
            <Button onClick={initializeModel}>
              Download Model ({formatBytes(model.size)})
            </Button>
          </Card>
        ) : state === State.INITIALIZING ? (
          <div className="mt-46 flex h-full items-center justify-center">
            <div className="flex h-full w-full max-w-[400px] flex-col items-center justify-center gap-2">
              <p className="flex w-full items-center justify-between">
                <span>
                  {modelDownloaded
                    ? "initializing the model..."
                    : "downloading the model..."}
                </span>
                <span>{downloadProgress.toFixed(2)}%</span>
              </p>
              <Slider width={Math.round(downloadProgress)} />
            </div>
          </div>
        ) : conversationMessages.length === 0 ? (
          <div className="mt-26 flex h-full items-center justify-center">
            <div className="flex flex-col gap-8">
              <h2 className="text-2xl font-bold">How can I help you?</h2>
              <div className="flex gap-4">
                {CONVERSATION_STARTERS.map((starter, index) => (
                  <Button
                    iconLeft={starter.icon}
                    key={index}
                    onClick={() => setStarterCategory(index)}
                    variant={index === starterCategory ? "solid" : "outline"}
                  >
                    {starter.category}
                  </Button>
                ))}
              </div>
              <div className="gap- flex flex-col border-t border-gray-300 dark:border-gray-700">
                {CONVERSATION_STARTERS[starterCategory].prompts.map(
                  (prompt, index) => (
                    <div
                      key={prompt}
                      className="border-b border-gray-300 py-1 dark:border-gray-700"
                    >
                      <Button
                        key={index}
                        onClick={() => generate(prompt)}
                        variant="ghost"
                        color="mono"
                        className="justify-start"
                      >
                        {prompt}
                      </Button>
                    </div>
                  )
                )}
              </div>
            </div>
          </div>
        ) : (
          <div className="flex flex-col gap-4">
            {conversationMessages.map((message, index) => (
              <div
                className={cn("w-3/4", {
                  "self-end": message.role === "user",
                })}
                key={index}
              >
                {message.role === "user" ? (
                  <Card className="mt-2 mb-12">
                    <p>{(message as ChatMessageUser).content}</p>
                  </Card>
                ) : message.role === "assistant" ? (
                  <Message message={message as ChatMessageAssistant} />
                ) : null}
              </div>
            ))}
            <div ref={messagesEndRef} />
          </div>
        )}
      </div>
      <div className="fixed right-0 bottom-6 left-0 mx-auto w-full max-w-4xl space-y-2">
        <Card className="mt-auto">
          <ChatForm
            disabled={!ready}
            onSubmit={(prompt) => generate(prompt)}
            isGenerating={state === State.GENERATING}
            onAbort={() => generator.abort()}
          />
        </Card>
        <div className="flex justify-between dark:border-gray-700">
          <Button
            iconLeft={<Settings />}
            size="xs"
            variant="ghost"
            color="mono"
            onClick={openSettingsModal}
          >
            {settings?.modelKey ? MODELS[settings.modelKey].title : ""}
            <span className="mx-2 text-gray-400 dark:text-gray-500">•</span>
            {settings?.tools.length}/{TOOLS.length} tool
            {settings?.tools.length !== 1 ? "s" : ""} active
          </Button>
          <Button
            iconLeft={<RotateCcw />}
            size="xs"
            variant="ghost"
            color="mono"
            onClick={initializeConversation}
          >
            new conversation
          </Button>
        </div>
      </div>
    </>
  );
}
