export const HttpResponse = Object.freeze({
    create() {
        const state = {
            statusCode: 200,
            headers: { 'Content-Type': 'application/json' },
            body: null,
            isBase64Encoded: false,
        };
        const self = {
            get statusCode() {
                return state.statusCode;
            },
            set statusCode(val) {
                state.statusCode = val;
            },
            get headers() {
                return state.headers;
            },
            set headers(val) {
                state.headers = val;
            },
            get body() {
                return state.body;
            },
            set body(val) {
                state.body = val;
            },
            get isBase64Encoded() {
                return state.isBase64Encoded;
            },
            set isBase64Encoded(val) {
                state.isBase64Encoded = val;
            },
            setStatus(code) {
                state.statusCode = code;
                return self;
            },
            setHeader(key, value) {
                state.headers[key] = value;
                return self;
            },
            setHeaders(newHeaders) {
                state.headers = { ...state.headers, ...newHeaders };
                return self;
            },
            setBody(newBody, isBase64) {
                state.body = newBody;
                state.isBase64Encoded = isBase64 ?? false;
                return self;
            },
            setJSON(data) {
                state.headers['Content-Type'] = 'application/json';
                state.body = JSON.stringify(data);
                state.isBase64Encoded = false;
                return self;
            },
            toResponse() {
                return {
                    statusCode: state.statusCode,
                    headers: state.headers,
                    body: state.body ?? undefined,
                    isBase64Encoded: state.isBase64Encoded,
                };
            },
        };
        return self;
    },
});
/**
 * Error response helper
 */
export const ErrorResponse = Object.freeze({
    create(statusCode, message, details) {
        const response = HttpResponse.create();
        response.setStatus(statusCode);
        response.setJSON({
            error: message,
            statusCode,
            timestamp: new Date().toISOString(),
            ...(details === undefined ? {} : { details }),
        });
        return response;
    },
});
/**
 * Create mock Node.js request/response objects for platform compatibility
 */
const coerceFirstForwardedFor = (value) => {
    if (Array.isArray(value)) {
        const first = value[0];
        return typeof first === 'string' && first.trim() !== '' ? first.trim() : undefined;
    }
    if (typeof value === 'string' && value.trim() !== '')
        return value.trim();
    return undefined;
};
const resolveRemoteAddress = (request) => {
    const forwardedFor = coerceFirstForwardedFor(request.headers?.['x-forwarded-for']);
    if (typeof request.remoteAddr === 'string' && request.remoteAddr.trim() !== '') {
        return request.remoteAddr.trim();
    }
    return (forwardedFor?.split(',')[0]?.trim() ?? '') || '0.0.0.0';
};
const createResponseData = () => ({
    statusCode: 200,
    headers: { 'content-type': 'application/json' },
    body: null,
});
const createMockRequest = (request, remoteAddress) => {
    const hasQuery = request.query !== undefined && request.query !== null && Object.keys(request.query).length > 0;
    const queryString = (() => {
        if (!hasQuery)
            return '';
        const params = new URLSearchParams();
        for (const [key, rawValue] of Object.entries(request.query ?? {})) {
            if (Array.isArray(rawValue)) {
                for (const value of rawValue)
                    params.append(key, value);
                continue;
            }
            params.append(key, rawValue);
        }
        return params.toString();
    })();
    const url = queryString === '' ? request.path : `${request.path}?${queryString}`;
    return {
        method: request.method,
        url,
        headers: request.headers,
        body: request.body ?? undefined,
        remoteAddress,
        socket: {
            remoteAddress,
        },
        connection: {
            remoteAddress,
        },
    };
};
const applyWriteHead = (responseData, statusCode, headers) => {
    responseData.statusCode = statusCode;
    if (headers) {
        const normalizedHeaders = {};
        for (const [key, value] of Object.entries(headers)) {
            normalizedHeaders[key.toLowerCase()] = value;
        }
        responseData.headers = { ...responseData.headers, ...normalizedHeaders };
    }
    if (responseData.headers['content-type'] === 'text/event-stream') {
        // @ts-ignore - ReadableStream/TransformStream might not be in all TS envs type definitions
        if (typeof TransformStream !== 'undefined') {
            // @ts-ignore
            const stream = new TransformStream();
            responseData.body = stream.readable;
            return stream.writable.getWriter();
        }
    }
    return null;
};
const closeWriterOnAbort = (request, response) => {
    if (!request.signal || !response._writer)
        return;
    request.signal.addEventListener('abort', () => {
        if (response._writer) {
            try {
                response._writer.close();
            }
            catch {
                // Ignore close errors
            }
        }
        response.emit('close');
    });
};
const writeToResponseWriter = (response, chunk) => {
    if (!response._writer)
        return false;
    try {
        response._writer.write(new TextEncoder().encode(String(chunk)));
        return true;
    }
    catch {
        return false;
    }
};
const emitResponseListeners = (listeners, event, args) => {
    if (listeners[event] === undefined)
        return;
    [...listeners[event]].forEach((fn) => {
        fn(...args);
    });
};
const isRequestAborted = (request) => request.signal?.aborted === true;
const resData = (responseData, request) => {
    return {
        get statusCode() {
            return responseData.statusCode;
        },
        set statusCode(value) {
            responseData.statusCode = value;
        },
        get headers() {
            return responseData.headers;
        },
        set headers(value) {
            responseData.headers = value;
        },
        writeHead: function (statusCode, headers) {
            this._writer = applyWriteHead(responseData, statusCode, headers);
            closeWriterOnAbort(request, this);
            return this;
        },
        setHeader: function (name, value) {
            responseData.headers[name.toLowerCase()] = value;
            return this;
        },
        end: function (chunk) {
            if (this._writer) {
                if (chunk !== undefined && chunk !== null) {
                    this._writer.write(new TextEncoder().encode(String(chunk)));
                }
                this._writer.close();
                return this;
            }
            if (chunk !== undefined) {
                responseData.body = chunk;
            }
            this.emit('finish');
            return this;
        },
        write: function (chunk) {
            if (this._writer) {
                return writeToResponseWriter(this, chunk);
            }
            responseData.body = chunk;
            return isRequestAborted(request) === false;
        },
        on: function (event, listener) {
            this._listeners ??= {};
            this._listeners[event] ??= [];
            this._listeners[event].push(listener);
            return this;
        },
        once: function (event, listener) {
            const wrapper = (...args) => {
                this.removeListener(event, wrapper);
                listener(...args);
            };
            return this.on(event, wrapper);
        },
        removeListener: function (event, listener) {
            if (this._listeners?.[event] !== undefined) {
                this._listeners[event] = this._listeners[event].filter((l) => l !== listener);
            }
            return this;
        },
        emit: function (event, ...args) {
            emitResponseListeners(this._listeners, event, args);
        },
        _writer: null,
        _listeners: {},
    };
};
export function createMockHttpObjects(request) {
    const remoteAddress = resolveRemoteAddress(request);
    const responseData = createResponseData();
    const req = createMockRequest(request, remoteAddress);
    const res = resData(responseData, request);
    return { req, res, responseData };
}
