var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import { SQL, Statement } from "../Utils/AnalyticsDatabase/Statement";
import { getQuerySettings } from "../Utils/AnalyticsDatabase/QuerySettingsHelper";
import ExceptionInstanceService from "./ExceptionInstanceService";
import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
import BadDataException from "../../Types/Exception/BadDataException";
import Includes from "../../Types/BaseDatabase/Includes";
import AnalyticsTableName from "../../Types/AnalyticsDatabase/AnalyticsTableName";
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
import ServiceType from "../../Types/Telemetry/ServiceType";
export class ExceptionAggregationService {
    static async getHistogram(request) {
        const statement = ExceptionAggregationService.buildHistogramStatement(request);
        const dbResult = await ExceptionInstanceService.executeQuery(statement);
        const response = await dbResult.json();
        const rows = response.data || [];
        return rows.map((row) => {
            return {
                time: String(row["bucket"] || ""),
                series: ExceptionAggregationService.mapEscapedToSeries(row["escaped"]),
                count: Number(row["cnt"] || 0),
            };
        });
    }
    static async getFacetValues(request) {
        const statement = ExceptionAggregationService.buildFacetStatement(request);
        const dbResult = await ExceptionInstanceService.executeQuery(statement);
        const response = await dbResult.json();
        const rows = response.data || [];
        return rows
            .map((row) => {
            return {
                value: String(row["val"] || ""),
                count: Number(row["cnt"] || 0),
            };
        })
            .filter((facet) => {
            return facet.value.length > 0;
        });
    }
    static mapEscapedToSeries(raw) {
        // ClickHouse returns booleans as 0/1
        if (raw === true || raw === 1 || raw === "1" || raw === "true") {
            return "unhandled";
        }
        return "handled";
    }
    static buildHistogramStatement(request) {
        const intervalSeconds = request.bucketSizeInMinutes * 60;
        const statement = SQL `
      SELECT
        toStartOfInterval(time, INTERVAL ${{
            type: TableColumnType.Number,
            value: intervalSeconds,
        }} SECOND) AS bucket,
        escaped,
        count() AS cnt
      FROM ${ExceptionAggregationService.TABLE_NAME}
      WHERE projectId = ${{
            type: TableColumnType.ObjectID,
            value: request.projectId,
        }}
        AND time >= ${{
            type: TableColumnType.Date,
            value: request.startTime,
        }}
        AND time <= ${{
            type: TableColumnType.Date,
            value: request.endTime,
        }}
    `;
        /*
         * Read-side retention filter: rows past their per-service retention
         * stay in their part until the whole part drops (ttl_only_drop_parts).
         * Raw `time` predicates already make these queries ineligible for the
         * proj_exception_group projection, so the extra predicate is free.
         */
        statement.append(" AND retentionDate >= now()");
        ExceptionAggregationService.appendCommonFilters(statement, request);
        statement.append(" GROUP BY bucket, escaped ORDER BY bucket ASC");
        /*
         * Defense in depth: cap histogram runtime below nginx's 60s
         * proxy_read_timeout. 'break' returns partial aggregated results
         * rather than throwing, which is acceptable for a density viz.
         */
        statement.append(getQuerySettings({
            maxExecutionTimeInSeconds: 45,
            timeoutOverflowMode: "break",
        }));
        return statement;
    }
    static buildFacetStatement(request) {
        var _a;
        // Pre-rename alias from stale clients; the V3 column is primaryEntityId.
        if (request.facetKey === "serviceId") {
            request.facetKey = "primaryEntityId";
        }
        const limit = (_a = request.limit) !== null && _a !== void 0 ? _a : ExceptionAggregationService.DEFAULT_FACET_LIMIT;
        ExceptionAggregationService.validateFacetKey(request.facetKey);
        const resourceServiceType = ExceptionAggregationService.RESOURCE_FACET_KEYS.get(request.facetKey);
        const isResourceFacet = resourceServiceType !== undefined;
        const isTopLevelColumn = isResourceFacet ||
            ExceptionAggregationService.isTopLevelColumn(request.facetKey);
        const statement = new Statement();
        if (isResourceFacet) {
            /*
             * Virtual facet — group primaryEntityId values whose row carries the
             * matching ServiceType discriminator (Host / DockerHost /
             * KubernetesCluster).
             */
            statement.append(SQL `SELECT toString(primaryEntityId) AS val, count() AS cnt FROM ${ExceptionAggregationService.TABLE_NAME}`);
        }
        else if (isTopLevelColumn) {
            statement.append(SQL `SELECT toString(${request.facetKey}) AS val, count() AS cnt FROM ${ExceptionAggregationService.TABLE_NAME}`);
        }
        else {
            statement.append(SQL `SELECT attributes[${{
                type: TableColumnType.Text,
                value: request.facetKey,
            }}] AS val, count() AS cnt FROM ${ExceptionAggregationService.TABLE_NAME}`);
        }
        statement.append(SQL ` WHERE projectId = ${{
            type: TableColumnType.ObjectID,
            value: request.projectId,
        }} AND time >= ${{
            type: TableColumnType.Date,
            value: request.startTime,
        }} AND time <= ${{
            type: TableColumnType.Date,
            value: request.endTime,
        }}`);
        if (isResourceFacet) {
            statement.append(SQL ` AND primaryEntityType = ${{
                type: TableColumnType.Text,
                value: resourceServiceType,
            }}`);
        }
        else if (request.facetKey === "primaryEntityId") {
            /*
             * Constrain the canonical Services facet to rows that actually
             * belong to a Service. NULL / empty primaryEntityType covers legacy
             * rows ingested before the discriminator existed.
             */
            statement.append(SQL ` AND (primaryEntityType = '' OR primaryEntityType = ${{
                type: TableColumnType.Text,
                value: ServiceType.OpenTelemetry,
            }})`);
        }
        else if (!isTopLevelColumn) {
            statement.append(SQL ` AND mapContains(attributes, ${{
                type: TableColumnType.Text,
                value: request.facetKey,
            }})`);
        }
        statement.append(" AND retentionDate >= now()");
        ExceptionAggregationService.appendCommonFilters(statement, request);
        statement.append(SQL ` GROUP BY val ORDER BY cnt DESC LIMIT ${{
            type: TableColumnType.Number,
            value: limit,
        }}`);
        /*
         * Defense in depth: cap individual facet query runtime below nginx's
         * 60s proxy_read_timeout so a slow facet never starves the endpoint.
         */
        statement.append(getQuerySettings({
            maxExecutionTimeInSeconds: 45,
            timeoutOverflowMode: "break",
        }));
        return statement;
    }
    static appendCommonFilters(statement, request) {
        if (request.serviceIds && request.serviceIds.length > 0) {
            statement.append(SQL ` AND primaryEntityId IN (${{
                type: TableColumnType.ObjectID,
                value: new Includes(request.serviceIds.map((id) => {
                    return id.toString();
                })),
            }})`);
        }
        if (request.exceptionTypes && request.exceptionTypes.length > 0) {
            statement.append(SQL ` AND exceptionType IN (${{
                type: TableColumnType.Text,
                value: new Includes(request.exceptionTypes),
            }})`);
        }
        if (request.environments && request.environments.length > 0) {
            statement.append(SQL ` AND environment IN (${{
                type: TableColumnType.Text,
                value: new Includes(request.environments),
            }})`);
        }
        if (request.fingerprints && request.fingerprints.length > 0) {
            statement.append(SQL ` AND fingerprint IN (${{
                type: TableColumnType.Text,
                value: new Includes(request.fingerprints),
            }})`);
        }
        if (request.traceIds && request.traceIds.length > 0) {
            statement.append(SQL ` AND traceId IN (${{
                type: TableColumnType.Text,
                value: new Includes(request.traceIds),
            }})`);
        }
        if (request.escaped !== undefined) {
            statement.append(SQL ` AND escaped = ${{
                type: TableColumnType.Boolean,
                value: request.escaped,
            }}`);
        }
        if (request.messageSearchText &&
            request.messageSearchText.trim().length > 0) {
            statement.append(SQL ` AND message ILIKE ${{
                type: TableColumnType.Text,
                value: `%${request.messageSearchText.trim()}%`,
            }}`);
        }
    }
    static isTopLevelColumn(key) {
        return ExceptionAggregationService.TOP_LEVEL_COLUMNS.has(key);
    }
    static validateFacetKey(facetKey) {
        if (typeof facetKey !== "string") {
            throw new BadDataException("Invalid facetKey");
        }
        if (facetKey.length === 0 ||
            facetKey.length > ExceptionAggregationService.MAX_FACET_KEY_LENGTH) {
            throw new BadDataException("Invalid facetKey");
        }
        if (ExceptionAggregationService.isTopLevelColumn(facetKey) ||
            ExceptionAggregationService.RESOURCE_FACET_KEYS.has(facetKey)) {
            return;
        }
        if (!ExceptionAggregationService.ATTRIBUTE_KEY_PATTERN.test(facetKey)) {
            throw new BadDataException("Invalid facetKey");
        }
    }
}
ExceptionAggregationService.DEFAULT_FACET_LIMIT = 500;
ExceptionAggregationService.TABLE_NAME = AnalyticsTableName.ExceptionInstance;
ExceptionAggregationService.TOP_LEVEL_COLUMNS = new Set([
    "primaryEntityId",
    "exceptionType",
    "environment",
    "fingerprint",
    "traceId",
    "spanId",
    "escaped",
    "release",
]);
/*
 * Virtual facet keys — same scheme as TraceAggregationService /
 * LogAggregationService. The `primaryEntityId` slot is reused for host /
 * docker host / k8s cluster ids, disambiguated by the `primaryEntityType`
 * discriminator column on each ExceptionInstance row.
 */
ExceptionAggregationService.RESOURCE_FACET_KEYS = new Map([
    ["hostId", ServiceType.Host],
    ["dockerHostId", ServiceType.DockerHost],
    ["kubernetesClusterId", ServiceType.KubernetesCluster],
    ["serverlessFunctionId", ServiceType.ServerlessFunction],
    ["cloudResourceId", ServiceType.CloudResource],
    ["rumApplicationId", ServiceType.RealUserMonitor],
]);
ExceptionAggregationService.ATTRIBUTE_KEY_PATTERN = /^[a-zA-Z0-9._:/-]+$/;
ExceptionAggregationService.MAX_FACET_KEY_LENGTH = 256;
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ExceptionAggregationService, "getHistogram", null);
__decorate([
    CaptureSpan(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], ExceptionAggregationService, "getFacetValues", null);
export default ExceptionAggregationService;
//# sourceMappingURL=ExceptionAggregationService.js.map