import axios, { AxiosInstance } from "axios"; let apiClient: AxiosInstance | null = null; let _onUnauthorized: (() => void) | undefined; function serializeParams(params: Record): string { const searchParams = new URLSearchParams(); for (const [key, value] of Object.entries(params ?? {})) { if (value === undefined || value === null) continue; if (Array.isArray(value)) { for (const item of value) searchParams.append(key, String(item)); } else if (typeof value === "object") { for (const [nestedKey, nestedValue] of Object.entries(value)) { if (nestedValue === undefined || nestedValue === null) continue; searchParams.append(`${key}[${nestedKey}]`, String(nestedValue)); } } else { searchParams.append(key, String(value)); } } return searchParams.toString(); } export function initApi(baseUrl: string, getToken?: () => string | null, onUnauthorized?: () => void): AxiosInstance { if (apiClient && apiClient.defaults.baseURL === baseUrl) { _onUnauthorized = onUnauthorized; return apiClient; } _onUnauthorized = onUnauthorized; apiClient = axios.create({ baseURL: baseUrl, headers: { "Content-Type": "application/json" }, paramsSerializer: serializeParams, }); apiClient.interceptors.request.use((config) => { const token = getToken?.(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); apiClient.interceptors.response.use( (res) => res, (error) => { if (error.response?.status === 401) { console.log("[useApi] 401 from %s %s - dispatching auth:unauthorized", error.config?.method, error.config?.url); window.dispatchEvent(new CustomEvent("auth:unauthorized")); _onUnauthorized?.(); } else { console.log("[useApi] non-401 error %s from %s %s", error.response?.status, error.config?.method, error.config?.url); } return Promise.reject(error); } ); return apiClient; } export function getApi(): AxiosInstance { if (!apiClient) { throw new Error("API client not initialized. Make sure AppProvider is mounted."); } return apiClient; }