var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
  if (typeof require !== "undefined")
    return require.apply(this, arguments);
  throw new Error('Dynamic require of "' + x + '" is not supported');
});
var __publicField = (obj, key, value) => {
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  return value;
};

// ../../node_modules/tsup/assets/esm_shims.js
import { fileURLToPath } from "url";
import path from "path";
var getFilename = /* @__PURE__ */ __name(() => fileURLToPath(import.meta.url), "getFilename");
var getDirname = /* @__PURE__ */ __name(() => path.dirname(getFilename()), "getDirname");
var __dirname = /* @__PURE__ */ getDirname();

// src/strategies/context/IContextFetchingStrategy.ts
async function managerToFetchingStrategyOptions(manager) {
  const {
    buildIdentifyThrottler,
    buildStrategy,
    retrieveSessionInfo,
    updateSessionInfo,
    shardCount,
    shardIds,
    rest,
    ...managerOptions
  } = manager.options;
  return {
    ...managerOptions,
    gatewayInformation: await manager.fetchGatewayInformation(),
    shardCount: await manager.getShardCount()
  };
}
__name(managerToFetchingStrategyOptions, "managerToFetchingStrategyOptions");

// src/strategies/context/SimpleContextFetchingStrategy.ts
var _SimpleContextFetchingStrategy = class {
  constructor(manager, options) {
    this.manager = manager;
    this.options = options;
  }
  static async ensureThrottler(manager) {
    const throttler = _SimpleContextFetchingStrategy.throttlerCache.get(manager);
    if (throttler) {
      return throttler;
    }
    const newThrottler = await manager.options.buildIdentifyThrottler(manager);
    _SimpleContextFetchingStrategy.throttlerCache.set(manager, newThrottler);
    return newThrottler;
  }
  async retrieveSessionInfo(shardId) {
    return this.manager.options.retrieveSessionInfo(shardId);
  }
  updateSessionInfo(shardId, sessionInfo) {
    return this.manager.options.updateSessionInfo(shardId, sessionInfo);
  }
  async waitForIdentify(shardId, signal) {
    const throttler = await _SimpleContextFetchingStrategy.ensureThrottler(this.manager);
    await throttler.waitForIdentify(shardId, signal);
  }
};
var SimpleContextFetchingStrategy = _SimpleContextFetchingStrategy;
__name(SimpleContextFetchingStrategy, "SimpleContextFetchingStrategy");
// This strategy assumes every shard is running under the same process - therefore we need a single
// IdentifyThrottler per manager.
__publicField(SimpleContextFetchingStrategy, "throttlerCache", /* @__PURE__ */ new WeakMap());

// src/strategies/context/WorkerContextFetchingStrategy.ts
import { isMainThread, parentPort } from "node:worker_threads";
import { Collection as Collection2 } from "@discordjs/collection";

