import { get, writable } from "svelte/store"; import { createTodoItem as createTodoItemCommand, createTodoList as createTodoListCommand, deleteTodoItem as deleteTodoItemCommand, deleteTodoList as deleteTodoListCommand, listTodoLists, updateTodoItem as updateTodoItemCommand, updateTodoList as updateTodoListCommand, type TodoListDto } from "$lib/backend/todos"; // TodoItem keeps a numeric `id` for local array operations (used by EditorPanel) // plus a `backendId` (guid string) for backend persistence. export type TodoItem = { id: number; text: string; done: boolean; backendId?: string }; export type TodoListMeta = { id: string; label: string; backendId?: string }; export const todoListsStore = writable([]); export const todosStore = writable>({}); export const todosBusyStore = writable(false); // ── ID helpers ─────────────────────────────────────────────────── function toStoreId(guid: string): string { return `todos/${guid}`; } function toBackendId(storeId: string): string | null { const prefix = "todos/"; if (!storeId.startsWith(prefix)) return null; const backendId = storeId.slice(prefix.length).trim(); return backendId || null; } export function createTodoId(): number { return Date.now() + Math.floor(Math.random() * 1000); } // ── DTO mapping ────────────────────────────────────────────────── function dtoToMeta(dto: TodoListDto): TodoListMeta { return { id: toStoreId(dto.id), label: dto.label, backendId: dto.id }; } function dtoToItems(dto: TodoListDto): TodoItem[] { return dto.items.map((item, index) => ({ id: createTodoId() + index, text: item.text, done: item.done, backendId: item.id })); } // ── Hydration ──────────────────────────────────────────────────── export async function hydrateTodos(): Promise { todosBusyStore.set(true); try { const lists = await listTodoLists(); const metas: TodoListMeta[] = lists.map(dtoToMeta); const items: Record = {}; for (const dto of lists) { items[toStoreId(dto.id)] = dtoToItems(dto); } todoListsStore.set(metas); todosStore.set(items); } catch (error) { console.error("[todos] hydrate:error", error); throw error; } finally { todosBusyStore.set(false); } } // ── List CRUD ──────────────────────────────────────────────────── export async function createTodoListFromLabel( label: string ): Promise<{ meta: TodoListMeta; items: TodoItem[] }> { const resolvedLabel = label.trim() || "New List"; const created = await createTodoListCommand({ label: resolvedLabel }); const meta = dtoToMeta(created); todoListsStore.update((metas) => [meta, ...metas]); todosStore.update((lists) => ({ ...lists, [meta.id]: [] })); return { meta, items: [] }; } export async function deleteTodoListByStoreId(storeId: string): Promise { const backendId = toBackendId(storeId); if (!backendId) return false; const ok = await deleteTodoListCommand(backendId); if (!ok) return false; todoListsStore.update((metas) => metas.filter((m) => m.id !== storeId)); todosStore.update((lists) => { const { [storeId]: _, ...rest } = lists; return rest; }); return true; } // ── Item CRUD (backend-backed) ─────────────────────────────────── export async function addTodoItemBackend( storeId: string, text: string ): Promise { const backendListId = toBackendId(storeId); if (!backendListId || !text.trim()) return null; const items = get(todosStore)[storeId] ?? []; const sortOrder = items.length; const created = await createTodoItemCommand({ listId: backendListId, text: text.trim(), sortOrder }); const item: TodoItem = { id: createTodoId(), text: created.text, done: created.done, backendId: created.id }; todosStore.update((lists) => ({ ...lists, [storeId]: [item, ...(lists[storeId] ?? [])] })); return item; } export async function toggleTodoItemBackend( storeId: string, localId: number ): Promise { const items = get(todosStore)[storeId]; const todo = items?.find((t) => t.id === localId); if (!todo?.backendId) return false; const ok = await updateTodoItemCommand(todo.backendId, { done: !todo.done }); if (!ok) return false; todosStore.update((lists) => ({ ...lists, [storeId]: (lists[storeId] ?? []).map((t) => t.id === localId ? { ...t, done: !t.done } : t ) })); return true; } export async function updateTodoItemTextBackend( storeId: string, localId: number, text: string ): Promise { const items = get(todosStore)[storeId]; const todo = items?.find((t) => t.id === localId); if (!todo?.backendId || !text.trim()) return false; const ok = await updateTodoItemCommand(todo.backendId, { text: text.trim() }); if (!ok) return false; todosStore.update((lists) => ({ ...lists, [storeId]: (lists[storeId] ?? []).map((t) => t.id === localId ? { ...t, text: text.trim() } : t ) })); return true; } export async function removeTodoItemBackend( storeId: string, localId: number ): Promise { const items = get(todosStore)[storeId]; const todo = items?.find((t) => t.id === localId); if (!todo?.backendId) return false; const ok = await deleteTodoItemCommand(todo.backendId); if (!ok) return false; todosStore.update((lists) => ({ ...lists, [storeId]: (lists[storeId] ?? []).filter((t) => t.id !== localId) })); return true; } // ── Pure helpers (used by EditorPanel for local state) ─────────── export function serializeTodoList(title: string, todos: TodoItem[]): string { const heading = title?.trim() ? `# ${title}` : "# To-Do List"; const lines = todos.map((todo) => `- [${todo.done ? "x" : " "}] ${todo.text}`); return `${heading}\n\n${lines.join("\n")}`; } export function parseTodoList(content: string): TodoItem[] { const lines = content.replace(/\r\n/g, "\n").split("\n"); const parsed: TodoItem[] = []; for (const line of lines) { const match = line.match(/^- \[( |x)\]\s+(.+)$/i); if (!match) continue; parsed.push({ id: createTodoId(), text: match[2].trim(), done: match[1].toLowerCase() === "x" }); } return parsed; } export function getOrCreateTodoList( lists: Record, documentId: string, fallbackContent: string ): { lists: Record; todos: TodoItem[] } { const existing = lists[documentId]; if (existing) { return { lists, todos: existing }; } const parsed = parseTodoList(fallbackContent); return { lists: { ...lists, [documentId]: parsed }, todos: parsed }; } export function setTodoList( lists: Record, documentId: string, todos: TodoItem[] ): Record { return { ...lists, [documentId]: todos }; } export function addTodoItem(todos: TodoItem[], text: string): TodoItem[] { return [{ id: createTodoId(), text: text.trim(), done: false }, ...todos]; } export function toggleTodoItem(todos: TodoItem[], id: number): TodoItem[] { return todos.map((todo) => (todo.id === id ? { ...todo, done: !todo.done } : todo)); } export function updateTodoItemText(todos: TodoItem[], id: number, text: string): TodoItem[] { return todos.map((todo) => (todo.id === id ? { ...todo, text: text.trim() } : todo)); } export function removeTodoItem(todos: TodoItem[], id: number): TodoItem[] { return todos.filter((todo) => todo.id !== id); } export function createTodoListDraft(): { meta: TodoListMeta; items: TodoItem[] } { const id = `todos/draft-${Date.now()}`; return { meta: { id, label: "New List" }, items: [] }; }