///|
/// Transport-neutral services available to interaction handlers.
///
/// Executors construct these values; the public API exposes read-only
/// accessors.
pub struct AppCtx {
  priv client_ : @dhttp.Client
  priv application_id_ : @model.ApplicationId
  priv latency_ms_ : (() -> Int64?)?
}

///|
/// The Discord REST client used by this application.
pub fn AppCtx::http(self : AppCtx) -> @dhttp.Client {
  self.client_
}

///|
/// The application id learned during startup.
pub fn AppCtx::application_id(self : AppCtx) -> @model.ApplicationId {
  self.application_id_
}

///|
/// The latest gateway heartbeat round-trip time in milliseconds. For a
/// multi-shard bot, this is the arithmetic mean of shards that have measured
/// latency. HTTP interaction executors and gateway connections awaiting their
/// first heartbeat acknowledgement return `None`; use
/// `GatewayCtx::latency_ms()` when the exact value for one shard is required.
pub fn AppCtx::latency_ms(self : AppCtx) -> Int64? {
  match self.latency_ms_ {
    Some(provider) => provider()
    None => None
  }
}

///|
/// The current application bound to its Discord REST client.
pub fn AppCtx::application_ref(self : AppCtx) -> @dhttp.ApplicationRef {
  self.client_.application_ref(self.application_id_)
}

///|
/// Executor-provided lookup behind `wait_for_component`: resolves the
/// next component interaction matching a custom id, with an optional
/// timeout in milliseconds.
pub type ComponentWaiter = async (String, Int?) -> @framework.ComponentCtx?

///|
/// The response state when an interaction handler failed.
pub(all) enum ResponsePhase {
  BeforeInitial
  AfterDeferred
  AfterInitial
} derive(Debug, Eq)

///|
fn require_guild(
  scope : @framework.GuildInvocation?,
) -> @framework.GuildInvocation raise HandlerError {
  match scope {
    Some(guild) => guild
    None => raise GuildOnly
  }
}

///|
/// Read-only command context for handlers that return their initial response.
pub struct ImmediateCtx {
  priv raw_ : @framework.CommandCtx
  priv bot_ : AppCtx
}

///|
/// The app-level services: the REST client and application id.
pub fn ImmediateCtx::app(self : ImmediateCtx) -> AppCtx {
  self.bot_
}

///|
/// Guild or DM invocation scope, carrying the invoking member or user.
pub fn ImmediateCtx::scope(self : ImmediateCtx) -> @framework.InvocationScope {
  self.raw_.scope()
}

///|
/// The validated guild invocation. In a DM this raises
/// `HandlerError::GuildOnly`, which the error policy renders normally.
pub fn ImmediateCtx::guild_scope(
  self : ImmediateCtx,
) -> @framework.GuildInvocation raise HandlerError {
  require_guild(self.raw_.guild_scope())
}

///|
/// The invoking user.
pub fn ImmediateCtx::user(self : ImmediateCtx) -> @model.User {
  self.raw_.user()
}

///|
/// The full interaction payload.
pub fn ImmediateCtx::interaction(self : ImmediateCtx) -> @model.Interaction {
  self.raw_.interaction
}

///|
/// The guild the interaction was invoked in, or `None` outside guilds. Use
/// `guild_scope()` for flows that require a guild; this accessor is for
/// maybe-guild flows where DMs are valid.
pub fn ImmediateCtx::guild_id(self : ImmediateCtx) -> @model.GuildId? {
  self.raw_.interaction.guild_id
}

///|
/// Escape hatch for advanced read-only inspection. Calling response methods on
/// this raw value opts out of the immediate-handler response discipline.
pub fn ImmediateCtx::raw(self : ImmediateCtx) -> @framework.CommandCtx {
  self.raw_
}

///|
/// Command context made available after the initial deferred response.
pub struct DeferredCtx {
  priv raw_ : @framework.CommandCtx
  priv bot_ : AppCtx
  priv waiter_ : ComponentWaiter?
  priv warn_ : (String) -> Unit
}

///|
/// The app-level services: the REST client and application id.
pub fn DeferredCtx::app(self : DeferredCtx) -> AppCtx {
  self.bot_
}

///|
/// Guild or DM invocation scope, carrying the invoking member or user.
pub fn DeferredCtx::scope(self : DeferredCtx) -> @framework.InvocationScope {
  self.raw_.scope()
}

///|
/// The validated guild invocation. In a DM this raises
/// `HandlerError::GuildOnly`, which the error policy renders normally.
pub fn DeferredCtx::guild_scope(
  self : DeferredCtx,
) -> @framework.GuildInvocation raise HandlerError {
  require_guild(self.raw_.guild_scope())
}

///|
/// The invoking user.
pub fn DeferredCtx::user(self : DeferredCtx) -> @model.User {
  self.raw_.user()
}

///|
/// The full interaction payload.
pub fn DeferredCtx::interaction(self : DeferredCtx) -> @model.Interaction {
  self.raw_.interaction
}

///|
/// The guild the interaction was invoked in, or `None` outside guilds. Use
/// `guild_scope()` for flows that require a guild; this accessor is for
/// maybe-guild flows where DMs are valid.
pub fn DeferredCtx::guild_id(self : DeferredCtx) -> @model.GuildId? {
  self.raw_.interaction.guild_id
}

///|
/// Escape hatch for advanced inspection. Calling response methods on the
/// raw value opts out of this wrapper's response discipline.
pub fn DeferredCtx::raw(self : DeferredCtx) -> @framework.CommandCtx {
  self.raw_
}

///|
/// Edit the original response after deferring.
pub async fn DeferredCtx::edit_original(
  self : DeferredCtx,
  content? : String,
  clear_content? : Bool = false,
  embeds? : Array[@model.Embed],
  components? : Array[@model.Component],
  allowed_mentions? : @model.AllowedMentions,
  files? : Array[@dhttp.FileUpload],
) -> @model.Message {
  self.raw_.edit_response(
    content?,
    clear_content~,
    embeds?,
    components?,
    allowed_mentions?,
    files?,
  )
}

///|
/// Send a followup message after the initial deferred response.
pub async fn DeferredCtx::followup(
  self : DeferredCtx,
  content? : String,
  embeds? : Array[@model.Embed],
  components? : Array[@model.Component],
  files? : Array[@dhttp.FileUpload],
  allowed_mentions? : @model.AllowedMentions,
  ephemeral? : Bool = false,
) -> @model.Message {
  self.raw_.followup(
    content?,
    embeds?,
    components?,
    files?,
    allowed_mentions?,
    ephemeral~,
  )
}

///|
/// Wait for the next component interaction with an exact custom id.
pub async fn DeferredCtx::wait_for_component(
  self : DeferredCtx,
  custom_id~ : String,
  timeout_ms? : Int,
) -> @framework.ComponentCtx? {
  match self.waiter_ {
    Some(waiter) => waiter(custom_id, timeout_ms)
    None => {
      (self.warn_)(
        "component waiting is unavailable without a gateway connection",
      )
      None
    }
  }
}