import type { Filing, Holding, PortfolioInsight, PortfolioSummary, Task, User, WatchlistItem } from './types'; import { resolveApiBaseURL } from './runtime-url'; const API_BASE = resolveApiBaseURL(process.env.NEXT_PUBLIC_API_URL); export class ApiError extends Error { status: number; constructor(message: string, status: number) { super(message); this.name = 'ApiError'; this.status = status; } } async function apiFetch(path: string, init?: RequestInit): Promise { const headers = new Headers(init?.headers); if (!headers.has('Content-Type')) { headers.set('Content-Type', 'application/json'); } const response = await fetch(`${API_BASE}${path}`, { ...init, credentials: 'include', headers, cache: 'no-store' }); const body = await response.json().catch(() => ({})); if (!response.ok) { const message = typeof body?.error === 'string' ? body.error : `Request failed (${response.status})`; throw new ApiError(message, response.status); } return body as T; } export async function getMe() { return await apiFetch<{ user: User }>('/api/me'); } export async function listWatchlist() { return await apiFetch<{ items: WatchlistItem[] }>('/api/watchlist'); } export async function upsertWatchlistItem(input: { ticker: string; companyName: string; sector?: string }) { return await apiFetch<{ item: WatchlistItem }>('/api/watchlist', { method: 'POST', body: JSON.stringify(input) }); } export async function deleteWatchlistItem(id: number) { return await apiFetch<{ success: boolean }>(`/api/watchlist/${id}`, { method: 'DELETE' }); } export async function listHoldings() { return await apiFetch<{ holdings: Holding[] }>('/api/portfolio/holdings'); } export async function getPortfolioSummary() { return await apiFetch<{ summary: PortfolioSummary }>('/api/portfolio/summary'); } export async function upsertHolding(input: { ticker: string; shares: number; avgCost: number; currentPrice?: number; }) { return await apiFetch<{ holding: Holding }>('/api/portfolio/holdings', { method: 'POST', body: JSON.stringify(input) }); } export async function deleteHolding(id: number) { return await apiFetch<{ success: boolean }>(`/api/portfolio/holdings/${id}`, { method: 'DELETE' }); } export async function queuePriceRefresh() { return await apiFetch<{ task: Task }>('/api/portfolio/refresh-prices', { method: 'POST' }); } export async function queuePortfolioInsights() { return await apiFetch<{ task: Task }>('/api/portfolio/insights/generate', { method: 'POST' }); } export async function getLatestPortfolioInsight() { return await apiFetch<{ insight: PortfolioInsight | null }>('/api/portfolio/insights/latest'); } export async function listFilings(query?: { ticker?: string; limit?: number }) { const params = new URLSearchParams(); if (query?.ticker) { params.set('ticker', query.ticker); } if (query?.limit) { params.set('limit', String(query.limit)); } const suffix = params.size > 0 ? `?${params.toString()}` : ''; return await apiFetch<{ filings: Filing[] }>(`/api/filings${suffix}`); } export async function queueFilingSync(input: { ticker: string; limit?: number }) { return await apiFetch<{ task: Task }>('/api/filings/sync', { method: 'POST', body: JSON.stringify(input) }); } export async function queueFilingAnalysis(accessionNumber: string) { return await apiFetch<{ task: Task }>(`/api/filings/${accessionNumber}/analyze`, { method: 'POST' }); } export async function getTask(taskId: string) { return await apiFetch<{ task: Task }>(`/api/tasks/${taskId}`); } export async function listRecentTasks(limit = 20) { return await apiFetch<{ tasks: Task[] }>(`/api/tasks?limit=${limit}`); }