import { EventEmitter } from 'events';
import { HttpRequest } from '../../models/HttpRequest.js';
import { SentRequest } from '../../models/SentRequest.js';
import { Response } from '../../models/Response.js';
import { ErrorResponse } from '../../models/ErrorResponse.js';
import { RequestsSize } from '../../models/RequestsSize.js';
import { HttpResponse } from '../../models/HttpResponse.js';
import { ResponseRedirect } from '../../models/ResponseRedirect.js';
import { RequestLog } from '../../models/RequestLog.js';
import { SerializableError } from '../../models/SerializableError.js';
import { ResponseAuthorization } from '../../models/ResponseAuthorization.js';
import { Headers } from '../../lib/headers/Headers.js';
import { getPort } from './RequestUtils.js';
import { HttpResponseParser, RequestState } from './parsers/index.js';
import { ConnectionManager } from './connections/index.js';
import { AuthManager } from './auth/index.js';
import { MessageBuilder } from './message/index.js';
import { HttpEngineErrorFactory } from './errors/index.js';
import { isMethodWithoutBody, HEADER_PROXY_AUTHORIZATION } from './constants.js';
import { decompress } from './compression/index.js';
import { checkServerIdentity } from './certificates/index.js';
import { readUrl, getHostHeader, getRedirectLocation, isRedirectLoop } from './url/index.js';
import { processRedirectCookies } from './cookies/index.js';
import { computeStats } from './statistics/index.js';
import { processResponse } from './response/index.js';
import { getCodeMessage } from './HttpErrorCodes.js';
import { createLogger } from '../../lib/logging/index.js';
import * as PayloadSupport from './PayloadSupport.js';
import { addContentLength } from './RequestUtils.js';
import { PayloadSerializer } from '../../lib/transformers/PayloadSerializer.js';
import { ProxyAuthenticationError } from './errors/HttpEngineErrors.js';
export const mainPromiseSymbol = Symbol('mainPromise');
/**
 * API Client's HTTP engine with refactored parsing and connection handling.
 * An HTTP 1.1 engine working directly on the socket. It communicates with the remote machine and
 * collects stats about the request and response.
 */
