"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ragChartsService = exports.RagChartsService = void 0;
const date_fns_1 = require("date-fns");
const clickhouseService_1 = require("./clickhouseService");
const utilts_1 = require("./observe/utils/utilts");
const decimal_js_1 = __importDefault(require("decimal.js"));
const DEFAULT_START_DAYS = 7;
// Bucketing thresholds (aligned with observeCharts): short range → hourly, then daily, weekly, monthly.
const TABLE_SWITCH_DAYS = 3;
const WEEKLY_BUCKET_AFTER_MONTHS = 1;
const MONTHLY_BUCKET_AFTER_MONTHS = 4;
const HOURLY_TIMESTAMP_THRESHOLD_HOURS = 48;
function parseStart(s) {
    if (s && s.trim()) {
        const d = new Date(s.trim());
        if (Number.isFinite(d.getTime())) {
            return d;
        }
    }
    return (0, date_fns_1.sub)(new Date(), { days: DEFAULT_START_DAYS });
}
function parseEnd(s) {
    if (s && s.trim()) {
        const d = new Date(s.trim());
        if (Number.isFinite(d.getTime())) {
            return d;
        }
    }
    return new Date();
}
function shouldUseHourlyTimestamp(start, end) {
    const startDate = parseStart(start);
    const endDate = parseEnd(end);
    const diffHours = Math.abs(endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60);
    return diffHours <= HOURLY_TIMESTAMP_THRESHOLD_HOURS;
}
/** Filter condition for RAG-only rows in agg_daily/agg_hourly (vectordb_name is set). */
const RAG_ROW_FILTER = `AND length(trim(vectordb_name)) > 0`;
class RagChartsService {
    constructor() { }
    static getInstance() {
        if (RagChartsService.instance === null) {
            RagChartsService.instance = new RagChartsService();
        }
        return RagChartsService.instance;
    }
    /** Range-based bucket config: &lt; 3 days → hourly; 1–3 months → weekly; &gt;4 months → monthly; else daily. */
    getBucketConfig(params) {
        const start = parseStart(params.start);
        const end = parseEnd(params.end);
        const diffDays = Math.abs(end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24);
        const diffMonthsApprox = diffDays / 30;
        const useHourly = diffDays < TABLE_SWITCH_DAYS;
        const bucketUnit = useHourly
            ? 'hour'
            : diffMonthsApprox > MONTHLY_BUCKET_AFTER_MONTHS
                ? 'month'
                : diffMonthsApprox > WEEKLY_BUCKET_AFTER_MONTHS
                    ? 'week'
                    : 'day';
        const table = useHourly ? 'agg_hourly' : 'agg_daily';
        const bucketCol = useHourly ? 'hour' : 'day';
        const bucketTypeFn = useHourly ? 'toDateTime' : 'toDate';
        const bucketStart = useHourly ? (0, date_fns_1.format)(start, 'yyyy-MM-dd HH:mm:ss') : (0, date_fns_1.format)(start, 'yyyy-MM-dd');
        const bucketEnd = useHourly ? (0, date_fns_1.format)(end, 'yyyy-MM-dd HH:mm:ss') : (0, date_fns_1.format)(end, 'yyyy-MM-dd');
        const bucketExpr = bucketUnit === 'hour' || bucketUnit === 'day'
            ? bucketCol
            : bucketUnit === 'week'
                ? `toDate(toStartOfWeek(toDateTime(${bucketCol})))`
                : `toDate(toStartOfMonth(toDateTime(${bucketCol})))`;
        return {
            table,
            bucketCol,
            bucketExpr,
            bucketTypeFn,
            bucketStart,
            bucketEnd,
            bucketUnit,
        };
    }
    generateTimeSeriesCTE(config) {
        const { bucketUnit, bucketStart, bucketEnd, bucketTypeFn } = config;
        if (bucketUnit === 'hour') {
            return `
time_series AS (
  SELECT toStartOfHour(${bucketTypeFn}('${bucketStart}') + INTERVAL number HOUR) AS bucket
  FROM numbers(toUInt64(dateDiff('hour', ${bucketTypeFn}('${bucketStart}'), ${bucketTypeFn}('${bucketEnd}')) + 1))
  WHERE bucket <= ${bucketTypeFn}('${bucketEnd}')
)`;
        }
        if (bucketUnit === 'day') {
            return `
time_series AS (
  SELECT toDate(${bucketTypeFn}('${bucketStart}') + INTERVAL number DAY) AS bucket
  FROM numbers(toUInt64(dateDiff('day', ${bucketTypeFn}('${bucketStart}'), ${bucketTypeFn}('${bucketEnd}')) + 1))
  WHERE bucket <= ${bucketTypeFn}('${bucketEnd}')
)`;
        }
        if (bucketUnit === 'week') {
            return `
time_series AS (
  SELECT toDate(toStartOfWeek(toDateTime('${bucketStart}')) + INTERVAL number WEEK) AS bucket
  FROM numbers(toUInt64(dateDiff('week', toStartOfWeek(toDateTime('${bucketStart}')), toStartOfWeek(toDateTime('${bucketEnd}'))) + 1))
  WHERE bucket <= toDate('${bucketEnd}')
)`;
        }
        return `
time_series AS (
  SELECT toDate(toStartOfMonth(toDateTime('${bucketStart}')) + INTERVAL number MONTH) AS bucket
  FROM numbers(toUInt64(dateDiff('month', toStartOfMonth(toDateTime('${bucketStart}')), toStartOfMonth(toDateTime('${bucketEnd}'))) + 1))
  WHERE bucket <= toDate('${bucketEnd}')
)`;
    }
    buildWhere(params, config) {
        const workspaceId = (params.orgId ?? '').replace(/'/g, "''");
        const projectId = (params.projectId ?? '').replace(/'/g, "''");
        const applicationId = (params.applicationId ?? '').replace(/'/g, "''");
        return `
      ${config.bucketCol} >= ${config.bucketTypeFn}('${config.bucketStart}')
      AND ${config.bucketCol} <= ${config.bucketTypeFn}('${config.bucketEnd}')
      AND workspaceId = '${workspaceId}'
      AND projectId = '${projectId}'
      AND applicationId = '${applicationId}'
      AND agent = '__all__'
      AND toolName = '__all__'
      AND model = '__all__'
      ${RAG_ROW_FILTER}
    `.trim();
    }
    /**
     * 1. Requests Breakdown - Passed, Error, Failed counts for pie chart.
     */
    async getRequestsBreakdown(params) {
        const config = this.getBucketConfig(params);
        const where = this.buildWhere(params, config);
        const query = `
      SELECT
        sumMerge(vectordb_pass_count_state) AS passed,
        sumMerge(vectordb_fail_count_state) AS failed,
        sumMerge(vectordb_error_count_state) AS error
      FROM ${config.table}
      WHERE ${where}
    `;
        const rows = await clickhouseService_1.clickHouseService.query(query);
        const row = rows?.[0];
        return {
            passed: Math.max(0, Number(row?.passed ?? 0)),
            failed: Math.max(0, Number(row?.failed ?? 0)),
            error: Math.max(0, Number(row?.error ?? 0)),
        };
    }
    /**
     * 2. Retrieval Hit Rate - Line chart: day vs percentage; optional mode for percentile of latency.
     */
    async getRetrievalHitRate(params) {
        const config = this.getBucketConfig(params);
        const where = this.buildWhere(params, config);
        const mode = (params.mode ?? 'avg').toLowerCase();
        const percentileHitsExpr = mode === 'p60'
            ? `arrayElement(quantilesMerge(0.6, 0.9, 0.99)(vectordb_retrievalHit_quantiles_state), 1)`
            : mode === 'p90'
                ? `arrayElement(quantilesMerge(0.6, 0.9, 0.99)(vectordb_retrievalHit_quantiles_state), 2)`
                : `arrayElement(quantilesMerge(0.6, 0.9, 0.99)(vectordb_retrievalHit_quantiles_state), 3)`;
        const percentageExprInAgg = mode === 'p60'
            ? `if(total > 0, round((${percentileHitsExpr} / total) * 100, 2), 0)`
            : mode === 'p90'
                ? `if(total > 0, round((${percentileHitsExpr} / total) * 100, 2), 0)`
                : mode === 'p99'
                    ? `if(total > 0, round((${percentileHitsExpr} / total) * 100, 2), 0)`
                    : `if(total > 0, round((hits / total) * 100, 2), 0)`;
        const dayFmt = shouldUseHourlyTimestamp(params.start, params.end)
            ? '%Y-%m-%d %H:00:00'
            : '%Y-%m-%d';
        const query = `
      WITH ${this.generateTimeSeriesCTE(config).trim()},
      agg AS (
        SELECT
          ${config.bucketExpr} AS bucket,
          sumMerge(vectordb_retrievalHit_count_state) AS hits,
          sumMerge(vectordb_retrievalTotal_count_state) AS total,
          ${percentageExprInAgg} AS percentage
        FROM ${config.table}
        WHERE ${where}
        GROUP BY ${config.bucketExpr}
      )
      SELECT
        formatDateTime(ts.bucket, '${dayFmt}') AS day,
        ifNull(agg.percentage, 0) AS percentage,
        agg.hits AS hits
      FROM time_series ts
      LEFT JOIN agg ON ts.bucket = agg.bucket
      ORDER BY ts.bucket ASC
    `;
        const rows = await clickhouseService_1.clickHouseService.query(query);
        return (rows ?? []).map((r) => ({
            day: String(r?.day ?? ''),
            percentage: Number(r?.percentage ?? 0),
            hits: Number(r?.hits ?? 0),
        }));
    }
    /**
     * 3. Retrieval Latency - Line chart: day vs latency (avg or p60/p90/p99).
     */
    async getRetrievalLatency(params) {
        const config = this.getBucketConfig(params);
        const where = this.buildWhere(params, config);
        const mode = (params.mode ?? 'avg').toLowerCase();
        const valueExpr = mode === 'p60'
            ? `quantilesMerge(0.6)(vectordb_retrievalLatency_quantiles_state)[1]`
            : mode === 'p90'
                ? `quantilesMerge(0.9)(vectordb_retrievalLatency_quantiles_state)[1]`
                : mode === 'p99'
                    ? `quantilesMerge(0.99)(vectordb_retrievalLatency_quantiles_state)[1]`
                    : `sumMerge(vectordb_retrievalLatency_state) / greatest(sumMerge(vectordb_retrievalLatencyCount_state), 1)`;
        const dayFmt = shouldUseHourlyTimestamp(params.start, params.end)
            ? '%Y-%m-%d %H:00:00'
            : '%Y-%m-%d';
        const query = `
      WITH ${this.generateTimeSeriesCTE(config).trim()},
      agg AS (
        SELECT
          ${config.bucketExpr} AS bucket,
          ${valueExpr} AS latencyMs
        FROM ${config.table}
        WHERE ${where}
        GROUP BY ${config.bucketExpr}
      )
      SELECT
        formatDateTime(ts.bucket, '${dayFmt}') AS day,
        ifNull(agg.latencyMs, 0) AS latencyMs
      FROM time_series ts
      LEFT JOIN agg ON ts.bucket = agg.bucket
      ORDER BY ts.bucket ASC
    `;
        const rows = await clickhouseService_1.clickHouseService.query(query);
        return (rows ?? []).map((r) => ({
            day: String(r?.day ?? ''),
            latencyMs: new decimal_js_1.default(r?.latencyMs ?? 0).toDecimalPlaces(8).toNumber(),
        }));
    }
    /**
     * 4. Retrieval Latency Distribution - Histogram (bin, count).
     */
    async getRetrievalLatencyDistribution(params) {
        const config = this.getBucketConfig(params);
        const where = this.buildWhere(params, config);
        const query = `
      SELECT
        bins[i] AS bin,
        counts[i] AS count
      FROM (
        SELECT
          tupleElement(merged, 1) AS bins,
          tupleElement(merged, 2) AS counts
        FROM (
          SELECT sumMapMerge(vectordb_retrievalLatency_hist_sumMap_state) AS merged
          FROM ${config.table}
          WHERE ${where}
        )
      )
      ARRAY JOIN arrayEnumerate(bins) AS i
      ORDER BY toInt32OrZero(splitByString('-', bin)[1])
    `;
        const result = await clickhouseService_1.clickHouseService.query(query);
        const data = (result ?? []).map((row) => ({
            bin: String(row?.bin ?? ''),
            count: Number(row?.count ?? 0),
        }));
        return utilts_1.ChartUtils.padHistogramBinsToMin(data, { minPoints: 20, binWidth: 0.05, minStart: 0, maxPoints: 40 });
    }
    /**
     * 5. VectorDB Throughput - Multi-line: Queries, Documents Retrieved, Avg Docs/Request per day.
     */
    async getVectorDbThroughput(params) {
        const config = this.getBucketConfig(params);
        const where = this.buildWhere(params, config);
        const dayFmt = shouldUseHourlyTimestamp(params.start, params.end)
            ? '%Y-%m-%d %H:00:00'
            : '%Y-%m-%d';
        const query = `
      WITH ${this.generateTimeSeriesCTE(config).trim()},
      agg AS (
        SELECT
          ${config.bucketExpr} AS bucket,
          sumMerge(vectordb_queries_count_state) AS queries,
          sumMerge(vectordb_docsRetrieved_state) AS documentsRetrieved,
          sumMerge(vectordb_docsRetrievedCount_state) AS docsCount
        FROM ${config.table}
        WHERE ${where}
        GROUP BY ${config.bucketExpr}
      )
      SELECT
        formatDateTime(ts.bucket, '${dayFmt}') AS day,
        ifNull(agg.queries, 0) AS queries,
        ifNull(agg.documentsRetrieved, 0) AS documentsRetrieved,
        if(agg.docsCount > 0, round(agg.documentsRetrieved / agg.docsCount, 2), 0) AS avgDocsPerRequest
      FROM time_series ts
      LEFT JOIN agg ON ts.bucket = agg.bucket
      ORDER BY ts.bucket ASC
    `;
        const rows = await clickhouseService_1.clickHouseService.query(query);
        return (rows ?? []).map((r) => ({
            day: String(r?.day ?? ''),
            queries: Number(r?.queries ?? 0),
            documentsRetrieved: Number(r?.documentsRetrieved ?? 0),
            avgDocsPerRequest: Number(r?.avgDocsPerRequest ?? 0),
        }));
    }
    /**
     * 6. Vector DB Performance Trend - Table/summary: Queries, Documents Retrieved, Avg Docs/Request (totals).
     */
    async getVectorDbPerformanceTrend(params) {
        const config = this.getBucketConfig(params);
        const where = this.buildWhere(params, config);
        const query = `
      SELECT
        sumMerge(vectordb_queries_count_state) AS queries,
        sumMerge(vectordb_docsRetrieved_state) AS documentsRetrieved,
        sumMerge(vectordb_docsRetrievedCount_state) AS docsCount
      FROM ${config.table}
      WHERE ${where}
    `;
        const rows = await clickhouseService_1.clickHouseService.query(query);
        const row = rows?.[0];
        const queries = Math.max(0, Number(row?.queries ?? 0));
        const documentsRetrieved = Math.max(0, Number(row?.documentsRetrieved ?? 0));
        const docsCount = Math.max(0, Number(row?.docsCount ?? 0));
        return {
            queries,
            documentsRetrieved,
            avgDocsPerRequest: docsCount > 0 ? Math.round((documentsRetrieved / docsCount) * 100) / 100 : 0,
        };
    }
    /**
     * 7. Request Volume by Operation - Pie: operation -> count.
     * sumMapMerge returns a tuple (keys_array, values_array), not a Map; use tupleElement, not mapKeys/mapValues.
     */
    async getRequestVolumeByOperation(params) {
        const config = this.getBucketConfig(params);
        const where = this.buildWhere(params, config);
        const query = `
      SELECT
        tupleElement(pair, 1) AS operation,
        tupleElement(pair, 2) AS count
      FROM (
        SELECT
          coalesce(tupleElement(merged, 1), []) AS keysArr,
          coalesce(tupleElement(merged, 2), []) AS valuesArr
        FROM (
          SELECT sumMapMerge(vectordb_requestVolumeByOperation_sumMap_state) AS merged
          FROM ${config.table}
          WHERE ${where}
        )
      )
      ARRAY JOIN arrayZip(keysArr, valuesArr) AS pair
      ORDER BY count DESC
    `;
        const raw = await clickhouseService_1.clickHouseService.query(query);
        return (raw ?? []).map((r) => ({
            operation: String(r?.operation ?? 'unknown').trim() || 'unknown',
            count: Math.max(0, Number(r?.count ?? 0)),
        }));
    }
    /**
     * 8. Response Time Analysis By Operation - Multi-line: per operation, day vs latency (avg or percentile).
     */
    async getResponseTimeByOperation(params) {
        const config = this.getBucketConfig(params);
        const where = this.buildWhere(params, config);
        const dayFmt = config.bucketUnit === 'hour' ? '%Y-%m-%d %H:00:00' : '%Y-%m-%d';
        // Derive per-operation avg latency from columns that are reliably populated:
        //   - operation names from vectordb_requestVolumeByOperation_sumMap_state (proven working)
        //   - avg latency from vectordb_retrievalLatency_state / vectordb_retrievalLatencyCount_state
        // All operations in the same day bucket share the same overall avg since per-op breakdown
        // requires vectordb_responseTimeByOp_*_sumMap_state columns which only have data for traces
        // ingested after those columns were added to the schema.
        const query = `
      WITH ${this.generateTimeSeriesCTE(config).trim()},
      agg AS (
        SELECT
          ${config.bucketExpr} AS bucket,
          round(
            sumMerge(vectordb_retrievalLatency_state)
              / greatest(sumMerge(vectordb_retrievalLatencyCount_state), 1),
            2
          ) AS avgLatency,
          tupleElement(sumMapMerge(vectordb_requestVolumeByOperation_sumMap_state), 1) AS opKeys
        FROM ${config.table}
        WHERE ${where}
        GROUP BY ${config.bucketExpr}
      )
      SELECT
        formatDateTime(ts.bucket, '${dayFmt}') AS day,
        op AS operation,
        ifNull(agg.avgLatency, 0) AS latencyMs
      FROM time_series ts
      LEFT JOIN agg ON ts.bucket = agg.bucket
      ARRAY JOIN agg.opKeys AS op
      WHERE op != ''
      ORDER BY ts.bucket ASC, op ASC
    `;
        const rows = await clickhouseService_1.clickHouseService.query(query);
        return (rows ?? []).map((r) => ({
            day: String(r?.day ?? ''),
            operation: String(r?.operation ?? ''),
            latencyMs: new decimal_js_1.default(r?.latencyMs ?? 0).toDecimalPlaces(8).toNumber(),
        }));
    }
}
exports.RagChartsService = RagChartsService;
RagChartsService.instance = null;
exports.ragChartsService = RagChartsService.getInstance();
//# sourceMappingURL=ragChartsService.js.map