// src/strategies/sharding/WorkerShardingStrategy.ts
import { once } from "node:events";
import { join, isAbsolute, resolve } from "node:path";
import { Worker } from "node:worker_threads";
import { Collection } from "@discordjs/collection";
var WorkerSendPayloadOp = /* @__PURE__ */ ((WorkerSendPayloadOp2) => {
  WorkerSendPayloadOp2[WorkerSendPayloadOp2["Connect"] = 0] = "Connect";
  WorkerSendPayloadOp2[WorkerSendPayloadOp2["Destroy"] = 1] = "Destroy";
  WorkerSendPayloadOp2[WorkerSendPayloadOp2["Send"] = 2] = "Send";
  WorkerSendPayloadOp2[WorkerSendPayloadOp2["SessionInfoResponse"] = 3] = "SessionInfoResponse";
  WorkerSendPayloadOp2[WorkerSendPayloadOp2["ShardIdentifyResponse"] = 4] = "ShardIdentifyResponse";
  WorkerSendPayloadOp2[WorkerSendPayloadOp2["FetchStatus"] = 5] = "FetchStatus";
  return WorkerSendPayloadOp2;
})(WorkerSendPayloadOp || {});
var WorkerReceivePayloadOp = /* @__PURE__ */ ((WorkerReceivePayloadOp2) => {
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["Connected"] = 0] = "Connected";
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["Destroyed"] = 1] = "Destroyed";
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["Event"] = 2] = "Event";
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["RetrieveSessionInfo"] = 3] = "RetrieveSessionInfo";
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["UpdateSessionInfo"] = 4] = "UpdateSessionInfo";
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["WaitForIdentify"] = 5] = "WaitForIdentify";
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["FetchStatusResponse"] = 6] = "FetchStatusResponse";
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["WorkerReady"] = 7] = "WorkerReady";
  WorkerReceivePayloadOp2[WorkerReceivePayloadOp2["CancelIdentify"] = 8] = "CancelIdentify";
  return WorkerReceivePayloadOp2;
})(WorkerReceivePayloadOp || {});
var WorkerShardingStrategy = class {
  manager;
  options;
  #workers = [];
  #workerByShardId = new Collection();
  connectPromises = new Collection();
  destroyPromises = new Collection();
  fetchStatusPromises = new Collection();
  waitForIdentifyControllers = new Collection();
  throttler;
  constructor(manager, options) {
    this.manager = manager;
    this.options = options;
  }
  /**
   * {@inheritDoc IShardingStrategy.spawn}
   */
  async spawn(shardIds) {
    const shardsPerWorker = this.options.shardsPerWorker === "all" ? shardIds.length : this.options.shardsPerWorker;
    const strategyOptions = await managerToFetchingStrategyOptions(this.manager);
    const loops = Math.ceil(shardIds.length / shardsPerWorker);
    const promises = [];
    for (let idx = 0; idx < loops; idx++) {
      const slice = shardIds.slice(idx * shardsPerWorker, (idx + 1) * shardsPerWorker);
      const workerData2 = {
        ...strategyOptions,
        shardIds: slice
      };
      promises.push(this.setupWorker(workerData2));
    }
    await Promise.all(promises);
  }
  /**
   * {@inheritDoc IShardingStrategy.connect}
   */
  async connect() {
    const promises = [];
    for (const [shardId, worker] of this.#workerByShardId.entries()) {
      const payload = {
        op: 0 /* Connect */,
        shardId
      };
      const promise = new Promise((resolve2) => this.connectPromises.set(shardId, resolve2));
      worker.postMessage(payload);
      promises.push(promise);
    }
    await Promise.all(promises);
  }
  /**
   * {@inheritDoc IShardingStrategy.destroy}
   */
  async destroy(options = {}) {
    const promises = [];
    for (const [shardId, worker] of this.#workerByShardId.entries()) {
      const payload = {
        op: 1 /* Destroy */,
        shardId,
        options
      };
      promises.push(
        // eslint-disable-next-line no-promise-executor-return, promise/prefer-await-to-then
        new Promise((resolve2) => this.destroyPromises.set(shardId, resolve2)).then(async () => worker.terminate())
      );
      worker.postMessage(payload);
    }
    this.#workers = [];
    this.#workerByShardId.clear();
    await Promise.all(promises);
  }
  /**
   * {@inheritDoc IShardingStrategy.send}
   */
  send(shardId, data) {
    const worker = this.#workerByShardId.get(shardId);
    if (!worker) {
      throw new Error(`No worker found for shard ${shardId}`);
    }
    const payload = {
      op: 2 /* Send */,
      shardId,
      payload: data
    };
    worker.postMessage(payload);
  }
  /**
   * {@inheritDoc IShardingStrategy.fetchStatus}
   */
  async fetchStatus() {
    const statuses = new Collection();
    for (const [shardId, worker] of this.#workerByShardId.entries()) {
      const nonce = Math.random();
      const payload = {
        op: 5 /* FetchStatus */,
        shardId,
        nonce
      };
      const promise = new Promise((resolve2) => this.fetchStatusPromises.set(nonce, resolve2));
      worker.postMessage(payload);
      const status = await promise;
      statuses.set(shardId, status);
    }
    return statuses;
  }
  async setupWorker(workerData2) {
    const worker = new Worker(this.resolveWorkerPath(), { workerData: workerData2 });
    await once(worker, "online");
    await this.waitForWorkerReady(worker);
    worker.on("error", (err) => {
      throw err;
    }).on("messageerror", (err) => {
      throw err;
    }).on("message", async (payload) => this.onMessage(worker, payload));
    this.#workers.push(worker);
    for (const shardId of workerData2.shardIds) {
      this.#workerByShardId.set(shardId, worker);
    }
  }
  resolveWorkerPath() {
    const path2 = this.options.workerPath;
    if (!path2) {
      return join(__dirname, "defaultWorker.js");
    }
    if (isAbsolute(path2)) {
      return path2;
    }
    if (/^\.\.?[/\\]/.test(path2)) {
      return resolve(path2);
    }
    try {
      return __require.resolve(path2);
    } catch {
      return resolve(path2);
    }
  }
  async waitForWorkerReady(worker) {
    return new Promise((resolve2) => {
      const handler = /* @__PURE__ */ __name((payload) => {
        if (payload.op === 7 /* WorkerReady */) {
          resolve2();
          worker.off("message", handler);
        }
      }, "handler");
      worker.on("message", handler);
    });
  }
  async onMessage(worker, payload) {
    switch (payload.op) {
      case 0 /* Connected */: {
        this.connectPromises.get(payload.shardId)?.();
        this.connectPromises.delete(payload.shardId);
        break;
      }
      case 1 /* Destroyed */: {
        this.destroyPromises.get(payload.shardId)?.();
        this.destroyPromises.delete(payload.shardId);
        break;
      }
      case 2 /* Event */: {
        this.manager.emit(payload.event, { ...payload.data, shardId: payload.shardId });
        break;
      }
      case 3 /* RetrieveSessionInfo */: {
        const session = await this.manager.options.retrieveSessionInfo(payload.shardId);
        const response = {
          op: 3 /* SessionInfoResponse */,
          nonce: payload.nonce,
          session
        };
        worker.postMessage(response);
        break;
      }
      case 4 /* UpdateSessionInfo */: {
        await this.manager.options.updateSessionInfo(payload.shardId, payload.session);
        break;
      }
      case 5 /* WaitForIdentify */: {
        const throttler = await this.ensureThrottler();
        try {
          const controller = new AbortController();
          this.waitForIdentifyControllers.set(payload.nonce, controller);
          await throttler.waitForIdentify(payload.shardId, controller.signal);
        } catch {
          return;
        }
        const response = {
          op: 4 /* ShardIdentifyResponse */,
          nonce: payload.nonce,
          ok: true
        };
        worker.postMessage(response);
        break;
      }
      case 6 /* FetchStatusResponse */: {
        this.fetchStatusPromises.get(payload.nonce)?.(payload.status);
        this.fetchStatusPromises.delete(payload.nonce);
        break;
      }
      case 7 /* WorkerReady */: {
        break;
      }
      case 8 /* CancelIdentify */: {
        this.waitForIdentifyControllers.get(payload.nonce)?.abort();
        this.waitForIdentifyControllers.delete(payload.nonce);
        const response = {
          op: 4 /* ShardIdentifyResponse */,
          nonce: payload.nonce,
          ok: false
        };
        worker.postMessage(response);
        break;
      }
    }
  }
  async ensureThrottler() {
    this.throttler ??= await this.manager.options.buildIdentifyThrottler(this.manager);
    return this.throttler;
  }
};
__name(WorkerShardingStrategy, "WorkerShardingStrategy");

