// The Lucia project.
// Copyright 2023-2026 DSP, inc.  All rights reserved.
// Authors:
//   chiayin.kuo@dsp.im (chiayin), 2023/1/31
//   imacat.yang@dsp.im (imacat), 2023/9/23
//   cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module setChartData Chart.js data transformation utilities. */

import getMoment from "moment";

/**
 * Extends backend chart data with extrapolated boundary points for
 * line charts. Prepends a calculated minimum and appends a calculated
 * maximum data point based on linear extrapolation.
 *
 * @param {Array<{x: number, y: number}>} baseData - The 10 data points
 *   from the backend.
 * @param {number} xMax - The maximum x-axis timestamp.
 * @param {number} xMin - The minimum x-axis timestamp.
 * @param {boolean} isPercent - Whether values are percentages (0-1 range).
 * @param {number} yMax - The maximum y-axis value for clamping.
 * @param {number} yMin - The minimum y-axis value for clamping.
 * @returns {Array<{x: number, y: number}>} The extended data array
 *   with boundary points.
 */
export function setLineChartData(baseData, xMax, xMin, isPercent, yMax, yMin) {
  // Convert baseData to an array of objects with x and y properties
  let data = baseData.map((i) => ({ x: i.x, y: i.y }));

  // Calculate the Y-axis minimum value
  let b = calculateYMin(baseData, isPercent, yMin, yMax);

  // Calculate the Y-axis maximum value
  let mf = calculateYMax(baseData, isPercent, yMin, yMax);

  // Prepend the minimum value
  data.unshift({
    x: xMin,
    y: b,
  });

  // Append the maximum value
  data.push({
    x: xMax,
    y: mf,
  });

  return data;
}
/**
 * Extrapolates the Y-axis minimum boundary value using linear
 * interpolation from the first two data points.
 *
 * @param {Array<{x: number, y: number}>} baseData - The base data points.
 * @param {boolean} isPercent - Whether to clamp to 0-1 range.
 * @param {number} yMin - The minimum allowed Y value.
 * @param {number} yMax - The maximum allowed Y value.
 * @returns {number} The extrapolated and clamped Y minimum value.
 */
function calculateYMin(baseData, isPercent, yMin, yMax) {
  if (baseData.length < 2) return yMin;
  let a = 0;
  let c = 1;
  let d = baseData[0].y;
  let e = 2;
  let f = baseData[1].y;
  let b = (e * d - a * d - f * a - f * c) / (e - c - a);
  return clampValue(b, isPercent, yMin, yMax);
}
/**
 * Extrapolates the Y-axis maximum boundary value using linear
 * interpolation from the last two data points.
 *
 * @param {Array<{x: number, y: number}>} baseData - The base data points.
 * @param {boolean} isPercent - Whether to clamp to 0-1 range.
 * @param {number} yMin - The minimum allowed Y value.
 * @param {number} yMax - The maximum allowed Y value.
 * @returns {number} The extrapolated and clamped Y maximum value.
 */
function calculateYMax(baseData, isPercent, yMin, yMax) {
  if (baseData.length < 10) return yMax;
  let ma = 9;
  let mb = baseData[8].y;
  let mc = 10;
  let md = baseData[9].y;
  let me = 11;
  let mf = (mb * me - mb * mc - md * me + md * ma) / (ma - mc);
  return clampValue(mf, isPercent, yMin, yMax);
}
/**
 * Clamps a value within a specified range. If isPercent is true, the
 * value is clamped to [0, 1]; otherwise to [min, max].
 *
 * @param {number} value - The value to clamp.
 * @param {boolean} isPercent - Whether to use the percentage range [0, 1].
 * @param {number} min - The minimum bound (used when isPercent is false).
 * @param {number} max - The maximum bound (used when isPercent is false).
 * @returns {number} The clamped value.
 */
function clampValue(value, isPercent, min, max) {
  if (isPercent) {
    if (value >= 1) {
      return 1;
    } else if (value <= 0) {
      return 0;
    }
  } else {
    if (value >= max) {
      return max;
    }
    if (value <= min) {
      return min;
    }
  }
  return value;
}
/**
 * Converts backend chart data timestamps to formatted date strings
 * for bar charts.
 *
 * @param {Array<{x: string, y: number}>} baseData - The data points from
 *   the backend with ISO timestamp x values.
 * @returns {Array<{x: string, y: number}>} Data with x values formatted
 *   as "YYYY/M/D HH:mm:ss".
 */
export function setBarChartData(baseData) {
  let data = baseData.map((i) => {
    return {
      x: getMoment(i.x).format("YYYY/M/D HH:mm:ss"),
      y: i.y,
    };
  });
  return data;
}
/**
 * Divides a time range into evenly spaced time points.
 *
 * @param {number} minTime - The start time in seconds.
 * @param {number} maxTime - The end time in seconds.
 * @param {number} amount - The number of time points to generate.
 * @returns {Array<number>} An array of evenly spaced, rounded time
 *   values in seconds.
 */
