import { ILineChartMetricUnit, ILineChartGraphType } from './type.js';
import { HOUR, DAY, SECOND, MINUTE, unitRules, formatPercent, formatCount, formatBytes, formatBps, formatBitPerSecond, formatNanoSecond, formatFrequency, formatTemperature } from '../../utils/tower.js';
import dayjs from 'dayjs';
import _ from 'lodash';

var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
  for (var prop in b || (b = {}))
    if (__hasOwnProp.call(b, prop))
      __defNormalProp(a, prop, b[prop]);
  if (__getOwnPropSymbols)
    for (var prop of __getOwnPropSymbols(b)) {
      if (__propIsEnum.call(b, prop))
        __defNormalProp(a, prop, b[prop]);
    }
  return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __objRest = (source, exclude) => {
  var target = {};
  for (var prop in source)
    if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
      target[prop] = source[prop];
  if (source != null && __getOwnPropSymbols)
    for (var prop of __getOwnPropSymbols(source)) {
      if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
        target[prop] = source[prop];
    }
  return target;
};
function filterLineChartPointsByDateRange(points, dateRange) {
  const [startDate, endDate] = dateRange != null ? dateRange : [dayjs(), dayjs()];
  return points == null ? void 0 : points.filter((point) => {
    const pointDate = dayjs(point.t);
    if (startDate && pointDate.isBefore(startDate)) {
      return false;
    }
    if (endDate && pointDate.isAfter(endDate)) {
      return false;
    }
    return true;
  });
}
const parseLineChartRange = (range) => {
  const span = parseInt(range.slice(0, range.length - 1));
  const unit = range.slice(range.length - 1);
  return { span, unit };
};
function getRangeTimestamp(dateRange) {
  const [startDate, endDate] = dateRange;
  return endDate.valueOf() - startDate.valueOf();
}
const lineChartXaxisCal = (lastTime, dateRange, width) => {
  const tangeTime = getRangeTimestamp(dateRange);
  const baseline = lastTime || dateRange[1].valueOf();
  if (width < 290) {
    const tick = tangeTime / 4;
    return [baseline - tick * 3, baseline - tick];
  } else if (width < 380) {
    const tick = tangeTime / 6;
    return [baseline - tick * 5, baseline - tick * 3, baseline - tick];
  } else {
    const tick = tangeTime / 8;
    return [
      baseline - tick * 7,
      baseline - tick * 5,
      baseline - tick * 3,
      baseline - tick
    ];
  }
};
const lineChartTickFormatter = (tick, dateRange) => {
  const rangeTime = getLineChartRangeTimestamp(dateRange);
  if (rangeTime <= 2 * HOUR * 1e3) {
    return dayjs(tick).format("HH:mm:ss");
  }
  if (rangeTime <= 4 * DAY * 1e3) {
    return dayjs(tick).format("MM/DD HH:mm:ss");
  }
  return dayjs(tick).format("MM/DD");
};
function getLineChartXAxisDomain(dateRange, xaxisLastTime) {
  const [startDate, endDate] = dateRange;
  return [startDate.valueOf(), xaxisLastTime != null ? xaxisLastTime : endDate.valueOf()];
}
const getLineChartRangeTimestamp = (dateRange) => {
  const [startDate, endDate] = dateRange;
  return endDate.valueOf() - startDate.valueOf();
};
const getLineChartStep = (dateRange) => {
  const [startDate, endDate] = dateRange;
  const range = endDate.valueOf() - startDate.valueOf();
  if (range <= 2 * 60 * 60 * 1e3) {
    return SECOND * 30 * 1e3;
  }
  if (range <= 24 * 60 * 60 * 1e3) {
    return MINUTE * 5 * 1e3;
  }
  if (range <= 7 * 24 * 60 * 60 * 1e3) {
    return MINUTE * 30 * 1e3;
  }
  if (range <= 30 * 24 * 60 * 60 * 1e3) {
    return DAY * 1e3;
  }
  return DAY * 1e3;
};
const filterLineChartDataOverlapping = (data, startTimestamp, length, step, tolerance) => {
  const points = [...new Array(Math.round(length))].map((_2, index) => {
    return startTimestamp + index * step;
  }).map((timestamp) => {
    const closestPoint = _.minBy(
      data.filter((value) => {
        return Math.abs(value.t - timestamp) < tolerance;
      }),
      (value) => {
        return Math.abs(value.t - timestamp);
      }
    );
    if (closestPoint == null) {
      return {
        t: timestamp,
        v: -Infinity
      };
    }
    return closestPoint;
  });
  return points;
};
const convertLineChartDataStruct = (streams) => {
  const points = streams.flatMap((points2, index) => {
    return points2.map((point) => {
      return __spreadProps(__spreadValues({}, point), { [`v${index}`]: point.v });
    });
  });
  const groupedPoints = _.groupBy(points, (point) => {
    return point.t;
  });
  const combinedPoints = Object.entries(groupedPoints).map(([key, values]) => {
    return values.reduce((p, c) => {
      const _a = __spreadValues(__spreadValues({}, p), c), values2 = __objRest(_a, ["v"]);
      return values2;
    }, {});
  });
  return combinedPoints;
};
const filterLineChartOverlappingMetric = (metric, dateRange) => {
  const range = getLineChartRangeTimestamp(dateRange);
  return __spreadProps(__spreadValues({}, metric), {
    sample_streams: metric.sample_streams.map((stream) => __spreadProps(__spreadValues({}, stream), {
      points: filterLineChartDataOverlapping(
        stream.points,
        dateRange[0].valueOf(),
        range / stream.step,
        stream.step,
        stream.tolerance
      )
    }))
  });
};
const deletePointsOutOfRange = (data, timeRange, now) => {
  const first = now - getLineChartRangeTimestamp(timeRange);
  return (data == null ? void 0 : data.filter((item) => {
    return item.t >= first && item.t <= now;
  })) || [];
};
const getFirstExpectedTimestamp = (data, timeRange, now, step, dateRange) => {
  const first = timeRange ? now - getLineChartRangeTimestamp(timeRange) : dayjs(dateRange == null ? void 0 : dateRange[0]).valueOf();
  const firstRealTimestamp = data[0].t;
  return firstRealTimestamp - Math.floor((firstRealTimestamp - first) / step) * step;
};
const getFaultToleranceTime = (timeRange) => {
  const [startDate, endDate] = timeRange;
  const range = endDate.valueOf() - startDate.valueOf();
  switch (range) {
    case 5 * MINUTE:
      return 5 * 1e3;
    case 15 * MINUTE:
      return 15 * 1e3;
    case 1 * HOUR:
      return 60 * 1e3;
    case 2 * HOUR:
      return 120 * 1e3;
    case 24 * HOUR:
      return 10 * 60 * 1e3;
    case 7 * DAY:
    case 30 * DAY:
      return 60 * 60 * 1e3;
    case 182 * DAY:
      return 24 * 60 * 60 * 1e3;
  }
  return 120 * 1e3;
};
const addMissingDataWithZero = (data, timeRange, unit, step, dateRange) => {
  const now = Date.now();
  const inRangePoints = deletePointsOutOfRange(data, timeRange, now);
  if (!inRangePoints.length) {
    return [];
  }
  const firsExpectedTimestamp = getFirstExpectedTimestamp(
    inRangePoints,
    timeRange,
    now,
    step,
    dateRange
  );
  const expectedPoints = [];
  const tolerance = getFaultToleranceTime(timeRange);
  if (inRangePoints[0].t - firsExpectedTimestamp > tolerance + step) {
    inRangePoints.unshift({
      t: firsExpectedTimestamp,
      v: -Infinity
    });
  }
  if (now - inRangePoints[inRangePoints.length - 1].t > tolerance + step) {
    inRangePoints.push({
      t: now,
      v: -Infinity
    });
  }
  inRangePoints.forEach((item, index) => {
    var _a;
    expectedPoints.push({
      t: item == null ? void 0 : item.t,
      v: item == null ? void 0 : item.v
    });
    if (index + 1 <= inRangePoints.length - 1) {
      const nowTimeRange = ((_a = inRangePoints[index + 1]) == null ? void 0 : _a.t) - (item == null ? void 0 : item.t);
      if (nowTimeRange > tolerance + step) {
        const n = Math.floor(nowTimeRange / step);
        for (let i = 1; i < n; i++) {
          expectedPoints.push({
            t: (item == null ? void 0 : item.t) + step * i,
            v: -Infinity
          });
        }
      }
    }
  });
  return filterLineChartPointsByDateRange(
    expectedPoints.map((p) => __spreadProps(__spreadValues({}, p), { unit })),
    dateRange
  );
};
const convertLineChartUnit = (value, from, to, decimals) => {
  if (from === to || !value)
    return value;
  const ruleIndex = unitRules.findIndex((rule) => rule.unit.includes(from));
  if (ruleIndex === -1) {
    return value;
  }
  const { unit, divider } = unitRules[ruleIndex];
  let fromIdx = unit.indexOf(from);
  const toIdx = unit.indexOf(to);
  if (toIdx === -1) {
    return value;
  }
  if (fromIdx < toIdx) {
    while (fromIdx < toIdx) {
      value /= divider[fromIdx];
      fromIdx++;
    }
  } else {
    while (fromIdx > toIdx) {
      value *= divider[fromIdx - 1];
      fromIdx--;
    }
  }
  return _.isInteger(value) ? value : _.isNil(decimals) ? value : parseFloat(value.toFixed(decimals));
};
const UNIT_FORMATTER = {
  [ILineChartMetricUnit.Percent]: [formatPercent, "%", 2, false],
  [ILineChartMetricUnit.Ratio]: [formatPercent, "%"],
  [ILineChartMetricUnit.Count]: [formatCount, ""],
  [ILineChartMetricUnit.DataSize]: [formatBytes, "B"],
  [ILineChartMetricUnit.DataRateByte]: [formatBps, "Bps"],
  [ILineChartMetricUnit.DataRateBit]: [formatBitPerSecond, "bps"],
  [ILineChartMetricUnit.Time]: [formatNanoSecond, "ns"],
  [ILineChartMetricUnit.Frequency]: [formatFrequency, "Hz"],
  [ILineChartMetricUnit.Temperature]: [formatTemperature, "\u2103"],
  [ILineChartMetricUnit.Load]: [formatCount, ""]
};
const getLineChartYDataMax = (dataPoints, type) => {
  const values = dataPoints.map((p) => {
    if (_.isNumber(p == null ? void 0 : p.v)) {
      return p.v;
    }
    const entries = Object.entries(p);
    if (type === ILineChartGraphType.Stack) {
      let sum = 0;
      for (const [key, value] of entries) {
        if (/^v(\d*)$/.test(key) && _.isNumber(value)) {
          sum += value;
        }
      }
      return sum;
    } else {
      let max = 0;
      for (const [key, value] of entries) {
        if (/^v(\d*)$/.test(key) && _.isNumber(value) && value > max) {
          max = value;
        }
      }
      return max;
    }
  });
  return Math.max(...values, 0);
};
const getYAxisUpperBound = (max, type) => {
  const DIVISOR = 2;
  switch (type) {
    case ILineChartMetricUnit.Count:
    case ILineChartMetricUnit.Load:
    case ILineChartMetricUnit.DataSize:
    case ILineChartMetricUnit.DataRateByte:
    case ILineChartMetricUnit.Time:
    case ILineChartMetricUnit.Frequency:
    case ILineChartMetricUnit.DataRateBit:
    case ILineChartMetricUnit.Temperature: {
      const [format, base] = UNIT_FORMATTER[type];
      const { value, unit } = format(max);
      const _value = Math.ceil(Number(value) / DIVISOR) * DIVISOR;
      return convertLineChartUnit(_value, unit, base);
    }
    case ILineChartMetricUnit.Percent: {
      const [format, base, decimals, saturated] = UNIT_FORMATTER[type];
      const { value, unit } = format(max, decimals, saturated);
      const _value = Math.ceil(Number(value) / DIVISOR) * DIVISOR;
      return convertLineChartUnit(_value, unit, base);
    }
    case ILineChartMetricUnit.Ratio: {
      const [format, base] = UNIT_FORMATTER[type];
      const { value, unit } = format(max * 100);
      const _value = Math.ceil(Number(value) / DIVISOR) * DIVISOR;
      return convertLineChartUnit(_value, unit, base) / 100;
    }
    default:
      return 2;
  }
};
const getYAxisDomain = (dataPoints, graphType, unitType) => {
  const max = getLineChartYDataMax(dataPoints, graphType);
  if (!max) {
    if (unitType === ILineChartMetricUnit.Ratio) {
      return [0, 1];
    } else {
      return [0, 2];
    }
  }
  return [0, getYAxisUpperBound(max, unitType)];
};
function lineChartYaxisTickFormatter(value, unit) {
  if (_.isNil(unit)) {
    return `${value}`;
  }
  if (_.isNil(value)) {
    return "0";
  }
  const [format] = UNIT_FORMATTER[unit];
  const { value: _value, unit: _unit } = format(value);
  return `${_value} ${_unit}`;
}

export { UNIT_FORMATTER, addMissingDataWithZero, convertLineChartDataStruct, convertLineChartUnit, deletePointsOutOfRange, filterLineChartDataOverlapping, filterLineChartOverlappingMetric, filterLineChartPointsByDateRange, getFirstExpectedTimestamp, getLineChartRangeTimestamp, getLineChartStep, getLineChartXAxisDomain, getLineChartYDataMax, getYAxisDomain, getYAxisUpperBound, lineChartTickFormatter, lineChartXaxisCal, lineChartYaxisTickFormatter, parseLineChartRange };
