import { useCallback, useState, useEffect } from 'react'; import { useEntity } from './useEntity'; import { haConnection } from '@/services/homeAssistant'; import { useSettingsStore } from '@/stores/settingsStore'; export interface TodoItem { uid: string; summary: string; status: 'needs_action' | 'completed'; due?: string; description?: string; } export function useTodo() { const todoEntityId = useSettingsStore((state) => state.config.todoList); const entity = useEntity(todoEntityId || ''); const [items, setItems] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const fetchItems = useCallback(async () => { if (!todoEntityId) return; setIsLoading(true); setError(null); try { const connection = haConnection.getConnection(); if (!connection) { throw new Error('Not connected to Home Assistant'); } // Use the todo.get_items service const result = await connection.sendMessagePromise>({ type: 'call_service', domain: 'todo', service: 'get_items', target: { entity_id: todoEntityId }, return_response: true, }); // Extract items from response - handle different structures let entityItems: TodoItem[] = []; // Structure 1: { response: { "todo.entity": { items: [...] } } } const respWrapper = result?.response as Record | undefined; if (respWrapper?.[todoEntityId]?.items) { entityItems = respWrapper[todoEntityId].items; } // Structure 2: { "todo.entity": { items: [...] } } else if ((result?.[todoEntityId] as { items?: TodoItem[] })?.items) { entityItems = (result[todoEntityId] as { items: TodoItem[] }).items; } setItems(entityItems); } catch (err) { const message = err instanceof Error ? err.message : 'Failed to fetch todo items'; setError(message); console.error('Failed to fetch todo items:', err); } finally { setIsLoading(false); } }, [todoEntityId]); // Fetch items when entity changes and poll every 30 seconds useEffect(() => { if (entity) { fetchItems(); // Poll every 30 seconds for updates const interval = setInterval(fetchItems, 30000); return () => clearInterval(interval); } }, [entity, fetchItems]); const addItem = useCallback(async (summary: string) => { if (!todoEntityId) return; setIsLoading(true); setError(null); try { await haConnection.callService('todo', 'add_item', { item: summary }, { entity_id: todoEntityId }); // Refresh items await fetchItems(); } catch (err) { const message = err instanceof Error ? err.message : 'Failed to add todo item'; setError(message); throw err; } finally { setIsLoading(false); } }, [todoEntityId, fetchItems]); const completeItem = useCallback(async (uid: string) => { if (!todoEntityId) return; setIsLoading(true); setError(null); try { // Find the item to get its summary const item = items.find((i) => i.uid === uid); if (!item) { throw new Error('Item not found'); } await haConnection.callService( 'todo', 'update_item', { item: item.summary, status: 'completed' }, { entity_id: todoEntityId } ); // Refresh items await fetchItems(); } catch (err) { const message = err instanceof Error ? err.message : 'Failed to complete todo item'; setError(message); throw err; } finally { setIsLoading(false); } }, [todoEntityId, items, fetchItems]); const uncompleteItem = useCallback(async (uid: string) => { if (!todoEntityId) return; setIsLoading(true); setError(null); try { const item = items.find((i) => i.uid === uid); if (!item) { throw new Error('Item not found'); } await haConnection.callService( 'todo', 'update_item', { item: item.summary, status: 'needs_action' }, { entity_id: todoEntityId } ); // Refresh items await fetchItems(); } catch (err) { const message = err instanceof Error ? err.message : 'Failed to uncomplete todo item'; setError(message); throw err; } finally { setIsLoading(false); } }, [todoEntityId, items, fetchItems]); const removeItem = useCallback(async (uid: string) => { if (!todoEntityId) return; setIsLoading(true); setError(null); try { const item = items.find((i) => i.uid === uid); if (!item) { throw new Error('Item not found'); } await haConnection.callService( 'todo', 'remove_item', { item: item.summary }, { entity_id: todoEntityId } ); // Refresh items await fetchItems(); } catch (err) { const message = err instanceof Error ? err.message : 'Failed to remove todo item'; setError(message); throw err; } finally { setIsLoading(false); } }, [todoEntityId, items, fetchItems]); const clearError = useCallback(() => setError(null), []); // Computed values const activeItems = items.filter((item) => item.status === 'needs_action'); const completedItems = items.filter((item) => item.status === 'completed'); return { // State items, activeItems, completedItems, isLoading, error, // Actions fetchItems, addItem, completeItem, uncompleteItem, removeItem, clearError, }; }