///|
pub(all) enum EventType {
  ProviderReady
  ProviderError
  ProviderStale
  ProviderConfigurationChanged
} derive(Eq)

///|
pub extend EventType with Eq::{equal, not_equal}

///|
pub fn EventType::name(self : EventType) -> String {
  match self {
    ProviderReady => "PROVIDER_READY"
    ProviderError => "PROVIDER_ERROR"
    ProviderStale => "PROVIDER_STALE"
    ProviderConfigurationChanged => "PROVIDER_CONFIGURATION_CHANGED"
  }
}

///|
pub(all) struct ProviderEvent {
  event_type : EventType
  provider_name : String
  domain : String?
  message : String
  error : SdkError?
  flags : Array[String]
}

///|
pub fn ProviderEvent::new(
  event_type : EventType,
  provider_name : String,
  domain~ : String? = None,
  message~ : String = "",
  error~ : SdkError? = None,
  flags~ : Array[String] = [],
) -> ProviderEvent {
  { event_type, provider_name, domain, message, error, flags: flags.copy(), }
}

///|
pub fn ProviderEvent::copy(self : ProviderEvent) -> ProviderEvent {
  { ..self, flags: self.flags.copy(), }
}

///|
struct EventListener {
  internal : Bool
  event_type : EventType?
  domain : String?
  callback : (ProviderEvent) -> Result[Unit, SdkError]
}

///|
/// A synchronous, ordered event bus. Listener snapshots make changes in callbacks
/// effective for the next event, not partway through the current dispatch.
pub struct EventBus {
  priv mut next_id : Int32
  priv listeners : Map[Int32, EventListener]
  priv mut dispatching : Bool
  priv pending : Array[ProviderEvent]
  priv diagnostics : Array[SdkError]
}

///|
pub fn EventBus::new() -> EventBus {
  {
    next_id: 1,
    listeners: Map([]),
    dispatching: false,
    pending: [],
    diagnostics: [],
  }
}

///|
pub fn EventBus::on(
  self : EventBus,
  callback : (ProviderEvent) -> Result[Unit, SdkError],
  event_type~ : EventType? = None,
  domain~ : String? = None,
) -> Int32 {
  let id = self.next_id
  self.next_id += 1
  self.listeners[id] = { internal: false, event_type, domain, callback, }
  id
}

///|
pub fn EventBus::off(self : EventBus, id : Int32) -> Bool {
  let found = self.listeners.contains(id)
  self.listeners.remove(id)
  found
}

///|
pub fn EventBus::listener_count(self : EventBus) -> Int32 {
  self.listeners.length()
}

///|
pub fn EventBus::clear(self : EventBus) -> Unit {
  self.listeners.clear()
}

///|
pub fn EventBus::emit(self : EventBus, event : ProviderEvent) -> Unit {
  self.pending.push(event.copy())
  if self.dispatching {
    return
  }
  self.dispatching = true
  let mut processed = 0
  while self.pending.length() > 0 && processed < 1024 {
    let current = self.pending.remove(0)
    let ids = self.listeners.keys().to_array()
    ids.sort()
    let snapshot : Array[EventListener] = []
    // SDK ownership routing runs before application observers, even those
    // registered before binding. Ordinary observers retain registration order.
    for internal in [true, false] {
      for id in ids {
        match self.listeners.get(id) {
          Some(listener) =>
            if listener.internal == internal {
              snapshot.push(listener)
            }
          None => ()
        }
      }
    }
    for listener in snapshot {
      let type_matches = match listener.event_type {
        None => true
        Some(t) => t == current.event_type
      }
      let domain_matches = match listener.domain {
        None => true
        Some(d) => current.domain == Some(d)
      }
      if type_matches && domain_matches {
        match (listener.callback)(current.copy()) {
          Ok(_) => ()
          Err(error) => self.record_diagnostic(error)
        }
      }
    }
    processed += 1
  }
  if self.pending.length() > 0 {
    self.pending.clear()
    self.record_diagnostic(
      SdkError::new(General, "reentrant event dispatch limit reached"),
    )
  }
  self.dispatching = false
}

///|
fn EventBus::record_diagnostic(self : EventBus, error : SdkError) -> Unit {
  if self.diagnostics.length() == 128 {
    self.diagnostics.remove(0) |> ignore
  }
  self.diagnostics.push(error)
}

///|
pub fn EventBus::take_diagnostics(self : EventBus) -> Array[SdkError] {
  let result = self.diagnostics.copy()
  self.diagnostics.clear()
  result
}

///|
/// Only events corresponding to the current state receive immediate delivery.
/// Configuration changes are notifications, not a replayable lifecycle state.
pub fn EventType::matches_status(
  self : EventType,
  status : ProviderStatus,
) -> Bool {
  match (self, status) {
    (ProviderReady, Ready)
    | (ProviderError, Error | Fatal)
    | (ProviderStale, Stale) => true
    _ => false
  }
}

///|
/// Package-private subscription for ownership routing, never application work.
fn EventBus::on_internal(
  self : EventBus,
  callback : (ProviderEvent) -> Result[Unit, SdkError],
) -> Int32 {
  let id = self.on(callback)
  self.listeners[id] = { ..self.listeners[id], internal: true, }
  id
}