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<void>;
createNewDirective: (req: CreateDirectiveRequest) => Promise<DirectiveWithProgress | null>;
updateExistingDirective: (
directiveId: string,
req: UpdateDirectiveRequest
) => Promise<DirectiveWithProgress | null>;
archiveExistingDirective: (directiveId: string) => Promise<boolean>;
getDirectiveById: (directiveId: string) => Promise<DirectiveWithProgress | null>;
getGraph: (directiveId: string) => Promise<DirectiveGraphResponse | null>;
start: (directiveId: string) => Promise<StartDirectiveResponse | null>;
pause: (directiveId: string) => Promise<boolean>;
resume: (directiveId: string) => Promise<boolean>;
stop: (directiveId: string) => Promise<boolean>;
}
export function useDirectives(statusFilter?: DirectiveStatus): UseDirectivesResult {
const [directives, setDirectives] = useState<DirectiveSummary[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<DirectiveWithProgress | null> => {
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<DirectiveWithProgress | null> => {
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<boolean> => {
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<DirectiveWithProgress | null> => {
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<DirectiveGraphResponse | null> => {
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<StartDirectiveResponse | null> => {
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<boolean> => {
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<boolean> => {
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<boolean> => {
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<DirectiveEvent[]>([]);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState<string | null>(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 };
}