import { assert } from "../../../../shared/src/asserts.js";
import { test } from "../../../../shared/src/valita.js";
import { readFromHash } from "../../../../replicache/src/db/read.js";
import { withRead } from "../../../../replicache/src/with-transactions.js";
import { getClientGroup } from "../../../../replicache/src/persist/client-groups.js";
import { getClient, getClients } from "../../../../replicache/src/persist/clients.js";
import { mapValues } from "../../../../shared/src/objects.js";
import { asQueryInternals } from "../../../../zql/src/query/query-internals.js";
import { inspectAnalyzeQueryDownSchema, inspectAuthenticatedDownSchema, inspectMetricsDownSchema, inspectQueriesDownSchema, inspectVersionDownSchema } from "../../../../zero-protocol/src/inspect-down.js";
import { nanoid } from "../../util/nanoid.js";
import "../keys.js";
import { Client } from "./client.js";
import "./inspector.js";
import { TDigest } from "../../../../shared/src/tdigest.js";
import { createHTMLPasswordPrompt } from "./html-dialog-prompt.js";
import { Query } from "./query.js";
//#region ../zero-client/src/client/inspector/lazy-inspector.ts
async function rpc(socket, arg, downSchema) {
	try {
		return await rpcNoAuthTry(socket, arg, downSchema);
	} catch (e) {
		if (e instanceof UnauthenticatedError) {
			const password = await createHTMLPasswordPrompt("Enter password:");
			if (password) {
				if (await rpcNoAuthTry(socket, {
					op: "authenticate",
					value: password
				}, inspectAuthenticatedDownSchema)) return rpcNoAuthTry(socket, arg, downSchema);
			}
			throw new Error("Authentication failed");
		}
		throw e;
	}
}
function rpcNoAuthTry(socket, arg, downSchema) {
	return new Promise((resolve, reject) => {
		const id = nanoid();
		const f = (ev) => {
			const msg = JSON.parse(ev.data);
			if (msg[0] === "inspect") {
				const body = msg[1];
				if (body.id !== id) return;
				const res = test(body, downSchema);
				if (res.ok) if (res.value.op === "error") reject(new Error(res.value.value));
				else resolve(res.value.value);
				else {
					const authRes = test(body, inspectAuthenticatedDownSchema);
					if (authRes.ok) {
						assert(authRes.value.value === false, "Expected unauthenticated response");
						reject(new UnauthenticatedError());
					}
					reject(res.error);
				}
				socket.removeEventListener("message", f);
			}
		};
		socket.addEventListener("message", f);
		socket.send(JSON.stringify(["inspect", {
			...arg,
			id
		}]));
	});
}
function mergeMetrics(clientMetrics, serverMetrics) {
	return {
		...clientMetrics ?? newClientMetrics(),
		...serverMetrics ? convertServerMetrics(serverMetrics) : newServerMetrics()
	};
}
function newClientMetrics() {
	return {
		"query-materialization-client": new TDigest(),
		"query-materialization-end-to-end": new TDigest(),
		"query-update-client": new TDigest()
	};
}
function newServerMetrics() {
	return {
		"query-materialization-server": new TDigest(),
		"query-update-server": new TDigest()
	};
}
function convertServerMetrics(metrics) {
	return mapValues(metrics, (v) => TDigest.fromJSON(v));
}
async function inspectorMetrics(delegate) {
	const clientMetrics = delegate.metrics;
	return mergeMetrics(clientMetrics, await rpc(await delegate.getSocket(), { op: "metrics" }, inspectMetricsDownSchema));
}
function inspectorClients(delegate) {
	return withDagRead(delegate, (dagRead) => clients(delegate, dagRead));
}
function inspectorClientsWithQueries(delegate) {
	return withDagRead(delegate, (dagRead) => clientsWithQueries(delegate, dagRead));
}
async function withDagRead(delegate, f) {
	const { rep } = delegate;
	await rep.refresh();
	await rep.persist();
	return withRead(rep.perdag, f);
}
async function getBTree(dagRead, clientID) {
	const client = await getClient(clientID, dagRead);
	assert(client, `Client not found: ${clientID}`);
	const { clientGroupID } = client;
	const clientGroup = await getClientGroup(clientGroupID, dagRead);
	assert(clientGroup, `Client group not found: ${clientGroupID}`);
	return (await readFromHash(clientGroup.headHash, dagRead, 7)).map;
}
async function clients(delegate, dagRead, predicate = () => true) {
	return [...(await getClients(dagRead)).entries()].filter(predicate).map(([clientID, { clientGroupID }]) => new Client(delegate, clientID, clientGroupID));
}
async function clientsWithQueries(delegate, dagRead, predicate = () => true) {
	const allClients = await clients(delegate, dagRead, predicate);
	const clientsWithQueries = [];
	await Promise.all(allClients.map(async (client) => {
		if ((await client.queries()).length > 0) clientsWithQueries.push(client);
	}));
	return clientsWithQueries;
}
async function clientGroupClients(delegate, clientGroupID) {
	const id = await clientGroupID;
	return withDagRead(delegate, (dagRead) => clients(delegate, dagRead, ([_, v]) => v.clientGroupID === id));
}
async function clientGroupClientsWithQueries(delegate, clientGroupID) {
	const id = await clientGroupID;
	return withDagRead(delegate, (dagRead) => clientsWithQueries(delegate, dagRead, ([_, v]) => v.clientGroupID === id));
}
function clientGroupQueries(delegate) {
	return queries(delegate, { op: "queries" });
}
function clientMap(delegate, clientID) {
	return withDagRead(delegate, async (dagRead) => {
		const tree = await getBTree(dagRead, clientID);
		const map = /* @__PURE__ */ new Map();
		for await (const [key, value] of tree.scan("")) map.set(key, value);
		return map;
	});
}
function clientRows(delegate, clientID, tableName) {
	return withDagRead(delegate, async (dagRead) => {
		const prefix = "e/" + tableName + "/";
		const tree = await getBTree(dagRead, clientID);
		const rows = [];
		for await (const [key, value] of tree.scan(prefix)) {
			if (!key.startsWith(prefix)) break;
			rows.push(value);
		}
		return rows;
	});
}
async function serverVersion(delegate) {
	return rpc(await delegate.getSocket(), { op: "version" }, inspectVersionDownSchema);
}
function clientQueries(delegate, clientID) {
	return queries(delegate, {
		op: "queries",
		clientID
	});
}
async function queries(delegate, arg) {
	const queries = (await rpc(await delegate.getSocket(), arg, inspectQueriesDownSchema)).map((row) => new Query(row, delegate, delegate.getSocket));
	queries.sort((a, b) => (b.hydrateServer ?? 0) - (a.hydrateServer ?? 0));
	return queries;
}
async function analyzeQuery(delegate, query, options) {
	const qi = asQueryInternals(query);
	const { customQueryID } = qi;
	const queryParameters = customQueryID ? {
		name: customQueryID.name,
		args: customQueryID.args
	} : { ast: delegate.mapClientASTToServer(qi.ast) };
	return rpc(await delegate.getSocket(), {
		op: "analyze-query",
		...queryParameters,
		options
	}, inspectAnalyzeQueryDownSchema);
}
var UnauthenticatedError = class extends Error {};
//#endregion
export { analyzeQuery, clientGroupClients, clientGroupClientsWithQueries, clientGroupQueries, clientMap, clientQueries, clientRows, inspectorClients, inspectorClientsWithQueries, inspectorMetrics, mergeMetrics, rpc, serverVersion };

//# sourceMappingURL=lazy-inspector.js.map