///|
/// Context passed to handlers and middleware.
pub(all) struct EventContext {
  bus_name : String
  publish_index : Int
  handler_index : Int
} derive(Eq, Debug)

///|
pub(all) enum HandlerOutcome {
  OutcomeHandled(String)
  OutcomeIgnored(String)
  OutcomeFailed(String)
  OutcomeStopped(String)
} derive(Eq, Debug)

///|
pub(all) enum MiddlewareOutcome {
  MiddlewareContinue(Event)
  MiddlewareHalt(String)
} derive(Debug)

///|
pub type HandlerFn = (Event, EventContext) -> HandlerOutcome

///|
pub type MiddlewareFn = (Event, EventContext) -> MiddlewareOutcome

///|
pub(all) struct Handler {
  id : Int
  name : String
  subscription : Subscription
  priority : Int
  callback : HandlerFn
}

///|
pub(all) struct Middleware {
  name : String
  callback : MiddlewareFn
}

///|
pub(all) struct EventBus {
  name : String
  handlers : Array[Handler]
  middlewares : Array[Middleware]
  scheduled : Array[ScheduledEvent]
  trace : Array[TraceEntry]
  publish_count : Int
  next_id : Int
}

///|
pub fn Handler::new(
  id : Int,
  name : String,
  subscription : Subscription,
  priority : Int,
  callback : HandlerFn,
) -> Handler {
  { id, name, priority, subscription, callback }
}

///|
pub fn Middleware::new(name : String, callback : MiddlewareFn) -> Middleware {
  { name, callback }
}

///|
pub fn EventBus::new(name? : String = "default") -> EventBus {
  {
    name,
    handlers: [],
    middlewares: [],
    scheduled: [],
    trace: [],
    publish_count: 0,
    next_id: 1,
  }
}

///|
pub fn EventBus::handler_count(self : EventBus) -> Int {
  self.handlers.length()
}

///|
pub fn EventBus::middleware_count(self : EventBus) -> Int {
  self.middlewares.length()
}

///|
pub fn EventBus::scheduled_count(self : EventBus) -> Int {
  self.scheduled.length()
}

///|
pub fn EventBus::trace_entries(self : EventBus) -> Array[TraceEntry] {
  self.trace
}

///|
pub fn EventBus::with_handler(self : EventBus, handler : Handler) -> EventBus {
  let handlers : Array[Handler] = []
  for existing in self.handlers {
    handlers.push(existing)
  }
  handlers.push(handler)
  { ..self, handlers, next_id: self.next_id + 1 }
}

///|
pub fn EventBus::subscribe(
  self : EventBus,
  name : String,
  topic? : String = "",
  tag? : String = "",
  min_priority? : Int = 0,
  once? : Bool = false,
  callback : HandlerFn,
  handler_priority? : Int = 0,
) -> EventBus {
  let sub = Subscription::new(
    self.next_id,
    name,
    topic~,
    tag~,
    min_priority~,
    once~,
  )
  self.with_handler(
    Handler::new(self.next_id, name, sub, handler_priority, callback),
  )
}

///|
fn copy_handler_with_active(handler : Handler, active : Bool) -> Handler {
  { ..handler, subscription: { ..handler.subscription, active, } }
}

///|
pub fn EventBus::pause(self : EventBus, id : Int) -> EventBus {
  let handlers : Array[Handler] = []
  for handler in self.handlers {
    if handler.id == id {
      handlers.push(copy_handler_with_active(handler, false))
    } else {
      handlers.push(handler)
    }
  }
  { ..self, handlers, }
}

///|
pub fn EventBus::resume_handler(self : EventBus, id : Int) -> EventBus {
  let handlers : Array[Handler] = []
  for handler in self.handlers {
    if handler.id == id {
      handlers.push(copy_handler_with_active(handler, true))
    } else {
      handlers.push(handler)
    }
  }
  { ..self, handlers, }
}

///|
pub fn EventBus::unsubscribe(self : EventBus, id : Int) -> EventBus {
  let handlers : Array[Handler] = []
  for handler in self.handlers {
    if handler.id != id {
      handlers.push(handler)
    }
  }
  { ..self, handlers, }
}

///|
pub fn EventBus::has_handler(self : EventBus, id : Int) -> Bool {
  for handler in self.handlers {
    if handler.id == id {
      return true
    }
  }
  false
}

///|
pub fn EventBus::active_handler_count(self : EventBus) -> Int {
  let mut count = 0
  for handler in self.handlers {
    if handler.subscription.active {
      count = count + 1
    }
  }
  count
}

///|
fn insert_handler_by_priority(
  ordered : Array[Handler],
  handler : Handler,
) -> Array[Handler] {
  let result : Array[Handler] = []
  let mut inserted = false
  for existing in ordered {
    if !inserted && handler.priority > existing.priority {
      result.push(handler)
      inserted = true
    }
    result.push(existing)
  }
  if !inserted {
    result.push(handler)
  }
  result
}

