///|
pub(all) struct Placement {
  logical_page : Int32
  sheet : Int32
  back : Bool
  right : Bool
  rotation : Int32
}

///|
pub(all) struct PreflightIssue {
  blocking : Bool
  field : String
  message : String
}

///|
pub(all) struct Preflight {
  logical_pages : Int32
  content_pages : Int32
  explicit_blanks : Int32
  padding_blanks : Int32
  sheets : Int32
  gatherings : Int32
  repeated_references : Int32
  issues : Array[PreflightIssue]
}

///|
/// Locate every use of a source page, including repeated selections.
pub fn locate_source(
  booklet : Booklet,
  source : String,
  number : Int32,
) -> Result[Array[Placement], WorkflowError] {
  match recover_booklet(booklet) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  if source.is_blank() || number < 1 || number > 100000 {
    return Err(workflow_error("source", "invalid lookup reference"))
  }
  let result : Array[Placement] = []
  for sheet_index, sheet in booklet.sheets {
    for back, face in [sheet.front, sheet.back] {
      for right, slot in [face.left, face.right] {
        match slot {
          Page(n) =>
            if booklet.document.pages[n - 1] == Source(source, number) {
              result.push({
                logical_page: n,
                sheet: sheet_index,
                back: back == 1,
                right: right == 1,
                rotation: face.rotation,
              })
            }
          Blank => ()
        }
      }
    }
  }
  Ok(result)
}

///|
/// Structural errors return Err; paper and content advisories are collected.
/// A repeated reference is an advisory because selection duplication is allowed.
pub fn preflight(
  booklet : Booklet,
  paper : Paper,
) -> Result[Preflight, WorkflowError] {
  match recover_booklet(booklet) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let issues : Array[PreflightIssue] = []
  let mut content_pages = 0
  let mut repeated_references = 0
  let references : Map[(String, Int32), Bool] = Map([])
  for page in booklet.document.pages {
    match page {
      Source(source, number) => {
        content_pages += 1
        let key = (source, number)
        if references.contains(key) {
          repeated_references += 1
        }
        references.set(key, true)
      }
      Empty => ()
    }
  }
  if repeated_references > 0 {
    issues.push({
      blocking: false,
      field: "document",
      message: "\{repeated_references} repeated source references; confirm selection intent",
    })
  }
  if content_pages == 0 {
    issues.push({
      blocking: false,
      field: "document",
      message: "all logical pages are blank",
    })
  }
  for index, sheet in booklet.sheets {
    match layout(paper, sheet.depth) {
      Err(_) =>
        issues.push({
          blocking: true,
          field: "sheets[\{index}].geometry",
          message: "panels clip overlap or have invalid dimensions",
        })
      Ok(_) => ()
    }
  }
  let logical_pages = booklet.document.pages.length()
  let padding_blanks = booklet.sheets.length() * 4 - logical_pages
  if padding_blanks > logical_pages {
    issues.push({
      blocking: false,
      field: "gatherings",
      message: "padding occupies more than half the printed slots",
    })
  }
  Ok({
    logical_pages,
    content_pages,
    explicit_blanks: logical_pages - content_pages,
    padding_blanks,
    sheets: booklet.sheets.length(),
    gatherings: booklet.gatherings.length(),
    repeated_references,
    issues,
  })
}