import { appendFile, mkdir, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
//#region src/error-collector.ts
const DEFAULT_ENDPOINT = "/__lovable/error-collector";
const DEFAULT_OUTPUT_FILE = "/tmp/runtime-errors.log";
const DEFAULT_MAX_BODY_BYTES = 64 * 1024;
function isViteClientCarrier(id) {
	return id.includes("/vite/dist/client/client.mjs");
}
function validateEndpoint(endpoint) {
	if (!endpoint.startsWith("/") || endpoint.startsWith("//") || endpoint.includes("?") || endpoint.includes("#")) throw new Error(`errorCollectorPlugin endpoint must be a root-relative path, received ${JSON.stringify(endpoint)}`);
}
function validateMaxBodyBytes(maxBodyBytes) {
	if (!Number.isSafeInteger(maxBodyBytes) || maxBodyBytes <= 0) throw new Error("errorCollectorPlugin maxBodyBytes must be a positive safe integer");
}
function readJsonBody$1(req, maxBodyBytes) {
	return new Promise((resolveBody) => {
		let receivedBytes = 0;
		let settled = false;
		const chunks = [];
		const settle = (result) => {
			if (settled) return;
			settled = true;
			resolveBody(result);
		};
		req.on("data", (chunk) => {
			if (settled) return;
			const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
			receivedBytes += buffer.length;
			if (receivedBytes > maxBodyBytes) {
				settle({
					ok: false,
					reason: "too_large"
				});
				return;
			}
			chunks.push(buffer);
		});
		req.on("end", () => {
			if (settled) return;
			if (chunks.length === 0) {
				settle({
					ok: false,
					reason: "empty"
				});
				return;
			}
			try {
				settle({
					ok: true,
					value: JSON.parse(Buffer.concat(chunks).toString("utf8"))
				});
			} catch {
				settle({
					ok: false,
					reason: "invalid_json"
				});
			}
		});
		req.on("error", () => settle({
			ok: false,
			reason: "read_error"
		}));
	});
}
function writeJson$1(res, statusCode, value) {
	res.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
	res.end(JSON.stringify(value));
}
function browserClientScript(endpoint) {
	return `
(() => {
  if (globalThis.__lovableErrorCollectorInstalled) return;
  globalThis.__lovableErrorCollectorInstalled = true;
  const endpoint = ${JSON.stringify(endpoint).replaceAll("<", "\\u003c")};
  const clip = (value) => String(value ?? "").slice(0, 10000);
  const describe = (value, fallback) => {
    if (value && typeof value === "object") {
      const message = "message" in value ? clip(value.message) : "";
      const stack = "stack" in value ? clip(value.stack) : "";
      const name = "name" in value ? clip(value.name) : "";
      if (message || stack || name) return { name: name || undefined, message: message || fallback, stack: stack || undefined };
      try {
        const seen = new WeakSet();
        const json = JSON.stringify(value, (_key, nested) => {
          if (typeof nested === "bigint") return String(nested);
          if (nested && typeof nested === "object") {
            if (seen.has(nested)) return "[Circular]";
            seen.add(nested);
          }
          return nested;
        });
        if (json) return { message: clip(json) };
      } catch {}
    }
    return { message: value == null ? fallback : clip(value) };
  };
  const post = (report) => {
    try {
      const body = JSON.stringify({
        ...report,
        timestamp: new Date().toISOString(),
        pageUrl: location.href,
        userAgent: navigator.userAgent,
      });
      void fetch(endpoint, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body,
        keepalive: true,
      }).catch(() => {});
    } catch {}
  };
  addEventListener("error", (event) => {
    const target = event.target;
    if (target && target !== globalThis && typeof target === "object" && "tagName" in target) {
      const tagName = clip(target.tagName).toLowerCase();
      const resourceUrl = clip(target.currentSrc || target.src || target.href || "");
      post({
        type: "resource-error",
        message: resourceUrl ? \`Failed to load <\${tagName}>: \${resourceUrl}\` : \`Failed to load <\${tagName}>\`,
        tagName,
        resourceUrl: resourceUrl || undefined,
      });
      return;
    }
    post({
      type: "runtime-error",
      ...describe(event.error || event.message, "Unknown runtime error"),
      filename: clip(event.filename) || undefined,
      line: Number.isFinite(event.lineno) ? event.lineno : undefined,
      column: Number.isFinite(event.colno) ? event.colno : undefined,
    });
  }, true);
  addEventListener("unhandledrejection", (event) => {
    post({
      type: "unhandled-rejection",
      ...describe(event.reason, "Unhandled promise rejection"),
    });
  });
})();
`;
}
function createErrorCollectorHooks(options = {}) {
	const endpoint = options.endpoint ?? DEFAULT_ENDPOINT;
	const outputFile = options.outputFile ?? DEFAULT_OUTPUT_FILE;
	const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
	const resetOnStart = options.resetOnStart ?? true;
	validateEndpoint(endpoint);
	validateMaxBodyBytes(maxBodyBytes);
	const clientScript = browserClientScript(endpoint);
	let root = process.cwd();
	let outputPath = resolve(root, outputFile);
	return {
		configResolved: (config) => {
			root = config.root;
			outputPath = resolve(root, outputFile);
		},
		configureServer: (server) => {
			Promise.resolve(server.watcher.unwatch(outputPath)).catch(() => {});
			const initialize = mkdir(dirname(outputPath), { recursive: true }).then(async () => {
				if (resetOnStart) await writeFile(outputPath, "", "utf8");
			});
			initialize.catch(() => {});
			let writeQueue = Promise.resolve();
			server.middlewares.use((req, res, next) => {
				const path = (req.url ?? "").split("?", 1)[0];
				if (req.method !== "POST" || path !== endpoint) return next();
				readJsonBody$1(req, maxBodyBytes).then(async (parsed) => {
					if (!parsed.ok) {
						writeJson$1(res, parsed.reason === "too_large" ? 413 : 400, { error: parsed.reason });
						return;
					}
					if (!parsed.value || typeof parsed.value !== "object" || Array.isArray(parsed.value)) {
						writeJson$1(res, 400, { error: "body_must_be_an_object" });
						return;
					}
					const line = `${JSON.stringify({
						...parsed.value,
						receivedAt: (/* @__PURE__ */ new Date()).toISOString()
					})}\n`;
					const write = writeQueue.then(async () => {
						await initialize;
						await appendFile(outputPath, line, "utf8");
					});
					writeQueue = write.catch(() => {});
					try {
						await write;
						res.writeHead(204);
						res.end();
					} catch (error) {
						server.config.logger.error(`Failed to write browser error to ${outputPath}: ${String(error)}`);
						writeJson$1(res, 500, { error: "write_failed" });
					}
				});
			});
		},
		transform: (code, id, transformOptions) => {
			if (transformOptions?.ssr || !isViteClientCarrier(id)) return null;
			return {
				code: `${code}\n${clientScript}\n`,
				map: null
			};
		},
		transformIndexHtml: () => {
			return [{
				tag: "script",
				children: clientScript,
				injectTo: "head-prepend"
			}];
		}
	};
}
function errorCollectorPlugin(options = {}) {
	const hooks = createErrorCollectorHooks(options);
	return {
		name: "error-collector",
		apply: "serve",
		configResolved(config) {
			hooks.configResolved(config);
		},
		configureServer(server) {
			hooks.configureServer(server);
		},
		transform(code, id, transformOptions) {
			return hooks.transform(code, id, transformOptions);
		},
		transformIndexHtml() {
			return hooks.transformIndexHtml();
		}
	};
}
function createErrorReportAppender(options = {}) {
	const outputPath = resolve(process.cwd(), options.outputFile ?? DEFAULT_OUTPUT_FILE);
	let initialized;
	let writeQueue = Promise.resolve();
	return (report) => {
		initialized ??= mkdir(dirname(outputPath), { recursive: true }).then(() => void 0);
		const write = writeQueue.then(async () => {
			const line = `${JSON.stringify({
				...report,
				receivedAt: (/* @__PURE__ */ new Date()).toISOString()
			})}\n`;
			await initialized;
			await appendFile(outputPath, line, "utf8");
		}).catch(() => void 0);
		writeQueue = write;
		return write;
	};
}
//#endregion
//#region src/index.ts
const RESTART_ANNOUNCE_PATH = "/__lovable/restart-announce";
const RESTART_EVENT = "lovable:dev-server-restarting";
const PREVIEW_EXECUTE_PATH = "/_sandbox/preview/execute";
const PREVIEW_VIEWERS_PATH = "/_sandbox/preview/viewers";
const PREVIEW_EXECUTE_EVENT = "lovable:preview-execute";
const PREVIEW_EXECUTE_RESULT_EVENT = "lovable:preview-execute-result";
const PREVIEW_EXECUTE_CLIENT_MODULE_ID = "virtual:lovable-preview-execute-client";
const RESOLVED_PREVIEW_EXECUTE_CLIENT_MODULE_ID = "\0virtual:lovable-preview-execute-client";
const RESTART_MAX_BODY_BYTES = 1024;
const PREVIEW_EXECUTE_MAX_BODY_BYTES = 64 * 1024;
const PREVIEW_EXECUTE_MAX_CODE_LENGTH = 50 * 1024;
const PREVIEW_EXECUTE_DEFAULT_TIMEOUT_MS = 1500;
const PREVIEW_EXECUTE_MAX_TIMEOUT_MS = 5e3;
let requestSequence = 0;
function nextRequestID() {
	requestSequence += 1;
	return `preview-exec-${Date.now().toString(36)}-${requestSequence.toString(36)}`;
}
function readJsonBody(req, maxBodyBytes) {
	return new Promise((resolve) => {
		let received = 0;
		const chunks = [];
		let aborted = false;
		req.on("data", (chunk) => {
			if (aborted) return;
			received += chunk.length;
			if (received > maxBodyBytes) {
				aborted = true;
				resolve({
					ok: false,
					reason: "too_large"
				});
				return;
			}
			chunks.push(chunk);
		});
		req.on("end", () => {
			if (aborted) return;
			if (chunks.length === 0) {
				resolve({
					ok: false,
					reason: "empty"
				});
				return;
			}
			try {
				resolve({
					ok: true,
					value: JSON.parse(Buffer.concat(chunks).toString("utf8"))
				});
			} catch {
				resolve({
					ok: false,
					reason: "invalid_json"
				});
			}
		});
		req.on("error", () => resolve({
			ok: false,
			reason: "read_error"
		}));
	});
}
function writeJson(res, statusCode, value) {
	res.writeHead(statusCode, { "content-type": "application/json; charset=utf-8" });
	res.end(JSON.stringify(value));
}
function clampTimeoutMs(value) {
	if (typeof value !== "number" || !Number.isFinite(value)) return PREVIEW_EXECUTE_DEFAULT_TIMEOUT_MS;
	return Math.max(1, Math.min(PREVIEW_EXECUTE_MAX_TIMEOUT_MS, Math.trunc(value)));
}
function parsePreviewExecuteRequest(value) {
	if (!value || typeof value !== "object") return "body must be a JSON object";
	const code = value.code;
	if (typeof code !== "string" || code.trim() === "") return "code must be a non-empty string";
	if (code.length > PREVIEW_EXECUTE_MAX_CODE_LENGTH) return `code must be at most ${PREVIEW_EXECUTE_MAX_CODE_LENGTH} characters`;
	return {
		code,
		timeout_ms: clampTimeoutMs(value.timeout_ms)
	};
}
function isPreviewExecuteResult(value) {
	if (!value || typeof value !== "object") return false;
	const result = value;
	return typeof result.request_id === "string" && typeof result.viewer_id === "string" && typeof result.ok === "boolean" && typeof result.duration_ms === "number";
}
function executeInViewers(ws, request, pendingRequests) {
	const requestID = nextRequestID();
	const viewerCount = ws.clients?.size ?? 0;
	const results = /* @__PURE__ */ new Map();
	pendingRequests.set(requestID, results);
	if (viewerCount === 0) {
		pendingRequests.delete(requestID);
		return Promise.resolve({
			request_id: requestID,
			viewer_count: 0,
			responded_count: 0,
			timed_out_count: 0,
			results: []
		});
	}
	ws.send({
		type: "custom",
		event: PREVIEW_EXECUTE_EVENT,
		data: {
			request_id: requestID,
			code: request.code
		}
	});
	return new Promise((resolve) => {
		let done = false;
		let timeout;
		let checkComplete;
		const finish = () => {
			if (done) return;
			done = true;
			clearTimeout(timeout);
			clearInterval(checkComplete);
			pendingRequests.delete(requestID);
			resolve({
				request_id: requestID,
				viewer_count: viewerCount,
				responded_count: results.size,
				timed_out_count: Math.max(0, viewerCount - results.size),
				results: [...results.values()]
			});
		};
		timeout = setTimeout(finish, request.timeout_ms);
		checkComplete = setInterval(() => {
			if (results.size >= viewerCount) finish();
		}, 10);
	});
}
function previewExecuteClientScript() {
	return `
if (import.meta.hot) {
  // Two carriers can instantiate this module in one page, and Vite's stale-listener
  // cleanup in createHotContext can silently drop an earlier instance's listeners —
  // so every instance registers, and requests dedupe through globalThis state.
  // Served untranspiled: keep the syntax at ES2020 (no ??=).
  const viewerID =
    globalThis.__lovablePreviewExecuteViewerID ||
    (globalThis.__lovablePreviewExecuteViewerID =
      globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2));
  const handled =
    globalThis.__lovablePreviewExecuteHandled || (globalThis.__lovablePreviewExecuteHandled = new Set());
  const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
  const serialize = (value) => {
    if (value === undefined) return { type: "undefined" };
    try {
      return structuredClone(value);
    } catch {
      try {
        return JSON.parse(JSON.stringify(value));
      } catch {
        return String(value);
      }
    }
  };
  import.meta.hot.on("${PREVIEW_EXECUTE_EVENT}", async (payload) => {
    const started = performance.now();
    const requestID = payload && typeof payload.request_id === "string" ? payload.request_id : "";
    const code = payload && typeof payload.code === "string" ? payload.code : "";
    if (!requestID || !code || handled.has(requestID)) return;
    // Request ids embed a timestamp + sequence and are never reused, so clearing
    // at the cap cannot resurrect an old request; it only bounds realm lifetime growth.
    if (handled.size >= 10000) handled.clear();
    handled.add(requestID);
    try {
      const value = await new AsyncFunction(code)();
      import.meta.hot.send("${PREVIEW_EXECUTE_RESULT_EVENT}", {
        request_id: requestID,
        viewer_id: viewerID,
        ok: true,
        value: serialize(value),
        duration_ms: Math.round(performance.now() - started),
      });
    } catch (err) {
      import.meta.hot.send("${PREVIEW_EXECUTE_RESULT_EVENT}", {
        request_id: requestID,
        viewer_id: viewerID,
        ok: false,
        error: {
          name: err && typeof err === "object" && "name" in err ? String(err.name) : undefined,
          message: err instanceof Error ? err.message : String(err),
          stack: err instanceof Error ? err.stack : undefined,
        },
        duration_ms: Math.round(performance.now() - started),
      });
    }
  });
}
`;
}
//   @vitejs/plugin-react and vitejs/devtools use — see vitejs/vite#22864. Gated to
function isPreviewExecuteClientCarrier(id) {
	return id.includes("/vite/dist/client/client.mjs");
}
function devServerBridgePlugin(options = {}) {
	let isBundledDev = false;
	const errorCollector = options.errorCollector === false ? void 0 : createErrorCollectorHooks(options.errorCollector);
	const injectClientCarriers = () => {
		const tags = errorCollector?.transformIndexHtml() ?? [];
		if (isBundledDev) tags.push({
			tag: "script",
			attrs: { type: "module" },
			children: `import ${JSON.stringify(PREVIEW_EXECUTE_CLIENT_MODULE_ID)};`,
			injectTo: "head"
		});
		return tags.length > 0 ? tags : void 0;
	};
	return {
		name: "lovable-dev-server-bridge",
		apply: "serve",
		configResolved(config) {
			errorCollector?.configResolved(config);
			isBundledDev = config.experimental?.bundledDev === true;
		},
		configureServer(srv) {
			errorCollector?.configureServer(srv);
			const pendingPreviewExecuteRequests = /* @__PURE__ */ new Map();
			const ws = srv.ws;
			let connectionSeq = 0;
			ws.on?.("connection", () => {
				connectionSeq += 1;
			});
			ws.on?.(PREVIEW_EXECUTE_RESULT_EVENT, (data, client) => {
				if (!isPreviewExecuteResult(data)) return;
				const results = pendingPreviewExecuteRequests.get(data.request_id);
				if (!results) return;
				results.set(client ?? data.viewer_id, data);
			});
			srv.middlewares.use((req, res, next) => {
				const path = (req.url ?? "").split("?", 1)[0];
				if (req.method === "GET" && path === PREVIEW_VIEWERS_PATH) {
					writeJson(res, 200, {
						viewer_count: ws.clients?.size ?? 0,
						connection_seq: connectionSeq
					});
					return;
				}
				if (req.method !== "POST") return next();
				if (path !== RESTART_ANNOUNCE_PATH && path !== PREVIEW_EXECUTE_PATH) return next();
				readJsonBody(req, path === PREVIEW_EXECUTE_PATH ? PREVIEW_EXECUTE_MAX_BODY_BYTES : RESTART_MAX_BODY_BYTES).then((parsed) => {
					if (path === PREVIEW_EXECUTE_PATH) {
						if (!parsed.ok) {
							writeJson(res, parsed.reason === "too_large" ? 413 : 400, { error: parsed.reason });
							return;
						}
						const previewRequest = parsePreviewExecuteRequest(parsed.value);
						if (typeof previewRequest === "string") {
							writeJson(res, 400, { error: previewRequest });
							return;
						}
						executeInViewers(ws, previewRequest, pendingPreviewExecuteRequests).then((response) => {
							writeJson(res, 200, response);
						});
						return;
					}
					const reason = parsed.ok && parsed.value && typeof parsed.value === "object" && "reason" in parsed.value && typeof parsed.value.reason === "string" ? parsed.value.reason : void 0;
					srv.ws.send({
						type: "custom",
						event: RESTART_EVENT,
						data: reason ? { reason } : {}
					});
					res.writeHead(204);
					res.end();
				});
			});
		},
		resolveId(id) {
			if (id === PREVIEW_EXECUTE_CLIENT_MODULE_ID) return RESOLVED_PREVIEW_EXECUTE_CLIENT_MODULE_ID;
			return null;
		},
		load(id) {
			if (id === RESOLVED_PREVIEW_EXECUTE_CLIENT_MODULE_ID) return previewExecuteClientScript();
			return null;
		},
		transform(code, id, options) {
			const errorCollectorTransform = errorCollector?.transform(code, id, options);
			if (options?.ssr || !isPreviewExecuteClientCarrier(id)) return errorCollectorTransform ?? null;
			return {
				code: `${errorCollectorTransform?.code ?? code}\nimport(${JSON.stringify(PREVIEW_EXECUTE_CLIENT_MODULE_ID)});\n`,
				map: null
			};
		},
		transformIndexHtml: {
			order: "pre",
			handler: injectClientCarriers,
			enforce: "pre",
			transform: injectClientCarriers
		}
	};
}
//#endregion
export { createErrorReportAppender, devServerBridgePlugin, errorCollectorPlugin };