// src/strategies/context/WorkerContextFetchingStrategy.ts
var WorkerContextFetchingStrategy = class {
  constructor(options) {
    this.options = options;
    if (isMainThread) {
      throw new Error("Cannot instantiate WorkerContextFetchingStrategy on the main thread");
    }
    parentPort.on("message", (payload) => {
      if (payload.op === 3 /* SessionInfoResponse */) {
        this.sessionPromises.get(payload.nonce)?.(payload.session);
        this.sessionPromises.delete(payload.nonce);
      }
      if (payload.op === 4 /* ShardIdentifyResponse */) {
        const promise = this.waitForIdentifyPromises.get(payload.nonce);
        if (payload.ok) {
          promise?.resolve();
        } else {
          promise?.reject();
        }
        this.waitForIdentifyPromises.delete(payload.nonce);
      }
    });
  }
  sessionPromises = new Collection2();
  waitForIdentifyPromises = new Collection2();
  async retrieveSessionInfo(shardId) {
    const nonce = Math.random();
    const payload = {
      op: 3 /* RetrieveSessionInfo */,
      shardId,
      nonce
    };
    const promise = new Promise((resolve2) => this.sessionPromises.set(nonce, resolve2));
    parentPort.postMessage(payload);
    return promise;
  }
  updateSessionInfo(shardId, sessionInfo) {
    const payload = {
      op: 4 /* UpdateSessionInfo */,
      shardId,
      session: sessionInfo
    };
    parentPort.postMessage(payload);
  }
  async waitForIdentify(shardId, signal) {
    const nonce = Math.random();
    const payload = {
      op: 5 /* WaitForIdentify */,
      nonce,
      shardId
    };
    const promise = new Promise(
      (resolve2, reject) => (
        // eslint-disable-next-line no-promise-executor-return
        this.waitForIdentifyPromises.set(nonce, { resolve: resolve2, reject })
      )
    );
    parentPort.postMessage(payload);
    const listener = /* @__PURE__ */ __name(() => {
      const payload2 = {
        op: 8 /* CancelIdentify */,
        nonce
      };
      parentPort.postMessage(payload2);
    }, "listener");
    signal.addEventListener("abort", listener);
    try {
      await promise;
    } finally {
      signal.removeEventListener("abort", listener);
    }
  }
};
__name(WorkerContextFetchingStrategy, "WorkerContextFetchingStrategy");

// src/strategies/sharding/SimpleShardingStrategy.ts
import { Collection as Collection6 } from "@discordjs/collection";

// src/ws/WebSocketShard.ts
import { Buffer as Buffer2 } from "node:buffer";
import { once as once2 } from "node:events";
import { clearInterval, clearTimeout, setInterval, setTimeout } from "node:timers";
import { setTimeout as sleep2 } from "node:timers/promises";
import { URLSearchParams } from "node:url";
import { TextDecoder } from "node:util";
import { inflate } from "node:zlib";
import { Collection as Collection5 } from "@discordjs/collection";
import { lazy as lazy2 } from "@discordjs/util";
import { AsyncQueue as AsyncQueue2 } from "@sapphire/async-queue";
import { AsyncEventEmitter } from "@vladfrangu/async_event_emitter";
import {
  GatewayCloseCodes,
  GatewayDispatchEvents,
  GatewayOpcodes as GatewayOpcodes2
} from "discord-api-types/v10";
import { WebSocket } from "ws";

// src/utils/constants.ts
import process from "node:process";
import { Collection as Collection4 } from "@discordjs/collection";
import { lazy } from "@discordjs/util";
import { APIVersion, GatewayOpcodes } from "discord-api-types/v10";

// src/throttling/SimpleIdentifyThrottler.ts
import { setTimeout as sleep } from "node:timers/promises";
import { Collection as Collection3 } from "@discordjs/collection";
import { AsyncQueue } from "@sapphire/async-queue";
var SimpleIdentifyThrottler = class {
  constructor(maxConcurrency) {
    this.maxConcurrency = maxConcurrency;
  }
  states = new Collection3();
  /**
   * {@inheritDoc IIdentifyThrottler.waitForIdentify}
   */
  async waitForIdentify(shardId, signal) {
    const key = shardId % this.maxConcurrency;
    const state = this.states.ensure(key, () => {
      return {
        queue: new AsyncQueue(),
        resetsAt: Number.POSITIVE_INFINITY
      };
    });
    await state.queue.wait({ signal });
    try {
      const diff = state.resetsAt - Date.now();
      if (diff <= 5e3) {
        const time = diff + Math.random() * 1500;
        await sleep(time);
      }
      state.resetsAt = Date.now() + 5e3;
    } finally {
      state.queue.shift();
    }
  }
};
__name(SimpleIdentifyThrottler, "SimpleIdentifyThrottler");

// src/utils/constants.ts
var Encoding = /* @__PURE__ */ ((Encoding2) => {
  Encoding2["JSON"] = "json";
  return Encoding2;
})(Encoding || {});
var CompressionMethod = /* @__PURE__ */ ((CompressionMethod2) => {
  CompressionMethod2["ZlibStream"] = "zlib-stream";
  return CompressionMethod2;
})(CompressionMethod || {});
var DefaultDeviceProperty = `@discordjs/ws 0.8.3`;
var getDefaultSessionStore = lazy(() => new Collection4());
var DefaultWebSocketManagerOptions = {
  async buildIdentifyThrottler(manager) {
    const info = await manager.fetchGatewayInformation();
    return new SimpleIdentifyThrottler(info.session_start_limit.max_concurrency);
  },
  buildStrategy: (manager) => new SimpleShardingStrategy(manager),
  shardCount: null,
  shardIds: null,
  largeThreshold: null,
  initialPresence: null,
  identifyProperties: {
    browser: DefaultDeviceProperty,
    device: DefaultDeviceProperty,
    os: process.platform
  },
  version: APIVersion,
  encoding: "json" /* JSON */,
  compression: null,
  retrieveSessionInfo(shardId) {
    const store = getDefaultSessionStore();
    return store.get(shardId) ?? null;
  },
  updateSessionInfo(shardId, info) {
    const store = getDefaultSessionStore();
    if (info) {
      store.set(shardId, info);
    } else {
      store.delete(shardId);
    }
  },
  handshakeTimeout: 3e4,
  helloTimeout: 6e4,
  readyTimeout: 15e3
};
var ImportantGatewayOpcodes = /* @__PURE__ */ new Set([
  GatewayOpcodes.Heartbeat,
  GatewayOpcodes.Identify,
  GatewayOpcodes.Resume
]);
function getInitialSendRateLimitState() {
  return {
    remaining: 120,
    resetAt: Date.now() + 6e4
  };
}
__name(getInitialSendRateLimitState, "getInitialSendRateLimitState");

