///|
pub struct AccessibilityPolicy {
  normal_min_x100 : Int
  large_min_x100 : Int
  error_below_x100 : Int
  require_text_surface_pairs : Bool
} derive(Debug, Eq)

///|
pub fn AccessibilityPolicy::wcag_aa() -> AccessibilityPolicy {
  {
    normal_min_x100: 450,
    large_min_x100: 300,
    error_below_x100: 300,
    require_text_surface_pairs: true,
  }
}

///|
pub fn AccessibilityPolicy::wcag_aaa() -> AccessibilityPolicy {
  {
    normal_min_x100: 700,
    large_min_x100: 450,
    error_below_x100: 300,
    require_text_surface_pairs: true,
  }
}

///|
pub fn AccessibilityPolicy::for_thresholds(
  normal_min_x100 : Int,
  large_min_x100 : Int,
) -> AccessibilityPolicy {
  {
    normal_min_x100,
    large_min_x100,
    error_below_x100: if large_min_x100 < 1 {
      1
    } else {
      large_min_x100
    },
    require_text_surface_pairs: false,
  }
}

///|
pub fn ContrastPair::passes(
  self : ContrastPair,
  policy : AccessibilityPolicy,
) -> Bool {
  self.ratio_x100 >= policy.normal_min_x100
}

///|
pub fn ContrastPair::passes_large_text(
  self : ContrastPair,
  policy : AccessibilityPolicy,
) -> Bool {
  self.ratio_x100 >= policy.large_min_x100
}

///|
pub fn ContrastPair::ratio_text(self : ContrastPair) -> String {
  let whole = self.ratio_x100 / 100
  let fraction = self.ratio_x100 % 100
  let fraction_text = if fraction < 10 {
    "0" + fraction.to_string()
  } else {
    fraction.to_string()
  }
  whole.to_string() + "." + fraction_text + ":1"
}

///|
pub fn Palette::audit_contrast(
  self : Palette,
  policy : AccessibilityPolicy,
) -> AuditReport {
  let findings : Array[Finding] = []
  let text_tokens = self.colors.filter(fn(color) {
    color.role.contains("text") ||
    color.role.contains("body") ||
    color.name == "ink"
  })
  let surfaces = self.colors.filter(fn(color) {
    color.role.contains("surface") ||
    color.role.contains("background") ||
    color.name == "paper"
  })
  if policy.require_text_surface_pairs &&
    (text_tokens.length() == 0 || surfaces.length() == 0) {
    findings.push(Finding::{
      severity: Warning,
      path: "palette.colors",
      message: "accessibility policy could not find a text/surface pair",
    })
  }
  for foreground in text_tokens {
    for background in surfaces {
      if foreground.name != background.name {
        let pair = foreground.contrast_against(background)
        if pair.ratio_x100 < policy.error_below_x100 {
          findings.push(Finding::{
            severity: Error,
            path: "palette.contrast." + foreground.name + "." + background.name,
            message: pair.ratio_text() + " is below the large-text threshold",
          })
        } else if pair.ratio_x100 < policy.normal_min_x100 {
          findings.push(Finding::{
            severity: Warning,
            path: "palette.contrast." + foreground.name + "." + background.name,
            message: pair.ratio_text() +
            " passes large text but not normal text",
          })
        }
      }
    }
  }
  AuditReport::from_findings(findings)
}

///|
pub fn BrandBook::accessibility_score(self : BrandBook) -> Int {
  let pairs = self.palette.contrast_pairs()
  if pairs.length() == 0 {
    return 0
  }
  let mut passed = 0
  for pair in pairs {
    if pair.grade != ContrastGrade::Fail {
      passed += 1
    }
  }
  passed * 100 / pairs.length()
}

///|
pub fn Palette::contrast_matrix_markdown(self : Palette) -> String {
  let lines : Array[String] = [
    "## Contrast matrix", "", "| Foreground | Background | Ratio | Grade |", "| --- | --- | ---: | --- |",
  ]
  for pair in self.contrast_pairs() {
    if pair.foreground.name != pair.background.name {
      lines.push(
        "| " +
        pair.foreground.name +
        " | " +
        pair.background.name +
        " | " +
        pair.ratio_text() +
        " | " +
        pair.grade.label() +
        " |",
      )
    }
  }
  lines.join("\n")
}

///|
pub fn ColorToken::is_light(self : ColorToken) -> Bool {
  self.relative_luminance_x10000() > 5000
}

///|
pub fn ColorToken::is_dark(self : ColorToken) -> Bool {
  !self.is_light()
}

///|
pub fn ColorToken::can_be_text_on(
  self : ColorToken,
  background : ColorToken,
) -> Bool {
  self.contrast_against(background).ratio_x100 >= 450
}

///|
pub fn Palette::recommended_text_color(
  self : Palette,
  background : ColorToken,
) -> ColorToken? {
  let mut best : ColorToken? = None
  let mut best_ratio = -1
  for color in self.colors {
    let ratio = color.contrast_against(background).ratio_x100
    if ratio > best_ratio {
      best_ratio = ratio
      best = Some(color)
    }
  }
  best
}