const metrics = {
  alpha: {
    // combine sigma and bigTheta
    option: "Geometric Difference (α)",
    displayName: String.raw`\alpha`,
    displayBody: (p) => String.raw`
      \sigma\left(${p}\right)
      \Theta\left(${p}\right)^{
        \left(C\left(\left\{\vec{q}\right\}\right) + L\left(\left\{\vec{q}\right\}\right)\right)
      }
    `,
    evaluate: () => 0, // calculated below
  },
  sigma: {
    // RMS
    option: "RMS Deviation (σ)",
    displayName: String.raw`\sigma`,
    displayBody: (p) => String.raw`
      \sqrt{I\left(${p}\right) - 2\vec{q}\cdot\vec{\mu}\left(${p}\right) + \left|\left|\vec{q}\right|\right|^2}
    `,
    evaluate: (data, target) =>
      Math.sqrt(
        data.inertia -
          2 * vectorDot(data.mu.vector, target.vector) +
          target.sqMag
      ),
  },
  bigTheta: {
    // 1 - arith mean of cosine similarity
    option: "Cosine Difference (Θ)",
    displayName: String.raw`\Theta`,
    displayBody: (p) => String.raw`
      1 - \hat{q}\cdot\vec{\nu}\left(${p}\right)
    `,
    evaluate: (data, target) => 1 - vectorDot(data.nu, target.unit),
  },
  theta: {
    // angle of mean
    option: "Angular Difference (θ)",
    displayName: String.raw`\theta`,
    displayBody: (p) => String.raw`
      \cos^{-1}\left( \hat{q}\cdot\hat{\mu}\left(${p}\right) \right)
    `,
    evaluate: (data, target) =>
      rad2deg * Math.acos(vectorDot(data.mu.unit, target.unit)),
  },
  phi: {
    // hue angle
    option: "Hue Azimuth (ϕ)",
    displayName: String.raw`\phi`,
    displayBody: (p, space) => String.raw`
      \angle \left(\text{oproj}_{\vec{${
        space === "jab" ? "J" : "L"
      }}}{\vec{q}}, \text{oproj}_{\vec{${
      space === "jab" ? "J" : "L"
    }}}{\vec{\mu}\left(${p}\right)} \right)
    `,
    evaluate: (data, target) => {
      const raw = Math.abs(data.mu.hue - target.hue);
      return Math.min(raw, 360 - raw);
    },
  },
  delta: {
    // euclidean
    option: "Euclidean (δ)",
    displayName: String.raw`\delta`,
    displayBody: (p) => String.raw`
      \left|\left| \vec{q} - \vec{\mu}\left(${p}\right) \right|\right|
    `,
    evaluate: (data, target) =>
      vectorMag(data.mu.vector.map((x, i) => x - target.vector[i])),
  },
  manhattan: {
    // manhattan distance
    option: "Manhattan (M)",
    displayName: "M",
    displayBody: (p) => String.raw`
      \sum_{i} \left| \vec{\mu}\left(${p}\right)_i - \vec{q}_i \right|
    `,
    evaluate: (data, target) =>
      data.mu.vector
        .map((x, i) => Math.abs(x - target.vector[i]))
        .reduce((x, y) => x + y),
  },
  ch: {
    // chebyshev
    option: "Chebyshev (Ч)",
    displayName: "Ч",
    displayBody: (p) => String.raw`
      \max_{i} \left| \vec{\mu}\left(${p}\right)_i - \vec{q}_i \right|
    `,
    evaluate: (data, target) =>
      Math.max(...data.mu.vector.map((x, i) => Math.abs(x - target.vector[i]))),
  },
  lightnessDiff: {
    option: "Lightness (ℓ)",
    displayName: String.raw`\ell`,
    displayBody: (p, space) => String.raw`
      \left| ${space === "jab" ? "\\frac{1}{100}" : ""}\text{comp}_{\vec{${
      space === "jab" ? "J" : "L"
    }}}{\vec{q}} - ${
      space === "jab" ? "\\frac{1}{100}" : ""
    }\text{comp}_{\vec{${
      space === "jab" ? "J" : "L"
    }}}{\vec{\mu}\left(${p}\right)} \right|
    `,
    evaluate: (data, target) => Math.abs(data.mu.lightness - target.lightness),
  },
  inertia: {
    option: "Inertia (I)",
    displayName: "I",
    displayBody: (p) => String.raw`
      \frac{1}{\left|${p}\right|} \sum_{p\in ${p}}{\left|\left|\vec{p}\right|\right|^2}
    `,
    evaluate: (data) => data.inertia,
  },
  variance: {
    option: "Variance (V)",
    displayName: "V",
    displayBody: (p) => String.raw`
      I\left(${p}\right) - \left|\left|\vec{\mu}\left(${p}\right)\right|\right|^2
    `,
    evaluate: (data) => data.inertia - data.mu.sqMag,
  },
  muNuAngle: {
    option: "Mu-Nu Angle (Φ)",
    displayName: "\\Phi",
    displayBody: (p) =>
      String.raw`\angle \left( \vec{\mu}\left(${p}\right), \vec{\nu}\left(${p}\right) \right)`,
    evaluate: (data) => data.muNuAngle,
  },
  size: {
    option: "Size (N)",
    displayName: "N",
    displayBody: (p) => String.raw`\left|${p}\right|`,
    evaluate: (data) => data.size,
  },
  lightness: {
    option: "Mean Lightness (L)",
    displayName: "L",
    displayBody: (p, space) => String.raw`
      ${space === "jab" ? "\\frac{1}{100}" : ""}\text{comp}_{\vec{${
      space === "jab" ? "J" : "L"
    }}}{\vec{\mu}\left(${p}\right)}
    `,
    evaluate: (data) => data.mu.lightness,
  },
  chroma: {
    option: "Mean Chroma (C)",
    displayName: "C",
    displayBody: (p) =>
      String.raw`\text{chroma}\left(\vec{\mu}\left(${p}\right)\right)`,
    evaluate: (data) => data.mu.chroma,
  },
  importance: {
    option: "Visual Importance (β)",
    displayName: String.raw`\beta`,
    displayBody: (p) => String.raw`
      \begin{aligned}
        &C\left(${p}\right) + L\left(${p}\right) + \frac{\left|${p}\right|}{\left|P\right|}\\
      + &\tanh{\left(100\left(C\left(${p}\right) - 0.25\right)\right)} \\
      + &\tanh{\left(100\left(C\left(${p}\right) - 0.4\right)\right)} \\
      + &\tanh{\left(100\left(L\left(${p}\right) - 0.5\right)\right)} \\
      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.05\right)\right)} \\
      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.10\right)\right)} \\
      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.15\right)\right)} \\
      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.25\right)\right)} \\
      + &\tanh{\left(100\left(\frac{\left|${p}\right|}{\left|P\right|} - 0.80\right)\right)}
      \end{aligned}
    `,
    evaluate: (data) => data.importance,
  },
};

const applyMetrics = (data, target) => {
  const scores = Object.fromEntries(
    Object.entries(metrics).map(([name, metric]) => [
      name,
      metric.evaluate(data, target),
    ])
  );
  scores.alpha =
    scores.sigma * Math.pow(scores.bigTheta, target.chroma + target.lightness);
  return scores;
};