// src/ws/WebSocketShard.ts
var getZlibSync = lazy2(async () => import("zlib-sync").then((mod) => mod.default).catch(() => null));
var WebSocketShardEvents = /* @__PURE__ */ ((WebSocketShardEvents2) => {
  WebSocketShardEvents2["Closed"] = "closed";
  WebSocketShardEvents2["Debug"] = "debug";
  WebSocketShardEvents2["Dispatch"] = "dispatch";
  WebSocketShardEvents2["Error"] = "error";
  WebSocketShardEvents2["HeartbeatComplete"] = "heartbeat";
  WebSocketShardEvents2["Hello"] = "hello";
  WebSocketShardEvents2["Ready"] = "ready";
  WebSocketShardEvents2["Resumed"] = "resumed";
  return WebSocketShardEvents2;
})(WebSocketShardEvents || {});
var WebSocketShardStatus = /* @__PURE__ */ ((WebSocketShardStatus2) => {
  WebSocketShardStatus2[WebSocketShardStatus2["Idle"] = 0] = "Idle";
  WebSocketShardStatus2[WebSocketShardStatus2["Connecting"] = 1] = "Connecting";
  WebSocketShardStatus2[WebSocketShardStatus2["Resuming"] = 2] = "Resuming";
  WebSocketShardStatus2[WebSocketShardStatus2["Ready"] = 3] = "Ready";
  return WebSocketShardStatus2;
})(WebSocketShardStatus || {});
var WebSocketShardDestroyRecovery = /* @__PURE__ */ ((WebSocketShardDestroyRecovery2) => {
  WebSocketShardDestroyRecovery2[WebSocketShardDestroyRecovery2["Reconnect"] = 0] = "Reconnect";
  WebSocketShardDestroyRecovery2[WebSocketShardDestroyRecovery2["Resume"] = 1] = "Resume";
  return WebSocketShardDestroyRecovery2;
})(WebSocketShardDestroyRecovery || {});
var CloseCodes = /* @__PURE__ */ ((CloseCodes2) => {
  CloseCodes2[CloseCodes2["Normal"] = 1e3] = "Normal";
  CloseCodes2[CloseCodes2["Resuming"] = 4200] = "Resuming";
  return CloseCodes2;
})(CloseCodes || {});
var WebSocketShard = class extends AsyncEventEmitter {
  connection = null;
  useIdentifyCompress = false;
  inflate = null;
  textDecoder = new TextDecoder();
  replayedEvents = 0;
  isAck = true;
  sendRateLimitState = getInitialSendRateLimitState();
  initialHeartbeatTimeoutController = null;
  heartbeatInterval = null;
  lastHeartbeatAt = -1;
  // Indicates whether the shard has already resolved its original connect() call
  initialConnectResolved = false;
  // Indicates if we failed to connect to the ws url (ECONNREFUSED/ECONNRESET)
  failedToConnectDueToNetworkError = false;
  sendQueue = new AsyncQueue2();
  timeoutAbortControllers = new Collection5();
  strategy;
  id;
  #status = 0 /* Idle */;
  get status() {
    return this.#status;
  }
  constructor(strategy, id) {
    super();
    this.strategy = strategy;
    this.id = id;
  }
  async connect() {
    const promise = this.initialConnectResolved ? Promise.resolve() : once2(this, "ready" /* Ready */);
    void this.internalConnect();
    await promise;
    this.initialConnectResolved = true;
  }
  async internalConnect() {
    if (this.#status !== 0 /* Idle */) {
      throw new Error("Tried to connect a shard that wasn't idle");
    }
    const { version: version2, encoding, compression } = this.strategy.options;
    const params = new URLSearchParams({ v: version2, encoding });
    if (compression) {
      const zlib = await getZlibSync();
      if (zlib) {
        params.append("compress", compression);
        this.inflate = new zlib.Inflate({
          chunkSize: 65535,
          to: "string"
        });
      } else if (!this.useIdentifyCompress) {
        this.useIdentifyCompress = true;
        console.warn(
          "WebSocketShard: Compression is enabled but zlib-sync is not installed, falling back to identify compress"
        );
      }
    }
    const session = await this.strategy.retrieveSessionInfo(this.id);
    const url = `${session?.resumeURL ?? this.strategy.options.gatewayInformation.url}?${params.toString()}`;
    this.debug([`Connecting to ${url}`]);
    const connection = new WebSocket(url, { handshakeTimeout: this.strategy.options.handshakeTimeout ?? void 0 }).on("message", this.onMessage.bind(this)).on("error", this.onError.bind(this)).on("close", this.onClose.bind(this));
    connection.binaryType = "arraybuffer";
    this.connection = connection;
    this.#status = 1 /* Connecting */;
    this.sendRateLimitState = getInitialSendRateLimitState();
    const { ok } = await this.waitForEvent("hello" /* Hello */, this.strategy.options.helloTimeout);
    if (!ok) {
      return;
    }
    if (session?.shardCount === this.strategy.options.shardCount) {
      await this.resume(session);
    } else {
      await this.identify();
    }
  }
  async destroy(options = {}) {
    if (this.#status === 0 /* Idle */) {
      this.debug(["Tried to destroy a shard that was idle"]);
      return;
    }
    if (!options.code) {
      options.code = options.recover === 1 /* Resume */ ? 4200 /* Resuming */ : 1e3 /* Normal */;
    }
    this.debug([
      "Destroying shard",
      `Reason: ${options.reason ?? "none"}`,
      `Code: ${options.code}`,
      `Recover: ${options.recover === void 0 ? "none" : WebSocketShardDestroyRecovery[options.recover]}`
    ]);
    this.isAck = true;
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }
    if (this.initialHeartbeatTimeoutController) {
      this.initialHeartbeatTimeoutController.abort();
      this.initialHeartbeatTimeoutController = null;
    }
    this.lastHeartbeatAt = -1;
    for (const controller of this.timeoutAbortControllers.values()) {
      controller.abort();
    }
    this.timeoutAbortControllers.clear();
    this.failedToConnectDueToNetworkError = false;
    if (options.recover !== 1 /* Resume */) {
      await this.strategy.updateSessionInfo(this.id, null);
    }
    if (this.connection) {
      this.connection.removeAllListeners("message");
      this.connection.removeAllListeners("close");
      const shouldClose = this.connection.readyState === WebSocket.OPEN;
      this.debug([
        "Connection status during destroy",
        `Needs closing: ${shouldClose}`,
        `Ready state: ${this.connection.readyState}`
      ]);
      if (shouldClose) {
        this.connection.close(options.code, options.reason);
        await once2(this.connection, "close");
        this.emit("closed" /* Closed */, { code: options.code });
      }
      this.connection.removeAllListeners("error");
    } else {
      this.debug(["Destroying a shard that has no connection; please open an issue on GitHub"]);
    }
    this.#status = 0 /* Idle */;
    if (options.recover !== void 0) {
      await sleep2(500);
      return this.internalConnect();
    }
  }
  async waitForEvent(event, timeoutDuration) {
    this.debug([`Waiting for event ${event} ${timeoutDuration ? `for ${timeoutDuration}ms` : "indefinitely"}`]);
    const timeoutController = new AbortController();
    const timeout = timeoutDuration ? setTimeout(() => timeoutController.abort(), timeoutDuration).unref() : null;
    this.timeoutAbortControllers.set(event, timeoutController);
    const closeController = new AbortController();
    try {
      const closed = await Promise.race([
        once2(this, event, { signal: timeoutController.signal }).then(() => false),
        once2(this, "closed" /* Closed */, { signal: closeController.signal }).then(() => true)
      ]);
      return { ok: !closed };
    } catch {
      void this.destroy({
        code: 1e3 /* Normal */,
        reason: "Something timed out or went wrong while waiting for an event",
        recover: 0 /* Reconnect */
      });
      return { ok: false };
    } finally {
      if (timeout) {
        clearTimeout(timeout);
      }
      this.timeoutAbortControllers.delete(event);
      if (!closeController.signal.aborted) {
        closeController.abort();
      }
    }
  }
  async send(payload) {
    if (!this.connection) {
      throw new Error("WebSocketShard wasn't connected");
    }
    if (this.#status !== 3 /* Ready */ && !ImportantGatewayOpcodes.has(payload.op)) {
      this.debug(["Tried to send a non-crucial payload before the shard was ready, waiting"]);
      try {
        await once2(this, "ready" /* Ready */);
      } catch {
        return this.send(payload);
      }
    }
    await this.sendQueue.wait();
    if (--this.sendRateLimitState.remaining <= 0) {
      const now = Date.now();
      if (this.sendRateLimitState.resetAt > now) {
        const sleepFor = this.sendRateLimitState.resetAt - now;
        this.debug([`Was about to hit the send rate limit, sleeping for ${sleepFor}ms`]);
        const controller = new AbortController();
        const interrupted = await Promise.race([
          sleep2(sleepFor).then(() => false),
          once2(this, "closed" /* Closed */, { signal: controller.signal }).then(() => true)
        ]);
        if (interrupted) {
          this.debug(["Connection closed while waiting for the send rate limit to reset, re-queueing payload"]);
          this.sendQueue.shift();
          return this.send(payload);
        }
        controller.abort();
      }
      this.sendRateLimitState = getInitialSendRateLimitState();
    }
    this.sendQueue.shift();
    this.connection.send(JSON.stringify(payload));
  }
  async identify() {
    this.debug(["Waiting for identify throttle"]);
    const controller = new AbortController();
    const closeHandler = /* @__PURE__ */ __name(() => {
      controller.abort();
    }, "closeHandler");
    this.on("closed" /* Closed */, closeHandler);
    try {
      await this.strategy.waitForIdentify(this.id, controller.signal);
    } catch {
      this.debug(["Was waiting for an identify, but the shard closed in the meantime"]);
      return;
    } finally {
      this.off("closed" /* Closed */, closeHandler);
    }
    this.debug([
      "Identifying",
      `shard id: ${this.id.toString()}`,
      `shard count: ${this.strategy.options.shardCount}`,
      `intents: ${this.strategy.options.intents}`,
      `compression: ${this.inflate ? "zlib-stream" : this.useIdentifyCompress ? "identify" : "none"}`
    ]);
    const d = {
      token: this.strategy.options.token,
      properties: this.strategy.options.identifyProperties,
      intents: this.strategy.options.intents,
      compress: this.useIdentifyCompress,
      shard: [this.id, this.strategy.options.shardCount]
    };
    if (this.strategy.options.largeThreshold) {
      d.large_threshold = this.strategy.options.largeThreshold;
    }
    if (this.strategy.options.initialPresence) {
      d.presence = this.strategy.options.initialPresence;
    }
    await this.send({
      op: GatewayOpcodes2.Identify,
      d
    });
    await this.waitForEvent("ready" /* Ready */, this.strategy.options.readyTimeout);
  }
  async resume(session) {
    this.debug([
      "Resuming session",
      `resume url: ${session.resumeURL}`,
      `sequence: ${session.sequence}`,
      `shard id: ${this.id.toString()}`
    ]);
    this.#status = 2 /* Resuming */;
    this.replayedEvents = 0;
    return this.send({
      op: GatewayOpcodes2.Resume,
      d: {
        token: this.strategy.options.token,
        seq: session.sequence,
        session_id: session.sessionId
      }
    });
  }
  async heartbeat(requested = false) {
    if (!this.isAck && !requested) {
      return this.destroy({ reason: "Zombie connection", recover: 1 /* Resume */ });
    }
    const session = await this.strategy.retrieveSessionInfo(this.id);
    await this.send({
      op: GatewayOpcodes2.Heartbeat,
      d: session?.sequence ?? null
    });
    this.lastHeartbeatAt = Date.now();
    this.isAck = false;
  }
  async unpackMessage(data, isBinary) {
    const decompressable = new Uint8Array(data);
    if (!isBinary) {
      return JSON.parse(this.textDecoder.decode(decompressable));
    }
    if (this.useIdentifyCompress) {
      return new Promise((resolve2, reject) => {
        inflate(decompressable, { chunkSize: 65535 }, (err, result) => {
          if (err) {
            reject(err);
            return;
          }
          resolve2(JSON.parse(this.textDecoder.decode(result)));
        });
      });
    }
    if (this.inflate) {
      const l = decompressable.length;
      const flush = l >= 4 && decompressable[l - 4] === 0 && decompressable[l - 3] === 0 && decompressable[l - 2] === 255 && decompressable[l - 1] === 255;
      const zlib = await getZlibSync();
      this.inflate.push(Buffer2.from(decompressable), flush ? zlib.Z_SYNC_FLUSH : zlib.Z_NO_FLUSH);
      if (this.inflate.err) {
        this.emit("error" /* Error */, {
          error: new Error(`${this.inflate.err}${this.inflate.msg ? `: ${this.inflate.msg}` : ""}`)
        });
      }
      if (!flush) {
        return null;
      }
      const { result } = this.inflate;
      if (!result) {
        return null;
      }
      return JSON.parse(typeof result === "string" ? result : this.textDecoder.decode(result));
    }
    this.debug([
      "Received a message we were unable to decompress",
      `isBinary: ${isBinary.toString()}`,
      `useIdentifyCompress: ${this.useIdentifyCompress.toString()}`,
      `inflate: ${Boolean(this.inflate).toString()}`
    ]);
    return null;
  }
  async onMessage(data, isBinary) {
    const payload = await this.unpackMessage(data, isBinary);
    if (!payload) {
      return;
    }
    switch (payload.op) {
      case GatewayOpcodes2.Dispatch: {
        if (this.#status === 2 /* Resuming */) {
          this.replayedEvents++;
        }
        switch (payload.t) {
          case GatewayDispatchEvents.Ready: {
            this.#status = 3 /* Ready */;
            const session2 = {
              sequence: payload.s,
              sessionId: payload.d.session_id,
              shardId: this.id,
              shardCount: this.strategy.options.shardCount,
              resumeURL: payload.d.resume_gateway_url
            };
            await this.strategy.updateSessionInfo(this.id, session2);
            this.emit("ready" /* Ready */, { data: payload.d });
            break;
          }
          case GatewayDispatchEvents.Resumed: {
            this.#status = 3 /* Ready */;
            this.debug([`Resumed and replayed ${this.replayedEvents} events`]);
            this.emit("resumed" /* Resumed */);
            break;
          }
          default: {
            break;
          }
        }
        const session = await this.strategy.retrieveSessionInfo(this.id);
        if (session) {
          if (payload.s > session.sequence) {
            await this.strategy.updateSessionInfo(this.id, { ...session, sequence: payload.s });
          }
        } else {
          this.debug([
            `Received a ${payload.t} event but no session is available. Session information cannot be re-constructed in this state without a full reconnect`
          ]);
        }
        this.emit("dispatch" /* Dispatch */, { data: payload });
        break;
      }
      case GatewayOpcodes2.Heartbeat: {
        await this.heartbeat(true);
        break;
      }
      case GatewayOpcodes2.Reconnect: {
        await this.destroy({
          reason: "Told to reconnect by Discord",
          recover: 1 /* Resume */
        });
        break;
      }
      case GatewayOpcodes2.InvalidSession: {
        this.debug([`Invalid session; will attempt to resume: ${payload.d.toString()}`]);
        const session = await this.strategy.retrieveSessionInfo(this.id);
        if (payload.d && session) {
          await this.resume(session);
        } else {
          await this.destroy({
            reason: "Invalid session",
            recover: 0 /* Reconnect */
          });
        }
        break;
      }
      case GatewayOpcodes2.Hello: {
        this.emit("hello" /* Hello */);
        const jitter = Math.random();
        const firstWait = Math.floor(payload.d.heartbeat_interval * jitter);
        this.debug([`Preparing first heartbeat of the connection with a jitter of ${jitter}; waiting ${firstWait}ms`]);
        try {
          const controller = new AbortController();
          this.initialHeartbeatTimeoutController = controller;
          await sleep2(firstWait, void 0, { signal: controller.signal });
        } catch {
          this.debug(["Cancelled initial heartbeat due to #destroy being called"]);
          return;
        } finally {
          this.initialHeartbeatTimeoutController = null;
        }
        await this.heartbeat();
        this.debug([`First heartbeat sent, starting to beat every ${payload.d.heartbeat_interval}ms`]);
        this.heartbeatInterval = setInterval(() => void this.heartbeat(), payload.d.heartbeat_interval);
        break;
      }
      case GatewayOpcodes2.HeartbeatAck: {
        this.isAck = true;
        const ackAt = Date.now();
        this.emit("heartbeat" /* HeartbeatComplete */, {
          ackAt,
          heartbeatAt: this.lastHeartbeatAt,
          latency: ackAt - this.lastHeartbeatAt
        });
        break;
      }
    }
  }
  onError(error) {
    if ("code" in error && ["ECONNRESET", "ECONNREFUSED"].includes(error.code)) {
      this.debug(["Failed to connect to the gateway URL specified due to a network error"]);
      this.failedToConnectDueToNetworkError = true;
      return;
    }
    this.emit("error" /* Error */, { error });
  }
  async onClose(code) {
    this.emit("closed" /* Closed */, { code });
    switch (code) {
      case 1e3 /* Normal */: {
        return this.destroy({
          code,
          reason: "Got disconnected by Discord",
          recover: 0 /* Reconnect */
        });
      }
      case 4200 /* Resuming */: {
        break;
      }
      case GatewayCloseCodes.UnknownError: {
        this.debug([`An unknown error occurred: ${code}`]);
        return this.destroy({ code, recover: 1 /* Resume */ });
      }
      case GatewayCloseCodes.UnknownOpcode: {
        this.debug(["An invalid opcode was sent to Discord."]);
        return this.destroy({ code, recover: 1 /* Resume */ });
      }
      case GatewayCloseCodes.DecodeError: {
        this.debug(["An invalid payload was sent to Discord."]);
        return this.destroy({ code, recover: 1 /* Resume */ });
      }
      case GatewayCloseCodes.NotAuthenticated: {
        this.debug(["A request was somehow sent before the identify/resume payload."]);
        return this.destroy({ code, recover: 0 /* Reconnect */ });
      }
      case GatewayCloseCodes.AuthenticationFailed: {
        throw new Error("Authentication failed");
      }
      case GatewayCloseCodes.AlreadyAuthenticated: {
        this.debug(["More than one auth payload was sent."]);
        return this.destroy({ code, recover: 0 /* Reconnect */ });
      }
      case GatewayCloseCodes.InvalidSeq: {
        this.debug(["An invalid sequence was sent."]);
        return this.destroy({ code, recover: 0 /* Reconnect */ });
      }
      case GatewayCloseCodes.RateLimited: {
        this.debug(["The WebSocket rate limit has been hit, this should never happen"]);
        return this.destroy({ code, recover: 0 /* Reconnect */ });
      }
      case GatewayCloseCodes.SessionTimedOut: {
        this.debug(["Session timed out."]);
        return this.destroy({ code, recover: 1 /* Resume */ });
      }
      case GatewayCloseCodes.InvalidShard: {
        throw new Error("Invalid shard");
      }
      case GatewayCloseCodes.ShardingRequired: {
        throw new Error("Sharding is required");
      }
      case GatewayCloseCodes.InvalidAPIVersion: {
        throw new Error("Used an invalid API version");
      }
      case GatewayCloseCodes.InvalidIntents: {
        throw new Error("Used invalid intents");
      }
      case GatewayCloseCodes.DisallowedIntents: {
        throw new Error("Used disallowed intents");
      }
      default: {
        this.debug([
          `The gateway closed with an unexpected code ${code}, attempting to ${this.failedToConnectDueToNetworkError ? "reconnect" : "resume"}.`
        ]);
        return this.destroy({
          code,
          recover: this.failedToConnectDueToNetworkError ? 0 /* Reconnect */ : 1 /* Resume */
        });
      }
    }
  }
  debug(messages) {
    const message = `${messages[0]}${messages.length > 1 ? `
${messages.slice(1).map((m) => `	${m}`).join("\n")}` : ""}`;
    this.emit("debug" /* Debug */, { message });
  }
};
__name(WebSocketShard, "WebSocketShard");