export class CoreEngine extends EventEmitter {
    request;
    opts;
    logger;
    /**
     * The current sent request
     */
    sentRequest;
    redirects = [];
    /**
     * When true the request has been aborted.
     */
    aborted = false;
    /**
     * Parsed value of the request URL.
     */
    uri;
    socket;
    /**
     * Host header can be different than registered URL because of
     * `hosts` rules.
     * If a rule changes host value of the URL the original URL host value
     * is used when generating the request and not overwritten one.
     * This way virtual hosts can be tested using hosts.
     */
    hostHeader;
    hostTestReg = /^\s*host\s*:/im;
    /**
     * Set when the request is redirected.
     */
    redirecting = false;
    /**
     * The response headers.
     * The object may be empty when the response is not set.
     */
    currentHeaders = new Headers();
    /**
     * The response object build during the execution.
     */
    currentResponse;
    /**
     * Keeps the raw body in a temporary buffer while processing the response.
     */
    _rawBody;
    stats = {};
    auth;
    mainResolver;
    mainRejecter;
    [mainPromiseSymbol];
    _signal;
    // CoreEngine specific properties
    parser;
    connectionManager;
    authManager;
    messageBuilder;
    hasProxy;
    isProxyTunnel;
    isProxySsl;
    /**
     * @return True if following redirects is allowed.
     */
    get followRedirects() {
        const { opts } = this;
        if (typeof opts.followRedirects === 'boolean') {
            return opts.followRedirects;
        }
        return true;
    }
    /**
     * The request timeout.
     */
    get timeout() {
        const { opts } = this;
        if (typeof opts.timeout === 'number') {
            return opts.timeout;
        }
        return 0;
    }
    /**
     * The abort signal to set on this request.
     * Aborts the request when the signal fires.
     */
    get signal() {
        return this._signal;
    }
    set signal(value) {
        const old = this._signal;
        if (old === value) {
            return;
        }
        this._signal = value;
        if (old) {
            old.removeEventListener('abort', this._abortHandler);
        }
        if (value) {
            value.addEventListener('abort', this._abortHandler);
        }
    }
    constructor(request, opts = {}) {
        super();
        this.request = new HttpRequest({ ...request });
        this.opts = opts;
        this.logger = this.setupLogger(opts);
        this.sentRequest = new SentRequest({ ...request, startTime: Date.now() });
        this.uri = this.readUrl(request.url);
        this.hostHeader = this.getHostHeader(request.url);
        // CoreEngine specific initialization
        this.hasProxy = !!this.opts.proxy;
        this.isProxyTunnel = this.hasProxy && this.request.url.startsWith('https:');
        this.isProxySsl = !!this.opts.proxy && this.opts.proxy.startsWith('https:');
        // Initialize the HTTP response parser
        this.parser = new HttpResponseParser(this.logger, this.createParserCallbacks());
        // Initialize the connection manager
        this.connectionManager = new ConnectionManager(this.logger, this.stats);
        // Initialize the auth manager
        const proxyCredentials = {
            proxyUsername: this.opts.proxyUsername,
            proxyPassword: this.opts.proxyPassword,
            proxyAuthorization: this.opts.proxyAuthorization,
        };
        this.authManager = new AuthManager({
            logger: this.logger,
            proxyCredentials,
        });
        // Initialize the message builder
        this.messageBuilder = new MessageBuilder({
            logger: this.logger,
            request: this.request,
            hasProxy: this.hasProxy,
            isProxyTunnel: this.isProxyTunnel,
            hostHeader: this.hostHeader,
        });
        this._abortHandler = this._abortHandler.bind(this);
        if (opts.signal) {
            this.signal = opts.signal;
        }
    }
    /**
     * Creates a logger object to log debug output.
     */
    setupLogger(opts = {}) {
        if (opts.logger) {
            return opts.logger;
        }
        return createLogger();
    }
    /**
     * Updates the `uri` property from current request URL
     */
    readUrl(value) {
        return readUrl(value, { hosts: this.opts.hosts });
    }
    /**
     * Get host header for the request
     */
    getHostHeader(url) {
        return getHostHeader(url, { hosts: this.opts.hosts });
    }
    /**
     * Aborts current request.
     * It emits `error` event
     */
    abort() {
        this.logger.debug('Aborting the request...');
        this.aborted = true;
        if (!this.socket) {
            return;
        }
        this.socket.removeAllListeners();
        if (this.socket.destroyed) {
            this.socket = undefined;
            return;
        }
        this.socket.pause();
        this.socket.destroy();
        this.socket = undefined;
    }
    /**
     * Handler for the `abort` event on the `AbortSignal`.
     */
    _abortHandler() {
        const e = new SerializableError('Request aborted', 3);
        this._errorRequest(e);
        this.abort();
    }
    /**
     * Create parser callbacks to handle parsed data
     */
    createParserCallbacks() {
        return {
            onStatusParsed: (status, statusText) => {
                this.logger.debug('onStatusParsed called', status, statusText);
                const response = Response.fromValues(status, statusText);
                response.loadingTime = 0;
                this.currentResponse = response;
            },
            onHeadersParsed: (headers) => {
                this.logger.debug('onHeadersParsed called');
                this.currentHeaders = headers;
                if (this.currentResponse) {
                    this.currentResponse.headers = headers.toString();
                }
            },
            onBodyComplete: (body) => {
                this.logger.debug('onBodyComplete called');
                this._rawBody = body;
                this._reportResponse().catch((error) => {
                    this.logger.error('Error in _reportResponse:', error);
                    this._errorRequest({
                        message: `Response processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
                    });
                });
            },
            onError: (error) => {
                this.logger.debug('onError called', error);
                this._errorRequest({
                    message: error.message || 'Unknown error occurred',
                });
            },
            onAbort: () => {
                this.logger.debug('onAbort called');
                this.abort();
            },
            emit: (event, detail) => {
                this.logger.debug('emit called', event, detail);
                this.emit(event, detail);
            },
        };
    }
    /**
     * Sends the request
     */
    async send() {
        const promise = this.wrapExecution();
        this.sendRequest();
        return promise;
    }
    async sendRequest() {
        try {
            // Use the connection manager to establish the connection
            const socket = await this.establishConnection();
            if (!socket || this.aborted) {
                return;
            }
            const message = await this.prepareMessage();
            await this.writeMessage(message);
        }
        catch (cause) {
            if (cause instanceof ProxyAuthenticationError) {
                this.currentHeaders = new Headers(cause.response.headers);
                const currentResponse = Response.fromValues(cause.response.statusCode || 401, cause.response.statusMessage, this.currentHeaders.toString());
                currentResponse.loadingTime = 0;
                this.currentResponse = currentResponse;
                if (cause.head.length) {
                    this._rawBody = cause.head;
                    currentResponse.payload = PayloadSerializer.stringifyBuffer(cause.head);
                }
                this._publishResponse();
                return;
            }
            const e = cause;
            const err = new SerializableError(e.message, { cause: e });
            if (e.code || e.code === 0) {
                err.code = e.code;
            }
            this.abort();
            this._errorRequest({
                message: err.message,
                code: err.code,
            });
        }
    }
    /**
     * Establish connection using the connection manager
     */
    async establishConnection() {
        const port = getPort(this.uri.port, this.uri.protocol);
        const host = this.uri.hostname;
        const protocol = this.uri.protocol;
        const socket = await this.connectionManager.createConnection(host, port, protocol, this.opts);
        const { timeout } = this;
        if (timeout > 0) {
            socket.setTimeout(timeout);
        }
        this.socket = socket;
        this._addSocketListeners(socket);
        socket.resume();
        return socket;
    }
    /**
     * Decompresses received body if `content-encoding` header is set.
     */
    async decompress(body) {
        return decompress(body, {
            aborted: this.aborted,
            headers: this.currentHeaders,
        });
    }
    /**
     * Prepares headers list to be send to the remote machine.
     * If `defaultHeaders` option is set then it adds `user-agent` and `accept`
     * headers.
     */
    prepareHeaders(headers, withPayload = false) {
        if (this.opts.defaultHeaders) {
            if (!headers.has('user-agent')) {
                if (this.opts.defaultUserAgent) {
                    headers.set('user-agent', this.opts.defaultUserAgent);
                }
                else {
                    headers.set('user-agent', 'api client');
                }
            }
            if (!headers.has('accept')) {
                if (this.opts.defaultAccept) {
                    headers.set('accept', this.opts.defaultAccept);
                }
                else {
                    headers.set('accept', '*/*');
                }
            }
        }
        if (withPayload) {
            if (!headers.has('content-type') && this.request.payloadOptions) {
                if (this.request.payloadOptions.mime) {
                    headers.set('content-type', this.request.payloadOptions.mime);
                }
            }
        }
    }
    /**
     * Cleans the state after finished.
     */
    _cleanUp() {
        this.redirects = [];
        this.currentHeaders = new Headers();
        this.currentResponse = undefined;
        this._rawBody = undefined;
        this.redirecting = false;
        this.stats = {};
        this.mainRejecter = undefined;
        this.mainResolver = undefined;
        this._clearSocketEventListeners();
        this.parser.reset();
    }
    /**
     * Cleans up the state for redirect.
     */
    _cleanUpRedirect() {
        this.currentHeaders = new Headers();
        this.currentResponse = undefined;
        this._rawBody = undefined;
        this.stats = {};
        this._clearSocketEventListeners();
        this.parser.reset();
    }
    /**
     * Reports response when redirected.
     */
    _reportRedirect(status) {
        const { request, currentHeaders } = this;
        if (!currentHeaders) {
            return false;
        }
        const rerUrl = currentHeaders.get('location');
        // https://github.com/jarrodek/socket-fetch/issues/13
        const redirectOptions = this.getRedirectOptions(status, request.method, rerUrl);
        if (!redirectOptions.redirect) {
            return false;
        }
        this.redirecting = true;
        setTimeout(() => this._redirectRequest(redirectOptions));
        return true;
    }
    /**
     * Get redirect options based on status and method
     */
    getRedirectOptions(status, method, location) {
        if (!location) {
            return { redirect: false };
        }
        // 301, 302, 303, 307, 308 are redirect status codes
        if (status >= 300 && status < 400) {
            const forceGet = status === 301 || status === 302 || status === 303;
            return { redirect: true, location, forceGet };
        }
        return { redirect: false };
    }
    /**
     * Creates a response and adds it to the redirects list and redirects
     * the request to the new location.
     */
    async _redirectRequest(options) {
        if (this.followRedirects === false) {
            this._publishResponse();
            return;
        }
        const location = options.location && this.getRedirectLocation(options.location, this.request.url);
        if (!location) {
            this._errorRequest({ code: 302 });
            return;
        }
        // check if this is infinite loop
        if (this.isRedirectLoop(location, this.redirects)) {
            this._errorRequest({ code: 310 });
            return;
        }
        const detail = {
            location,
            returnValue: true,
        };
        this.emit('beforeredirect', detail);
        if (!detail.returnValue) {
            this._publishResponse();
            return;
        }
        try {
            const responseCookies = this.currentHeaders.get('set-cookie');
            const response = await this._createRedirectResponse(location);
            this.redirects.push(response);
            this._cleanUpRedirect();
            if (responseCookies) {
                this._processRedirectCookies(responseCookies, location);
            }
            this.redirecting = false;
            this.request.url = location;
            this.sentRequest.url = location;
            if (options.forceGet) {
                this.request.method = 'GET';
            }
            this.uri = this.readUrl(location);
            this.hostHeader = this.getHostHeader(location);
            // No idea why but without setTimeout the program loses it's
            // scope after calling the function.
            setTimeout(() => this.send());
        }
        catch (e) {
            const error = e;
            this._errorRequest({
                message: (error && error.message) || 'Unknown error occurred',
            });
        }
    }
    /**
     * Get redirect location
     */
    getRedirectLocation(location, currentUrl) {
        return getRedirectLocation(location, currentUrl);
    }
    /**
     * Check if redirect is a loop
     */
    isRedirectLoop(location, redirects) {
        return isRedirectLoop(location, redirects);
    }
    /**
     * @param location The redirect location.
     * @return Redirect response object
     */
    async _createRedirectResponse(location) {
        const { currentResponse = new Response() } = this;
        const response = HttpResponse.fromValues(currentResponse.status, currentResponse.statusText, currentResponse.headers);
        if (currentResponse.payload) {
            response.payload = currentResponse.payload;
        }
        const body = await this.decompress(this._rawBody);
        if (body) {
            await response.writePayload(body);
            currentResponse.payload = response.payload;
        }
        const redirect = ResponseRedirect.fromValues(location, response.toJSON(), this.stats.startTime, this.stats.responseTime);
        redirect.timings = this._computeStats(this.stats);
        return redirect;
    }
    /**
     * Creates a response object
     */
    async _createResponse() {
        if (this.aborted) {
            throw new Error(`Request aborted.`);
        }
        const { currentResponse } = this;
        if (!currentResponse) {
            throw new Error(`Tried to create a response but no response data is set.`);
        }
        const { status } = currentResponse;
        if (status === undefined) {
            throw new Error(`The response status is empty.
It means that the successful connection wasn't made.
Check your request parameters.`);
        }
        const body = await this.decompress(this._rawBody);
        const response = Response.fromValues(status, currentResponse.statusText, currentResponse.headers);
        response.timings = this._computeStats(this.stats);
        response.loadingTime = response.timings.total();
        if (body) {
            await response.writePayload(body);
            currentResponse.payload = response.payload;
        }
        if (status === 401) {
            this.logger.silly('Setting auth on the response...');
            response.auth = this._getAuth();
        }
        return response;
    }
    /**
     * Finishes the response with error message.
     */
    _errorRequest(opts) {
        const { currentResponse } = this;
        this.aborted = true;
        let message;
        if (opts.code && !opts.message) {
            message = getCodeMessage(opts.code);
        }
        else if (opts.message) {
            message = opts.message;
        }
        message = message || 'Unknown error occurred';
        const error = new SerializableError(message, opts.code);
        const log = RequestLog.fromRequest(this.sentRequest.toJSON());
        const response = ErrorResponse.fromError(error);
        log.response = response;
        if (currentResponse && currentResponse.status) {
            response.status = currentResponse.status;
            response.statusText = currentResponse.statusText;
            response.headers = currentResponse.headers;
            response.payload = currentResponse.payload;
        }
        this.finalizeRequest(log);
        this._cleanUp();
    }
    /**
     * Generates authorization info object from response.
     */
    _getAuth() {
        this.logger.debug('Getting auth from response headers');
        let auth = this.currentHeaders.get('www-authenticate');
        const result = new ResponseAuthorization();
        if (auth) {
            this.logger.silly('Auth header found', auth);
            auth = auth.toLowerCase();
            if (auth.indexOf('ntlm') !== -1) {
                this.logger.silly('Detected NTLM authorization');
                result.method = 'ntlm';
            }
            else if (auth.indexOf('basic') !== -1) {
                result.method = 'basic';
                this.logger.silly('Detected basic authorization');
            }
            else if (auth.indexOf('digest') !== -1) {
                result.method = 'digest';
                this.logger.silly('Detected digest authorization');
            }
        }
        return result;
    }
    /**
     * Creates HAR 1.2 timings object from stats.
     */
    _computeStats(stats) {
        return computeStats(stats);
    }
    /**
     * Handles cookie exchange when redirecting the request.
     */
    _processRedirectCookies(responseCookies, location) {
        this.request.headers = processRedirectCookies(responseCookies, location, this.request.url, this.request.toHeadersString());
    }
    /**
     * Checks certificate identity using TLS api.
     */
    _checkServerIdentity(host, cert) {
        return checkServerIdentity(host, cert);
    }
    /**
     * Clears event listeners of the socket object,
     */
    _clearSocketEventListeners() {
        if (!this.socket) {
            return;
        }
        this.socket.removeAllListeners('error');
        this.socket.removeAllListeners('timeout');
        this.socket.removeAllListeners('end');
    }
    /**
     * Called with the `send()` function to initialize the main promise returned by the send function.
     * The send function returns a promise that is resolved when the request finish.
     */
    wrapExecution() {
        let promise;
        if (this[mainPromiseSymbol]) {
            promise = this[mainPromiseSymbol];
        }
        else {
            promise = new Promise((resolve, reject) => {
                this.mainResolver = resolve;
                this.mainRejecter = reject;
            });
            this[mainPromiseSymbol] = promise;
        }
        return promise;
    }
    /**
     * Called by the request finalizer or error finalized to report the response.
     */
    finalizeRequest(log) {
        const { mainRejecter, mainResolver } = this;
        if (!mainRejecter || !mainResolver) {
            return;
        }
        if (log instanceof SerializableError) {
            mainRejecter(log);
        }
        else {
            mainResolver(log.toJSON());
        }
        this.mainRejecter = undefined;
        this.mainResolver = undefined;
        this[mainPromiseSymbol] = undefined;
    }
    /**
     * Prepares an HTTP message from API Client's request object.
     */
    async prepareMessage() {
        let payload = this.request.payload;
        if (isMethodWithoutBody(this.request.method)) {
            payload = undefined;
        }
        const headers = new Headers(this.request.toHeadersString());
        this.prepareHeaders(headers, !!payload);
        const auth = this.hasProxy && !this.isProxyTunnel ? this._proxyAuthHeader() : undefined;
        if (auth) {
            headers.set(HEADER_PROXY_AUTHORIZATION, auth);
        }
        const buffer = PayloadSupport.payloadToBuffer(headers, payload);
        if (buffer) {
            addContentLength(this.request.method || 'GET', buffer, headers);
        }
        this._handleAuthorization(headers);
        this.sentRequest.headers = headers.toString();
        // Update message builder with current host header
        this.messageBuilder.updateHostHeader(this.hostHeader || '');
        const message = this.messageBuilder.buildMessage(headers, buffer);
        if (this.auth) {
            // This restores altered by authorization original headers
            // so it can be safe to use when redirecting
            if (this.auth.headers) {
                this.request.headers = this.auth.headers;
                delete this.auth.headers;
            }
        }
        return message;
    }
    /**
     * Sends a data to a socket.
     */
    writeMessage(buffer) {
        this.logger.debug(`Writing the message to the socket...`);
        let msg = buffer.toString();
        const limit = this.opts.sentMessageLimit;
        if (limit && limit > 0 && msg.length >= limit) {
            msg = msg.substring(0, limit);
            msg += ' ...';
        }
        this.sentRequest.httpMessage = msg;
        const startTime = Date.now();
        this.stats.startTime = startTime;
        this.sentRequest.startTime = startTime;
        this.stats.messageStart = Date.now();
        return new Promise((resolve) => {
            this.socket?.write(buffer, () => {
                this.logger.debug(`The message has been sent.`);
                this.stats.sentTime = Date.now();
                try {
                    this.emit('loadstart');
                }
                catch {
                    //
                }
                resolve();
            });
        });
    }
    /**
     * Alters authorization header depending on the `auth` object
     */
    _handleAuthorization(headers) {
        const { authorization } = this.opts;
        this.authManager.handleRequestAuthorization(headers, authorization, this.uri.host);
        // Sync the auth state from AuthManager to parent class for compatibility
        const authState = this.authManager.getAuthState();
        if (authState) {
            this.auth = authState;
        }
    }
    /**
     * Generate proxy authorization header
     */
    _proxyAuthHeader() {
        return this.authManager.generateProxyAuthHeader();
    }
    /**
     * Add event listeners to existing socket.
     */
    _addSocketListeners(socket) {
        let received = false;
        socket.on('data', (data) => {
            this.logger.debug(`Received server data from the socket...`);
            if (!received) {
                const now = Date.now();
                this.stats.firstReceiveTime = now;
                this.emit('firstbyte');
                received = true;
            }
            data = Buffer.from(data);
            try {
                // Use the new parser instead of the old parsing methods
                this.parser.processData(data);
                this.logger.debug('Parser state:', this.parser.getState());
            }
            catch (e) {
                this.logger.error('Error in _addSocketListeners:', e);
                const err = e;
                this._errorRequest({
                    message: err.message || 'Unknown error occurred',
                });
                return;
            }
        });
        socket.once('timeout', () => {
            this.logger.debug(`Socket timeout occurred after ${this.timeout}ms.`);
            this.parser.abort();
            this._errorRequest(HttpEngineErrorFactory.connectionTimeout(this.timeout));
            socket.destroy();
        });
        socket.on('end', () => {
            const endTime = Date.now();
            this.logger.debug(`Server connection ended.`);
            socket.removeAllListeners('timeout');
            socket.removeAllListeners('error');
            socket.removeAllListeners('data');
            this.stats.lastReceivedTime = endTime;
            this.sentRequest.endTime = endTime;
            // console.log(`Socket ended at ${endTime} in state`, this.parser.getState())
            if (this.parser.getState() !== RequestState.Done) {
                if (!this.currentResponse) {
                    const message = `Connection closed without receiving any data.`;
                    this.logger.error(message);
                    const e = HttpEngineErrorFactory.connectionError(message, 100);
                    this._errorRequest(e);
                }
                else {
                    this.emit('loadend');
                    this._publishResponse();
                }
            }
        });
        socket.once('error', (err) => {
            this.logger.error(`Socket error occurred: ${err.message}`);
            socket.removeAllListeners('timeout');
            this._errorRequest(err);
        });
        return socket;
    }
    /**
     * Reports response after processing it.
     */
    async _reportResponse() {
        this._clearSocketEventListeners();
        const { aborted, currentResponse } = this;
        if (aborted || !currentResponse) {
            return;
        }
        const { status } = currentResponse;
        const endTime = Date.now();
        this.stats.lastReceivedTime = endTime;
        this.sentRequest.endTime = endTime;
        const result = processResponse({
            status,
            currentResponse,
            currentHeaders: this.currentHeaders,
            followRedirects: this.followRedirects,
            auth: this.auth,
            aborted: this.aborted,
        });
        switch (result.action) {
            case 'redirect':
                if (this._reportRedirect(status)) {
                    this.closeClient();
                    return;
                }
                break;
            case 'auth':
                if (result.data?.method === 'ntlm') {
                    await this.handleNtlmResponse();
                    return;
                }
                break;
            case 'publish':
                this.closeClient();
                this.emit('loadend');
                this._publishResponse();
                break;
            case 'abort':
                // Already aborted, do nothing
                break;
        }
    }
    /**
     * Generate response object and publish it to the listeners.
     */
    async _publishResponse() {
        if (this.aborted) {
            return;
        }
        try {
            const response = await this._createResponse();
            const result = RequestLog.fromRequestResponse(this.sentRequest.toJSON(), response.toJSON());
            if (this.redirects.length) {
                result.redirects = this.redirects;
            }
            result.size = new RequestsSize();
            if (this.sentRequest.httpMessage) {
                result.size.request = Buffer.from(this.sentRequest.httpMessage).length;
            }
            if (response.payload) {
                if (typeof response.payload === 'string') {
                    result.size.response = response.payload.length;
                }
                else {
                    result.size.response = response.payload.data.length;
                }
            }
            this.finalizeRequest(result);
        }
        catch (e) {
            const error = e;
            this.logger.error('Error in _publishResponse:', error);
            this._errorRequest({
                message: (error && error.message) || 'Unknown error occurred',
            });
        }
        this.abort();
        this._cleanUp();
    }
    /**
     * closes the connection, if any
     */
    closeClient() {
        if (this.socket && !this.socket.destroyed) {
            this.logger.debug('Closing the socket connection...');
            this.socket.destroy();
        }
    }
    /**
     * Handles the response with NTLM authorization
     */
    async handleNtlmResponse() {
        this.logger.debug('Handling NTLM response...');
        this.logger.debug('Current headers:', this.currentHeaders?.toString());
        this.logger.debug('Auth state:', this.authManager.getAuthState());
        const shouldRetry = this.authManager.handleNtlmResponse(this.currentHeaders);
        this.logger.debug('Should retry NTLM:', shouldRetry);
        if (shouldRetry) {
            // Update the parent auth state to match AuthManager state
            this.auth = this.authManager.getAuthState();
            this._cleanUpRedirect();
            try {
                const message = await this.prepareMessage();
                await this.writeMessage(message);
                // Don't return here - let the socket listeners handle the response
            }
            catch (error) {
                this.logger.error('Failed to retry NTLM authentication:', error);
                this._errorRequest({
                    message: `NTLM retry failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
                });
            }
            return;
        }
        this.logger.debug('NTLM authentication completed');
        // Clear both auth states
        this.authManager.clearAuthState();
        delete this.auth;
        this.emit('loadend');
        this._publishResponse();
    }
}
//# sourceMappingURL=CoreEngine.js.map