import { useState, useCallback, useEffect, useRef, useMemo } from "react"; import { ResourceReference, PromptReference, } from "@modelcontextprotocol/sdk/types.js"; interface CompletionState { completions: Record; loading: Record; } // eslint-disable-next-line @typescript-eslint/no-explicit-any function debounce PromiseLike>( func: T, wait: number, ): (...args: Parameters) => void { let timeout: ReturnType; return (...args: Parameters) => { clearTimeout(timeout); timeout = setTimeout(() => { void func(...args); }, wait); }; } export function useCompletionState( handleCompletion: ( ref: ResourceReference | PromptReference, argName: string, value: string, signal?: AbortSignal, ) => Promise, completionsSupported: boolean = true, debounceMs: number = 300, ) { const [state, setState] = useState({ completions: {}, loading: {}, }); const abortControllerRef = useRef(null); const cleanup = useCallback(() => { if (abortControllerRef.current) { abortControllerRef.current.abort(); abortControllerRef.current = null; } }, []); // Cleanup on unmount useEffect(() => { return cleanup; }, [cleanup]); const clearCompletions = useCallback(() => { cleanup(); setState({ completions: {}, loading: {}, }); }, [cleanup]); const requestCompletions = useMemo(() => { return debounce( async ( ref: ResourceReference | PromptReference, argName: string, value: string, ) => { if (!completionsSupported) { return; } cleanup(); const abortController = new AbortController(); abortControllerRef.current = abortController; setState((prev) => ({ ...prev, loading: { ...prev.loading, [argName]: true }, })); try { const values = await handleCompletion( ref, argName, value, abortController.signal, ); if (!abortController.signal.aborted) { setState((prev) => ({ ...prev, completions: { ...prev.completions, [argName]: values }, loading: { ...prev.loading, [argName]: false }, })); } } catch { if (!abortController.signal.aborted) { setState((prev) => ({ ...prev, loading: { ...prev.loading, [argName]: false }, })); } } finally { if (abortControllerRef.current === abortController) { abortControllerRef.current = null; } } }, debounceMs, ); }, [handleCompletion, completionsSupported, cleanup, debounceMs]); // Clear completions when support status changes useEffect(() => { if (!completionsSupported) { clearCompletions(); } }, [completionsSupported, clearCompletions]); return { ...state, clearCompletions, requestCompletions, completionsSupported, }; }