import process from "node:process";
import net from "node:net";
import { Buffer } from "node:buffer";
import { isTest } from "std-env";
function getViteNodeOptionsEnvVar() {
	const envVar = process.env.NUXT_VITE_NODE_OPTIONS;
	try {
		return JSON.parse(envVar || "{}");
	} catch (e) {
		console.error("vite-node-shared: Failed to parse NUXT_VITE_NODE_OPTIONS environment variable.", e);
		return {};
	}
}
const viteNodeOptions = getViteNodeOptionsEnvVar();
const pendingRequests = /* @__PURE__ */ new Map();
let requestIdCounter = 0;
let clientSocket;
let currentConnectPromise;
const MAX_RETRY_ATTEMPTS = viteNodeOptions.maxRetryAttempts ?? 5;
const BASE_RETRY_DELAY_MS = viteNodeOptions.baseRetryDelay ?? 100;
const MAX_RETRY_DELAY_MS = viteNodeOptions.maxRetryDelay ?? 2e3;
const REQUEST_TIMEOUT_MS = viteNodeOptions.requestTimeout ?? 6e4;
function calculateRetryDelay(attempt) {
	const exponentialDelay = BASE_RETRY_DELAY_MS * Math.pow(2, attempt);
	const jitter = Math.random() * .1 * exponentialDelay;
	return Math.min(exponentialDelay + jitter, MAX_RETRY_DELAY_MS);
}
function connectSocket() {
	if (clientSocket && !clientSocket.destroyed) return Promise.resolve(clientSocket);
	if (currentConnectPromise) return currentConnectPromise;
	const thisPromise = new Promise((resolve, reject) => {
		if (!viteNodeOptions.socketPath) {
			console.error("vite-node-shared: NUXT_VITE_NODE_OPTIONS.socketPath is not defined.");
			return reject(/* @__PURE__ */ new Error("Vite Node IPC socket path not configured."));
		}
		const attemptConnection = (attempt = 0) => {
			const socket = net.createConnection(viteNodeOptions.socketPath);
			const INITIAL_BUFFER_SIZE = 64 * 1024;
			const MAX_BUFFER_SIZE = 1024 * 1024 * 1024;
			let buffer = Buffer.alloc(INITIAL_BUFFER_SIZE);
			let writeOffset = 0;
			let readOffset = 0;
			socket.setNoDelay(true);
			socket.setKeepAlive(true, 3e4);
			const cleanup = () => {
				socket.off("connect", onConnect);
				socket.off("data", onData);
				socket.off("error", onError);
				socket.off("close", onClose);
			};
			const resetBuffer = () => {
				writeOffset = 0;
				readOffset = 0;
			};
			const compactBuffer = () => {
				if (readOffset > 0) {
					const remainingData = writeOffset - readOffset;
					if (remainingData > 0) buffer.copy(buffer, 0, readOffset, writeOffset);
					writeOffset = remainingData;
					readOffset = 0;
				}
			};
			const ensureBufferCapacity = (additionalBytes) => {
				const requiredSize = writeOffset + additionalBytes;
				if (requiredSize > MAX_BUFFER_SIZE) throw new Error(`Buffer size limit exceeded: ${requiredSize} > ${MAX_BUFFER_SIZE}`);
				if (requiredSize > buffer.length) {
					compactBuffer();
					if (writeOffset + additionalBytes > buffer.length) {
						const newSize = Math.min(Math.max(buffer.length * 2, requiredSize), MAX_BUFFER_SIZE);
						const newBuffer = Buffer.alloc(newSize);
						buffer.copy(newBuffer, 0, 0, writeOffset);
						buffer = newBuffer;
					}
				}
			};
			const onConnect = () => {
				clientSocket = socket;
				resolve(socket);
			};
			const onData = (data) => {
				try {
					ensureBufferCapacity(data.length);
					data.copy(buffer, writeOffset);
					writeOffset += data.length;
					while (writeOffset - readOffset >= 4) {
						const messageLength = buffer.readUInt32BE(readOffset);
						if (writeOffset - readOffset < 4 + messageLength) return;
						const message = buffer.subarray(readOffset + 4, readOffset + 4 + messageLength).toString("utf-8");
						readOffset += 4 + messageLength;
						try {
							const response = JSON.parse(message);
							const requestHandlers = pendingRequests.get(response.id);
							if (requestHandlers) {
								const { resolve: resolveRequest, reject: rejectRequest } = requestHandlers;
								if (response.type === "error") {
									const err = new Error(response.error.message);
									err.stack = response.error.stack;
									err.data = response.error.data;
									err.statusCode = err.status = response.error.status || response.error.statusCode;
									rejectRequest(err);
								} else resolveRequest(response.data);
								pendingRequests.delete(response.id);
							}
						} catch (parseError) {
							console.warn("vite-node-shared: Failed to parse IPC response:", parseError);
						}
					}
					if (readOffset > buffer.length / 2) compactBuffer();
				} catch (error) {
					socket.destroy(error instanceof Error ? error : /* @__PURE__ */ new Error("Buffer management error"));
				}
			};
			const onError = (err) => {
				cleanup();
				resetBuffer();
				if (attempt < MAX_RETRY_ATTEMPTS) {
					const delay = calculateRetryDelay(attempt);
					setTimeout(() => attemptConnection(attempt + 1), delay);
				} else {
					if (currentConnectPromise === thisPromise) reject(err);
					for (const { reject: rejectRequest } of pendingRequests.values()) rejectRequest(err);
					pendingRequests.clear();
					if (clientSocket === socket) clientSocket = void 0;
					if (currentConnectPromise === thisPromise) currentConnectPromise = void 0;
				}
			};
			const onClose = () => {
				cleanup();
				resetBuffer();
				for (const { reject: rejectRequest } of pendingRequests.values()) rejectRequest(/* @__PURE__ */ new Error("IPC connection closed"));
				pendingRequests.clear();
				if (clientSocket === socket) clientSocket = void 0;
				if (currentConnectPromise === thisPromise) currentConnectPromise = void 0;
			};
			socket.on("connect", onConnect);
			socket.on("data", onData);
			socket.on("error", onError);
			socket.on("close", onClose);
		};
		attemptConnection();
	});
	currentConnectPromise = thisPromise;
	return currentConnectPromise;
}
async function sendRequest(type, payload) {
	const requestId = requestIdCounter++;
	let lastError;
	for (let requestAttempt = 0; requestAttempt <= MAX_RETRY_ATTEMPTS; requestAttempt++) try {
		const socket = await connectSocket();
		return new Promise((resolve, reject) => {
			const timeoutId = setTimeout(() => {
				pendingRequests.delete(requestId);
				reject(/* @__PURE__ */ new Error(`Request timeout after ${REQUEST_TIMEOUT_MS}ms for type: ${type}`));
			}, REQUEST_TIMEOUT_MS);
			pendingRequests.set(requestId, {
				resolve: (value) => {
					clearTimeout(timeoutId);
					resolve(value);
				},
				reject: (reason) => {
					clearTimeout(timeoutId);
					reject(reason);
				}
			});
			const message = JSON.stringify({
				id: requestId,
				type,
				payload
			});
			const messageBuffer = Buffer.from(message, "utf-8");
			const messageLength = messageBuffer.length;
			const fullMessage = Buffer.alloc(4 + messageLength);
			fullMessage.writeUInt32BE(messageLength, 0);
			messageBuffer.copy(fullMessage, 4);
			try {
				socket.write(fullMessage);
			} catch (error) {
				clearTimeout(timeoutId);
				pendingRequests.delete(requestId);
				reject(error);
			}
		});
	} catch (error) {
		lastError = error;
		if (requestAttempt < MAX_RETRY_ATTEMPTS) {
			const delay = calculateRetryDelay(requestAttempt);
			await new Promise((resolve) => setTimeout(resolve, delay));
			if (clientSocket) {
				clientSocket.destroy();
				clientSocket = void 0;
			}
			currentConnectPromise = void 0;
		}
	}
	throw lastError || /* @__PURE__ */ new Error("Request failed after all retry attempts");
}
const viteNodeFetch = {
	getManifest() {
		return sendRequest("manifest", void 0);
	},
	getInvalidates() {
		return sendRequest("invalidates", void 0);
	},
	resolveId(id, importer) {
		return sendRequest("resolve", {
			id,
			importer
		});
	},
	fetchModule(moduleId) {
		return sendRequest("module", { moduleId });
	},
	ensureConnected() {
		return connectSocket();
	}
};
let preConnectAttempted = false;
function preConnect() {
	if (preConnectAttempted || !viteNodeOptions.socketPath) return;
	preConnectAttempted = true;
	return connectSocket().catch(() => {});
}
if (typeof process !== "undefined" && !isTest) setTimeout(preConnect, 100);
export { viteNodeFetch, viteNodeOptions };
