import { HttpResponseError, HttpCancelledError, HttpNetworkError } from './errors.js';
import { buildUrl } from './url.js';
import { hasFiles } from '../form.js';
/**
 * Get the X-Requested-With header from Laravel's bootstrap config if available.
 */
function getAjaxHeader() {
    if (typeof window === 'undefined') {
        return null;
    }
    return window.axios?.defaults?.headers?.common?.['X-Requested-With'] ?? null;
}
/**
 * Convert data to FormData recursively.
 */
function toFormData(data, formData = new FormData(), parentKey = null) {
    for (const key in data) {
        if (Object.prototype.hasOwnProperty.call(data, key)) {
            appendToFormData(formData, parentKey ? `${parentKey}[${key}]` : key, data[key]);
        }
    }
    return formData;
}
function appendToFormData(formData, key, value) {
    if (Array.isArray(value)) {
        return value.forEach((val, index) => appendToFormData(formData, `${key}[${index}]`, val));
    }
    else if (value instanceof Date) {
        return formData.append(key, value.toISOString());
    }
    else if (typeof File !== 'undefined' && value instanceof File) {
        return formData.append(key, value, value.name);
    }
    else if (value instanceof Blob) {
        return formData.append(key, value);
    }
    else if (typeof value === 'boolean') {
        return formData.append(key, value ? '1' : '0');
    }
    else if (typeof value === 'string') {
        return formData.append(key, value);
    }
    else if (typeof value === 'number') {
        return formData.append(key, `${value}`);
    }
    else if (value === null || value === undefined) {
        return formData.append(key, '');
    }
    toFormData(value, formData, key);
}
/**
 * Prepare the request body.
 */
function prepareBody(data, headers) {
    if (data === undefined || data === null) {
        return undefined;
    }
    if (data instanceof FormData) {
        return data;
    }
    if (typeof data === 'object' && hasFiles(data)) {
        return toFormData(data);
    }
    if (typeof data === 'object' || headers['Content-Type']?.includes('application/json')) {
        return JSON.stringify(data);
    }
    return String(data);
}
/**
 * Parse response headers into a plain object.
 */
function parseHeaders(headers) {
    const result = {};
    headers.forEach((value, key) => {
        result[key.toLowerCase()] = value;
    });
    return result;
}
/**
 * Create a fetch-based HTTP client.
 */
export function createFetchClient(options = {}) {
    let xsrfCookieName = options.xsrfCookieName ?? 'XSRF-TOKEN';
    let xsrfHeaderName = options.xsrfHeaderName ?? 'X-XSRF-TOKEN';
    function getXsrfToken() {
        if (typeof document === 'undefined') {
            return null;
        }
        const match = document.cookie.match(new RegExp('(^|;\\s*)' + xsrfCookieName + '=([^;]*)'));
        return match ? decodeURIComponent(match[2]) : null;
    }
    return {
        setXsrfCookieName(name) {
            xsrfCookieName = name;
        },
        setXsrfHeaderName(name) {
            xsrfHeaderName = name;
        },
        async request(config) {
            const url = buildUrl(config.url, config.baseURL, config.params);
            const method = config.method.toUpperCase();
            const headers = {};
            // Inherit X-Requested-With from Laravel's bootstrap config if available
            const ajaxHeader = getAjaxHeader();
            if (ajaxHeader) {
                headers['X-Requested-With'] = ajaxHeader;
            }
            // Set default Content-Type for non-GET/DELETE requests with data
            if (config.data !== undefined && !['GET', 'DELETE'].includes(method)) {
                if (!(config.data instanceof FormData) && !hasFiles(config.data)) {
                    headers['Content-Type'] = 'application/json';
                }
            }
            // Copy user headers
            if (config.headers) {
                Object.entries(config.headers).forEach(([key, value]) => {
                    if (value !== undefined) {
                        headers[key] = String(value);
                    }
                });
            }
            // Add XSRF token
            const xsrfToken = getXsrfToken();
            if (xsrfToken && !['GET', 'HEAD', 'OPTIONS'].includes(method)) {
                headers[xsrfHeaderName] = xsrfToken;
            }
            // Handle timeout
            let signal = config.signal;
            let timeoutId;
            const timeout = config.timeout ?? 30000;
            if (timeout > 0 && !signal) {
                const controller = new AbortController();
                signal = controller.signal;
                timeoutId = setTimeout(() => controller.abort(), timeout);
            }
            // Prepare body (only for non-GET/DELETE requests)
            const body = ['GET', 'DELETE'].includes(method)
                ? undefined
                : prepareBody(config.data, headers);
            if (body instanceof FormData) {
                delete headers['Content-Type'];
            }
            try {
                const response = await fetch(url, {
                    method,
                    headers,
                    body,
                    signal,
                    credentials: config.credentials ?? 'same-origin',
                });
                if (timeoutId) {
                    clearTimeout(timeoutId);
                }
                let data;
                const contentType = response.headers.get('content-type');
                if (contentType?.includes('application/json')) {
                    data = await response.json();
                }
                else {
                    data = await response.text();
                }
                const httpResponse = {
                    status: response.status,
                    data,
                    headers: parseHeaders(response.headers),
                };
                if (!response.ok) {
                    throw new HttpResponseError(httpResponse);
                }
                return httpResponse;
            }
            catch (error) {
                if (timeoutId) {
                    clearTimeout(timeoutId);
                }
                if (error instanceof HttpResponseError) {
                    throw error;
                }
                if (error instanceof DOMException && error.name === 'AbortError') {
                    throw new HttpCancelledError();
                }
                if (error instanceof TypeError) {
                    throw new HttpNetworkError(error.message);
                }
                throw error;
            }
        },
    };
}
/**
 * Default fetch HTTP client instance.
 */
export const fetchHttpClient = createFetchClient();
