import { replaceTemplateVariables } from "../string-template.js";
import { ERRORS, normalizeError } from "../errors.js";
import { normalizeHeaders } from "../normalize-headers.js";
import { buildRequestSecurity } from "./build-request-security.js";
import { isElectron } from "../electron.js";
import { createFetchBody } from "./create-fetch-body.js";
import { createFetchHeaders } from "./create-fetch-headers.js";
import { createFetchQueryParams } from "./create-fetch-query-params.js";
import { decodeBuffer } from "./decode-buffer.js";
import { getCookieHeader, setRequestCookies } from "./set-request-cookies.js";
import { isDefined } from "@scalar/helpers/array/is-defined";
import { httpStatusCodes } from "@scalar/helpers/http/http-status-codes";
import { mergeUrls } from "@scalar/helpers/url/merge-urls";
import { redirectToProxy, shouldUseProxy } from "@scalar/helpers/url/redirect-to-proxy";
//#region src/libs/send-request/create-request-operation.ts
/** Execute the request */
var createRequestOperation = ({ environment, example, globalCookies, proxyUrl, request, securitySchemes, selectedSecuritySchemeUids = [], server, status, pluginManager }) => {
	try {
		const env = environment ?? {};
		const controller = new AbortController();
		/** Parsed and evaluated values for path parameters */
		const pathVariables = example.parameters.path.reduce((vars, param) => {
			if (param.enabled) {
				const substitutedValue = replaceTemplateVariables(param.value, env);
				vars[param.key] = encodeURIComponent(substitutedValue);
			}
			return vars;
		}, {});
		const serverString = replaceTemplateVariables(server?.url ?? "", env);
		const pathString = replaceTemplateVariables(replaceTemplateVariables(request.path, env), pathVariables);
		/**
		* Start building the main URL, we cannot use the URL class yet as it does not work with relative servers
		* Also handles the case of no server with pathString
		*/
		let url = serverString || pathString;
		if (!url) throw ERRORS.URL_EMPTY;
		Object.entries(server?.variables ?? {}).forEach(([k, v]) => {
			url = replaceTemplateVariables(url, { [k]: pathVariables[k] || v.default });
		});
		const _urlParams = createFetchQueryParams(example, env, request);
		const _headers = createFetchHeaders(example, env);
		const { body } = createFetchBody(request.method, example, env);
		const { cookieParams: _cookieParams } = setRequestCookies({
			example,
			env,
			globalCookies,
			serverUrl: url,
			proxyUrl
		});
		const security = buildRequestSecurity(selectedSecuritySchemeUids.flat().map((uid) => securitySchemes[uid]).filter(isDefined), env);
		const headers = {
			...Object.entries(security.headers).reduce((acc, [key, value]) => {
				acc[key.toLowerCase()] = value;
				return acc;
			}, {}),
			..._headers
		};
		const cookieParams = [..._cookieParams, ...security.cookies];
		const urlParams = new URLSearchParams([..._urlParams, ...security.urlParams]);
		if (isElectron() && headers["user-agent"]) headers["X-Scalar-User-Agent"] = headers["user-agent"];
		url = mergeUrls(url, pathString, urlParams);
		/** Cookie header */
		const cookieHeader = replaceTemplateVariables(getCookieHeader(cookieParams, headers["Cookie"]), env);
		if (cookieHeader) if (isElectron() || shouldUseProxy(proxyUrl, url)) {
			console.warn("We're using a `X-Scalar-Cookie` custom header to the request. The proxy will forward this as a `Cookie` header. We do this to avoid the browser omitting the `Cookie` header for cross-origin requests for security reasons.");
			headers["X-Scalar-Cookie"] = cookieHeader;
		} else {
			console.warn(`We're trying to add a Cookie header, but browsers often omit them for cross-origin requests for various security reasons. If it's not working, that's probably why. Here are the requirements for it to work:

          - The browser URL must be on the same domain as the server URL.
          - The connection must be made over HTTPS.
          `);
			headers["Cookie"] = cookieHeader;
		}
		const proxiedUrl = redirectToProxy(proxyUrl, url);
		const proxiedRequest = new Request(proxiedUrl, {
			method: request.method.toUpperCase(),
			body: body ?? null,
			headers
		});
		const sendRequest = async () => {
			status?.emit("start");
			if (pluginManager) try {
				await pluginManager.executeHook("onBeforeRequest", { request: proxiedRequest });
			} catch (e) {
				const _e = new Error(ERRORS.ON_BEFORE_REQUEST_FAILED, { cause: e });
				status?.emit("abort");
				return [normalizeError(_e), null];
			}
			const startTime = Date.now();
			try {
				const response = await fetch(proxiedRequest, { signal: controller.signal });
				/**
				* Checks if the response is streaming
				* Unfortunately we cannot check the transfer-encoding header as it is not set by the browser so not quite sure how to test when
				* content-type === 'text/plain' and transfer-encoding === 'chunked'
				*
				* Currently we are only checking for server sent events. In OpenApi 3.2.0 streams will be added to the spec
				*/
				const isStreaming = response.headers.get("content-type")?.startsWith("text/event-stream");
				status?.emit("stop");
				const duration = Date.now() - startTime;
				const responseToRead = response.clone();
				const responseHeaders = normalizeHeaders(response.headers, shouldUseProxy(proxyUrl, url));
				const responseType = response.headers.get("content-type") ?? "text/plain;charset=UTF-8";
				const arrayBuffer = await responseToRead.arrayBuffer();
				const responseData = decodeBuffer(arrayBuffer, responseType);
				const clonedResponse = response.clone();
				const statusText = clonedResponse.statusText || httpStatusCodes[clonedResponse.status]?.name || "";
				const shouldSkipBody = [
					204,
					205,
					304
				].includes(clonedResponse.status);
				const normalizedResponse = new Response(!shouldSkipBody ? clonedResponse.body : null, {
					status: clonedResponse.status,
					statusText,
					headers: clonedResponse.headers
				});
				if (pluginManager) await pluginManager.executeHook("onResponseReceived", {
					response: normalizedResponse,
					operation: request
				});
				const cookieHeaderKeys = "getSetCookie" in normalizedResponse.headers && typeof normalizedResponse.headers.getSetCookie === "function" ? normalizedResponse.headers.getSetCookie() : [];
				if (isStreaming && response.body) return [null, {
					timestamp: Date.now(),
					request: example,
					response: {
						...normalizedResponse,
						headers: responseHeaders,
						cookieHeaderKeys,
						reader: response.body?.getReader(),
						duration,
						method: request.method,
						path: pathString
					}
				}];
				return [null, {
					timestamp: Date.now(),
					request: example,
					response: {
						...response,
						headers: responseHeaders,
						cookieHeaderKeys,
						data: responseData,
						size: arrayBuffer.byteLength,
						duration: Date.now() - startTime,
						method: request.method,
						status: response.status,
						path: pathString
					}
				}];
			} catch (e) {
				status?.emit("abort");
				return [normalizeError(e, ERRORS.REQUEST_FAILED), null];
			}
		};
		return [null, {
			request: proxiedRequest,
			sendRequest,
			controller
		}];
	} catch (e) {
		console.error(e);
		status?.emit("abort");
		return [normalizeError(e), null];
	}
};
//#endregion
export { createRequestOperation };

//# sourceMappingURL=create-request-operation.js.map