import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useHaptics } from '@/aesthetics/useHaptics'; import { useProfile } from '@/profile/ProfileProvider'; import { useDatabase, useQuery } from '@/remote/database'; import { DayRepository } from '@/add-log/repository'; import { DEFAULT_LOGGERS } from './constants'; import { migrateDemoData, MemoLogRepository, MemoLoggerRepository } from './repository'; import type { InputType, MemoLog, MemoLogger } from './types'; export function useMemoLog() { const { profile, isLoading: profileLoading } = useProfile(); const profileId = profileLoading ? null : (profile?.id ?? 'demo'); const db = useDatabase(); const { notification, impact, NotificationType, ImpactStyle } = useHaptics(); const loggerRepo = useMemo(() => new MemoLoggerRepository(db), [db]); const logRepo = useMemo(() => new MemoLogRepository(db), [db]); const dayRepo = useMemo(() => new DayRepository(db), [db]); const seededRef = useRef>(new Set()); const migratedRef = useRef>(new Set()); useEffect(() => { if (!profileId || profileId === 'demo' || migratedRef.current.has(profileId)) return; migratedRef.current.add(profileId); migrateDemoData(db, profileId).catch(() => { migratedRef.current.delete(profileId); }); }, [profileId, db]); useEffect(() => { if (!profileId || seededRef.current.has(profileId)) return; seededRef.current.add(profileId); loggerRepo.seedDefaults(profileId, DEFAULT_LOGGERS).catch(() => { seededRef.current.delete(profileId); }); }, [profileId, loggerRepo]); const { data: loggerRows, isLoading: loggersLoading } = useQuery>( `SELECT * FROM memo_loggers WHERE profile_id = ? AND deleted_at IS NULL ORDER BY sort_order ASC`, [profileId ?? ''] ); const loggers = useMemo( () => (loggerRows ?? []).map(MemoLoggerRepository.mapRowToMemoLogger), [loggerRows] ); const { data: entryRows } = useQuery>( `SELECT * FROM memo_logs WHERE profile_id = ? AND deleted_at IS NULL ORDER BY logged_time DESC`, [profileId ?? ''] ); const entries = useMemo( () => (entryRows ?? []).map(MemoLogRepository.mapRowToMemoLog), [entryRows] ); const isLoading = profileLoading || loggersLoading; const loggerMap = useMemo(() => { const map = new Map(); loggers.forEach(l => map.set(l.id, l)); return map; }, [loggers]); const createLog = useCallback( async ( title: string, value: number | string, type: InputType, loggerId: string, unit?: string, icon?: string ) => { if (!profileId) return; notification(NotificationType.Success); const dayId = await dayRepo.getOrCreateDay(profileId, new Date()); await logRepo.create(profileId, dayId, { loggerId, type, title, value, valueUnit: unit, icon, }); }, [dayRepo, logRepo, profileId, notification, NotificationType] ); const createBasicLog = useCallback( async (title: string, value: number | string, type: InputType, unit?: string) => { await createLog(title, value, type, `basic-${Date.now()}`, unit); }, [createLog] ); const logWithLogger = useCallback( async (logger: MemoLogger, value: number | string, icon?: string, title?: string) => { await createLog(title ?? logger.name, value, logger.type, logger.id, logger.unit, icon); }, [createLog] ); const logWithLoggerOption = useCallback( async (logger: MemoLogger, optionIndex: number, title?: string) => { const opt = logger.options?.[optionIndex]; if (!opt) return; const value = opt.label ?? opt.icon ?? String(opt.value ?? optionIndex); await logWithLogger(logger, value, opt.icon, title); }, [logWithLogger] ); const logMultiselect = useCallback( async (logger: MemoLogger, indices: number[], title?: string) => { if (!logger.options) return; const selected = indices.map(i => logger.options![i]); const value = selected.map(opt => opt.label ?? opt.icon ?? '').join(', '); await logWithLogger(logger, value, undefined, title); }, [logWithLogger] ); const updateEntry = useCallback( async (id: string, value: number | string, icon?: string) => { notification(NotificationType.Success); await logRepo.update(id, { value, icon }); }, [logRepo, notification, NotificationType] ); const deleteEntry = useCallback( async (id: string) => { impact(ImpactStyle.Light); await logRepo.delete(id); }, [logRepo, impact, ImpactStyle] ); const createLogger = useCallback( async (logger: Omit) => { if (!profileId) return; notification(NotificationType.Success); await loggerRepo.create(profileId, logger); }, [loggerRepo, profileId, notification, NotificationType] ); const updateLogger = useCallback( async (id: string, updates: Partial>) => { notification(NotificationType.Success); await loggerRepo.update(id, updates); }, [loggerRepo, notification, NotificationType] ); const deleteLogger = useCallback( async (id: string) => { await loggerRepo.delete(id); }, [loggerRepo] ); return { entries, loggers, loggerMap, isLoading, createBasicLog, logWithLogger, logWithLoggerOption, logMultiselect, updateEntry, deleteEntry, createLogger, updateLogger, deleteLogger, }; }