/**
 * useChat hook
 * Manages chat state, LLM interactions, and conversation persistence
 */

import { useState, useCallback, useRef } from "react";
import { streamChatMessage, type ChatMessage as ApiChatMessage } from "@/lib/api/chat";
import { createConversation, updateConversation, getIdea, type Idea } from "@/lib/api/ideas";
import { safeJsonParse, isMessageArray } from "@/lib/utils/safe-json";

export interface Message {
  id: string;
  role: "user" | "assistant" | "system";
  content: string;
  thinking?: string;
  createdAt: string;
  model?: string;
  provider?: string;
  promptTokens?: number;
  completionTokens?: number;
  totalTokens?: number;
}

export interface UseChatOptions {
  model?: string;
  temperature?: number;
  maxTokens?: number;
  systemPrompt?: string;
  projectId?: string | null;
  agent?: string;
  onError?: (error: Error) => void;
}

export interface UseChatReturn {
  messages: Message[];
  isLoading: boolean;
  isStreaming: boolean;
  error: string | null;
  conversationId: string | null;
  conversationTitle: string | null;
  sendMessage: (content: string) => Promise<void>;
  abortStream: () => void;
  loadConversation: (ideaId: string) => Promise<void>;
  startNewConversation: (projectId?: string | null) => void;
  setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
  clearError: () => void;
}

const DEFAULT_MODEL = "llama3.2";
const WELCOME_MESSAGE: Message = {
  id: "welcome",
  role: "assistant",
  content: "Hello! I'm your AI assistant. How can I help you today?",
  createdAt: new Date().toISOString(),
};

/**
 * Hook for managing chat conversations
 */