// src/strategies/sharding/SimpleShardingStrategy.ts
var SimpleShardingStrategy = class {
  manager;
  shards = new Collection6();
  constructor(manager) {
    this.manager = manager;
  }
  /**
   * {@inheritDoc IShardingStrategy.spawn}
   */
  async spawn(shardIds) {
    const strategyOptions = await managerToFetchingStrategyOptions(this.manager);
    for (const shardId of shardIds) {
      const strategy = new SimpleContextFetchingStrategy(this.manager, strategyOptions);
      const shard = new WebSocketShard(strategy, shardId);
      for (const event of Object.values(WebSocketShardEvents)) {
        shard.on(event, (payload) => this.manager.emit(event, { ...payload, shardId }));
      }
      this.shards.set(shardId, shard);
    }
  }
  /**
   * {@inheritDoc IShardingStrategy.connect}
   */
  async connect() {
    const promises = [];
    for (const shard of this.shards.values()) {
      promises.push(shard.connect());
    }
    await Promise.all(promises);
  }
  /**
   * {@inheritDoc IShardingStrategy.destroy}
   */
  async destroy(options) {
    const promises = [];
    for (const shard of this.shards.values()) {
      promises.push(shard.destroy(options));
    }
    await Promise.all(promises);
    this.shards.clear();
  }
  /**
   * {@inheritDoc IShardingStrategy.send}
   */
  async send(shardId, payload) {
    const shard = this.shards.get(shardId);
    if (!shard) {
      throw new RangeError(`Shard ${shardId} not found`);
    }
    return shard.send(payload);
  }
  /**
   * {@inheritDoc IShardingStrategy.fetchStatus}
   */
  async fetchStatus() {
    return this.shards.mapValues((shard) => shard.status);
  }
};
__name(SimpleShardingStrategy, "SimpleShardingStrategy");

