// 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 type {AggregateFunction} from '../datagrid/model';

/**
 * Aggregation functions available for charts.
 * Extends the datagrid's AggregateFunction with COUNT, which is
 * field-independent (counts rows rather than aggregating a column).
 */
export type ChartAggregation = AggregateFunction | 'COUNT';

/**
 * Format a number for display on chart axes.
 */
export function formatNumber(value: number): string {
  if (Number.isInteger(value)) {
    return value.toLocaleString();
  }
  if (Math.abs(value) >= 1000) {
    return value.toLocaleString(undefined, {maximumFractionDigits: 0});
  }
  if (Math.abs(value) >= 1) {
    return value.toLocaleString(undefined, {maximumFractionDigits: 2});
  }
  // For very small numbers, use more precision
  return value.toPrecision(3);
}

/**
 * Whether an aggregation always produces integer results.
 */
export function isIntegerAggregation(agg: ChartAggregation): boolean {
  return agg === 'COUNT' || agg === 'COUNT_DISTINCT';
}

/**
 * Compute the p-th percentile of a numeric array using linear interpolation.
 * Returns NaN for empty arrays.
 */
export function percentile(values: number[], p: number): number {
  if (values.length === 0) return NaN;
  const sorted = [...values].sort((a, b) => a - b);
  const idx = (p / 100) * (sorted.length - 1);
  const lower = Math.floor(idx);
  const upper = Math.ceil(idx);
  if (lower === upper) return sorted[lower];
  return sorted[lower] + (sorted[upper] - sorted[lower]) * (idx - lower);
}

// ---------------------------------------------------------------------------
// SQL helpers shared across chart loaders
// ---------------------------------------------------------------------------

// Valid SQL column name: identifier chars only (letters, digits, underscore).
const VALID_COLUMN_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

/**
 * Validate that a string is a safe SQL column identifier.
 * Throws if the name contains non-identifier characters.
 */
export function validateColumnName(name: string): void {
  if (!VALID_COLUMN_RE.test(name)) {
    throw new Error(`Invalid SQL column name: '${name}'`);
  }
}

// ---------------------------------------------------------------------------
// ECharts brush helpers
// ---------------------------------------------------------------------------

// Re-export EChartBrushEndParams for use by other chart modules.
export {type EChartBrushEndParams} from './echart_view';

/**
 * Extract the numeric brush range from an ECharts brushEnd event.
 * Returns [min, max] if a valid range was selected, undefined otherwise.
 *
 * This utility centralizes the brush range extraction logic used across
 * different chart types (line, bar, histogram).
 */
export function extractBrushRange(
  params: unknown,
): [number, number] | undefined {
  const p = params as {
    areas?: ReadonlyArray<{coordRange?: [number, number]}>;
  };
  const areas = p.areas;
  if (areas !== undefined && areas.length > 0 && areas[0].coordRange) {
    const [a, b] = areas[0].coordRange;
    return [Math.min(a, b), Math.max(a, b)];
  }
  return undefined;
}

/**
 * Extract the 2D brush rect from an ECharts `brushEnd` event (rect brush).
 * Returns {xMin, xMax, yMin, yMax} if a valid rect was selected, undefined
 * otherwise.
 *
 * For a rect brush, ECharts puts [[xMin, xMax], [yMin, yMax]] in coordRange.
 */
export function extractBrushRect(
  params: unknown,
): {xMin: number; xMax: number; yMin: number; yMax: number} | undefined {
  const p = params as {
    areas?: ReadonlyArray<{
      coordRange?: [[number, number], [number, number]];
    }>;
  };
  const areas = p.areas;
  if (areas === undefined || areas.length === 0) return undefined;
  const coordRange = areas[0].coordRange;
  if (coordRange === undefined) return undefined;
  const [[x1, x2], [y1, y2]] = coordRange;
  return {
    xMin: Math.min(x1, x2),
    xMax: Math.max(x1, x2),
    yMin: Math.min(y1, y2),
    yMax: Math.max(y1, y2),
  };
}
