/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */
/**
 * Recursively limits array sizes in a JSON object
 */
function limitArraySizes(obj, maxSize) {
    if (Array.isArray(obj)) {
        const limited = obj.slice(0, maxSize);
        return limited.map(item => limitArraySizes(item, maxSize));
    }
    else if (obj !== null && typeof obj === 'object') {
        const result = {};
        for (const [key, value] of Object.entries(obj)) {
            result[key] = limitArraySizes(value, maxSize);
        }
        return result;
    }
    return obj;
}
/**
 * Checks if a content type is JSON
 */
function isJsonContentType(contentType) {
    if (!contentType) {
        return false;
    }
    const normalized = contentType.toLowerCase();
    return (normalized.includes('application/json') ||
        normalized.includes('application/ld+json') ||
        normalized.includes('application/vnd.api+json') ||
        normalized.includes('application/hal+json'));
}
/**
 * Intercepts and modifies network responses for XHR/fetch requests
 */
export class ResponseInterceptor {
    #responseLimitArraySize;
    #urlPatterns;
    #activeSessions = new WeakMap();
    #logger;
    constructor(responseLimitArraySize, urlPatterns, logger) {
        this.#responseLimitArraySize = responseLimitArraySize;
        this.#urlPatterns = urlPatterns;
        this.#logger = logger;
    }
    /**
     * Enable response interception for a page
     */
    async enableForPage(page) {
        // Check if already enabled for this page
        if (this.#activeSessions.has(page)) {
            return;
        }
        const client = await page.createCDPSession();
        this.#activeSessions.set(page, client);
        // Enable fetch domain with pattern for XHR and fetch requests
        await client.send('Fetch.enable', {
            patterns: [
                {
                    urlPattern: '*',
                    resourceType: 'XHR',
                    requestStage: 'Response',
                },
                {
                    urlPattern: '*',
                    resourceType: 'Fetch',
                    requestStage: 'Response',
                },
            ],
        });
        // Handle intercepted requests
        client.on('Fetch.requestPaused', async (event) => {
            if (!event.responseStatusCode && event.responseStatusCode !== 0) {
                try {
                    await client.send('Fetch.continueRequest', {
                        requestId: event.requestId,
                    });
                }
                catch (err) {
                    this.#logger(`ResponseInterceptor Error continuing request: ${err}`);
                }
                return;
            }
            try {
                // Check if URL matches any of the configured patterns (if patterns are configured)
                const shouldLimit = this.#urlPatterns.length === 0 ||
                    this.#urlPatterns.some(pattern => event.request.url.includes(pattern));
                // If URL doesn't match, continue with original response
                if (!shouldLimit) {
                    await client.send('Fetch.continueRequest', {
                        requestId: event.requestId,
                    });
                    return;
                }
                // Get the response body
                const { body, base64Encoded } = await client.send('Fetch.getResponseBody', {
                    requestId: event.requestId,
                });
                let modifiedBody = body;
                let shouldModify = false;
                // Check if response is JSON
                const responseHeaders = event.responseHeaders || [];
                const contentTypeHeader = responseHeaders.find(h => h.name.toLowerCase() === 'content-type');
                const contentType = contentTypeHeader?.value;
                this.#logger(`Content-Type: ${contentType || 'none'}`);
                if (contentTypeHeader && isJsonContentType(contentTypeHeader.value)) {
                    try {
                        // Decode if base64
                        const bodyText = base64Encoded
                            ? Buffer.from(body, 'base64').toString('utf-8')
                            : body;
                        // Parse and limit arrays
                        const parsed = JSON.parse(bodyText);
                        const limited = limitArraySizes(parsed, this.#responseLimitArraySize);
                        modifiedBody = JSON.stringify(limited);
                        shouldModify = true;
                    }
                    catch (err) {
                        this.#logger(`ResponseInterceptor Error parsing/limiting JSON: ${err}`);
                        // If parsing fails, use original body
                    }
                }
                if (shouldModify) {
                    // Fulfill with modified response
                    await client.send('Fetch.fulfillRequest', {
                        requestId: event.requestId,
                        responseCode: event.responseStatusCode || 200,
                        responseHeaders: event.responseHeaders,
                        body: Buffer.from(modifiedBody).toString('base64'),
                    });
                }
                else {
                    // Continue with original response
                    await client.send('Fetch.continueRequest', {
                        requestId: event.requestId,
                    });
                }
            }
            catch (error) {
                this.#logger(`ResponseInterceptor Error in Fetch.requestPaused handler: ${error}`);
                // If anything fails, continue with original request
                try {
                    await client.send('Fetch.continueRequest', {
                        requestId: event.requestId,
                    });
                }
                catch (err) {
                    this.#logger(`ResponseInterceptor Error continuing request in error handler: ${err}`);
                }
            }
        });
        // Clean up on page close
        page.once('close', () => {
            this.#activeSessions.delete(page);
            client.detach().catch(() => {
                // Ignore detach errors
            });
        });
    }
    /**
     * Disable response interception for a page
     */
    async disableForPage(page) {
        const client = this.#activeSessions.get(page);
        if (client) {
            try {
                await client.send('Fetch.disable');
                await client.detach();
            }
            catch {
                // Ignore errors
            }
            this.#activeSessions.delete(page);
        }
    }
}
