import { ref } from "vue"; import { defineStore } from "pinia"; import { apiGet, apiPost, apiPut, apiDelete } from "@/api/client"; import { useToastStore } from "@/stores/toast"; import type { Note, NoteListResponse } from "@/types/note"; export const useNotesStore = defineStore("notes", () => { const notes = ref([]); const currentNote = ref(null); const total = ref(0); const loading = ref(false); // Filter / pagination / sort state const activeTagFilters = ref([]); const limit = ref(20); const offset = ref(0); const sortField = ref("updated_at"); const sortOrder = ref<"asc" | "desc">("desc"); const searchQuery = ref(""); async function refresh() { loading.value = true; try { const searchParams = new URLSearchParams(); if (searchQuery.value) searchParams.set("q", searchQuery.value); for (const t of activeTagFilters.value) { searchParams.append("tag", t); } searchParams.set("sort", sortField.value); searchParams.set("order", sortOrder.value); searchParams.set("limit", String(limit.value)); searchParams.set("offset", String(offset.value)); const qs = searchParams.toString(); const data = await apiGet( `/api/notes${qs ? `?${qs}` : ""}` ); notes.value = data.notes; total.value = data.total; } catch (e) { useToastStore().show("Failed to load notes", "error"); throw e; } finally { loading.value = false; } } async function fetchNotes(params?: { q?: string; tag?: string[]; sort?: string; order?: string; limit?: number; offset?: number; }) { if (params?.q !== undefined) searchQuery.value = params.q || ""; if (params?.tag) activeTagFilters.value = params.tag; if (params?.sort) sortField.value = params.sort; if (params?.order) sortOrder.value = params.order as "asc" | "desc"; if (params?.limit) limit.value = params.limit; if (params?.offset !== undefined) offset.value = params.offset; await refresh(); } async function fetchNote(id: number) { loading.value = true; try { currentNote.value = await apiGet(`/api/notes/${id}`); } catch (e) { useToastStore().show("Failed to load note", "error"); throw e; } finally { loading.value = false; } } async function createNote(data: { title: string; body: string; tags?: string[]; project_id?: number | null; milestone_id?: number | null; note_type?: string; metadata?: Record | null; }): Promise { try { return await apiPost("/api/notes", data); } catch (e) { useToastStore().show("Failed to create note", "error"); throw e; } } async function updateNote( id: number, data: Partial> ): Promise { try { const note = await apiPut(`/api/notes/${id}`, data); if (currentNote.value?.id === id) { currentNote.value = note; } return note; } catch (e) { useToastStore().show("Failed to update note", "error"); throw e; } } async function deleteNote(id: number) { try { await apiDelete(`/api/notes/${id}`); notes.value = notes.value.filter((n) => n.id !== id); if (currentNote.value?.id === id) { currentNote.value = null; } } catch (e) { useToastStore().show("Failed to delete note", "error"); throw e; } } function addTagFilter(tag: string) { if (!activeTagFilters.value.includes(tag)) { activeTagFilters.value.push(tag); offset.value = 0; refresh(); } } function removeTagFilter(tag: string) { activeTagFilters.value = activeTagFilters.value.filter((t) => t !== tag); offset.value = 0; refresh(); } function clearTagFilters() { activeTagFilters.value = []; offset.value = 0; refresh(); } function setTagFilters(tags: string[]) { activeTagFilters.value = [...tags]; offset.value = 0; refresh(); } function setSort(field: string, order: "asc" | "desc") { sortField.value = field; sortOrder.value = order; offset.value = 0; refresh(); } function setOffset(newOffset: number) { offset.value = newOffset; refresh(); } function setSearch(q: string) { searchQuery.value = q; offset.value = 0; refresh(); } async function resolveTitle(title: string): Promise { return await apiPost("/api/notes/resolve-title", { title }); } async function convertToTask(noteId: number): Promise { try { const note = await apiPost( `/api/notes/${noteId}/convert-to-task`, {} ); if (currentNote.value?.id === noteId) { currentNote.value = note; } return note; } catch (e) { useToastStore().show("Failed to convert to task", "error"); throw e; } } async function convertToNote(noteId: number): Promise { try { const note = await apiPost( `/api/notes/${noteId}/convert-to-note`, {} ); if (currentNote.value?.id === noteId) { currentNote.value = note; } return note; } catch (e) { useToastStore().show("Failed to convert to note", "error"); throw e; } } async function fetchBacklinks( noteId: number ): Promise<{ type: string; id: number; title: string }[]> { const data = await apiGet<{ backlinks: { type: string; id: number; title: string }[]; }>(`/api/notes/${noteId}/backlinks`); return data.backlinks; } async function fetchAllTags(q?: string): Promise { const params = q ? `?q=${encodeURIComponent(q)}` : ""; const data = await apiGet<{ tags: string[] }>(`/api/notes/tags${params}`); return data.tags; } return { notes, currentNote, total, loading, activeTagFilters, limit, offset, sortField, sortOrder, searchQuery, fetchNotes, fetchNote, createNote, updateNote, deleteNote, addTagFilter, removeTagFilter, clearTagFilters, setTagFilters, setSort, setOffset, setSearch, refresh, resolveTitle, convertToTask, convertToNote, fetchBacklinks, fetchAllTags, }; });