///|
fn ordered_handlers(handlers : Array[Handler]) -> Array[Handler] {
  let mut result : Array[Handler] = []
  for handler in handlers {
    result = insert_handler_by_priority(result, handler)
  }
  result
}

///|
pub fn EventBus::use_middleware(
  self : EventBus,
  name : String,
  callback : MiddlewareFn,
) -> EventBus {
  let middlewares : Array[Middleware] = []
  for existing in self.middlewares {
    middlewares.push(existing)
  }
  middlewares.push(Middleware::new(name, callback))
  { ..self, middlewares, }
}

///|
fn EventBus::append_trace(
  self : EventBus,
  kind : TraceKind,
  event_name : String,
  actor : String,
  message : String,
) -> EventBus {
  let trace : Array[TraceEntry] = []
  for existing in self.trace {
    trace.push(existing)
  }
  trace.push(TraceEntry::new(trace.length(), kind, event_name, actor, message))
  { ..self, trace, }
}

///|
fn outcome_to_report(
  handler : String,
  outcome : HandlerOutcome,
) -> HandlerReport {
  match outcome {
    OutcomeHandled(note) => HandlerReport::ok(handler, note~)
    OutcomeIgnored(note) => HandlerReport::ignored(handler, note~)
    OutcomeFailed(reason) => HandlerReport::failed(handler, reason)
    OutcomeStopped(note) => HandlerReport::ok(handler, note~)
  }
}

///|
fn outcome_stops(outcome : HandlerOutcome) -> Bool {
  match outcome {
    OutcomeStopped(_) => true
    _ => false
  }
}

///|
fn run_middlewares(
  bus : EventBus,
  event : Event,
  context : EventContext,
) -> (EventBus, Event?, Array[HandlerReport]) {
  let errors : Array[HandlerReport] = []
  let mut current = event
  let mut next_bus = bus
  let mut halted = false
  for middleware in bus.middlewares {
    if halted {
      ()
    } else {
      next_bus = next_bus.append_trace(
        TraceMiddleware,
        current.name,
        middleware.name,
        "middleware start",
      )
      match (middleware.callback)(current, context) {
        MiddlewareContinue(updated) => current = updated
        MiddlewareHalt(reason) => {
          halted = true
          errors.push(HandlerReport::failed(middleware.name, reason))
          next_bus = next_bus.append_trace(
            TraceError,
            current.name,
            middleware.name,
            reason,
          )
        }
      }
    }
  }
  if halted {
    (next_bus, None, errors)
  } else {
    (next_bus, Some(current), errors)
  }
}

///|
pub fn EventBus::publish(
  self : EventBus,
  event : Event,
) -> (EventBus, DispatchReport) {
  let context = {
    bus_name: self.name,
    publish_index: self.publish_count,
    handler_index: 0,
  }
  let mut next_bus = self.append_trace(
    TracePublish,
    event.name,
    self.name,
    "publish start",
  )
  let (after_middleware, maybe_event, middleware_reports) = run_middlewares(
    next_bus, event, context,
  )
  next_bus = after_middleware
  let reports : Array[HandlerReport] = []
  for item in middleware_reports {
    reports.push(item)
  }
  let mut stopped = false
  let once_ids : Array[Int] = []
  match maybe_event {
    None => stopped = true
    Some(current) => {
      let mut handler_index = 0
      for handler in ordered_handlers(next_bus.handlers) {
        if stopped {
          ()
        } else if handler.subscription.matches(current) {
          let handler_context = {
            bus_name: self.name,
            publish_index: self.publish_count,
            handler_index,
          }
          next_bus = next_bus.append_trace(
            TraceHandler,
            current.name,
            handler.name,
            "handler start",
          )
          let outcome = (handler.callback)(current, handler_context)
          reports.push(outcome_to_report(handler.name, outcome))
          if handler.subscription.once {
            once_ids.push(handler.id)
          }
          if outcome_stops(outcome) {
            stopped = true
          }
        } else {
          reports.push(HandlerReport::ignored(handler.name, note="filtered"))
        }
        handler_index = handler_index + 1
      }
    }
  }
  next_bus = { ..next_bus, publish_count: next_bus.publish_count + 1 }
  for id in once_ids {
    next_bus = next_bus.unsubscribe(id)
  }
  (next_bus, DispatchReport::from_reports(event, reports, stopped~))
}

///|
pub fn EventBus::publish_many(
  self : EventBus,
  events : Array[Event],
) -> (EventBus, Array[DispatchReport]) {
  let reports : Array[DispatchReport] = []
  let mut bus = self
  for event in events {
    let (next, report) = bus.publish(event)
    bus = next
    reports.push(report)
  }
  (bus, reports)
}