///|
/// Selects which Gateway shards this process owns.
pub(all) enum ShardConfig {
  /// Use Discord's recommended shard count and run every shard locally.
  Auto
  /// Run one shard from a larger logical shard set. This is the default and
  /// preserves the pre-manager single-shard executor behavior.
  Single(id~ : Int, count~ : Int)
  /// Run shard ids `0.. Unit raise BotError {
  if count <= 0 {
    raise InvalidShardConfig(message="shard count must be positive")
  }
  if ids.is_empty() {
    raise InvalidShardConfig(message="at least one shard id must be selected")
  }
  let seen : Map[Int, Bool] = Map([])
  for id in ids {
    if id < 0 || id >= count {
      raise InvalidShardConfig(message="shard id \{id} is outside 0..<\{count}")
    }
    if seen.get(id) == Some(true) {
      raise InvalidShardConfig(message="duplicate shard id \{id}")
    }
    seen[id] = true
  }
}

///|
async fn Bot::gateway_bot_info(
  self : Bot,
  client : @dhttp.Client,
) -> @model.GatewayBotInfo raise @dhttp.DiscordHttpError {
  match self.gateway_bot_info_.val {
    Some(info) => info
    None => client.get_gateway_bot()
  }
}

///|
fn[X] Bot::start_managed_shard(
  self : Bot,
  group : @async.TaskGroup[X],
  token~ : String,
  intents~ : @model.Intents,
  gateway_url~ : String,
  shard_id~ : Int,
  shard_count~ : Int,
  identify_queue~ : &@queue.IdentifyQueue,
  event_filter~ : (@model.EventKind) -> Bool,
) -> @gateway.Shard {
  match self.connector_.val {
    Some(connector) =>
      @gateway.Shard::start(
        group,
        token~,
        intents~,
        gateway_url~,
        shard_id~,
        shard_count~,
        identify_queue~,
        event_filter~,
        connector~,
        compress=self.compress_,
        telemetry=event => self.emit_telemetry(event),
      )
    None =>
      @gateway.Shard::start(
        group,
        token~,
        intents~,
        gateway_url~,
        shard_id~,
        shard_count~,
        identify_queue~,
        event_filter~,
        compress=self.compress_,
        telemetry=event => self.emit_telemetry(event),
      )
  }
}

///|
async fn Bot::resolve_shards(
  self : Bot,
  client : @dhttp.Client,
) -> ResolvedShardConfig {
  match self.shards_ {
    Single(id~, count~) => {
      let ids = [id]
      validate_shard_ids(ids, count)
      let gateway_url = match self.gateway_url_ {
        Some(url) => url
        None => client.get_gateway()
      }
      { ids, count, gateway_url, max_concurrency: 1, }
    }
    Auto | Fixed(_) | Range(..) => {
      let info = self.gateway_bot_info(client)
      let (ids, count) = match self.shards_ {
        Auto => {
          let ids = []
          for id in 0.. {
          let ids = []
          for id in 0.. (ids.copy(), count)
        Single(..) => abort("handled above")
      }
      validate_shard_ids(ids, count)
      if info.session_start_limit.max_concurrency <= 0 {
        raise BotError::InvalidShardConfig(
          message="gateway max_concurrency must be positive",
        )
      }
      if ids.length() > info.session_start_limit.remaining {
        raise BotError::SessionStartLimitExceeded(
          required=ids.length(),
          remaining=info.session_start_limit.remaining,
          reset_after_ms=info.session_start_limit.reset_after,
        )
      }
      {
        ids,
        count,
        gateway_url: self.gateway_url_.unwrap_or(info.url),
        max_concurrency: info.session_start_limit.max_concurrency,
      }
    }
  }
}

///|
async fn close_shards(shards : Array[@gateway.Shard]) -> Unit noraise {
  for shard in shards {
    shard.close()
  }
}