"use strict";
/* Socket Lib - Built with rolldown */
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const require_primordials_error = require('../primordials/error.js');
const require_primordials_date = require('../primordials/date.js');
const require_primordials_object = require('../primordials/object.js');
const require_primordials_json = require('../primordials/json.js');
const require_primordials_promise = require('../primordials/promise.js');
const require_node_http = require('../node/http.js');
const require_node_https = require('../node/https.js');
const require_primordials_url = require('../primordials/url.js');
const require_http_request_errors = require('./errors.js');
const require_http_request_user_agent = require('./user-agent.js');

//#region src/http-request/request-attempt.ts
/**
* @file Single HTTP request attempt — the workhorse beneath the retrying
*   `httpRequest` orchestrator. Split out of `http-request/request.ts` for size
*   hygiene. Handles:
*
*   - Native `http`/`https` request issuance
*   - Redirect chasing (with cross-origin auth-header stripping + HTTPS→HTTP
*     downgrade refusal)
*   - Stream-mode bodies (Readable / form-data duck-typed)
*   - `maxResponseSize` enforcement
*   - Hook lifecycle (`onRequest` pre-issue, `onResponse` settle-or-error)
*     Exported (not private) so `download.ts` can drive it in stream mode for
*     its own download-attempt loop.
*/
/**
* Single HTTP request attempt (used internally by httpRequest with retry
* logic). Supports hooks (fire per-attempt), maxResponseSize, and rawResponse.
*/
async function httpRequestAttempt(url, options) {
	const { body, ca, followRedirects = true, headers = {}, hooks, maxRedirects = 5, maxResponseSize, method = "GET", signal, stream = false, timeout = 3e4 } = {
		__proto__: null,
		...options
	};
	const startTime = require_primordials_date.DateNow();
	const streamHeaders = body && typeof body === "object" && "getHeaders" in body && typeof body.getHeaders === "function" ? body.getHeaders() : void 0;
	const mergedHeaders = {
		...stream ? void 0 : { "Accept-Encoding": "gzip, br" },
		"User-Agent": require_http_request_user_agent.getSocketCallerUserAgent(),
		...streamHeaders,
		...headers
	};
	hooks?.onRequest?.({
		method,
		url,
		headers: mergedHeaders,
		timeout
	});
	return await new require_primordials_promise.PromiseCtor((resolve, reject) => {
		let settled = false;
		const resolveOnce = (response) => {
			/* c8 ignore start */
			if (settled) return;
			/* c8 ignore stop */
			settled = true;
			resolve(response);
		};
		const rejectOnce = (err) => {
			/* c8 ignore start */
			if (settled) return;
			/* c8 ignore stop */
			settled = true;
			if (body && typeof body === "object" && typeof body.destroy === "function") body.destroy();
			emitResponse({ error: err });
			reject(err);
		};
		const parsedUrl = new require_primordials_url.URLCtor(url);
		const isHttps = parsedUrl.protocol === "https:";
		const httpModule = isHttps ? /* @__PURE__ */ require_node_https.getNodeHttps() : /* @__PURE__ */ require_node_http.getNodeHttp();
		const requestOptions = {
			headers: mergedHeaders,
			hostname: parsedUrl.hostname,
			method,
			path: parsedUrl.pathname + parsedUrl.search,
			port: parsedUrl.port,
			timeout
		};
		/* c8 ignore next 3 */
		if (ca && isHttps) requestOptions["ca"] = ca;
		if (signal) requestOptions["signal"] = signal;
		const emitResponse = (info) => {
			try {
				hooks?.onResponse?.({
					duration: require_primordials_date.DateNow() - startTime,
					method,
					url,
					...info
				});
			} catch {}
		};
		/* c8 ignore start - External HTTP/HTTPS request */
		const request = httpModule.request(requestOptions, (res) => {
			if (followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
				res.resume();
				emitResponse({
					headers: res.headers,
					status: res.statusCode,
					statusText: res.statusMessage
				});
				if (maxRedirects <= 0) {
					settled = true;
					reject(new require_primordials_error.ErrorCtor(`Too many redirects (exceeded maximum: ${maxRedirects})`));
					return;
				}
				const redirectUrl = res.headers.location.startsWith("http") ? res.headers.location : new URL(res.headers.location, url).toString();
				const redirectParsed = new URL(redirectUrl);
				if (isHttps && redirectParsed.protocol !== "https:") {
					settled = true;
					reject(new require_primordials_error.ErrorCtor(`Redirect from HTTPS to HTTP is not allowed: ${redirectUrl}`));
					return;
				}
				let redirectHeaders = headers;
				if (new URL(url).origin !== redirectParsed.origin) {
					redirectHeaders = { __proto__: null };
					const stripped = new Set([
						"authorization",
						"cookie",
						"proxy-authenticate",
						"proxy-authorization"
					]);
					for (const key of require_primordials_object.ObjectKeys(headers)) if (!stripped.has(key.toLowerCase())) redirectHeaders[key] = headers[key];
				}
				settled = true;
				resolve(httpRequestAttempt(redirectUrl, {
					body,
					ca,
					followRedirects,
					headers: redirectHeaders,
					hooks,
					maxRedirects: maxRedirects - 1,
					maxResponseSize,
					method,
					stream,
					timeout
				}));
				return;
			}
			if (stream) {
				const status = res.statusCode || 0;
				const statusText = res.statusMessage || "";
				const ok = status >= 200 && status < 300;
				emitResponse({
					headers: res.headers,
					status,
					statusText
				});
				const emptyBody = Buffer.alloc(0);
				resolveOnce({
					arrayBuffer: () => emptyBody.buffer,
					body: emptyBody,
					headers: res.headers,
					json: () => {
						throw new require_primordials_error.ErrorCtor("Cannot parse JSON from a streaming response");
					},
					ok,
					rawResponse: res,
					status,
					statusText,
					text: () => ""
				});
				return;
			}
			const chunks = [];
			let totalBytes = 0;
			res.on("data", (chunk) => {
				totalBytes += chunk.length;
				if (maxResponseSize && totalBytes > maxResponseSize) {
					res.destroy();
					request.destroy();
					rejectOnce(new require_primordials_error.ErrorCtor(`Response exceeds maximum size limit (${(totalBytes / (1024 * 1024)).toFixed(2)}MB > ${(maxResponseSize / (1024 * 1024)).toFixed(2)}MB)`));
					return;
				}
				chunks.push(chunk);
			});
			res.on("end", () => {
				if (settled) return;
				const responseBody = Buffer.concat(chunks);
				const ok = res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 300;
				const response = {
					arrayBuffer() {
						return responseBody.buffer.slice(responseBody.byteOffset, responseBody.byteOffset + responseBody.byteLength);
					},
					body: responseBody,
					headers: res.headers,
					json() {
						return require_primordials_json.JSONParse(responseBody.toString("utf8"));
					},
					ok,
					rawResponse: res,
					status: res.statusCode || 0,
					statusText: res.statusMessage || "",
					text() {
						return responseBody.toString("utf8");
					}
				};
				emitResponse({
					headers: res.headers,
					status: res.statusCode,
					statusText: res.statusMessage
				});
				resolveOnce(response);
			});
			res.on("error", (error) => {
				rejectOnce(error);
			});
		});
		request.on("error", (error) => {
			rejectOnce(new require_primordials_error.ErrorCtor(require_http_request_errors.enrichErrorMessage(url, method, error), { cause: error }));
		});
		request.on("timeout", () => {
			request.destroy();
			rejectOnce(new require_primordials_error.ErrorCtor(`${method} request timed out after ${timeout}ms: ${url}\n→ Server did not respond in time.\n→ Try: Increase timeout or check network connectivity.`));
		});
		if (body) {
			if (typeof body === "object" && typeof body.pipe === "function") {
				const stream = body;
				stream.on("error", (err) => {
					request.destroy();
					rejectOnce(err);
				});
				stream.pipe(request);
				return;
			}
			request.write(body);
			request.end();
		} else request.end();
		/* c8 ignore stop */
	});
}

//#endregion
exports.httpRequestAttempt = httpRequestAttempt;