import { errorPlugin, wrapFetch } from "../_chunks/_plugins.mjs";
import { HOST_RE, forwardedHopValue, resolveClientIP, trustedHops } from "../_chunks/_trust-proxy.mjs";
function awsRequest(event, context, trustProxy) {
	const sourceIp = awsEventIP(event);
	const forwardedFor = awsForwardedFor(event);
	const hops = trustedHops(trustProxy, sourceIp, forwardedFor);
	const req = new Request(awsEventURL(event, hops), {
		method: awsEventMethod(event),
		headers: awsEventHeaders(event),
		body: awsEventBody(event)
	});
	req.runtime = {
		name: "aws-lambda",
		awsLambda: {
			event,
			context
		}
	};
	req.ip = resolveClientIP(trustProxy, sourceIp, forwardedFor);
	return req;
}
function awsForwardedFor(event) {
	return event.headers["X-Forwarded-For"] || event.headers["x-forwarded-for"];
}
function awsEventMethod(event) {
	return event.httpMethod || event.requestContext?.http?.method || "GET";
}
function awsEventIP(event) {
	return event.requestContext?.http?.sourceIp || event.requestContext?.identity?.sourceIp;
}
function awsEventURL(event, hops) {
	const rawPath = event.path || event.rawPath || "/";
	const path = rawPath[0] === "/" ? rawPath : `/${rawPath}`;
	const query = awsEventQuery(event);
	const forwardedHost = forwardedHopValue(event.headers["X-Forwarded-Host"] || event.headers["x-forwarded-host"], hops);
	const host = (forwardedHost && HOST_RE.test(forwardedHost) ? forwardedHost : void 0) || event.headers.host || event.headers.Host || event.requestContext?.domainName;
	const hostname = host && HOST_RE.test(host) ? host : "_invalid_";
	const protocol = forwardedHopValue(event.headers["X-Forwarded-Proto"] || event.headers["x-forwarded-proto"], hops) === "http" ? "http" : "https";
	return new URL(`${protocol}://${hostname}${path}${query ? `?${query}` : ""}`);
}
function awsEventQuery(event) {
	if (typeof event.rawQueryString === "string") return event.rawQueryString;
	return stringifyQuery({
		...event.queryStringParameters,
		...event.multiValueQueryStringParameters
	});
}
function awsEventHeaders(event) {
	const headers = new Headers();
	for (const [key, value] of Object.entries(event.headers)) if (value) headers.set(key, value);
	const cookies = "cookies" in event && event.cookies?.length ? event.cookies : void 0;
	if (cookies) {
		headers.delete("cookie");
		for (const cookie of cookies) headers.append("cookie", cookie);
	}
	return headers;
}
function awsEventBody(event) {
	if (!event.body) return;
	if (event.isBase64Encoded) return Buffer.from(event.body || "", "base64");
	return event.body;
}
function awsResponseHeaders(response, event) {
	const headers = Object.create(null);
	for (const [key, value] of response.headers) if (value) headers[key] = Array.isArray(value) ? value.join(",") : String(value);
	const cookies = response.headers.getSetCookie();
	if (cookies.length === 0) return { headers };
	return event?.version === "2.0" || !!event?.requestContext?.http ? {
		headers,
		cookies
	} : {
		headers,
		cookies,
		multiValueHeaders: { "set-cookie": cookies }
	};
}
async function awsResponseBody(response) {
	if (!response.body) return { body: "" };
	const buffer = await toBuffer(response.body);
	return isTextType(response.headers.get("content-type") || "") ? { body: buffer.toString("utf8") } : {
		body: buffer.toString("base64"),
		isBase64Encoded: true
	};
}
async function awsStreamResponse(response, responseStream, event) {
	const metadata = {
		statusCode: response.status,
		...awsResponseHeaders(response, event)
	};
	if (!metadata.headers["transfer-encoding"]) metadata.headers["transfer-encoding"] = "chunked";
	const writer = globalThis.awslambda.HttpResponseStream.from(responseStream, metadata);
	const body = response.body ?? new ReadableStream({ start(controller) {
		controller.enqueue("");
		controller.close();
	} });
	try {
		await streamToNodeStream(body, writer);
	} finally {
		writer.end();
	}
}
async function streamToNodeStream(body, writer) {
	const reader = body.getReader();
	try {
		let result = await reader.read();
		while (!result.done) {
			if (!writer.write(result.value)) await new Promise((resolve) => writer.once("drain", resolve));
			result = await reader.read();
		}
	} finally {
		reader.releaseLock();
	}
}
function isTextType(contentType = "") {
	return /^text\/|\/(javascript|json|xml)|utf-?8/i.test(contentType);
}
function toBuffer(data) {
	return new Promise((resolve, reject) => {
		const chunks = [];
		data.pipeTo(new WritableStream({
			write(chunk) {
				chunks.push(chunk);
			},
			close() {
				resolve(Buffer.concat(chunks));
			},
			abort(reason) {
				reject(reason);
			}
		})).catch(reject);
	});
}
function stringifyQuery(obj) {
	const params = new URLSearchParams();
	for (const [key, value] of Object.entries(obj)) {
		if (value == null) continue;
		if (Array.isArray(value)) for (const v of value) params.append(key, String(v));
		else params.append(key, String(value));
	}
	return params.toString();
}
async function requestToAwsEvent(request) {
	const url = new URL(request.url);
	const headers = {};
	const cookies = [];
	for (const [key, value] of request.headers) {
		if (key.toLowerCase() === "cookie") cookies.push(...splitCookieHeader(value));
		headers[key] = value;
	}
	let body;
	let isBase64Encoded = false;
	if (request.body) {
		const buffer = await toBuffer(request.body);
		if (isTextType(request.headers.get("content-type") || "")) body = buffer.toString("utf8");
		else {
			body = buffer.toString("base64");
			isBase64Encoded = true;
		}
	}
	const now = Date.now();
	return {
		httpMethod: request.method,
		path: url.pathname,
		resource: url.pathname,
		queryStringParameters: Object.fromEntries(url.searchParams),
		multiValueQueryStringParameters: parseMultiValueQuery(url.searchParams),
		pathParameters: void 0,
		stageVariables: void 0,
		multiValueHeaders: Object.fromEntries([...request.headers].map(([k, v]) => [k, [v]])),
		version: "2.0",
		rawPath: url.pathname,
		rawQueryString: url.search.slice(1),
		cookies: cookies.length > 0 ? cookies : void 0,
		routeKey: `${request.method} ${url.pathname}`,
		headers,
		body: body ?? null,
		isBase64Encoded,
		requestContext: {
			accountId: "000000000000",
			apiId: "local",
			resourceId: "local",
			stage: "$default",
			requestId: crypto.randomUUID(),
			identity: {
				sourceIp: "127.0.0.1",
				userAgent: request.headers.get("user-agent") || "",
				accessKey: null,
				accountId: null,
				apiKey: null,
				apiKeyId: null,
				caller: null,
				clientCert: null,
				cognitoAuthenticationProvider: null,
				cognitoAuthenticationType: null,
				cognitoIdentityId: null,
				cognitoIdentityPoolId: null,
				principalOrgId: null,
				user: null,
				userArn: null
			},
			resourcePath: url.pathname,
			httpMethod: request.method,
			path: url.pathname,
			protocol: "HTTP/1.1",
			requestTimeEpoch: now,
			authorizer: void 0,
			domainName: url.hostname,
			http: {
				method: request.method,
				path: url.pathname,
				protocol: "HTTP/1.1",
				sourceIp: "127.0.0.1",
				userAgent: request.headers.get("user-agent") || ""
			},
			routeKey: `${request.method} ${url.pathname}`,
			time: new Date(now).toISOString(),
			timeEpoch: now,
			domainPrefix: url.hostname.split(".")[0]
		}
	};
}
function splitCookieHeader(value) {
	return value.split(";").map((c) => c.trim()).filter(Boolean);
}
function parseMultiValueQuery(params) {
	const result = {};
	for (const [key, value] of params) {
		if (!result[key]) result[key] = [];
		result[key].push(value);
	}
	return result;
}
function awsResultToResponse(result) {
	if (typeof result === "string") return new Response(result, { status: 200 });
	const headers = new Headers();
	if (result.headers) {
		for (const [key, value] of Object.entries(result.headers)) if (value !== void 0) headers.set(key, String(value));
	}
	if ("multiValueHeaders" in result && result.multiValueHeaders) {
		for (const [key, values] of Object.entries(result.multiValueHeaders)) if (values) for (const value of values) headers.append(key, String(value));
	}
	if ("cookies" in result && result.cookies) for (const cookie of result.cookies) headers.append("set-cookie", cookie);
	let body;
	if (typeof result.body === "string") {
		if (result.isBase64Encoded) body = Buffer.from(result.body, "base64");
		else body = result.body;
	}
	const statusCode = typeof result.statusCode === "number" ? result.statusCode : 200;
	return new Response(body, {
		status: statusCode,
		headers
	});
}
function createMockContext() {
	const id = crypto.randomUUID();
	return {
		callbackWaitsForEmptyEventLoop: true,
		functionName: "local",
		functionVersion: "$LATEST",
		invokedFunctionArn: `arn:aws:lambda:us-east-1:000000000000:function:local`,
		memoryLimitInMB: "128",
		awsRequestId: id,
		logGroupName: "/aws/lambda/local",
		logStreamName: `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}/[$LATEST]${id}`,
		getRemainingTimeInMillis: () => 3e4,
		done: () => {},
		fail: () => {},
		succeed: () => {}
	};
}
function toLambdaHandler(options) {
	const server = new AWSLambdaServer(options);
	return (event, context) => server.fetch(event, context);
}
async function handleLambdaEvent(fetchHandler, event, context, trustProxy) {
	const response = await fetchHandler(awsRequest(event, context, trustProxy));
	return {
		statusCode: response.status,
		...awsResponseHeaders(response, event),
		...await awsResponseBody(response)
	};
}
async function handleLambdaEventWithStream(fetchHandler, event, responseStream, context, trustProxy) {
	await awsStreamResponse(await fetchHandler(awsRequest(event, context, trustProxy)), responseStream, event);
}
async function invokeLambdaHandler(handler, request) {
	return awsResultToResponse(await handler(await requestToAwsEvent(request), createMockContext()));
}
var AWSLambdaServer = class {
	runtime = "aws-lambda";
	options;
	fetch;
	constructor(options) {
		this.options = {
			...options,
			middleware: [...options.middleware || []]
		};
		for (const plugin of options.plugins || []) plugin(this);
		errorPlugin(this);
		const fetchHandler = wrapFetch(this);
		this.fetch = (event, context) => handleLambdaEvent(fetchHandler, event, context, this.options.trustProxy);
	}
	serve() {}
	ready() {
		return Promise.resolve().then(() => this);
	}
	close() {
		return Promise.resolve();
	}
};
export { handleLambdaEvent, handleLambdaEventWithStream, invokeLambdaHandler, toLambdaHandler };
