import { useState, useCallback, useEffect, useRef } from "react"; import { listDirectives, getDirective, createDirective, updateDirective, archiveDirective, startDirective, pauseDirective, resumeDirective, stopDirective, getDirectiveGraph, subscribeToDirectiveEvents, type DirectiveSummary, type DirectiveWithProgress, type DirectiveGraphResponse, type DirectiveStatus, type DirectiveEvent, type CreateDirectiveRequest, type UpdateDirectiveRequest, type StartDirectiveResponse, } from "../lib/api"; interface UseDirectivesResult { directives: DirectiveSummary[]; loading: boolean; error: string | null; refresh: () => Promise; createNewDirective: (req: CreateDirectiveRequest) => Promise; updateExistingDirective: ( directiveId: string, req: UpdateDirectiveRequest ) => Promise; archiveExistingDirective: (directiveId: string) => Promise; getDirectiveById: (directiveId: string) => Promise; getGraph: (directiveId: string) => Promise; start: (directiveId: string) => Promise; pause: (directiveId: string) => Promise; resume: (directiveId: string) => Promise; stop: (directiveId: string) => Promise; } export function useDirectives(statusFilter?: DirectiveStatus): UseDirectivesResult { const [directives, setDirectives] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchDirectives = useCallback(async () => { setLoading(true); setError(null); try { const response = await listDirectives(statusFilter); setDirectives(response.directives ?? []); } catch (err) { console.error("Failed to fetch directives:", err); setError(err instanceof Error ? err.message : "Failed to fetch directives"); } finally { setLoading(false); } }, [statusFilter]); useEffect(() => { fetchDirectives(); }, [fetchDirectives]); const createNewDirective = useCallback( async (req: CreateDirectiveRequest): Promise => { try { const directive = await createDirective(req); // Refresh the list await fetchDirectives(); // Return the full directive with progress return await getDirective(directive.id); } catch (err) { console.error("Failed to create directive:", err); setError(err instanceof Error ? err.message : "Failed to create directive"); return null; } }, [fetchDirectives] ); const updateExistingDirective = useCallback( async ( directiveId: string, req: UpdateDirectiveRequest ): Promise => { try { await updateDirective(directiveId, req); // Refresh the list await fetchDirectives(); // Return the updated directive return await getDirective(directiveId); } catch (err) { console.error("Failed to update directive:", err); setError(err instanceof Error ? err.message : "Failed to update directive"); return null; } }, [fetchDirectives] ); const archiveExistingDirective = useCallback( async (directiveId: string): Promise => { try { await archiveDirective(directiveId); // Refresh the list await fetchDirectives(); return true; } catch (err) { console.error("Failed to archive directive:", err); setError(err instanceof Error ? err.message : "Failed to archive directive"); return false; } }, [fetchDirectives] ); const getDirectiveById = useCallback( async (directiveId: string): Promise => { try { return await getDirective(directiveId); } catch (err) { console.error("Failed to get directive:", err); setError(err instanceof Error ? err.message : "Failed to get directive"); return null; } }, [] ); const getGraph = useCallback( async (directiveId: string): Promise => { try { return await getDirectiveGraph(directiveId); } catch (err) { console.error("Failed to get directive graph:", err); setError(err instanceof Error ? err.message : "Failed to get directive graph"); return null; } }, [] ); const start = useCallback( async (directiveId: string): Promise => { try { const response = await startDirective(directiveId); await fetchDirectives(); return response; } catch (err) { console.error("Failed to start directive:", err); setError(err instanceof Error ? err.message : "Failed to start directive"); return null; } }, [fetchDirectives] ); const pause = useCallback( async (directiveId: string): Promise => { try { await pauseDirective(directiveId); await fetchDirectives(); return true; } catch (err) { console.error("Failed to pause directive:", err); setError(err instanceof Error ? err.message : "Failed to pause directive"); return false; } }, [fetchDirectives] ); const resume = useCallback( async (directiveId: string): Promise => { try { await resumeDirective(directiveId); await fetchDirectives(); return true; } catch (err) { console.error("Failed to resume directive:", err); setError(err instanceof Error ? err.message : "Failed to resume directive"); return false; } }, [fetchDirectives] ); const stop = useCallback( async (directiveId: string): Promise => { try { await stopDirective(directiveId); await fetchDirectives(); return true; } catch (err) { console.error("Failed to stop directive:", err); setError(err instanceof Error ? err.message : "Failed to stop directive"); return false; } }, [fetchDirectives] ); return { directives, loading, error, refresh: fetchDirectives, createNewDirective, updateExistingDirective, archiveExistingDirective, getDirectiveById, getGraph, start, pause, resume, stop, }; } /** Hook for subscribing to real-time directive events via SSE */ export function useDirectiveEventSubscription( directiveId: string | null, onEvent?: (event: DirectiveEvent) => void ): { events: DirectiveEvent[]; isConnected: boolean; error: string | null; } { const [events, setEvents] = useState([]); const [isConnected, setIsConnected] = useState(false); const [error, setError] = useState(null); const cleanupRef = useRef<(() => void) | null>(null); useEffect(() => { // Clean up any existing subscription if (cleanupRef.current) { cleanupRef.current(); cleanupRef.current = null; } if (!directiveId) { setIsConnected(false); setEvents([]); return; } // Subscribe to events let mounted = true; const setupSubscription = async () => { try { const cleanup = await subscribeToDirectiveEvents( directiveId, (event) => { if (mounted) { setEvents((prev) => [...prev, event]); onEvent?.(event); } }, (err) => { if (mounted) { setError(err.message); setIsConnected(false); } } ); if (mounted) { cleanupRef.current = cleanup; setIsConnected(true); setError(null); } else { // Component unmounted during setup, clean up immediately cleanup(); } } catch (err) { if (mounted) { setError(err instanceof Error ? err.message : "Failed to subscribe to events"); setIsConnected(false); } } }; setupSubscription(); return () => { mounted = false; if (cleanupRef.current) { cleanupRef.current(); cleanupRef.current = null; } }; }, [directiveId, onEvent]); return { events, isConnected, error }; }