export function prettyBreaksClassification(
  data: number[],
  nClasses: number
): number[] {
  if (data.length === 0) return [];

  const minVal = Math.max(data.reduce((min, val) => Math.min(min, val), Infinity), 0);
  const maxVal = data.reduce((max, val) => Math.max(max, val), -Infinity);
  const dataRange = maxVal - minVal;
  const rawInterval = dataRange / nClasses;

  const magnitude = 10 ** Math.floor(Math.log10(rawInterval));
  const normalized = rawInterval / magnitude;

  let niceInterval: number;
  if (normalized <= 1) {
    niceInterval = magnitude;
  } else if (normalized <= 2) {
    niceInterval = 2 * magnitude;
  } else if (normalized <= 5) {
    niceInterval = 5 * magnitude;
  } else {
    niceInterval = 10 * magnitude;
  }

  const niceMin = Math.floor(minVal / niceInterval) * niceInterval;
  const niceMax = Math.ceil(maxVal / niceInterval) * niceInterval;

  const breaks: number[] = [];
  let current = niceMin;
  while (current <= niceMax && breaks.length < nClasses + 1) {
    breaks.push(current);
    current += niceInterval;
  }

  if (breaks.length === 0 || breaks[breaks.length - 1] < maxVal) {
    breaks.push(niceMax);
  }

  if (breaks.length > nClasses + 1) {
    breaks.splice(nClasses + 1);
  }

  return breaks;
}

function variance(data: number[], start: number, end: number): number {
  if (start >= end) return 0;
  const mean = data.slice(start, end + 1).reduce((a, b) => a + b, 0) / (end - start + 1);
  return data.slice(start, end + 1).reduce((sum, val) => sum + (val - mean) ** 2, 0);
}

function jenksBreaksJenkspy(data: number[], nClasses: number): number[] {
  if (data.length === 0) return [];
  if (nClasses >= data.length) return data.slice().sort((a, b) => a - b);

  const sortedData = data.slice().sort((a, b) => a - b);
  const n = sortedData.length;
  const k = nClasses;

  const lowerClassLimits = Array.from({ length: n + 1 }, () => Array(k + 1).fill(0));
  const varianceCombinations = Array.from({ length: n + 1 }, () => Array(k + 1).fill(0));

  for (let i = 1; i <= n; i++) {
    lowerClassLimits[i][1] = 1;
    varianceCombinations[i][1] = variance(sortedData, 0, i - 1);
    for (let j = 2; j <= k; j++) {
      varianceCombinations[i][j] = Infinity;
    }
  }

  for (let l = 2; l <= k; l++) {
    for (let m = l; m <= n; m++) {
      for (let i = l - 1; i < m; i++) {
        const v = varianceCombinations[i][l - 1] + variance(sortedData, i, m - 1);
        if (v < varianceCombinations[m][l]) {
          varianceCombinations[m][l] = v;
          lowerClassLimits[m][l] = i;
        }
      }
    }
  }

  const breaks: number[] = [];
  let current = n;
  for (let j = k; j >= 1; j--) {
    breaks.unshift(sortedData[lowerClassLimits[current][j] - 1] || sortedData[0]);
    current = lowerClassLimits[current][j];
  }
  breaks.push(sortedData[n - 1]);

  return breaks;
}

function jenksWithStratifiedSampling(
  data: number[],
  nClasses: number,
  sampleSize = 10000
): number[] {
  if (data.length <= sampleSize) {
    return jenksBreaksJenkspy(data, nClasses);
  }

  const sortedData = data.slice().sort((a, b) => a - b);
  const interval = sortedData.length / sampleSize;

  const sampledData: number[] = [];
  for (let i = 0; i < sampleSize; i++) {
    const index = Math.floor(i * interval);
    if (index < sortedData.length) {
      sampledData.push(sortedData[index]);
    }
  }

  return jenksBreaksJenkspy(sampledData, nClasses);
}

export function calculateClassification(
  data: number[],
  segments: number,
  classificationMethod: string
): number[] {
  if (!data || data.length === 0) {
    return [];
  }

  if (classificationMethod === "pretty_breaks") {
    return prettyBreaksClassification(data, segments);
  }

  if (classificationMethod === "jenks_optimized") {
    return jenksWithStratifiedSampling(data, segments);
  }

  return [];
}