// src/utils/WorkerBootstrapper.ts
import { isMainThread as isMainThread2, parentPort as parentPort2, workerData } from "node:worker_threads";
import { Collection as Collection7 } from "@discordjs/collection";
var WorkerBootstrapper = class {
  /**
   * The data passed to the worker thread
   */
  data = workerData;
  /**
   * The shards that are managed by this worker
   */
  shards = new Collection7();
  constructor() {
    if (isMainThread2) {
      throw new Error("Expected WorkerBootstrap to not be used within the main thread");
    }
  }
  /**
   * Helper method to initiate a shard's connection process
   */
  async connect(shardId) {
    const shard = this.shards.get(shardId);
    if (!shard) {
      throw new RangeError(`Shard ${shardId} does not exist`);
    }
    await shard.connect();
  }
  /**
   * Helper method to destroy a shard
   */
  async destroy(shardId, options) {
    const shard = this.shards.get(shardId);
    if (!shard) {
      throw new RangeError(`Shard ${shardId} does not exist`);
    }
    await shard.destroy(options);
  }
  /**
   * Helper method to attach event listeners to the parentPort
   */
  setupThreadEvents() {
    parentPort2.on("messageerror", (err) => {
      throw err;
    }).on("message", async (payload) => {
      switch (payload.op) {
        case 0 /* Connect */: {
          await this.connect(payload.shardId);
          const response = {
            op: 0 /* Connected */,
            shardId: payload.shardId
          };
          parentPort2.postMessage(response);
          break;
        }
        case 1 /* Destroy */: {
          await this.destroy(payload.shardId, payload.options);
          const response = {
            op: 1 /* Destroyed */,
            shardId: payload.shardId
          };
          parentPort2.postMessage(response);
          break;
        }
        case 2 /* Send */: {
          const shard = this.shards.get(payload.shardId);
          if (!shard) {
            throw new RangeError(`Shard ${payload.shardId} does not exist`);
          }
          await shard.send(payload.payload);
          break;
        }
        case 3 /* SessionInfoResponse */: {
          break;
        }
        case 4 /* ShardIdentifyResponse */: {
          break;
        }
        case 5 /* FetchStatus */: {
          const shard = this.shards.get(payload.shardId);
          if (!shard) {
            throw new Error(`Shard ${payload.shardId} does not exist`);
          }
          const response = {
            op: 6 /* FetchStatusResponse */,
            status: shard.status,
            nonce: payload.nonce
          };
          parentPort2.postMessage(response);
          break;
        }
      }
    });
  }
  /**
   * Bootstraps the worker thread with the provided options
   */
  async bootstrap(options = {}) {
    for (const shardId of this.data.shardIds) {
      const shard = new WebSocketShard(new WorkerContextFetchingStrategy(this.data), shardId);
      for (const event of options.forwardEvents ?? Object.values(WebSocketShardEvents)) {
        shard.on(event, (data) => {
          const payload = {
            op: 2 /* Event */,
            event,
            data,
            shardId
          };
          parentPort2.postMessage(payload);
        });
      }
      await options.shardCallback?.(shard);
      this.shards.set(shardId, shard);
    }
    this.setupThreadEvents();
    const message = {
      op: 7 /* WorkerReady */
    };
    parentPort2.postMessage(message);
  }
};
__name(WorkerBootstrapper, "WorkerBootstrapper");

