// Vector Math
const vectorDot = (u, v) => u.map((x, i) => x * v[i]).reduce((x, y) => x + y);
const vectorMag = (v) => Math.sqrt(vectorDot(v, v));

// Angle Math
const rad2deg = 180 / Math.PI;

// Contrast
const getContrastingTextColor = (hex) => {
  const { r, g, b } = d3.color(hex);
  return vectorDot([r, g, b], [0.3, 0.6, 0.1]) >= 128 ? "#222" : "#ddd";
};

// "Visual Importance"
const calcImportance = (chroma, lightness, proportion) =>
  chroma +
  Math.tanh(100 * (chroma - 0.25)) + // penalty for being <25%
  Math.tanh(100 * (chroma - 0.4)) + // penalty for being <40%
  lightness +
  Math.tanh(100 * (lightness - 0.5)) + // penalty for being <50%
  proportion +
  Math.tanh(100 * (proportion - 0.05)) + // penalty for being <5%
  Math.tanh(100 * (proportion - 0.1)) + // penalty for being <15%
  Math.tanh(100 * (proportion - 0.15)) + // penalty for being <15%
  Math.tanh(100 * (proportion - 0.25)) + // penalty for being <25%
  Math.tanh(100 * (proportion - 0.8)); // penalty for being <50%

// Conversions
const jab2hex = (jab) => d3.jab(...jab).formatHex();
const rgb2hex = (rgb) => d3.rgb(...rgb).formatHex();
const jab2hue = (jab) => d3.jch(d3.jab(...jab)).h || 0;
const rgb2hue = (rgb) => d3.hsl(d3.rgb(...rgb)).h || 0;
const jab2lit = ([j]) => j / 100;
const rgb2lit = (rgb) => d3.hsl(d3.rgb(...rgb)).l || 0;
const jab2chroma = (jab) => d3.jch(d3.jab(...jab)).C / 100;
const rgb2chroma = (rgb) => d3.jch(d3.rgb(...rgb)).C / 100;

// Pre-computation
const buildVectorData = (vector, toHue, toLightness, toChroma, toHex) => {
  const sqMag = vectorDot(vector, vector);
  const mag = Math.sqrt(sqMag);
  const unit = vector.map((c) => c / mag);
  const hue = toHue(vector);
  const lightness = toLightness(vector);
  const chroma = toChroma(vector);
  const hex = toHex(vector);
  return { vector, sqMag, mag, unit, hue, lightness, chroma, hex };
};

const buildClusterData = (
  size,
  inertia,
  mu1,
  mu2,
  mu3,
  nu1,
  nu2,
  nu3,
  totalSize,
  toHue,
  toLightness,
  toChroma,
  toHex
) => {
  const mu = buildVectorData([mu1, mu2, mu3], toHue, toLightness, toChroma, toHex);
  const nu = [nu1, nu2, nu3];
  const muNuAngle = rad2deg * Math.acos(vectorDot(mu.unit, nu) / vectorMag(nu));
  const proportion = size / totalSize;
  const importance = calcImportance(mu.chroma, mu.lightness, proportion);
  return {
    size,
    inverseSize: 1 / size,
    inertia,
    mu,
    nu,
    muNuAngle,
    proportion,
    inverseProportion: 1 / proportion,
    importance,
  };
};

const buildPokemonData = ([name, size, ...values]) => ({
  name,
  jab: {
    total: buildClusterData(
      size,
      ...values.slice(0, 7),
      size,
      jab2hue,
      jab2lit,
      jab2chroma,
      jab2hex
    ),
    clusters: [
      buildClusterData(
        ...values.slice(7, 15),
        size,
        jab2hue,
        jab2lit,
        jab2chroma,
        jab2hex
      ),
      buildClusterData(
        ...values.slice(15, 23),
        size,
        jab2hue,
        jab2lit,
        jab2chroma,
        jab2hex
      ),
      buildClusterData(
        ...values.slice(23, 31),
        size,
        jab2hue,
        jab2lit,
        jab2chroma,
        jab2hex
      ),
      buildClusterData(
        ...values.slice(31, 39),
        size,
        jab2hue,
        jab2lit,
        jab2chroma,
        jab2hex
      ),
    ].filter((c) => c.size !== 0),
  },
  rgb: {
    total: buildClusterData(
      size,
      ...values.slice(39, 46),
      size,
      rgb2hue,
      rgb2lit,
      rgb2chroma,
      rgb2hex
    ),
    clusters: [
      buildClusterData(
        ...values.slice(46, 54),
        size,
        rgb2hue,
        rgb2lit,
        rgb2chroma,
        rgb2hex
      ),
      buildClusterData(
        ...values.slice(54, 62),
        size,
        rgb2hue,
        rgb2lit,
        rgb2chroma,
        rgb2hex
      ),
      buildClusterData(
        ...values.slice(62, 70),
        size,
        rgb2hue,
        rgb2lit,
        rgb2chroma,
        rgb2hex
      ),
      buildClusterData(
        ...values.slice(70, 78),
        size,
        rgb2hue,
        rgb2lit,
        rgb2chroma,
        rgb2hex
      ),
    ].filter((c) => c.size !== 0),
  },
});

const pokemonData = databaseV3.map((row) => buildPokemonData(row));

const calcScores = (data, target) => {
  const sigma = Math.sqrt(
    data.inertia - 2 * vectorDot(data.mu.vector, target.vector) + target.sqMag
  );
  const bigTheta = 1 - vectorDot(data.nu, target.unit);
  const rawPhi = Math.abs(data.mu.hue - target.hue);
  return {
    sigma,
    bigTheta,
    alpha: sigma * Math.pow(bigTheta, target.chroma + target.lightness),

    theta: rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit)),
    phi: Math.min(rawPhi, 360 - rawPhi),
    delta: vectorMag(data.mu.vector.map((x, i) => x - target.vector[i])),
    manhattan: data.mu.vector
      .map((x, i) => Math.abs(x - target.vector[i]))
      .reduce((x, y) => x + y),
    ch: Math.max(...data.mu.vector.map((x, i) => Math.abs(x - target.vector[i]))),
    lightnessDiff: Math.abs(data.mu.lightness - target.lightness),

    inertia: data.inertia,
    variance: data.inertia - data.mu.sqMag,
    muNuAngle: data.muNuAngle,
    size: data.size,
    lightness: data.mu.lightness,
    chroma: data.mu.chroma,
    importance: data.importance,

    inverseSize: data.inverseSize,
    proportion: data.proportion,
    inverseProportion: data.inverseProportion,

    muHex: data.mu.hex,
  };
};
