///|
/// One validated batch input with a caller-controlled stable identifier.
pub struct BatchItem {
  identifier_value : String
  source_value : String
} derive(Eq, Debug)

///|
/// Per-item outcome; failures do not discard successful neighbors.
pub(all) enum BatchOutcome {
  BatchSucceeded(ConversionResult)
  BatchFailed(PinyinError)
} derive(Eq, Debug)

///|
/// One auditable batch output in the same position as its input.
pub struct BatchItemResult {
  item_value : BatchItem
  outcome_value : BatchOutcome
} derive(Eq, Debug)

///|
/// Aggregate conversion counts derived from item outcomes.
pub struct ConversionSummary {
  total_value : Int
  succeeded_value : Int
  failed_value : Int
  unknown_failure_value : Int
  ambiguous_failure_value : Int
  rendered_token_value : Int
  diagnostic_value : Int
} derive(Eq, Debug)

///|
/// Ordered per-item outcomes plus a precomputed summary.
pub struct BatchResult {
  item_values : Array[BatchItemResult]
  summary_value : ConversionSummary
} derive(Eq, Debug)

///|
fn identifier_has_line_break(value : String) -> Bool {
  for code in text_code_points(value) {
    if code == 10 || code == 13 {
      return true
    }
  }
  false
}

///|
pub fn batch_item(
  identifier : String,
  source : String,
) -> Result[BatchItem, PinyinError] {
  if identifier.length() == 0 {
    return Err(InvalidBatchItem(identifier, "empty_identifier"))
  }
  if identifier_has_line_break(identifier) {
    return Err(InvalidBatchItem(identifier, "line_break_in_identifier"))
  }
  Ok({ identifier_value: identifier, source_value: source })
}

///|
fn copy_batch_item_results(
  values : Array[BatchItemResult],
) -> Array[BatchItemResult] {
  let output : Array[BatchItemResult] = []
  for value in values {
    output.push(value)
  }
  output
}

///|
/// Converts all items without short-circuiting after an item failure.
pub fn convert_batch(
  items : Array[BatchItem],
  lexicon : Lexicon,
  options : ConvertOptions,
) -> BatchResult {
  let results : Array[BatchItemResult] = []
  let mut succeeded = 0
  let mut failed = 0
  let mut unknown_failures = 0
  let mut ambiguous_failures = 0
  let mut rendered_tokens = 0
  let mut diagnostics = 0
  for item in items {
    let outcome = match convert_text(item.source_value, lexicon, options) {
      Ok(converted) => {
        succeeded = succeeded + 1
        rendered_tokens = rendered_tokens + converted.tokens().length()
        diagnostics = diagnostics + converted.diagnostics().length()
        BatchSucceeded(converted)
      }
      Err(error) => {
        failed = failed + 1
        match error {
          UnknownCharacter(_) => unknown_failures = unknown_failures + 1
          AmbiguousCharacter(_, _) =>
            ambiguous_failures = ambiguous_failures + 1
          _ => ()
        }
        BatchFailed(error)
      }
    }
    results.push({ item_value: item, outcome_value: outcome })
  }
  {
    item_values: results,
    summary_value: {
      total_value: items.length(),
      succeeded_value: succeeded,
      failed_value: failed,
      unknown_failure_value: unknown_failures,
      ambiguous_failure_value: ambiguous_failures,
      rendered_token_value: rendered_tokens,
      diagnostic_value: diagnostics,
    },
  }
}

///|
pub fn BatchItem::identifier(self : BatchItem) -> String {
  self.identifier_value
}

///|
pub fn BatchItem::source(self : BatchItem) -> String {
  self.source_value
}

///|
pub fn BatchItemResult::item(self : BatchItemResult) -> BatchItem {
  self.item_value
}

///|
pub fn BatchItemResult::outcome(self : BatchItemResult) -> BatchOutcome {
  self.outcome_value
}

///|
pub fn BatchItemResult::is_success(self : BatchItemResult) -> Bool {
  self.outcome_value is BatchSucceeded(_)
}

///|
pub fn BatchItemResult::conversion(self : BatchItemResult) -> ConversionResult? {
  match self.outcome_value {
    BatchSucceeded(value) => Some(value)
    BatchFailed(_) => None
  }
}

///|
pub fn BatchItemResult::error(self : BatchItemResult) -> PinyinError? {
  match self.outcome_value {
    BatchSucceeded(_) => None
    BatchFailed(error) => Some(error)
  }
}

///|
pub fn BatchResult::items(self : BatchResult) -> Array[BatchItemResult] {
  copy_batch_item_results(self.item_values)
}

///|
pub fn BatchResult::summary(self : BatchResult) -> ConversionSummary {
  self.summary_value
}

///|
pub fn ConversionSummary::total(self : ConversionSummary) -> Int {
  self.total_value
}

///|
pub fn ConversionSummary::succeeded(self : ConversionSummary) -> Int {
  self.succeeded_value
}

///|
pub fn ConversionSummary::failed(self : ConversionSummary) -> Int {
  self.failed_value
}

///|
pub fn ConversionSummary::unknown_failures(self : ConversionSummary) -> Int {
  self.unknown_failure_value
}

///|
pub fn ConversionSummary::ambiguous_failures(self : ConversionSummary) -> Int {
  self.ambiguous_failure_value
}

///|
pub fn ConversionSummary::rendered_tokens(self : ConversionSummary) -> Int {
  self.rendered_token_value
}

///|
pub fn ConversionSummary::diagnostics(self : ConversionSummary) -> Int {
  self.diagnostic_value
}