#ifndef ACCENT_SCORE_H
#define ACCENT_SCORE_H

#include <stdint.h>

typedef struct {
  uint8_t r;
  uint8_t g;
  uint8_t b;
} RGB;

// Compute a heuristic score for how suitable an accent colour is for
// non-text UI components/accents against a background colour.
//
// Candidates below the required contrast ratio are rejected.
// Remaining candidates are ranked by perceptual lightness separation
// and chroma.
//
// Returns a value in [0, 1], where higher values indicate a better fit.
float accent_score_custom(
  RGB accent,
  RGB background,
  float contrast_requirement,
  float lightness_delta_preference,
  float chroma_preference,
  float lightness_tolerance,
  float chroma_tolerance,
  float lightness_weight,
  float chroma_weight
);

// accent_score_custom with sensible defaults:
//   contrast requirement:         3.00
//   preferred lightness delta:    0.30
//   preferred chroma:             0.15
//   lightness tolerance:          0.20
//   chroma tolerance:             0.15
//   lightness weight:             0.75
//   chroma weight:                0.25
//
// Suggested heuristic interpretation for these default parameters:
//
// 0.00..0.40  - poor
// 0.40..0.60  - marginal
// 0.60..0.75  - viable
// 0.75..0.90  - good
// 0.90..=1.00 - excellent
//
// A typical policy is to prefer good or excellent candidates, accept
// viable candidates, and reject poor or marginal candidates.
float accent_score(RGB accent, RGB background);

#ifdef ACCENT_SCORE_IMPLEMENTATION

#include <math.h>

typedef struct {
  float L;  // 0..1
  float C;  // chroma
} OKLC;

static float srgb_linear(float x) {
  x /= 255.0f;

  if (x <= 0.04045f) {
    return x / 12.92f;
  } else {
    return powf((x + 0.055f) / 1.055f, 2.4f);
  }
}


static float relative_luminance(RGB c) {
  float r = srgb_linear(c.r);
  float g = srgb_linear(c.g);
  float b = srgb_linear(c.b);

  return 0.2126f * r + 0.7152f * g + 0.0722f * b;
}

static float contrast_ratio(RGB a, RGB b) {
  float la = relative_luminance(a);
  float lb = relative_luminance(b);

  if (la < lb) {
    float tmp = la;
    la = lb;
    lb = tmp;
  }

  return (la + 0.05f) / (lb + 0.05f);
}


static OKLC rgb_to_oklc(RGB c) {
  float r = srgb_linear(c.r);
  float g = srgb_linear(c.g);
  float b = srgb_linear(c.b);

  // Linear sRGB -> LMS
  float l =
    0.4122214708f * r +
    0.5363325363f * g +
    0.0514459929f * b;

  float m =
    0.2119034982f * r +
    0.6806995451f * g +
    0.1073969566f * b;

  float s =
    0.0883024619f * r +
    0.2817188376f * g +
    0.6299787005f * b;

  l = cbrtf(l);
  m = cbrtf(m);
  s = cbrtf(s);

  
  // LMS -> Oklab
  float L =
    0.2104542553f * l +
    0.7936177850f * m -
    0.0040720468f * s;

  float A =
    1.9779984951f * l -
    2.4285922050f * m +
    0.4505937099f * s;

  float B =
    0.0259040371f * l +
    0.7827717662f * m -
    0.8086757660f * s;

  OKLC out;

  out.L = L;
  out.C = sqrtf(A * A + B * B);

  return out;
}


static float clamp01(float x) {
  if (x < 0.0f) return 0.0f;
  if (x > 1.0f) return 1.0f;
  return x;
}

float accent_score_custom(
  RGB accent,
  RGB background,
  float contrast_requirement,
  float lightness_delta_preference,
  float chroma_preference,
  float lightness_tolerance,
  float chroma_tolerance,
  float lightness_weight,
  float chroma_weight
) {
  if (
    contrast_requirement < 1.0f ||
    lightness_delta_preference < 0.0f ||
    chroma_preference < 0.0f ||
    lightness_tolerance <= 0.0f ||
    chroma_tolerance <= 0.0f ||
    lightness_weight < 0.0f ||
    chroma_weight < 0.0f
  ) {
    return 0.0f;
  }

  float weight_sum = lightness_weight + chroma_weight;

  if (weight_sum <= 0.0f) {
    return 0.0f;
  }

  OKLC a = rgb_to_oklc(accent);
  OKLC b = rgb_to_oklc(background);

  float contrast = contrast_ratio(accent, background);

  if (contrast < contrast_requirement) {
    return 0.0f;
  }

  float dL = fabsf(a.L - b.L);

  
  // Score reaches 1 at the preferred ΔL and falls to 0 when
  // the distance from that preference reaches lightness_tolerance.

  float lightness_score = clamp01(
    1.0f -
    fabsf(dL - lightness_delta_preference) / lightness_tolerance
  );

  // Score reaches 1 at the preferred chroma and falls to 0 when
  // the distance from that preference reaches chroma_tolerance.

  float chroma_score = clamp01(
    1.0f -
    fabsf(a.C - chroma_preference) / chroma_tolerance
  );

  return (
    lightness_weight * lightness_score +
    chroma_weight * chroma_score
  ) / weight_sum;
}

float accent_score(RGB accent, RGB background) {
  return accent_score_custom(
    accent,
    background,
    3.0f,   // minimum contrast ratio
    0.30f,  // preferred lightness delta
    0.15f,  // preferred chroma
    0.20f,  // lightness tolerance
    0.15f,  // chroma tolerance
    0.75f,  // lightness weight
    0.25f   // chroma weight
  );
}

#endif // ACCENT_SCORE_IMPLEMENTATION
#endif // ACCENT_SCORE_H