import { isDocument, isFormData, isNullish, isObject, isString, } from '../utils/type-utils';
import { formDataToQuery } from '../utils/request-utils';
import { configs } from './configs';
function _tryReadFetchBody({ body, url, }) {
    if (isNullish(body)) {
        return null;
    }
    if (isString(body)) {
        return body;
    }
    if (isDocument(body)) {
        return body.textContent;
    }
    if (isFormData(body)) {
        return formDataToQuery(body);
    }
    if (isObject(body)) {
        try {
            return JSON.stringify(body);
        }
        catch (_a) {
            return '[Fetch] Failed to stringify request object';
        }
    }
    return `[Fetch] Cannot read body of type ${toString.call(body)}`;
}
async function _tryReadResponseBody(response) {
    var _a, _b;
    try {
        // Clone the response to avoid consuming the original stream
        const clonedResponse = response.clone();
        // Try different methods to read the body
        if ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('application/json')) {
            const json = await clonedResponse.json();
            return JSON.stringify(json);
        }
        else if ((_b = response.headers.get('content-type')) === null || _b === void 0 ? void 0 : _b.includes('text/')) {
            return await clonedResponse.text();
        }
        else {
            // For other content types, try text first, fallback to arrayBuffer
            try {
                return await clonedResponse.text();
            }
            catch (_c) {
                try {
                    const arrayBuffer = await clonedResponse.arrayBuffer();
                    return `[Fetch] Binary data (${arrayBuffer.byteLength} bytes)`;
                }
                catch (_d) {
                    return '[Fetch] Unable to read response body';
                }
            }
        }
    }
    catch (error) {
        return `[Fetch] Error reading response body: ${error instanceof Error ? error.message : 'Unknown error'}`;
    }
}
function _headersToObject(headers) {
    const result = {};
    headers.forEach((value, key) => {
        result[key] = value;
    });
    return result;
}
// Convert HeadersInit to a plain object without needing to construct a Request
function _headersInitToObject(headersInit) {
    if (!headersInit)
        return {};
    if (headersInit instanceof Headers) {
        return _headersToObject(headersInit);
    }
    const result = {};
    if (Array.isArray(headersInit)) {
        for (const [key, value] of headersInit) {
            result[String(key).toLowerCase()] = String(value);
        }
        return result;
    }
    // Record<string, string>
    for (const [key, value] of Object.entries(headersInit)) {
        result[String(key).toLowerCase()] = String(value);
    }
    return result;
}
if (typeof window !== 'undefined' && typeof window.fetch !== 'undefined') {
    // Store original fetch
    const originalFetch = window.fetch;
    // Override fetch
    window.fetch = async function (input, 
    // eslint-disable-next-line
    init) {
        const networkRequest = {};
        // Capture request data
        const inputIsRequest = typeof Request !== 'undefined' && input instanceof Request;
        const safeToConstructRequest = !inputIsRequest || !input.bodyUsed;
        // Only construct a new Request when it's safe (i.e., body not already used)
        let requestForMetadata = null;
        if (safeToConstructRequest) {
            try {
                requestForMetadata = new Request(input, init);
            }
            catch (_a) {
                // If construction fails for any reason, fall back to using available data
                requestForMetadata = null;
            }
        }
        if (configs.recordRequestHeaders) {
            if (requestForMetadata) {
                networkRequest.requestHeaders = _headersToObject(requestForMetadata.headers);
            }
            else if (inputIsRequest) {
                networkRequest.requestHeaders = _headersToObject(input.headers);
            }
            else {
                networkRequest.requestHeaders = _headersInitToObject(init === null || init === void 0 ? void 0 : init.headers);
            }
        }
        if (configs.shouldRecordBody) {
            // Prefer reading from the safely constructed Request; else fallback to init.body
            const urlStr = inputIsRequest
                ? input.url
                : (typeof input === 'string' || input instanceof URL ? String(input) : '');
            const candidateBody = requestForMetadata
                ? requestForMetadata.body
                : (inputIsRequest ? init === null || init === void 0 ? void 0 : init.body : init === null || init === void 0 ? void 0 : init.body);
            if (!isNullish(candidateBody)) {
                const requestBody = _tryReadFetchBody({
                    body: candidateBody,
                    url: urlStr,
                });
                if ((requestBody === null || requestBody === void 0 ? void 0 : requestBody.length) &&
                    new Blob([requestBody]).size <= configs.maxCapturingHttpPayloadSize) {
                    networkRequest.requestBody = requestBody;
                }
            }
        }
        try {
            // Make the actual fetch request
            const response = await originalFetch(input, init);
            // Capture response data
            if (configs.recordResponseHeaders) {
                networkRequest.responseHeaders = _headersToObject(response.headers);
            }
            if (configs.shouldRecordBody) {
                const responseBody = await _tryReadResponseBody(response);
                if ((responseBody === null || responseBody === void 0 ? void 0 : responseBody.length) &&
                    new Blob([responseBody]).size <= configs.maxCapturingHttpPayloadSize) {
                    networkRequest.responseBody = responseBody;
                }
            }
            // Attach network request data to the response for later access
            // @ts-ignore
            response.networkRequest = networkRequest;
            return response;
        }
        catch (error) {
            // Even if the fetch fails, we can still capture the request data
            // Attach captured request data to the thrown error for downstream handling
            // @ts-ignore
            if (error && typeof error === 'object') {
                // @ts-ignore
                error.networkRequest = networkRequest;
            }
            throw error;
        }
    };
    // Preserve the original fetch function's properties
    Object.setPrototypeOf(window.fetch, originalFetch);
    Object.defineProperty(window.fetch, 'name', { value: 'fetch' });
    Object.defineProperty(window.fetch, 'length', { value: originalFetch.length });
}
//# sourceMappingURL=fetch.js.map