/**
 * A centralized API client that wraps around the native fetch API.
 * It automatically handles authentication headers, JSON parsing, and error handling.
 *
 * This client is used across the app for interacting with backend endpoints.
 *
 * @todo modify all endpoints to use api client and remove old utils
 */

const API_BASE_URL = import.meta.env.VITE_API_BASE

/**
 * Helper: Reads the JWT token from localStorage (if available)
 */
function getAuthToken(): string | null {
    try {
        const sessionData = localStorage.getItem("session");
        if (!sessionData) return null;

        const parsed = JSON.parse(sessionData);
        return parsed.token ?? null;
    } catch (err) {
        console.error("Failed to parse token from localStorage:", err);
        return null;
    }
}

/**
 * Custom error class for API responses.
 */
export class ApiError extends Error {
    public status: number;
    public details?: string;

    constructor(message: string, status: number, details?: string) {
        super(message);
        this.status = status;
        this.details = details;
    }
}

/**
 * Wrapper for making API requests.
 * Handles JSON encoding/decoding and authorization headers automatically.
 *
 * @param endpoint - API endpoint (e.g. "user/me")
 * @param options - fetch configuration (method, headers, body)
 * @returns parsed JSON response
 */
export async function apiRequest<T>(
    endpoint: string,
    options: RequestInit = {}
): Promise<T> {
    const token = getAuthToken();

    const headers: HeadersInit = {
        "Content-Type": "application/json",
        ...(token ? {Authorization: `Bearer ${token}`} : {}),
        ...options.headers,
    };

    const response = await fetch(`${API_BASE_URL}${endpoint}`, {
        ...options,
        headers,
    });

    // Handle non-OK responses gracefully
    if (!response.ok) {
        let details;
        try {
            details = await response.json();
        } catch {
            details = await response.text();
        }
        throw new ApiError(`Request to ${endpoint} failed`, response.status, details);
    }

    // Handle empty response bodies
    if (response.status === 204) {
        return {} as T;
    }

    return response.json() as Promise<T>;
}

/**
 * Shorthand helpers for convenience.
 */
export const apiClient = {
    get: <T>(endpoint: string) => apiRequest<T>(endpoint, {method: "GET"}),
    post: <T>(endpoint: string, body: object) =>
        apiRequest<T>(endpoint, {method: "POST", body: JSON.stringify(body)}),
    put: <T>(endpoint: string, body: object) =>
        apiRequest<T>(endpoint, {method: "PUT", body: JSON.stringify(body)}),
    delete: <T>(endpoint: string) =>
        apiRequest<T>(endpoint, {method: "DELETE"}),
};