export function useChat(options: UseChatOptions = {}): UseChatReturn {
  const {
    model = DEFAULT_MODEL,
    temperature,
    maxTokens,
    systemPrompt,
    projectId,
    agent,
    onError,
  } = options;

  const [messages, setMessages] = useState<Message[]>([WELCOME_MESSAGE]);
  const [isLoading, setIsLoading] = useState(false);
  const [isStreaming, setIsStreaming] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [conversationId, setConversationId] = useState<string | null>(null);
  const [conversationTitle, setConversationTitle] = useState<string | null>(null);

  // Track project ID in ref to prevent stale closures
  const projectIdRef = useRef<string | null>(projectId ?? null);
  projectIdRef.current = projectId ?? null;

  // Track agent in ref to prevent stale closures
  const agentRef = useRef<string | undefined>(agent);
  agentRef.current = agent;

  // Track messages in ref to prevent stale closures during rapid sends
  const messagesRef = useRef<Message[]>(messages);
  messagesRef.current = messages;

  // AbortController ref for the active stream
  const abortControllerRef = useRef<AbortController | null>(null);

  // Track conversation state in refs to avoid stale closures in streaming callbacks
  const conversationIdRef = useRef<string | null>(conversationId);
  conversationIdRef.current = conversationId;

  const conversationTitleRef = useRef<string | null>(conversationTitle);
  conversationTitleRef.current = conversationTitle;

  /**
   * Convert our Message format to API ChatMessage format
   */
  const convertToApiMessages = useCallback((msgs: Message[]): ApiChatMessage[] => {
    return msgs
      .filter((msg) => msg.role !== "system" || msg.id !== "welcome")
      .map((msg) => ({
        role: msg.role,
        content: msg.content,
      }));
  }, []);

  /**
   * Generate a conversation title from the first user message
   */
  const generateTitle = useCallback((firstMessage: string): string => {
    const maxLength = 60;
    const trimmed = firstMessage.trim();

    if (trimmed.length <= maxLength) {
      return trimmed;
    }

    return trimmed.substring(0, maxLength - 3) + "...";
  }, []);

  /**
   * Serialize messages to JSON for storage
   */
  const serializeMessages = useCallback((msgs: Message[]): string => {
    return JSON.stringify(msgs, null, 2);
  }, []);

  /**
   * Deserialize messages from JSON with runtime type validation
   */
  const deserializeMessages = useCallback((json: string): Message[] => {
    return safeJsonParse(json, isMessageArray, [WELCOME_MESSAGE]);
  }, []);

  /**
   * Save conversation to backend.
   * Uses refs for conversation state to avoid stale closures in streaming callbacks.
   */
  const saveConversation = useCallback(
    async (msgs: Message[], title: string): Promise<string> => {
      const content = serializeMessages(msgs);
      const currentConvId = conversationIdRef.current;

      if (currentConvId) {
        await updateConversation(currentConvId, content, title);
        return currentConvId;
      } else {
        const idea = await createConversation(title, content, projectIdRef.current ?? undefined);
        setConversationId(idea.id);
        setConversationTitle(title);
        conversationIdRef.current = idea.id;
        conversationTitleRef.current = title;
        return idea.id;
      }
    },
    [serializeMessages]
  );

  /**
   * Abort an active stream
   */
  const abortStream = useCallback((): void => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
      abortControllerRef.current = null;
    }
    setIsStreaming(false);
    setIsLoading(false);
  }, []);

  /**
   * Send a message to the LLM using streaming, with fallback to non-streaming
   */
  const sendMessage = useCallback(
    async (content: string): Promise<void> => {
      if (!content.trim() || isLoading || isStreaming) {
        return;
      }

      const userMessage: Message = {
        id: `user-${Date.now().toString()}-${Math.random().toString(36).slice(2, 8)}`,
        role: "user",
        content: content.trim(),
        createdAt: new Date().toISOString(),
      };

      setMessages((prev) => {
        const updated = [...prev, userMessage];
        messagesRef.current = updated;
        return updated;
      });
      setIsLoading(true);
      setError(null);

      const assistantMessageId = `assistant-${Date.now().toString()}-${Math.random().toString(36).slice(2, 8)}`;
      const placeholderMessage: Message = {
        id: assistantMessageId,
        role: "assistant",
        content: "",
        createdAt: new Date().toISOString(),
        model,
      };

      const currentMessages = messagesRef.current;
      const apiMessages = convertToApiMessages(currentMessages);

      const request = {
        model,
        messages: apiMessages,
        ...(temperature !== undefined && { temperature }),
        ...(maxTokens !== undefined && { maxTokens }),
        ...(systemPrompt !== undefined && { systemPrompt }),
        ...(agentRef.current && { agent: agentRef.current }),
      };

      const controller = new AbortController();
      abortControllerRef.current = controller;

      try {
        await new Promise<void>((resolve, reject) => {
          let hasReceivedData = false;

          streamChatMessage(
            request,
            (chunk: string) => {
              if (!hasReceivedData) {
                hasReceivedData = true;
                setIsLoading(false);
                setIsStreaming(true);
                setMessages((prev) => {
                  const updated = [...prev, { ...placeholderMessage }];
                  messagesRef.current = updated;
                  return updated;
                });
              }

              setMessages((prev) => {
                const updated = prev.map((msg) =>
                  msg.id === assistantMessageId ? { ...msg, content: msg.content + chunk } : msg
                );
                messagesRef.current = updated;
                return updated;
              });
            },
            () => {
              setIsStreaming(false);
              abortControllerRef.current = null;
              resolve();
            },
            (err: Error) => {
              reject(err);
            },
            controller.signal
          );
        });
      } catch (err: unknown) {
        if (controller.signal.aborted) {
          setIsStreaming(false);
          setIsLoading(false);
          abortControllerRef.current = null;

          // Remove placeholder if no content was received
          setMessages((prev) => {
            const assistantMsg = prev.find((m) => m.id === assistantMessageId);
            if (assistantMsg?.content === "") {
              const updated = prev.filter((m) => m.id !== assistantMessageId);
              messagesRef.current = updated;
              return updated;
            }
            messagesRef.current = prev;
            return prev;
          });
          return;
        }

        // Streaming failed — show error (no guest fallback, auth required)
        console.warn("Streaming failed", {
          error: err instanceof Error ? err : new Error(String(err)),
        });

        setMessages((prev) => {
          const withoutPlaceholder = prev.filter((m) => m.id !== assistantMessageId);
          messagesRef.current = withoutPlaceholder;
          return withoutPlaceholder;
        });
        setIsStreaming(false);
        setIsLoading(false);

        const errorMsg = err instanceof Error ? err.message : "Chat unavailable";
        setError(`Chat error: ${errorMsg}`);
        return;
      }

      setIsLoading(false);

      const finalMessages = messagesRef.current;

      const isFirstMessage =
        !conversationIdRef.current && finalMessages.filter((m) => m.role === "user").length === 1;
      const title = isFirstMessage
        ? generateTitle(content)
        : (conversationTitleRef.current ?? "Chat Conversation");

      try {
        await saveConversation(finalMessages, title);
      } catch (saveErr) {
        const saveErrorMsg =
          saveErr instanceof Error ? saveErr.message : "Unknown persistence error";
        setError("Message sent but failed to save. Please try again.");
        onError?.(saveErr instanceof Error ? saveErr : new Error(saveErrorMsg));
        console.error("Failed to save conversation", {
          error: saveErr,
          errorType: "PERSISTENCE_ERROR",
          conversationId: conversationIdRef.current,
          detail: saveErrorMsg,
        });
      }
    },
    [
      isLoading,
      isStreaming,
      model,
      temperature,
      maxTokens,
      systemPrompt,
      onError,
      convertToApiMessages,
      generateTitle,
      saveConversation,
    ]
  );

  /**
   * Load an existing conversation from the backend
   */
  const loadConversation = useCallback(
    async (ideaId: string): Promise<void> => {
      try {
        setIsLoading(true);
        setError(null);

        const idea: Idea = await getIdea(ideaId);
        const msgs = deserializeMessages(idea.content);

        setMessages(msgs);
        setConversationId(idea.id);
        setConversationTitle(idea.title ?? null);
        conversationIdRef.current = idea.id;
        conversationTitleRef.current = idea.title ?? null;
      } catch (err) {
        const errorMsg = err instanceof Error ? err.message : "Failed to load conversation";
        setError("Unable to load conversation. Please try again.");
        onError?.(err instanceof Error ? err : new Error(errorMsg));
        console.error("Failed to load conversation", {
          error: err,
          errorType: "LOAD_ERROR",
          ideaId,
          timestamp: new Date().toISOString(),
        });
      } finally {
        setIsLoading(false);
      }
    },
    [deserializeMessages, onError]
  );

  /**
   * Start a new conversation
   */
  const startNewConversation = useCallback((newProjectId?: string | null): void => {
    setMessages([WELCOME_MESSAGE]);
    setConversationId(null);
    setConversationTitle(null);
    setError(null);
    conversationIdRef.current = null;
    conversationTitleRef.current = null;
    projectIdRef.current = newProjectId ?? null;
  }, []);

  /**
   * Clear error message
   */
  const clearError = useCallback((): void => {
    setError(null);
  }, []);

  return {
    messages,
    isLoading,
    isStreaming,
    error,
    conversationId,
    conversationTitle,
    sendMessage,
    abortStream,
    loadConversation,
    startNewConversation,
    setMessages,
    clearError,
  };
}
