import type { InterpolatorFn } from "linear-interpolator";
import linearInterpolator from "linear-interpolator";
import computePriceRawData from "./PricingCalculatorSection.Data.Compute";
import { OneMillionRUPriceByRegion } from "./PricingCalculatorSection.Data.Region";

const PricingBaseRegion = "us-west-2";

const StorageRowSizeFactor = 2.014; // According to 4M × 1536d experiment
const StorageColumnSizeFactor = 1.006; // According to 4M × 1536d experiment

const Calculator = {
  RUWrite: function (dimension: number, rows: number): number {
    throw new Error("Price table not initialized");
  },

  RURead: function (
    dimension: number,
    table_rows: number,
    queries: number,
  ): number {
    throw new Error("Price table not initialized");
  },

  PriceWrite: function (dimension: number, rows: number): number {
    return (
      (Calculator.RUWrite(dimension, rows) / 1_000_000) *
      OneMillionRUPriceByRegion[PricingBaseRegion]
    );
  },

  PriceRead: function (
    dimension: number,
    table_rows: number,
    queries: number,
  ): number {
    return (
      (Calculator.RURead(dimension, table_rows, queries) / 1_000_000) *
      OneMillionRUPriceByRegion[PricingBaseRegion]
    );
  },

  PriceStorage: function (dimension: number, rows: number): number {
    if (dimension == 0) return 0;

    const baseSizeGiB =
      (dimension * rows * 4 + (8 + 4) * rows * 4) / 1024 / 1024 / 1024;

    return (
      baseSizeGiB * StorageRowSizeFactor * 0.2 +
      baseSizeGiB * StorageColumnSizeFactor * 0.05
    );
  },
};

export default Calculator;

function initPriceTable() {
  const ruTable: Record<
    string,
    { kind: string; rows: number; dimension: number; ru: number }
  > = {};

  computePriceRawData
    .trim()
    .split("\n")
    .forEach((line) => {
      const [kind, rows, dimension, ru] = line.split(",");
      const item = { kind, rows: +rows, dimension: +dimension, ru: +ru };
      const key = `${kind}_${rows}_${dimension}`;
      if (ruTable[key] == null || ruTable[key].ru < item.ru) {
        // Only count the maximum RU for the same slot
        ruTable[key] = item;
      }
    });

  const ruWritePointsByDimension: Record<number, Array<[number, number]>> = {};
  const ruSearchPointsByDimension: Record<number, Array<[number, number]>> = {};

  for (const key in ruTable) {
    const item = ruTable[key];
    switch (item.kind) {
      case "Write":
        if (ruWritePointsByDimension[item.dimension] == null) {
          ruWritePointsByDimension[item.dimension] = [[0, 0]];
        }
        ruWritePointsByDimension[item.dimension].push([item.rows, item.ru]);
        break;

      case "Search10000":
        if (ruSearchPointsByDimension[item.dimension] == null) {
          ruSearchPointsByDimension[item.dimension] = [[0, 0]];
        }
        ruSearchPointsByDimension[item.dimension].push([
          item.rows,
          item.ru / 10000,
        ]);
        break;
    }
  }

  const intpWriteByDimension: Record<number, InterpolatorFn> = {};
  const intpSearchByDimension: Record<number, InterpolatorFn> = {};
  for (const dimension in ruWritePointsByDimension) {
    intpWriteByDimension[dimension] = linearInterpolator(
      ruWritePointsByDimension[dimension],
    );
  }
  for (const dimension in ruSearchPointsByDimension) {
    intpSearchByDimension[dimension] = linearInterpolator(
      ruSearchPointsByDimension[dimension],
    );
  }

  Calculator.RUWrite = function (dimension: number, rows: number) {
    if (dimension == 0) return 0;
    if (intpWriteByDimension[dimension] == null) {
      throw new Error(`No write RU data for dimension ${dimension}`);
    }
    return intpWriteByDimension[dimension](rows);
  };

  Calculator.RURead = function (
    dimension: number,
    table_rows: number,
    queries: number,
  ) {
    if (dimension == 0) return 0;
    if (intpSearchByDimension[dimension] == null) {
      throw new Error(`No search RU data for dimension ${dimension}`);
    }
    return intpSearchByDimension[dimension](table_rows) * queries;
  };
}

initPriceTable();