export function timeRange(minTime, maxTime, amount) {
  if (amount <= 1) return [Math.round(minTime)];
  // The X-axis (time axis) range is max - min; accumulate intervals from min to max by index
  const startTime = minTime;
  const endTime = maxTime;
  let timeRange = []; // Initialize the return data array
  const timeGap = (endTime - startTime) / (amount - 1); // Divide into segments

  for (let i = 0; i < amount; i++) {
    timeRange.push(startTime + timeGap * i);
  }
  timeRange = timeRange.map((value) => Math.round(value));
  return timeRange;
}
/**
 * Cubic Bezier curve formula.
 * @param {number} t - The interpolation parameter (0 to 1).
 * @param {number} a1 - Control point 1.
 * @param {number} a2 - Control point 2.
 * @param {number} a3 - Control point 3.
 * @param {number} a4 - Control point 4.
 * @returns {number} The interpolated value.
 */
function threebsr(t, a1, a2, a3, a4) {
  return (
    (1 - t) * (1 - t) * (1 - t) * a1 +
    3 * t * (1 - t) * (1 - t) * a2 +
    3 * t * t * (1 - t) * a3 +
    t * t * t * a4
  );
}

/**
 * Generates smooth Y-axis values using cubic Bezier interpolation
 * to produce evenly spaced ticks matching the X-axis divisions.
 *
 * @param {Array<{x: number, y: number}>} data - The source data points.
 * @param {number} yAmount - The desired number of Y-axis ticks.
 * @param {number} yMax - The maximum Y value (unused, kept for API).
 * @returns {Array<number>} An array of interpolated Y values.
 */
export function yTimeRange(data, yAmount, yMax) {
  if (yAmount <= 1) return data.map((d) => d.y);
  const yRange = [];
  const yGap = 1 / (yAmount - 1);

  for (let j = 0; j < data.length - 1; j++) {
    for (let i = 0; i <= 1; i += yGap * 11) {
      yRange.push(
        threebsr(i, data[j].y, data[j].y, data[j + 1].y, data[j + 1].y),
      );
    }
  }

  if (yRange.length < yAmount) {
    let len = yAmount - yRange.length;
    for (let i = 0; i < len; i++) {
      yRange.push(yRange[yRange.length - 1]);
    }
  } else if (yRange.length > yAmount) {
    let len = yRange.length - yAmount;
    for (let i = 0; i < len; i++) {
      yRange.splice(1, 1);
    }
  }

  return yRange;
}
/**
 * Finds the index of the closest value in an array to the given target.
 *
 * @param {Array<number>} data - The array of values to search.
 * @param {number} xValue - The target value to find the closest match for.
 * @returns {number} The index of the closest value in the array.
 */
export function getXIndex(data, xValue) {
  let closestIndex = xValue; // Assume the first element index is 0
  let smallestDifference = Math.abs(xValue - data[0]); // Initialize difference to the gap between the first element and target

  for (let i = 0; i < data.length; i++) {
    let difference = Math.abs(xValue - data[i]);

    if (difference <= smallestDifference) {
      closestIndex = i;
      smallestDifference = difference;
    }
  }

  return closestIndex;
}
/**
 * Formats a duration in seconds to a compact string with d/h/m/s units.
 *
 * @param {number} seconds - The total number of seconds.
 * @returns {string|null} The formatted string (e.g. "2d3h15m30s"),
 *   or null if the input is NaN.
 */
export function formatTime(seconds) {
  if (Number.isNaN(Number(seconds))) {
    return null;
  } else {
    const remainingSeconds = seconds % 60;
    const minutes = (Math.floor(seconds - remainingSeconds) / 60) % 60;
    const hours = Math.floor(seconds / 3600) % 24;
    const days = Math.floor(seconds / (3600 * 24));

    let result = "";
    if (days > 0) {
      result += `${days}d`;
    }
    if (hours > 0) {
      result += `${hours}h`;
    }
    if (minutes > 0) {
      result += `${minutes}m`;
    }
    result += `${remainingSeconds}s`;

    return result.trim(); // Remove trailing whitespace
  }
}
/**
 * Truncates each time string to show only the two largest time units.
 *
 * @param {Array<string>} times - Array of duration strings (e.g. "2d3h15m30s").
 * @returns {Array<string>} Array of truncated strings (e.g. "2d 3h").
 */
export function formatMaxTwo(times) {
  const formattedTimes = [];
  for (let time of times) {
    // Match numbers and units (days, hours, minutes, seconds); assume numbers have at most 10 digits
    let units = time.match(/\d{1,10}[dhms]/g) || [];
    let formattedTime = "";
    let count = 0;

    // Keep only the two largest units
    for (let unit of units) {
      if (count >= 2) {
        break;
      }
      formattedTime += unit + " ";
      count++;
    }
    formattedTimes.push(formattedTime.trim()); // Remove trailing whitespace
  }
  return formattedTimes;
}
