///| End-to-end recognition: the top-level OCR pipeline that turns a page of

///| digits and letters into a string.

///| Components smaller than this many pixels are treated as noise and dropped

///| before classification. Every supported glyph has at least ~20 ink pixels,

///| so this never removes a real character while catching salt-and-pepper

///|
/// specks introduced by a scanner or a noisy photograph.
fn min_glyph_area() -> Int {
  8
}

///| Recognize the components of one line, left to right, against the digit

///|

///|
/// and letter templates. `line` is sorted by horizontal position in place.
fn recognize_line(
  bin : Image,
  line : Array[Component],
  refs : Array[Reference],
) -> String {
  line.sort_by(fn(a, b) { a.x - b.x })
  let mut out = ""
  for i = 0; i < line.length(); i = i + 1 {
    let g = glyph_grid(bin, line[i], 8)
    match classify(g, refs) {
      None => ()
      Some(m) => out = out + m.label
    }
  }
  out
}

///| Recognize a single line of digits and letters, reading left to

///|

///| right. The image is binarized with Otsu's method, segmented into connected

///|

///| components, sorted by horizontal position, and each glyph is classified

///| against the built-in templates. Specks of noise are dropped and glyphs

///|
/// that match nothing are skipped.
pub fn recognize_digits(img : Image) -> String {
  let bin = binarize_otsu(img)
  let cs = filter_small(
    merge_parts(connected_components(bin)),
    min_glyph_area(),
  )
  recognize_line(bin, cs, alphanumeric_references())
}

///| Recognize multiple lines of digits and letters. Lines are

///|

///| separated by vertical gaps, detected via `group_lines`, read top to bottom,

///|
/// and joined with newlines.
pub fn recognize_text(img : Image) -> String {
  let bin = binarize_otsu(img)
  let lines = group_lines(connected_components(bin))
  let refs = alphanumeric_references()
  let mut out = ""
  for li = 0; li < lines.length(); li = li + 1 {
    if li > 0 {
      out = out + "\n"
    }
    let kept = filter_small(merge_parts(lines[li]), min_glyph_area())
    out = out + recognize_line(bin, kept, refs)
  }
  out
}