import { assert } from "../../../shared/src/asserts.js";
import { mapValues } from "../../../shared/src/objects.js";
import { TDigest } from "../../../shared/src/tdigest.js";
import { hashOfNameAndArgs } from "../../../zero-protocol/src/query-hash.js";
import { isServerMetric, } from "../../../zql/src/query/metrics-delegate.js";
import { isDevelopmentMode } from "../config/normalize.js";
/**
 * Set of authenticated client group IDs. We keep this outside of the class to
 * share this state across all instances of the InspectorDelegate.
 */
const authenticatedClientGroupIDs = new Set();
export class InspectorDelegate {
    #globalMetrics = newMetrics();
    #perQueryServerMetrics = new Map();
    #hashToIDs = new Map();
    #queryIDToTransformationHash = new Map();
    #transformationASTs = new Map();
    #customQueryTransformer;
    constructor(customQueryTransformer) {
        this.#customQueryTransformer = customQueryTransformer;
    }
    addMetric(metric, value, ...args) {
        assert(isServerMetric(metric), `Invalid server metric: ${metric}`);
        const transformationHash = args[0];
        for (const queryID of this.#hashToIDs.get(transformationHash) ?? []) {
            let serverMetrics = this.#perQueryServerMetrics.get(queryID);
            if (!serverMetrics) {
                serverMetrics = newMetrics();
                this.#perQueryServerMetrics.set(queryID, serverMetrics);
            }
            serverMetrics[metric].add(value);
        }
        this.#globalMetrics[metric].add(value);
    }
    getMetricsJSONForQuery(queryID) {
        const serverMetrics = this.#perQueryServerMetrics.get(queryID);
        return serverMetrics ? mapValues(serverMetrics, v => v.toJSON()) : null;
    }
    getMetricsJSON() {
        return mapValues(this.#globalMetrics, v => v.toJSON());
    }
    getASTForQuery(queryID) {
        const transformationHash = this.#queryIDToTransformationHash.get(queryID);
        return transformationHash
            ? this.#transformationASTs.get(transformationHash)
            : undefined;
    }
    removeQuery(queryID) {
        this.#perQueryServerMetrics.delete(queryID);
        this.#queryIDToTransformationHash.delete(queryID);
        // Remove queryID from all hash-to-ID mappings
        for (const [transformationHash, idSet] of this.#hashToIDs.entries()) {
            idSet.delete(queryID);
            if (idSet.size === 0) {
                this.#hashToIDs.delete(transformationHash);
                this.#transformationASTs.delete(transformationHash);
            }
        }
    }
    addQuery(transformationHash, queryID, ast) {
        const existing = this.#hashToIDs.get(transformationHash);
        if (existing === undefined) {
            this.#hashToIDs.set(transformationHash, new Set([queryID]));
        }
        else {
            existing.add(queryID);
        }
        this.#queryIDToTransformationHash.set(queryID, transformationHash);
        this.#transformationASTs.set(transformationHash, ast);
    }
    /**
     * Check if the client is authenticated. We only require authentication once
     * per "worker".
     */
    isAuthenticated(clientGroupID) {
        return (isDevelopmentMode() || authenticatedClientGroupIDs.has(clientGroupID));
    }
    setAuthenticated(clientGroupID) {
        authenticatedClientGroupIDs.add(clientGroupID);
    }
    clearAuthenticated(clientGroupID) {
        authenticatedClientGroupIDs.delete(clientGroupID);
    }
    /**
     * Transforms a single custom query by name and args using the configured
     * CustomQueryTransformer. This is primarily used by the inspector to transform
     * queries for analysis.
     */
    async transformCustomQuery(name, args, headerOptions, userQueryURL) {
        assert(this.#customQueryTransformer, 'Custom query transformation requested but no CustomQueryTransformer is configured');
        // Create a fake CustomQueryRecord for the single query
        const queryID = hashOfNameAndArgs(name, args);
        const queries = [
            {
                id: queryID,
                type: 'custom',
                name,
                args,
                clientState: {},
            },
        ];
        const results = await this.#customQueryTransformer.transform(headerOptions, queries, userQueryURL);
        const result = results[0];
        if (!result) {
            throw new Error('No transformation result returned');
        }
        if ('error' in result) {
            throw new Error(`Error transforming custom query ${name}: ${result.error} ${JSON.stringify(result.details)}`);
        }
        return result.transformedAst;
    }
}
function newMetrics() {
    return {
        'query-materialization-server': new TDigest(),
        'query-update-server': new TDigest(),
    };
}
//# sourceMappingURL=inspector-delegate.js.map