const BASE = '/api'; async function apiFetch(path: string, init?: RequestInit): Promise { const res = await fetch(`${BASE}${path}`, { headers: { 'Content-Type': 'application/json', ...init?.headers }, ...init, }); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); return res.json() as Promise; } export type Address = { name: string | null; email: string | null }; export type Folder = { id: string; imap_path: string; display_name: string; special_use: string | null; unread_count: number; total_count: number; synced_at: string | null; }; export type Thread = { id: string; subject: string; participants: Address[]; message_count: number; unread_count: number; last_message_at: string; snippet: string | null; has_flagged: boolean; has_attachment: boolean; }; export type MessageSummary = { id: string; folder_id: string; thread_id: string | null; from_addr: Address; to_addrs: Address[]; cc_addrs: Address[]; subject: string; date_sent: string; snippet: string; flags: { seen: boolean; flagged: boolean; answered: boolean; draft: boolean }; attachments: Array<{ name: string; size: number; type: string }>; }; export type MessageFull = MessageSummary & { body_text: string | null; body_html: string | null; }; export type SyncRequest = { account_id: string; client_version: number; mutations: Mutation[]; }; export type MutationTargetKind = 'thread' | 'message' | 'draft'; export type Mutation = { id: string; op: 'flag' | 'move' | 'delete' | 'send' | 'draft_save' | 'draft_delete'; target_kind: MutationTargetKind; target_id: string; payload: Record; }; export type SyncResponse = { applied: string[]; rejected: Array<{ id: string; reason: string }>; server_version: number; changes: Array<{ type: 'message' | 'thread'; data: unknown }>; }; export const api = { accounts: { list: () => apiFetch<{ accounts: Array<{ id: string; email: string }> }>('/accounts'), }, folders: { list: (accountId: string) => apiFetch<{ folders: Folder[] }>(`/accounts/${accountId}/folders`), }, threads: { list: (folderId: string, params?: { limit?: number; before?: string }) => { const qs = new URLSearchParams(); if (params?.limit) qs.set('limit', String(params.limit)); if (params?.before) qs.set('before', params.before); return apiFetch<{ threads: Thread[]; next_cursor: string | null }>( `/folders/${folderId}/threads?${qs}` ); }, }, messages: { inThread: (threadId: string) => apiFetch<{ messages: MessageSummary[] }>(`/threads/${threadId}/messages`), get: (id: string) => apiFetch(`/messages/${id}`), }, search: (accountId: string, q: string) => apiFetch<{ results: MessageSummary[] }>(`/search?account_id=${accountId}&q=${encodeURIComponent(q)}`), sync: (body: SyncRequest) => apiFetch('/sync', { method: 'POST', body: JSON.stringify(body) }), };