// src/ws/WebSocketManager.ts
import { range } from "@discordjs/util";
import { AsyncEventEmitter as AsyncEventEmitter2 } from "@vladfrangu/async_event_emitter";
import {
  Routes
} from "discord-api-types/v10";
var WebSocketManager = class extends AsyncEventEmitter2 {
  /**
   * The options being used by this manager
   */
  options;
  /**
   * Internal cache for a GET /gateway/bot result
   */
  gatewayInformation = null;
  /**
   * Internal cache for the shard ids
   */
  shardIds = null;
  /**
   * Strategy used to manage shards
   *
   * @defaultValue `SimpleShardingStrategy`
   */
  strategy;
  constructor(options) {
    super();
    this.options = { ...DefaultWebSocketManagerOptions, ...options };
    this.strategy = this.options.buildStrategy(this);
  }
  /**
   * Fetches the gateway information from Discord - or returns it from cache if available
   *
   * @param force - Whether to ignore the cache and force a fresh fetch
   */
  async fetchGatewayInformation(force = false) {
    if (this.gatewayInformation) {
      if (this.gatewayInformation.expiresAt <= Date.now()) {
        this.gatewayInformation = null;
      } else if (!force) {
        return this.gatewayInformation.data;
      }
    }
    const data = await this.options.rest.get(Routes.gatewayBot());
    this.gatewayInformation = { data, expiresAt: Date.now() + data.session_start_limit.reset_after };
    return this.gatewayInformation.data;
  }
  /**
   * Updates your total shard count on-the-fly, spawning shards as needed
   *
   * @param shardCount - The new shard count to use
   */
  async updateShardCount(shardCount) {
    await this.strategy.destroy({ reason: "User is adjusting their shards" });
    this.options.shardCount = shardCount;
    const shardIds = await this.getShardIds(true);
    await this.strategy.spawn(shardIds);
    return this;
  }
  /**
   * Yields the total number of shards across for your bot, accounting for Discord recommendations
   */
  async getShardCount() {
    if (this.options.shardCount) {
      return this.options.shardCount;
    }
    const shardIds = await this.getShardIds();
    return Math.max(...shardIds) + 1;
  }
  /**
   * Yields the ids of the shards this manager should manage
   */
  async getShardIds(force = false) {
    if (this.shardIds && !force) {
      return this.shardIds;
    }
    let shardIds;
    if (this.options.shardIds) {
      if (Array.isArray(this.options.shardIds)) {
        shardIds = this.options.shardIds;
      } else {
        const { start, end } = this.options.shardIds;
        shardIds = [...range({ start, end: end + 1 })];
      }
    } else {
      const data = await this.fetchGatewayInformation();
      shardIds = [...range(this.options.shardCount ?? data.shards)];
    }
    this.shardIds = shardIds;
    return shardIds;
  }
  async connect() {
    const shardCount = await this.getShardCount();
    const data = await this.fetchGatewayInformation();
    if (data.session_start_limit.remaining < shardCount) {
      throw new Error(
        `Not enough sessions remaining to spawn ${shardCount} shards; only ${data.session_start_limit.remaining} remaining; resets at ${new Date(Date.now() + data.session_start_limit.reset_after).toISOString()}`
      );
    }
    await this.updateShardCount(shardCount);
    await this.strategy.connect();
  }
  destroy(options) {
    return this.strategy.destroy(options);
  }
  send(shardId, payload) {
    return this.strategy.send(shardId, payload);
  }
  fetchStatus() {
    return this.strategy.fetchStatus();
  }
};
__name(WebSocketManager, "WebSocketManager");

// src/index.ts
var version = "0.8.3";
export {
  CloseCodes,
  CompressionMethod,
  DefaultDeviceProperty,
  DefaultWebSocketManagerOptions,
  Encoding,
  ImportantGatewayOpcodes,
  SimpleContextFetchingStrategy,
  SimpleIdentifyThrottler,
  SimpleShardingStrategy,
  WebSocketManager,
  WebSocketShard,
  WebSocketShardDestroyRecovery,
  WebSocketShardEvents,
  WebSocketShardStatus,
  WorkerBootstrapper,
  WorkerContextFetchingStrategy,
  WorkerReceivePayloadOp,
  WorkerSendPayloadOp,
  WorkerShardingStrategy,
  getInitialSendRateLimitState,
  managerToFetchingStrategyOptions,
  version
};
//# sourceMappingURL=index.mjs.map