// Filename: frontend/js/services/api.js

class APIClientError extends Error {
    constructor(message, status, code, rawMessageFromServer) {
        super(message);
        this.name = 'APIClientError';
        this.status = status;
        this.code = code;
        this.rawMessageFromServer = rawMessageFromServer;
    }
}

// Global Promise cache for raw responses
const apiPromiseCache = new Map();

/**
 * [CORRECTED & CACHE-AWARE] A low-level fetch wrapper.
 * It handles caching, authentication, and centralized error handling.
 * On success (2xx), it returns the raw, unread Response object.
 * On failure (non-2xx), it consumes the body to throw a detailed APIClientError.
 */
export async function apiFetch(url, options = {}) {
    // For non-GET requests or noCache requests, we bypass the promise cache.
    const isGetRequest = !options.method || options.method.toUpperCase() === 'GET';
    const cacheKey = isGetRequest && !options.noCache ? url : null;

    if (cacheKey && apiPromiseCache.has(cacheKey)) {
        return apiPromiseCache.get(cacheKey);
    }

    const token = localStorage.getItem('bearerToken');
    const headers = {
        'Content-Type': 'application/json',
        ...options.headers,
    };
    if (token) {
        headers['Authorization'] = `Bearer ${token}`;
    }

    const requestPromise = (async () => {
        try {
            const response = await fetch(url, { ...options, headers });

            if (response.status === 401) {
                // On auth error, always clear caches for this URL.
                if (cacheKey) apiPromiseCache.delete(cacheKey);
                // ... (rest of the 401 logic is correct)
                localStorage.removeItem('bearerToken');
                if (window.location.pathname !== '/login') {
                    window.location.href = '/login?error=会话已过期，请重新登录。';
                }
                throw new APIClientError('Unauthorized', 401, 'UNAUTHORIZED', 'Session expired or token is invalid.');
            }

            if (!response.ok) {
                let errorData = null;
                let rawMessage = '';
                try {
                    // This is the ONLY place the body is consumed in the failure path.
                    rawMessage = await response.text();
                    if(rawMessage) { // Avoid parsing empty string
                      errorData = JSON.parse(rawMessage);
                    }
                } catch (e) {
                    errorData = { error: { code: 'UNKNOWN_FORMAT', message: rawMessage || response.statusText } };
                }

                const code = errorData?.error?.code || 'UNKNOWN_ERROR';
                const messageFromServer = errorData?.error?.message || rawMessage || 'No message provided by server.';
                
                const error = new APIClientError(
                    `API request failed: ${response.status}`,
                    response.status,
                    code,
                    messageFromServer
                );
                
                // Throwing the error will cause this promise to reject.
                throw error;
            }

            // On success, the promise resolves with the PRISTINE response object.
            return response;

        } catch (error) {
            // If an error occurred (either thrown by us or a network error), 
            // ensure the promise cache is cleared for this key before re-throwing.
            if (cacheKey) apiPromiseCache.delete(cacheKey);
            throw error; // Re-throw to propagate the failure.
        }
    })();

    // If we are caching, store the promise.
    if (cacheKey) {
        apiPromiseCache.set(cacheKey, requestPromise);
    }

    return requestPromise;
}

/**
 * [CORRECTED & CACHE-AWARE] High-level wrapper that expects a JSON response.
 * It leverages apiFetch and is ONLY responsible for calling .json() on a successful response.
 */
export async function apiFetchJson(url, options = {}) {
    try {
        // 1. Get the raw response from apiFetch. It's either fresh or from the promise cache.
        // If it fails, apiFetch will throw, and this function will propagate the error.
        const response = await apiFetch(url, options);

        // 2. We have a successful response. We need to clone it before reading the body.
        // This is CRITICAL because the original response in the promise cache MUST remain unread.
        const clonedResponse = response.clone();

        // 3. Now we can safely consume the body of the CLONE.
        const jsonData = await clonedResponse.json();
        
        return jsonData;

    } catch (error) {
        // Just propagate the detailed error from apiFetch.
        throw error;
    }
}
