///|
/// Retry and dead-letter helpers for failed dispatch flows.
pub(all) enum RetryDecision {
  Retry(Event)
  DeadLetter(Event)
  Drop(String)
} derive(Eq, Debug)

///|
pub(all) struct RetryPolicy {
  max_attempts : Int
  dead_letter_topic : String
  retry_tag : String
} derive(Eq, Debug)

///|
pub fn RetryPolicy::new(
  max_attempts? : Int = 3,
  dead_letter_topic? : String = "dead-letter",
  retry_tag? : String = "retry",
) -> RetryPolicy {
  { max_attempts, dead_letter_topic, retry_tag }
}

///|
pub fn RetryPolicy::decide(
  self : RetryPolicy,
  event : Event,
  attempt : Int,
  reason : String,
) -> RetryDecision {
  if attempt < self.max_attempts {
    Retry({
      name: event.name,
      topic: event.topic,
      payload: event.payload + "|retry=" + attempt.to_string(),
      tags: append_tag(event.tags, self.retry_tag),
      priority: event.priority,
      correlation_id: event.correlation_id,
    })
  } else if self.dead_letter_topic == "" {
    Drop(reason)
  } else {
    DeadLetter({
      name: "dead." + event.name,
      topic: self.dead_letter_topic,
      payload: event.payload + "|reason=" + reason,
      tags: append_tag(event.tags, "dead-letter"),
      priority: event.priority,
      correlation_id: event.correlation_id,
    })
  }
}

///|
pub fn append_tag(tags : Array[String], tag : String) -> Array[String] {
  let next : Array[String] = []
  for existing in tags {
    next.push(existing)
  }
  if !next.contains(tag) {
    next.push(tag)
  }
  next
}

///|
pub fn report_needs_retry(report : DispatchReport) -> Bool {
  report.failed > 0
}

///|
pub fn collect_failures(report : DispatchReport) -> Array[String] {
  let reasons : Array[String] = []
  for item in report.reports {
    match item.status {
      Failed(reason) => reasons.push(reason)
      _ => ()
    }
  }
  reasons
}

///|
pub fn dead_letter_queue(
  reports : Array[DispatchReport],
  policy : RetryPolicy,
) -> EventQueue {
  let mut queue = EventQueue::new()
  for report in reports {
    if report_needs_retry(report) {
      match
        policy.decide(report.event, policy.max_attempts, "dispatch failed") {
        DeadLetter(event) => queue = queue.push(event)
        _ => ()
      }
    }
  }
  queue
}