///|
/// Built-in provider families supported by MoonHook adapters.
pub(all) enum HookProvider {
  GitHub
  GitLab
  Gitee
  Feishu
  WeCom
  Stripe
  Custom(String)
} derive(Eq, Debug)

///|
/// A normalized incoming WebHook request.
pub(all) struct HookRequest {
  provider : HookProvider
  event_type : String
  delivery_id : String
  timestamp : String?
  headers : Map[String, String]
  body : String
} derive(Eq, Debug)

///|
/// A normalized event after provider-specific parsing.
pub(all) struct HookEvent {
  provider : HookProvider
  event_type : String
  delivery_id : String
  idempotency_key : String
  body : String
  attributes : Map[String, String]
} derive(Eq, Debug)

///|
/// Create a request with normalized names used by all adapters.
pub fn HookRequest::HookRequest(
  provider : HookProvider,
  event_type : StringView,
  delivery_id : StringView,
  body : StringView,
  timestamp? : StringView,
  headers? : Map[String, String] = Map([]),
) -> HookRequest {
  {
    provider,
    event_type: event_type.to_owned(),
    delivery_id: delivery_id.to_owned(),
    timestamp: timestamp.map(v => v.to_owned()),
    headers,
    body: body.to_owned(),
  }
}

///|
/// Convert a raw request into the minimal normalized event.
pub fn HookRequest::to_event(self : HookRequest) -> HookEvent {
  {
    provider: self.provider,
    event_type: self.event_type,
    delivery_id: self.delivery_id,
    idempotency_key: make_idempotency_key(self.provider, self.delivery_id),
    body: self.body,
    attributes: Map([]),
  }
}

///|
/// Return a stable provider slug for logs, ids, and JSON fields.
pub fn HookProvider::slug(self : HookProvider) -> String {
  match self {
    GitHub => "github"
    GitLab => "gitlab"
    Gitee => "gitee"
    Feishu => "feishu"
    WeCom => "wecom"
    Stripe => "stripe"
    Custom(name) => name
  }
}

///|
/// Build the default idempotency key from provider and delivery id.
pub fn make_idempotency_key(
  provider : HookProvider,
  delivery_id : String,
) -> String {
  provider.slug() + ":" + delivery_id
}

///|
/// Return a header by normalized, case-insensitive key.
pub fn HookRequest::header(self : HookRequest, name : StringView) -> String? {
  let normalized = name.to_owned().to_lower()
  match self.headers.get(normalized) {
    Some(value) => Some(value)
    None => {
      for key, value in self.headers {
        if key.to_lower() == normalized {
          return Some(value)
        }
      }
      None
    }
  }
}

///|
/// Read a header from a raw map using HTTP's case-insensitive name rules.
pub fn header_value(
  headers : Map[String, String],
  name : StringView,
) -> String? {
  let normalized = name.to_owned().to_lower()
  match headers.get(normalized) {
    Some(value) => Some(value)
    None => {
      for key, value in headers {
        if key.to_lower() == normalized {
          return Some(value)
        }
      }
      None
    }
  }
}

///|
/// Check whether the request carries an event type.
pub fn HookRequest::has_event_type(
  self : HookRequest,
  event_type : StringView,
) -> Bool {
  self.event_type == event_type.to_owned()
}

///|
/// Check whether the request comes from a provider.
pub fn HookRequest::is_from(
  self : HookRequest,
  provider : HookProvider,
) -> Bool {
  self.provider == provider
}

///|
/// Check whether the event matches the given provider.
pub fn HookEvent::is_from(self : HookEvent, provider : HookProvider) -> Bool {
  self.provider == provider
}

///|
/// Check whether the event has a specific normalized type.
pub fn HookEvent::is_type(self : HookEvent, event_type : StringView) -> Bool {
  self.event_type == event_type.to_owned()
}

///|
/// Attach a single parsed attribute to an event.
pub fn HookEvent::with_attribute(
  self : HookEvent,
  key : StringView,
  value : StringView,
) -> HookEvent {
  let attrs = self.attributes
  attrs[key.to_owned()] = value.to_owned()
  {
    provider: self.provider,
    event_type: self.event_type,
    delivery_id: self.delivery_id,
    idempotency_key: self.idempotency_key,
    body: self.body,
    attributes: attrs,
  }
}

///|
/// Replace normalized event identity and rebuild the idempotency key.
pub fn HookEvent::with_identity(
  self : HookEvent,
  event_type? : String = self.event_type,
  delivery_id? : String = self.delivery_id,
) -> HookEvent {
  {
    provider: self.provider,
    event_type,
    delivery_id,
    idempotency_key: make_idempotency_key(self.provider, delivery_id),
    body: self.body,
    attributes: self.attributes,
  }
}

///|
/// Read an optional parsed attribute.
pub fn HookEvent::attribute(self : HookEvent, key : StringView) -> String? {
  self.attributes.get(key.to_owned())
}