/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 * */
import { isUtf8 } from 'node:buffer';
import { DevTools, } from '../third_party/index.js';
const BODY_CONTEXT_SIZE_LIMIT = 10000;
export class NetworkFormatter {
    #request;
    #options;
    #requestBody;
    #responseBody;
    #requestBodyFilePath;
    #responseBodyFilePath;
    constructor(request, options) {
        this.#request = request;
        this.#options = options;
    }
    static async from(request, options) {
        const instance = new NetworkFormatter(request, options);
        if (options.fetchData) {
            await instance.#loadDetailedData();
        }
        return instance;
    }
    async #loadDetailedData() {
        // Load Request Body
        if (this.#request.hasPostData()) {
            let data;
            try {
                data =
                    this.#request.postData() ?? (await this.#request.fetchPostData());
            }
            catch {
                // Ignore parsing errors
            }
            const requestBodyNotAvailableMessage = '<Request body not available anymore>';
            if (this.#options.requestFilePath) {
                if (!this.#options.saveFile) {
                    throw new Error('saveFile is not provided');
                }
                if (data) {
                    const result = await this.#options.saveFile(Buffer.from(data), this.#options.requestFilePath, '.network-request');
                    this.#requestBodyFilePath = result.filename;
                }
                else {
                    this.#requestBody = requestBodyNotAvailableMessage;
                }
            }
            else {
                if (data) {
                    this.#requestBody = getSizeLimitedString(data, BODY_CONTEXT_SIZE_LIMIT);
                }
                else {
                    this.#requestBody = requestBodyNotAvailableMessage;
                }
            }
        }
        // Load Response Body
        const response = this.#request.response();
        if (response) {
            const responseBodyNotAvailableMessage = '<Response body not available anymore>';
            if (this.#options.responseFilePath) {
                try {
                    const buffer = await response.buffer();
                    if (!this.#options.saveFile) {
                        throw new Error('saveFile is not provided');
                    }
                    const result = await this.#options.saveFile(buffer, this.#options.responseFilePath, '.network-response');
                    this.#responseBodyFilePath = result.filename;
                }
                catch {
                    // Flatten error handling for buffer() failure and save failure
                }
                if (!this.#responseBodyFilePath) {
                    this.#responseBody = responseBodyNotAvailableMessage;
                }
            }
            else {
                this.#responseBody = await this.#getFormattedResponseBody(response, BODY_CONTEXT_SIZE_LIMIT);
            }
        }
    }
    toString() {
        return convertNetworkRequestConciseToString(this.toJSON());
    }
    toStringDetailed() {
        return converNetworkRequestDetailedToStringDetailed(this.toJSONDetailed());
    }
    toJSON() {
        return {
            requestId: this.#options.requestId,
            method: this.#request.method(),
            url: this.#request.url(),
            status: this.#getStatusFromRequest(this.#request),
            selectedInDevToolsUI: this.#options.selectedInDevToolsUI,
        };
    }
    #redactNetworkHeaders(headers) {
        const headersList = Object.entries(headers).map(item => {
            return { name: item[0], value: item[1] };
        });
        const redacted = DevTools.NetworkRequestFormatter.sanitizeHeaders(headersList);
        return redacted.reduce((acc, item) => {
            acc[item.name] = item.value;
            return acc;
        }, {});
    }
    toJSONDetailed() {
        const redirectChain = this.#request.redirectChain();
        const formattedRedirectChain = redirectChain.reverse().map(request => {
            const id = this.#options.requestIdResolver
                ? this.#options.requestIdResolver(request)
                : undefined;
            const formatter = new NetworkFormatter(request, {
                requestId: id,
                saveFile: this.#options.saveFile,
                redactNetworkHeaders: this.#options.redactNetworkHeaders,
            });
            return formatter.toJSON();
        });
        const responseHeaders = this.#request.response()?.headers();
        return {
            ...this.toJSON(),
            requestHeaders: this.#options.redactNetworkHeaders
                ? this.#redactNetworkHeaders(this.#request.headers())
                : this.#request.headers(),
            requestBody: this.#requestBody,
            requestBodyFilePath: this.#requestBodyFilePath,
            responseHeaders: this.#options.redactNetworkHeaders && responseHeaders
                ? this.#redactNetworkHeaders(responseHeaders)
                : this.#request.response()?.headers(),
            responseBody: this.#responseBody,
            responseBodyFilePath: this.#responseBodyFilePath,
            failure: this.#request.failure()?.errorText,
            redirectChain: formattedRedirectChain.length
                ? formattedRedirectChain
                : undefined,
        };
    }
    #getStatusFromRequest(request) {
        const httpResponse = request.response();
        const failure = request.failure();
        let status;
        if (httpResponse) {
            status = httpResponse.status().toString();
        }
        else if (failure) {
            status = failure.errorText;
        }
        else {
            status = 'pending';
        }
        return status;
    }
    async #getFormattedResponseBody(httpResponse, sizeLimit = BODY_CONTEXT_SIZE_LIMIT) {
        try {
            const responseBuffer = await httpResponse.buffer();
            if (isUtf8(responseBuffer)) {
                const responseAsTest = responseBuffer.toString('utf-8');
                if (responseAsTest.length === 0) {
                    return '<empty response>';
                }
                return getSizeLimitedString(responseAsTest, sizeLimit);
            }
            return '<binary data>';
        }
        catch {
            return '<not available anymore>';
        }
    }
}
function getSizeLimitedString(text, sizeLimit) {
    if (text.length > sizeLimit) {
        return text.substring(0, sizeLimit) + '... <truncated>';
    }
    return text;
}
function convertNetworkRequestConciseToString(data) {
    // TODO truncate the URL
    return `reqid=${data.requestId} ${data.method} ${data.url} [${data.status}]${data.selectedInDevToolsUI ? ` [selected in the DevTools Network panel]` : ''}`;
}
function formatHeadlers(headers) {
    const response = [];
    for (const [name, value] of Object.entries(headers)) {
        response.push(`- ${name}:${value}`);
    }
    return response;
}
function converNetworkRequestDetailedToStringDetailed(data) {
    const response = [];
    response.push(`## Request ${data.url}`);
    response.push(`Status: ${data.status}`);
    response.push(`### Request Headers`);
    for (const line of formatHeadlers(data.requestHeaders)) {
        response.push(line);
    }
    if (data.requestBody) {
        response.push(`### Request Body`);
        response.push(data.requestBody);
    }
    else if (data.requestBodyFilePath) {
        response.push(`### Request Body`);
        response.push(`Saved to ${data.requestBodyFilePath}.`);
    }
    if (data.responseHeaders) {
        response.push(`### Response Headers`);
        for (const line of formatHeadlers(data.responseHeaders)) {
            response.push(line);
        }
    }
    if (data.responseBody) {
        response.push(`### Response Body`);
        response.push(data.responseBody);
    }
    else if (data.responseBodyFilePath) {
        response.push(`### Response Body`);
        response.push(`Saved to ${data.responseBodyFilePath}.`);
    }
    if (data.failure) {
        response.push(`### Request failed with`);
        response.push(data.failure);
    }
    const redirectChain = data.redirectChain;
    if (redirectChain?.length) {
        response.push(`### Redirect chain`);
        let indent = 0;
        for (const request of redirectChain.reverse()) {
            response.push(`${'  '.repeat(indent)}${convertNetworkRequestConciseToString(request)}`);
            indent++;
        }
    }
    return response.join('\n');
}
