///|
fn slot_number(slot : Slot, blank : String) -> String {
  match slot {
    Page(n) => n.to_string()
    Blank => blank
  }
}

///|
fn face_json(face : Face) -> String {
  "{\"left\":\{slot_number(face.left, "null")},\"right\":\{slot_number(face.right, "null")},\"rotation\":\{face.rotation}}"
}

///|
/// Only fixed field names and checked integers enter JSON, so no raw text escapes.
pub fn json_report(plan : Plan) -> Result[String, PlanError] {
  match verify(plan) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let sheets = plan.sheets
    .map(fn(s) {
      "{\"signature\":\{s.signature + 1},\"depth\":\{s.depth},\"front\":\{face_json(s.front)},\"back\":\{face_json(s.back)}}"
    })
    .join(",")
  let binding = if plan.binding == Left { "left" } else { "right" }
  let duplex = if plan.duplex == LongEdge { "long-edge" } else { "short-edge" }
  Ok(
    "{\"page_count\":\{plan.page_count},\"binding\":\"\{binding}\",\"duplex\":\"\{duplex}\",\"sheets\":[\{sheets}]}",
  )
}

///|
/// Each row is one face, with empty fields for deliberate blanks.
pub fn csv_report(plan : Plan) -> Result[String, PlanError] {
  match verify(plan) {
    Err(e) => return Err(e)
    Ok(_) => ()
  }
  let rows = ["signature,depth,face,left,right,rotation"]
  for s in plan.sheets {
    rows.push(
      "\{s.signature + 1},\{s.depth},front,\{slot_number(s.front.left, "")},\{slot_number(s.front.right, "")},\{s.front.rotation}",
    )
    rows.push(
      "\{s.signature + 1},\{s.depth},back,\{slot_number(s.back.left, "")},\{slot_number(s.back.right, "")},\{s.back.rotation}",
    )
  }
  Ok(rows.join("\n") + "\n")
}