import {LegendComponentOption} from 'echarts';

import {t} from 'sentry/locale';
import {Series} from 'sentry/types/echarts';
import {defined} from 'sentry/utils';
import {aggregateOutputType} from 'sentry/utils/discover/fields';
import {
  DAY,
  formatAbbreviatedNumber,
  formatPercentage,
  getDuration,
  HOUR,
  MINUTE,
  SECOND,
  WEEK,
} from 'sentry/utils/formatters';

/**
 * Formatter for chart tooltips that handle a variety of discover and metrics result values.
 * If the result is metric values, the value can be of type number or null
 */
export function tooltipFormatter(value: number | null, seriesName: string = ''): string {
  if (!defined(value)) {
    return '\u2014';
  }
  switch (aggregateOutputType(seriesName)) {
    case 'integer':
    case 'number':
      return value.toLocaleString();
    case 'percentage':
      return formatPercentage(value, 2);
    case 'duration':
      return getDuration(value / 1000, 2, true);
    default:
      return value.toString();
  }
}

/**
 * Formatter for chart axis labels that handle a variety of discover result values
 * This function is *very similar* to tooltipFormatter but outputs data with less precision.
 */
export function axisLabelFormatter(
  value: number,
  seriesName: string,
  abbreviation: boolean = false,
  durationUnit?: number
): string {
  switch (aggregateOutputType(seriesName)) {
    case 'integer':
    case 'number':
      return abbreviation ? formatAbbreviatedNumber(value) : value.toLocaleString();
    case 'percentage':
      return formatPercentage(value, 0);
    case 'duration':
      return axisDuration(value, durationUnit);
    default:
      return value.toString();
  }
}

/**
 * Specialized duration formatting for axis labels.
 * In that context we are ok sacrificing accuracy for more
 * consistent sizing.
 *
 * @param value Number of milliseconds to format.
 */
export function axisDuration(value: number, durationUnit?: number): string {
  durationUnit ??= categorizeDuration(value);
  if (value === 0) {
    return '0';
  }
  switch (durationUnit) {
    case WEEK: {
      const label = (value / WEEK).toFixed(0);
      return t('%swk', label);
    }
    case DAY: {
      const label = (value / DAY).toFixed(0);
      return t('%sd', label);
    }
    case HOUR: {
      const label = (value / HOUR).toFixed(0);
      return t('%shr', label);
    }
    case MINUTE: {
      const label = (value / MINUTE).toFixed(0);
      return t('%smin', label);
    }
    case SECOND: {
      const label = (value / SECOND).toFixed(0);
      return t('%ss', label);
    }
    default:
      const label = value.toFixed(0);
      return t('%sms', label);
  }
}

/**
 * Given an array of series and an eCharts legend object,
 * finds the range of y values (min and max) based on which series is selected in the legend
 * Assumes series[0] > series[1] > ...
 * @param series Array of eCharts series
 * @param legend eCharts legend object
 * @returns
 */
export function findRangeOfMultiSeries(series: Series[], legend?: LegendComponentOption) {
  let range: {max: number; min: number} | undefined;
  if (series[0]?.data) {
    let minSeries = series[0];
    let maxSeries;
    series.forEach(({seriesName}, idx) => {
      if (legend?.selected?.[seriesName] !== false) {
        minSeries = series[idx];
        maxSeries ??= series[idx];
      }
    });
    if (maxSeries?.data) {
      const max = Math.max(...maxSeries.data.map(({value}) => value));
      const min = Math.min(
        ...minSeries.data.map(({value}) => value).filter(value => !!value)
      );
      range = {max, min};
    }
  }
  return range;
}

/**
 * Given a eCharts series and legend, returns the unit to be used on the yAxis for a duration chart
 * @param series eCharts series array
 * @param legend eCharts legend object
 * @returns
 */
export function getDurationUnit(
  series: Series[],
  legend?: LegendComponentOption
): number {
  let durationUnit = 0;
  const range = findRangeOfMultiSeries(series, legend);
  if (range) {
    const avg = (range.max + range.min) / 2;
    durationUnit = categorizeDuration((range.max - range.min) / 5); // avg of 5 yAxis ticks per chart

    const numOfDigits = (avg / durationUnit).toFixed(0).length;
    if (numOfDigits > 6) {
      durationUnit = categorizeDuration(avg);
    }
  }
  return durationUnit;
}

/**
 * Categorizes the duration by Second, Minute, Hour, etc
 * Ex) categorizeDuration(1200) = MINUTE
 * @param value Duration in ms
 */
export function categorizeDuration(value): number {
  if (value >= WEEK) {
    return WEEK;
  }
  if (value >= DAY) {
    return DAY;
  }
  if (value >= HOUR) {
    return HOUR;
  }
  if (value >= MINUTE) {
    return MINUTE;
  }
  if (value >= SECOND) {
    return SECOND;
  }
  return 1;
}
