"use client";

import * as React from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { apiFetch } from "@/lib/api";
import { Message } from "@/components/chat/message";
import { ChatInput } from "@/components/chat/chat-input";
import { ChatHeader } from "@/components/chat/chat-header";
import { useShortcut } from "@/hooks/use-keyboard";
import { toast } from "sonner";
import type { ToolCallView } from "@/components/chat/tool-call-block";
import { ChatMessagesContext } from "@/components/chat/chat-messages-context";

export interface MessageRecord {
  id: string;
  chatId: string;
  role: "user" | "assistant" | "system" | "tool";
  content: string;
  thinking: string | null;
  toolCalls: string | null;
  toolName: string | null;
  toolCallId: string | null;
  toolMeta: string | null;
  promptTokens: number | null;
  tokens: number | null;
  totalDurationMs: number | null;
  evalDurationMs: number | null;
  createdAt: string;
}

export interface ChatRecord {
  id: string;
  title: string;
  model: string;
  contextWindow: number;
  systemPrompt: string;
  temperature: number;
  showThinking: boolean;
  keepAlive: number;
  messages: MessageRecord[];
}

interface StreamingState {
  content: string;
  thinking: string;
  startedAt: number;
  liveTokens: number;
  toolCalls: ToolCallView[];
  promptTokens: number | null;
  tokens: number | null;
  totalDurationMs: number | null;
  evalDurationMs: number | null;
}

