///|
/// Transformation performed by a routing rule.
pub(all) enum RouteAction {
  RouteForward(String)
  RouteFanOut(Array[String])
  RouteRename(String)
  RouteAddTag(String)
  RouteRaisePriority(Int)
  RouteDrop(String)
} derive(Eq, Debug)

///|
pub(all) struct RouteRule {
  name : String
  topic_pattern : String
  required_tag : String
  min_priority : Int
  rule_priority : Int
  terminal : Bool
  active : Bool
  action : RouteAction
} derive(Eq, Debug)

///|
pub(all) struct RouteNote {
  rule : String
  input_topic : String
  output_count : Int
  message : String
} derive(Eq, Debug)

///|
pub(all) struct RoutingResult {
  input : Event
  events : Array[Event]
  matched_rules : Int
  dropped : Bool
  notes : Array[RouteNote]
} derive(Eq, Debug)

///|
pub(all) struct EventRouter {
  rules : Array[RouteRule]
} derive(Eq, Debug)

///|
pub fn RouteRule::new(
  name : String,
  topic_pattern : String,
  action : RouteAction,
  required_tag? : String = "",
  min_priority? : Int = 0,
  rule_priority? : Int = 0,
  terminal? : Bool = false,
) -> RouteRule {
  {
    name,
    topic_pattern,
    required_tag,
    min_priority,
    rule_priority,
    terminal,
    active: true,
    action,
  }
}

///|
pub fn RouteRule::matches(self : RouteRule, event : Event) -> Bool {
  self.active &&
  topic_matches(self.topic_pattern, event.topic) &&
  (self.required_tag == "" || event.has_tag(self.required_tag)) &&
  event.priority >= self.min_priority
}

///|
pub fn RouteRule::is_valid(self : RouteRule) -> Bool {
  validate_topic_pattern(self.topic_pattern) == PatternValid
}

///|
pub fn EventRouter::new() -> EventRouter {
  { rules: [] }
}

///|
fn insert_rule_by_priority(
  rules : Array[RouteRule],
  rule : RouteRule,
) -> Array[RouteRule] {
  let result : Array[RouteRule] = []
  let mut inserted = false
  for existing in rules {
    if !inserted && rule.rule_priority > existing.rule_priority {
      result.push(rule)
      inserted = true
    }
    result.push(existing)
  }
  if !inserted {
    result.push(rule)
  }
  result
}

///|
pub fn EventRouter::add_rule(
  self : EventRouter,
  rule : RouteRule,
) -> EventRouter {
  { rules: insert_rule_by_priority(self.rules, rule) }
}

///|
pub fn EventRouter::rule_count(self : EventRouter) -> Int {
  self.rules.length()
}

///|
pub fn EventRouter::active_rule_count(self : EventRouter) -> Int {
  let mut count = 0
  for rule in self.rules {
    if rule.active {
      count = count + 1
    }
  }
  count
}

///|
pub fn EventRouter::disable(self : EventRouter, name : String) -> EventRouter {
  let rules : Array[RouteRule] = []
  for rule in self.rules {
    if rule.name == name {
      rules.push({ ..rule, active: false })
    } else {
      rules.push(rule)
    }
  }
  { rules, }
}

///|
pub fn EventRouter::enable(self : EventRouter, name : String) -> EventRouter {
  let rules : Array[RouteRule] = []
  for rule in self.rules {
    if rule.name == name {
      rules.push({ ..rule, active: true })
    } else {
      rules.push(rule)
    }
  }
  { rules, }
}

///|
pub fn EventRouter::remove_rule(
  self : EventRouter,
  name : String,
) -> EventRouter {
  let rules : Array[RouteRule] = []
  for rule in self.rules {
    if rule.name != name {
      rules.push(rule)
    }
  }
  { rules, }
}

///|
fn event_with_topic(event : Event, topic : String) -> Event {
  { ..event, topic, }
}

///|
fn apply_route_action(event : Event, action : RouteAction) -> Array[Event] {
  let output : Array[Event] = []
  match action {
    RouteForward(topic) => output.push(event_with_topic(event, topic))
    RouteFanOut(topics) =>
      for topic in topics {
        output.push(event_with_topic(event, topic))
      }
    RouteRename(name) => output.push({ ..event, name, })
    RouteAddTag(tag) =>
      output.push({ ..event, tags: append_tag(event.tags, tag) })
    RouteRaisePriority(priority) =>
      if event.priority < priority {
        output.push({ ..event, priority, })
      } else {
        output.push(event)
      }
    RouteDrop(_) => ()
  }
  output
}

///|
fn route_action_message(action : RouteAction) -> String {
  match action {
    RouteForward(topic) => "forward:" + topic
    RouteFanOut(topics) => "fan-out:" + topics.length().to_string()
    RouteRename(name) => "rename:" + name
    RouteAddTag(tag) => "tag:" + tag
    RouteRaisePriority(priority) => "priority:" + priority.to_string()
    RouteDrop(reason) => "drop:" + reason
  }
}

///|
fn apply_rule_to_events(
  rule : RouteRule,
  events : Array[Event],
) -> (Array[Event], Int, Array[RouteNote]) {
  let output : Array[Event] = []
  let notes : Array[RouteNote] = []
  let mut matches = 0
  for event in events {
    if rule.matches(event) {
      matches = matches + 1
      let transformed = apply_route_action(event, rule.action)
      notes.push({
        rule: rule.name,
        input_topic: event.topic,
        output_count: transformed.length(),
        message: route_action_message(rule.action),
      })
      for next in transformed {
        output.push(next)
      }
    } else {
      output.push(event)
    }
  }
  (output, matches, notes)
}

///|
pub fn EventRouter::route(self : EventRouter, event : Event) -> RoutingResult {
  let mut current = [event]
  let notes : Array[RouteNote] = []
  let mut matched_rules = 0
  let mut terminated = false
  for rule in self.rules {
    if !terminated && current.length() > 0 {
      let (next, matches, rule_notes) = apply_rule_to_events(rule, current)
      current = next
      if matches > 0 {
        matched_rules = matched_rules + 1
        if rule.terminal {
          terminated = true
        }
      }
      for note in rule_notes {
        notes.push(note)
      }
    }
  }
  {
    input: event,
    events: current,
    matched_rules,
    dropped: current.length() == 0,
    notes,
  }
}

///|
pub fn EventRouter::route_many(
  self : EventRouter,
  events : Array[Event],
) -> Array[RoutingResult] {
  let results : Array[RoutingResult] = []
  for event in events {
    results.push(self.route(event))
  }
  results
}

///|
pub fn RoutingResult::output_count(self : RoutingResult) -> Int {
  self.events.length()
}

///|
pub fn RoutingResult::first(self : RoutingResult) -> Event? {
  self.events.get(0)
}

///|
pub fn RoutingResult::publish(
  self : RoutingResult,
  bus : EventBus,
) -> (EventBus, Array[DispatchReport]) {
  bus.publish_many(self.events)
}

///|
pub fn EventRouter::invalid_rules(self : EventRouter) -> Array[RouteRule] {
  let invalid : Array[RouteRule] = []
  for rule in self.rules {
    if !rule.is_valid() {
      invalid.push(rule)
    }
  }
  invalid
}