"use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback, useEffect, useRef, useState } from "react"; import { buildDisplayMessages, createPendingStateOnError, createPendingStateOnSend, type MentorChatMessage, type MentorPendingState, shouldBlockSend, } from "@/lib/mentor-chat-utils"; const DELETE_CONFIRM_MESSAGE = "Delete this chat? Your saved AI memories will not be affected."; type ChatSessionData = { session: { id: string; title: string }; messages: MentorChatMessage[]; }; type SendChatResponse = { memorySuggestions?: unknown[]; memoryAction?: { type: string; intent?: string; status?: string; error?: string; }; }; export function useMentorChat(sessionId: string | null) { const queryClient = useQueryClient(); const bottomRef = useRef(null); const [input, setInput] = useState(""); const [pendingState, setPendingState] = useState(null); const [deleteError, setDeleteError] = useState(null); const { data: chatData } = useQuery({ queryKey: ["chat-session", sessionId], queryFn: async () => { const res = await fetch(`/api/ai/chat/sessions/${sessionId}`); if (!res.ok) throw new Error("Failed to load chat"); return res.json(); }, enabled: !!sessionId, }); const serverMessages = chatData?.messages ?? []; const displayMessages = buildDisplayMessages(serverMessages, pendingState); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: "smooth" }); }, [displayMessages.length, pendingState]); const send = useMutation({ mutationFn: async (content: string) => { const res = await fetch(`/api/ai/chat/sessions/${sessionId}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ content }), }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error ?? "Send failed"); } return res.json() as Promise; }, onMutate: (content) => { setInput(""); setPendingState(createPendingStateOnSend(content)); }, onSuccess: (data) => { setPendingState(null); queryClient.invalidateQueries({ queryKey: ["chat-session", sessionId] }); queryClient.invalidateQueries({ queryKey: ["chat-sessions"] }); queryClient.invalidateQueries({ queryKey: ["ai-memory-suggestions"] }); queryClient.invalidateQueries({ queryKey: ["ai-knows"] }); if ( data.memoryAction?.type === "memory_created" || data.memoryAction?.type === "memory_suggestion_accepted" ) { queryClient.invalidateQueries({ queryKey: ["ai-memory"] }); } }, onError: (_err, content) => { setPendingState(createPendingStateOnError(content)); }, }); const archive = useMutation({ mutationFn: async () => { const res = await fetch(`/api/ai/chat/sessions/${sessionId}`, { method: "DELETE" }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error ?? "Delete failed"); } return res.json(); }, onMutate: () => { setDeleteError(null); }, onSuccess: () => { setPendingState(null); setInput(""); queryClient.invalidateQueries({ queryKey: ["chat-sessions"] }); }, onError: (err: Error) => { setDeleteError(err.message || "Could not delete chat. Please try again."); }, }); const sendMessage = useCallback( (content: string) => { if (!sessionId || shouldBlockSend(send.isPending, content)) return; send.mutate(content.trim()); }, [sessionId, send] ); const retry = useCallback(() => { const content = pendingState?.lastFailedContent; if (!content || send.isPending) return; setPendingState(null); send.mutate(content); }, [pendingState?.lastFailedContent, send]); const deleteChat = useCallback(() => { if (!sessionId || archive.isPending) return; if (!confirm(DELETE_CONFIRM_MESSAGE)) return; archive.mutate(); }, [sessionId, archive]); const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { if (e.key === "Enter" && input.trim()) { e.preventDefault(); sendMessage(input); } }, [input, sendMessage] ); return { input, setInput, displayMessages, sendMessage, retry, deleteChat, handleKeyDown, bottomRef, isSendPending: send.isPending, isDeletePending: archive.isPending, canRetry: !!pendingState?.lastFailedContent, deleteError, archiveSuccess: archive.isSuccess, resetArchiveSuccess: archive.reset, }; } export async function createChatSession(title?: string): Promise<{ id: string }> { const res = await fetch("/api/ai/chat/sessions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(title ? { title } : {}), }); if (!res.ok) throw new Error("Failed to create session"); return res.json(); } export async function fetchChatSessions(): Promise<{ sessions: { id: string; title: string }[] }> { const res = await fetch("/api/ai/chat/sessions"); if (!res.ok) throw new Error("Failed to load sessions"); return res.json(); }