export function ChatView({ chatId }: { chatId: string }) {
  const qc = useQueryClient();
  const chatQuery = useQuery({
    queryKey: ["chat", chatId],
    queryFn: () => apiFetch<{ chat: ChatRecord }>(`/api/chats/${chatId}`),
  });

  const [streaming, setStreaming] = React.useState<StreamingState | null>(null);
  const abortRef = React.useRef<AbortController | null>(null);
  const scrollRef = React.useRef<HTMLDivElement>(null);

  function scrollToBottom() {
    const el = scrollRef.current;
    if (!el) return;
    el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
  }

  React.useEffect(() => {
    scrollToBottom();
  }, [chatQuery.data?.chat.messages.length, streaming?.content, streaming?.toolCalls.length]);

  const stopGeneration = React.useCallback(() => {
    abortRef.current?.abort();
    abortRef.current = null;
  }, []);

  useShortcut("Escape", () => {
    if (streaming) stopGeneration();
  });

  const sendMessage = React.useCallback(
    async (text: string) => {
      const trimmed = text.trim();
      if (!trimmed) return;

      qc.setQueryData<{ chat: ChatRecord }>(["chat", chatId], (prev) => {
        if (!prev) return prev;
        return {
          chat: {
            ...prev.chat,
            messages: [
              ...prev.chat.messages,
              {
                id: `tmp-${Date.now()}`,
                chatId,
                role: "user",
                content: trimmed,
                thinking: null,
                toolCalls: null,
                toolName: null,
                toolCallId: null,
                toolMeta: null,
                promptTokens: null,
                tokens: null,
                totalDurationMs: null,
                evalDurationMs: null,
                createdAt: new Date().toISOString(),
              },
            ],
          },
        };
      });

      const startedAt = Date.now();
      setStreaming({
        content: "",
        thinking: "",
        startedAt,
        liveTokens: 0,
        toolCalls: [],
        promptTokens: null,
        tokens: null,
        totalDurationMs: null,
        evalDurationMs: null,
      });

      const ctl = new AbortController();
      abortRef.current = ctl;

      try {
        const res = await fetch("/api/chat", {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({ chatId, userMessage: trimmed }),
          signal: ctl.signal,
        });
        if (!res.ok || !res.body) throw new Error(`Stream failed: ${res.status}`);
        const reader = res.body.getReader();
        const decoder = new TextDecoder();
        let buffer = "";
        let aggContent = "";
        let aggThinking = "";
        let liveTokens = 0;

        outer: while (true) {
          const { value, done } = await reader.read();
          if (done) break;
          buffer += decoder.decode(value, { stream: true });
          let nl: number;
          while ((nl = buffer.indexOf("\n\n")) >= 0) {
            const block = buffer.slice(0, nl);
            buffer = buffer.slice(nl + 2);
            const line = block.trim();
            if (!line.startsWith("data:")) continue;
            const payload = line.slice(5).trim();
            try {
              const evt = JSON.parse(payload) as { type: string; data?: unknown };
              if (evt.type === "thinking" && typeof evt.data === "string") {
                aggThinking += evt.data;
                setStreaming((s) => (s ? { ...s, thinking: aggThinking } : s));
              } else if (evt.type === "content" && typeof evt.data === "string") {
                aggContent += evt.data;
                liveTokens += 1;
                setStreaming((s) => (s ? { ...s, content: aggContent, liveTokens } : s));
              } else if (evt.type === "tool_call_start") {
                const d = (evt.data ?? {}) as {
                  id: string;
                  name: string;
                  arguments: unknown;
                };
                setStreaming((s) =>
                  s
                    ? {
                        ...s,
                        toolCalls: [
                          ...s.toolCalls,
                          {
                            id: d.id,
                            name: d.name,
                            arguments: (d.arguments ?? {}) as Record<string, unknown> | string,
                            status: "running",
                          },
                        ],
                      }
                    : s,
                );
              } else if (evt.type === "tool_call_result") {
                const d = (evt.data ?? {}) as {
                  id: string;
                  result: string;
                  error: string | null;
                  meta: Record<string, unknown> | null;
                  durationMs?: number;
                };
                setStreaming((s) =>
                  s
                    ? {
                        ...s,
                        toolCalls: s.toolCalls.map((tc) =>
                          tc.id === d.id
                            ? {
                                ...tc,
                                status: d.error ? "error" : "done",
                                result: d.result,
                                error: d.error,
                                meta: d.meta,
                                durationMs: d.durationMs,
                              }
                            : tc,
                        ),
                      }
                    : s,
                );
                // New round about to start: reset content but keep tool calls visible
                aggContent = "";
                liveTokens = 0;
                setStreaming((s) => (s ? { ...s, content: "", liveTokens: 0 } : s));
              } else if (evt.type === "done") {
                const d = (evt.data ?? {}) as {
                  promptTokens?: number;
                  evalTokens?: number;
                  evalDurationMs?: number;
                  totalDurationMs?: number;
                };
                setStreaming((s) =>
                  s
                    ? {
                        ...s,
                        promptTokens: d.promptTokens ?? null,
                        tokens: d.evalTokens ?? null,
                        evalDurationMs: d.evalDurationMs ?? null,
                        totalDurationMs: d.totalDurationMs ?? null,
                      }
                    : s,
                );
                break outer;
              } else if (evt.type === "warning" && typeof evt.data === "string") {
                toast.warning(evt.data);
              } else if (evt.type === "error" && typeof evt.data === "string") {
                toast.error(evt.data);
                break outer;
              }
            } catch {
              /* ignore malformed */
            }
          }
        }
      } catch (e) {
        if ((e as Error).name !== "AbortError") {
          toast.error(e instanceof Error ? e.message : "Stream failed");
        }
      } finally {
        abortRef.current = null;
        await qc.invalidateQueries({ queryKey: ["chat", chatId] });
        await qc.invalidateQueries({ queryKey: ["chats"] });
        setStreaming(null);

        const fresh = qc.getQueryData<{ chat: ChatRecord }>(["chat", chatId]);
        if (fresh && fresh.chat.title === "New chat" && fresh.chat.messages.length >= 2) {
          try {
            await apiFetch(`/api/chats/${chatId}/title`, { method: "POST" });
            qc.invalidateQueries({ queryKey: ["chat", chatId] });
            qc.invalidateQueries({ queryKey: ["chats"] });
          } catch {
            /* non-fatal */
          }
        }
      }
    },
    [chatId, qc],
  );

  const sentPendingRef = React.useRef(false);
  React.useEffect(() => {
    if (sentPendingRef.current) return;
    if (!chatQuery.data) return;
    if (chatQuery.data.chat.messages.length > 0) return;
    try {
      const pending = sessionStorage.getItem(`pending-message:${chatId}`);
      if (pending) {
        sessionStorage.removeItem(`pending-message:${chatId}`);
        sentPendingRef.current = true;
        sendMessage(pending);
      }
    } catch {
      /* ignore */
    }
  }, [chatQuery.data, chatId, sendMessage]);

  if (chatQuery.isLoading) {
    return (
      <div className="flex h-full items-center justify-center">
        <div className="spinner h-6 w-6" />
      </div>
    );
  }
  if (chatQuery.isError || !chatQuery.data) {
    return (
      <div className="flex h-full items-center justify-center text-sm text-muted-foreground">
        Chat not found.
      </div>
    );
  }

  const chat = chatQuery.data.chat;

  return (
    <div className="flex h-full flex-col">
      <ChatHeader chat={chat} />

      <ChatMessagesContext.Provider value={chat.messages}>
      <div ref={scrollRef} className="scroll-area flex-1 overflow-y-auto">
        <div className="mx-auto w-full max-w-3xl px-6 py-8">
          {chat.messages.length === 0 && !streaming && (
            <div className="py-16 text-center text-sm text-muted-foreground">
              Start the conversation below.
            </div>
          )}
          {chat.messages.map((m, idx) => (
            <Message
              key={m.id}
              message={m}
              showThinking={chat.showThinking}
              onRegenerate={
                m.role === "user" && idx === chat.messages.length - 1
                  ? () => sendMessage(m.content)
                  : undefined
              }
            />
          ))}
          {streaming && (
            <Message
              streaming
              showThinking={chat.showThinking}
              streamingStartedAt={streaming.startedAt}
              streamingLiveTokens={streaming.liveTokens}
              streamingToolCalls={streaming.toolCalls}
              message={{
                id: "streaming",
                chatId,
                role: "assistant",
                content: streaming.content,
                thinking: streaming.thinking || null,
                toolCalls: null,
                toolName: null,
                toolCallId: null,
                toolMeta: null,
                promptTokens: streaming.promptTokens,
                tokens: streaming.tokens,
                totalDurationMs: streaming.totalDurationMs,
                evalDurationMs: streaming.evalDurationMs,
                createdAt: new Date(streaming.startedAt).toISOString(),
              }}
            />
          )}
        </div>
      </div>

      </ChatMessagesContext.Provider>

      <ChatInput
        onSend={sendMessage}
        onStop={stopGeneration}
        isStreaming={!!streaming}
      />
    </div>
  );
}
