///|
/// One unresolved item that needs human investigation or recorded evidence.
pub(all) struct ReadinessItem {
  category : String
  entity_id : String
  detail : String
} derive(Eq, Debug, ToJson, FromJson)

///|
pub(all) struct ReadinessReport {
  recall_id : String
  as_of : Int64
  affected_lot_count : Int32
  affected_shipment_count : Int32
  physical_count_count : Int32
  acknowledged_shipment_count : Int32
  items : Array[ReadinessItem]
  evidence_review_complete : Bool
} derive(Eq, Debug, ToJson, FromJson)

///|
fn readiness_item(
  category : String,
  entity_id : String,
  detail : String,
) -> ReadinessItem {
  { category, entity_id, detail, }
}

///|
/// Combine physical count and outreach evidence for a single recall. This
/// intentionally makes no claim that product is safe, that a legal recall is
/// complete, or that a recipient actually quarantined stock.
pub fn review_readiness(
  ledger : Ledger,
  request : RecallRequest,
  counts : Array[StockCount],
  events : Array[ResponseEvent],
) -> Result[ReadinessReport, ValidationReport] {
  let impact = match analyze(ledger, request) {
    Err(validation) => return Err(validation)
    Ok(value) => value
  }
  let stock = match reconcile_stock(ledger, counts, request.as_of) {
    Err(validation) => return Err(validation)
    Ok(value) => value
  }
  let response = match review_responses(ledger, request, events) {
    Err(validation) => return Err(validation)
    Ok(value) => value
  }
  let counted : Map[String, ReconciliationRow] = Map([])
  let responded : Map[String, ResponseRow] = Map([])
  for row in stock.rows {
    counted[row.lot_id] = row
  }
  for row in response.rows {
    responded[row.shipment_id] = row
  }
  let items : Array[ReadinessItem] = []
  let mut physical_count_count = 0
  for lot in impact.affected_lots {
    match counted.get(lot.lot_id) {
      None =>
        items.push(
          readiness_item(
            "missing-count",
            lot.lot_id,
            "no physical count was supplied for an affected lot",
          ),
        )
      Some(row) => {
        physical_count_count += 1
        if row.difference_g < 0L {
          items.push(
            readiness_item(
              "stock-shortage",
              lot.lot_id,
              "physical count is \{0L - row.difference_g} g below book stock; check \{row.count_reference}",
            ),
          )
        } else if row.difference_g > 0L {
          items.push(
            readiness_item(
              "stock-surplus",
              lot.lot_id,
              "physical count is \{row.difference_g} g above book stock; check \{row.count_reference}",
            ),
          )
        }
      }
    }
  }
  let mut acknowledged_shipment_count = 0
  for shipment in impact.affected_shipments {
    match responded.get(shipment.shipment_id) {
      None =>
        items.push(
          readiness_item(
            "missing-response",
            shipment.shipment_id,
            "affected shipment has no response row",
          ),
        )
      Some(row) =>
        if row.status == "acknowledged" {
          acknowledged_shipment_count += 1
        } else {
          items.push(
            readiness_item(
              "contact-open",
              shipment.shipment_id,
              "latest contact status is \{row.status}; acknowledgement evidence is outstanding",
            ),
          )
        }
    }
  }
  Ok({
    recall_id: request.id,
    as_of: request.as_of,
    affected_lot_count: impact.affected_lots.length(),
    affected_shipment_count: impact.affected_shipments.length(),
    physical_count_count,
    acknowledged_shipment_count,
    items,
    evidence_review_complete: items.is_empty(),
  })
}

///|
pub fn render_readiness_markdown(report : ReadinessReport) -> String {
  let out = StringBuilder()
  out.write_string("# Evidence review: \{markdown_cell(report.recall_id)}\n\n")
  out.write_string("Cutoff (Unix seconds): \{report.as_of}\n\n")
  out.write_string(
    "This is an evidence checklist for human review, not a legal recall completion certificate.\n\n",
  )
  out.write_string(
    "Physical counts: \{report.physical_count_count}/\{report.affected_lot_count} affected lots.\n\n",
  )
  out.write_string(
    "Acknowledgements: \{report.acknowledged_shipment_count}/\{report.affected_shipment_count} affected shipments.\n\n",
  )
  out.write_string("| Category | Entity | Outstanding evidence |\n")
  out.write_string("| --- | --- | --- |\n")
  for item in report.items {
    out.write_string(
      "| \{markdown_cell(item.category)} | \{markdown_cell(item.entity_id)} | \{markdown_cell(item.detail)} |\n",
    )
  }
  if report.items.is_empty() {
    out.write_string("| none | — | No item remains in this checklist |\n")
  }
  out.to_string()
}