// Copyright (C) 2026 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {QuerySlot, type QueryResult} from '../../../base/query_slot';
import type {Engine} from '../../../trace_processor/engine';
import {NUM, STR} from '../../../trace_processor/query_result';
import type {BoxplotData} from './boxplot';
import {validateColumnName} from './chart_utils';

/**
 * Configuration for boxplot loaders.
 */
export interface BoxplotLoaderConfig {
  /**
   * Maximum number of categories to show.
   * Categories are ordered by median value descending.
   */
  readonly limit?: number;
}

/**
 * Configuration for SQLBoxplotLoader.
 */
export interface SQLBoxplotLoaderOpts {
  /** The trace processor engine to run queries against. */
  readonly engine: Engine;

  /** SQL query that provides the data. */
  readonly query: string;

  /** Column name containing the category/grouping values. */
  readonly categoryColumn: string;

  /** Column name containing the numeric values to compute stats over. */
  readonly valueColumn: string;
}

/**
 * SQL-based boxplot loader.
 *
 * Computes quartile statistics (min, Q1, median, Q3, max) per category
 * using ROW_NUMBER and conditional aggregation to approximate percentiles
 * in SQLite (which lacks native PERCENTILE_CONT).
 */
export class SQLBoxplotLoader {
  private readonly engine: Engine;
  private readonly query: string;
  private readonly categoryColumn: string;
  private readonly valueColumn: string;
  private readonly querySlot = new QuerySlot<BoxplotData>();

  constructor(opts: SQLBoxplotLoaderOpts) {
    validateColumnName(opts.categoryColumn);
    validateColumnName(opts.valueColumn);
    this.engine = opts.engine;
    this.query = opts.query;
    this.categoryColumn = opts.categoryColumn;
    this.valueColumn = opts.valueColumn;
  }

  use(config: BoxplotLoaderConfig): QueryResult<BoxplotData> {
    const limit = config.limit ?? 20;
    const cat = this.categoryColumn;
    const val = this.valueColumn;

    const sql = `
WITH _src AS (
  SELECT
    CAST(${cat} AS TEXT) AS _cat,
    CAST(${val} AS REAL) AS _val
  FROM (${this.query})
  WHERE ${val} IS NOT NULL
),
_stats AS (
  SELECT
    _cat,
    MIN(_val) AS _min,
    MAX(_val) AS _max,
    COUNT(*) AS _cnt
  FROM _src
  GROUP BY _cat
),
_ranked AS (
  SELECT
    s._cat,
    s._val,
    ROW_NUMBER() OVER (PARTITION BY s._cat ORDER BY s._val) AS _rn,
    st._cnt
  FROM _src s
  JOIN _stats st ON s._cat = st._cat
),
_quartiles AS (
  SELECT
    _cat,
    MAX(CASE WHEN _rn = MAX(1, CAST((_cnt + 1) * 0.25 AS INT)) THEN _val END) AS _q1,
    MAX(CASE WHEN _rn = MAX(1, CAST((_cnt + 1) * 0.50 AS INT)) THEN _val END) AS _median,
    MAX(CASE WHEN _rn = MAX(1, CAST((_cnt + 1) * 0.75 AS INT)) THEN _val END) AS _q3
  FROM _ranked
  GROUP BY _cat
)
SELECT
  s._cat,
  s._min,
  q._q1,
  q._median,
  q._q3,
  s._max
FROM _stats s
JOIN _quartiles q ON s._cat = q._cat
ORDER BY q._median DESC
LIMIT ${limit}`.trim();

    return this.querySlot.use({
      key: {sql},
      retainOn: ['sql'],
      queryFn: async () => {
        const queryResult = await this.engine.query(sql);
        const items = [];

        const iter = queryResult.iter({
          _cat: STR,
          _min: NUM,
          _q1: NUM,
          _median: NUM,
          _q3: NUM,
          _max: NUM,
        });

        for (; iter.valid(); iter.next()) {
          items.push({
            label: iter._cat,
            min: iter._min,
            q1: iter._q1,
            median: iter._median,
            q3: iter._q3,
            max: iter._max,
          });
        }

        return {items};
      },
    });
  }

  dispose(): void {
    this.querySlot.dispose();
  }
}
