"use client";

import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { toast } from "sonner";

import { cn } from "@turbostarter/ui";
import { Button } from "@turbostarter/ui-web/button";
import { Icons } from "@turbostarter/ui-web/icons";
import { ScrollArea } from "@turbostarter/ui-web/scroll-area";

import { api } from "~/lib/api/client";
import { MemoizedMarkdown } from "~/modules/common/markdown/memoized-markdown";
import { Prose } from "~/modules/common/prose";

import type { DiagramType } from "~/modules/diagram/types/graph";

interface CopilotPanelProps {
  diagramId: string;
  diagramType: DiagramType;
}

export function CopilotPanel({ diagramId, diagramType }: CopilotPanelProps) {
  const chatId = useMemo(() => `copilot-${diagramId}`, [diagramId]);
  const [input, setInput] = useState("");
  const scrollRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLTextAreaElement>(null);
  const userScrolledRef = useRef(false);

  // Fetch existing chat history on mount (H1 fix)
  const { data: initialMessages } = useQuery({
    queryKey: ["copilot", "messages", chatId],
    queryFn: async () => {
      const res = await api.ai.copilot.messages.$get({
        query: { chatId },
      });
      if (!res.ok) return [];
      return res.json();
    },
    staleTime: Infinity,
  });

  // Memoize transport to avoid recreation on every render (M2 fix)
  const transport = useMemo(
    () =>
      new DefaultChatTransport({
        api: api.ai.copilot.$url().toString(),
        prepareSendMessagesRequest: ({ messages, id }) => {
          const lastMessage = messages.at(-1);
          return {
            body: {
              ...lastMessage,
              chatId: id,
              diagramId,
              diagramType,
            },
          };
        },
      }),
    [diagramId, diagramType],
  );

  const { messages, sendMessage, status, error, stop, setMessages } = useChat({
    id: chatId,
    transport,
    onError: (err) => {
      console.error("[copilot]", err);
      toast.error("Failed to get AI response");
    },
  });

  // Seed chat with persisted history once loaded
  useEffect(() => {
    if (initialMessages && initialMessages.length > 0 && messages.length === 0) {
      setMessages(
        initialMessages.map((m) => ({
          ...m,
          createdAt: new Date(),
        })),
      );
    }
  }, [initialMessages, messages.length, setMessages]);

  const isSubmitting = status === "submitted" || status === "streaming";

  // Auto-scroll on new content, but pause if user scrolled up
  useEffect(() => {
    if (userScrolledRef.current) return;
    const viewport = scrollRef.current?.querySelector(
      "[data-radix-scroll-area-viewport]",
    );
    if (viewport) {
      viewport.scrollTop = viewport.scrollHeight;
    }
  }, [messages]);

  // Detect user scroll
  useEffect(() => {
    const viewport = scrollRef.current?.querySelector(
      "[data-radix-scroll-area-viewport]",
    );
    if (!viewport) return;

    let timeoutId: NodeJS.Timeout;
    const handleScroll = () => {
      const isAtBottom =
        Math.abs(
          viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight,
        ) < 80;
      userScrolledRef.current = !isAtBottom;
      clearTimeout(timeoutId);
      timeoutId = setTimeout(() => {
        userScrolledRef.current = false;
      }, 2000);
    };

    viewport.addEventListener("scroll", handleScroll);
    return () => {
      viewport.removeEventListener("scroll", handleScroll);
      clearTimeout(timeoutId);
    };
  }, []);

  const handleSend = useCallback(() => {
    const text = input.trim();
    if (!text || isSubmitting) return;

    void sendMessage({
      text,
      metadata: {},
    });
    setInput("");
    userScrolledRef.current = false;
  }, [input, isSubmitting, sendMessage]);

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

  return (
    <div className="flex h-full flex-col">
      {/* Messages area */}
      <ScrollArea ref={scrollRef} className="flex-1">
        <div className="flex flex-col gap-1 p-3">
          {messages.length === 0 && (
            <EmptyState />
          )}

          {messages.map((message) => (
            <div
              key={message.id}
              className={cn(
                "flex flex-col gap-1",
                message.role === "user" ? "items-end" : "items-start",
              )}
            >
              {message.role === "user" ? (
                <UserBubble message={message} />
              ) : (
                <AssistantBubble
                  message={message}
                  isStreaming={
                    status === "streaming" &&
                    message.id === messages.at(-1)?.id
                  }
                />
              )}
            </div>
          ))}

          {/* Typing indicator */}
          {status === "submitted" && (
            <div className="flex items-start gap-2 py-2">
              <div className="flex gap-1">
                <span className="size-1.5 rounded-full bg-muted-foreground/50 animate-bounce [animation-delay:0ms]" />
                <span className="size-1.5 rounded-full bg-muted-foreground/50 animate-bounce [animation-delay:150ms]" />
                <span className="size-1.5 rounded-full bg-muted-foreground/50 animate-bounce [animation-delay:300ms]" />
              </div>
            </div>
          )}

          {/* Error display */}
          {error && (
            <div className="rounded-lg bg-destructive/10 px-3 py-2 text-xs text-destructive">
              Something went wrong. Please try again.
            </div>
          )}
        </div>
      </ScrollArea>

      {/* Input area */}
      <div className="shrink-0 border-t border-border p-3">
        <div className="relative">
          <textarea
            ref={inputRef}
            value={input}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={handleKeyDown}
            placeholder="Ask about your diagram..."
            rows={1}
            className="w-full resize-none rounded-lg border border-border bg-background px-3 py-2 pr-20 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
          />
          <div className="absolute right-1.5 bottom-1.5 flex items-center gap-1">
            {/* Mic button placeholder (Epic 5) */}
            <Button
              size="icon"
              variant="ghost"
              className="size-7"
              disabled
              title="Voice input (coming soon)"
            >
              <Icons.Mic className="size-3.5 text-muted-foreground" />
            </Button>

            {/* Send / Stop button */}
            {isSubmitting ? (
              <Button
                size="icon"
                variant="ghost"
                className="size-7"
                onClick={() => stop()}
              >
                <Icons.Square className="size-3 fill-current" />
              </Button>
            ) : (
              <Button
                size="icon"
                variant="ghost"
                className="size-7"
                disabled={!input.trim()}
                onClick={handleSend}
              >
                <Icons.ArrowUp className="size-3.5" />
              </Button>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

function EmptyState() {
  return (
    <div className="flex flex-col items-center justify-center py-12 text-center">
      <Icons.Sparkles className="mb-3 size-8 text-muted-foreground/30" />
      <p className="text-sm font-medium text-muted-foreground">AI Copilot</p>
      <p className="mt-1 text-xs text-muted-foreground/60">
        Ask questions about your diagram or describe what you want to build
      </p>
    </div>
  );
}

const UserBubble = memo<{ message: { id: string; parts: Array<{ type: string; text?: string }> } }>(
  ({ message }) => (
    <div className="max-w-[85%] rounded-2xl rounded-br-sm bg-primary px-3 py-2 text-primary-foreground">
      {message.parts.map((part, i) =>
        part.type === "text" ? (
          <p key={`${message.id}-${i}`} className="text-sm whitespace-pre-wrap">
            {part.text}
          </p>
        ) : null,
      )}
    </div>
  ),
);
UserBubble.displayName = "UserBubble";

const AssistantBubble = memo<{
  message: { id: string; parts: Array<{ type: string; text?: string }> };
  isStreaming: boolean;
}>(({ message, isStreaming }) => (
  <div className="max-w-[95%]">
    <Prose className="text-sm">
      {message.parts.map((part, i) =>
        part.type === "text" && part.text ? (
          <MemoizedMarkdown
            key={`${message.id}-${i}`}
            content={part.text}
            id={`copilot-${message.id}-${i}`}
          />
        ) : null,
      )}
      {isStreaming && message.parts.length === 0 && (
        <span className="inline-block size-2 animate-pulse rounded-full bg-muted-foreground/50" />
      )}
    </Prose>
  </div>
));
AssistantBubble.displayName = "AssistantBubble";
