///|
/// One recognized text region. `quad` keeps the rotated geometry, `bounds` is
/// the axis-aligned rectangle used by `Page::render`, and the two scores keep
/// detection and recognition confidence separate.
pub(all) struct TextSpan {
  quad : DetQuad
  bounds : GlyphBox
  text : String
  det_score : Double
  rec_confidence : Double
} derive(Debug, Eq, ToJson)

///|
pub extend TextSpan with Eq::{not_equal, equal}

///|
pub extend TextSpan with ToJson::{to_json}

///|
pub extend TextSpan with @debug.Debug::{to_repr}

///|
/// Spans that share a reading line, already ordered left to right. `text` is
/// computed from `spans`, so the two cannot disagree.
pub(all) struct TextLine {
  spans : Array[TextSpan]
} derive(Debug, Eq)

///|
pub extend TextLine with Eq::{not_equal, equal}

///|
pub extend TextLine with ToJson::{to_json}

///|
pub extend TextLine with @debug.Debug::{to_repr}

///|
/// Build a line from spans that are already in left-to-right order.
pub fn TextLine::from_spans(spans : Array[TextSpan]) -> TextLine {
  { spans, }
}

///|
/// Concatenate the span texts without inserting separators.
pub fn TextLine::text(self : TextLine) -> String {
  let builder = StringBuilder()
  for span in self.spans {
    builder.write_string(span.text)
  }
  builder.to_string()
}

///|
/// Serialize a line as `{ "text": ..., "spans": [...] }`.
pub impl ToJson for TextLine with fn to_json(self : TextLine) -> Json {
  Json::object({
    "text": Json::string(self.text()),
    "spans": Json::array(self.spans.map(span => Json::Json(span))),
  })
}

///|
/// A page of reading lines.
pub(all) struct Page {
  lines : Array[TextLine]
} derive(Debug, Eq)

///|
pub extend Page with Eq::{not_equal, equal}

///|
pub extend Page with ToJson::{to_json}

///|
pub extend Page with @debug.Debug::{to_repr}

///|
/// Build a page from lines that are already in reading order.
pub fn Page::from_lines(lines : Array[TextLine]) -> Page {
  { lines, }
}

///|
/// All spans in reading order.
pub fn Page::spans(self : Page) -> Array[TextSpan] {
  let spans : Array[TextSpan] = []
  for line in self.lines {
    for span in line.spans {
      spans.push(span)
    }
  }
  spans
}

///|
/// Serialize the page as `{ "lines": [...] }`.
pub impl ToJson for Page with fn to_json(self : Page) -> Json {
  Json::object({
    "lines": Json::array(self.lines.map(line => Json::Json(line))),
  })
}

///|
/// Serialize the page to a compact JSON string.
pub fn Page::to_json_string(self : Page) -> String {
  Json::Json(self).stringify()
}

///|
pub impl Show for Page with fn output(self, logger) {
  for index in 0.. 0 {
      logger.write_string("\n")
    }
    logger.write_string(self.lines[index].text())
  }
}

///|
pub extend Page with Show::{to_string, output}

///|
/// Text of the page, with one reading line per row.
pub fn Page::text(self : Page) -> String {
  self.to_string()
}

///|
/// `text` when `boxes` is false. Otherwise one span per row:
/// `x,y,width,heightdet_scorerec_confidencetext`.
///
/// # Example
/// ```mbt check
/// test {
///   let quad : @paddleocr.DetQuad = {
///     top_left: { x: 1.0, y: 2.0, },
///     top_right: { x: 4.0, y: 2.0, },
///     bottom_right: { x: 4.0, y: 6.0, },
///     bottom_left: { x: 1.0, y: 6.0, },
///     score: 0.5,
///   }
///   let page = @paddleocr.Page::from_lines([
///     @paddleocr.TextLine::from_spans([
///       {
///         quad,
///         bounds: { x: 1, y: 2, width: 3, height: 4, score: 0.5, },
///         text: "你好",
///         det_score: 0.5,
///         rec_confidence: 0.9,
///       },
///     ]),
///   ])
///   inspect(page.render(), content="你好")
///   inspect(page.render(boxes=true), content="1,2,3,4\t0.5\t0.9\t你好")
/// }
/// ```
pub fn Page::render(self : Page, boxes~ : Bool = false) -> String {
  if boxes {
    let builder = StringBuilder()
    let mut started = false
    for line in self.lines {
      for span in line.spans {
        if started {
          builder.write_char('\n')
        }
        started = true
        let bounds = span.bounds
        builder.write_string(
          "\{bounds.x},\{bounds.y},\{bounds.width},\{bounds.height}\t\{span.det_score}\t\{span.rec_confidence}\t\{span.text}",
        )
      }
    }
    builder.to_string()
  } else {
    self.text()
  }
}