"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/index.ts
var index_exports = {};
__export(index_exports, {
  ChannelConnection: () => Connection,
  Stream: () => Stream,
  api: () => api_exports,
  entities: () => entities_exports,
  ffVisibility: () => ffVisibility2,
  noteVisibilities: () => noteVisibilities2,
  notificationTypes: () => notificationTypes2,
  permissions: () => permissions2
});
module.exports = __toCommonJS(index_exports);

// src/streaming.ts
var import_eventemitter3 = require("eventemitter3");
var import_reconnecting_websocket = __toESM(require("reconnecting-websocket"));
function autobind(instance) {
  const prototype = Object.getPrototypeOf(instance);
  const propertyNames = Object.getOwnPropertyNames(prototype);
  for (const key of propertyNames) {
    const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
    if (typeof descriptor?.value === "function" && key !== "constructor") {
      Object.defineProperty(instance, key, {
        value: instance[key].bind(instance),
        enumerable: descriptor.enumerable,
        configurable: descriptor.configurable,
        writable: descriptor.writable
      });
    }
  }
}
function urlQuery(obj) {
  const params = Object.entries(obj).filter(([, v]) => Array.isArray(v) ? v.length : v !== void 0).reduce(
    (a, [k, v]) => (a[k] = v, a),
    {}
  );
  return Object.entries(params).map((e) => `${e[0]}=${encodeURIComponent(e[1])}`).join("&");
}
var Stream = class extends import_eventemitter3.EventEmitter {
  constructor(origin, user, options) {
    super();
    this.state = "initializing";
    this.sharedConnectionPools = [];
    this.sharedConnections = [];
    this.nonSharedConnections = [];
    this.idCounter = 0;
    autobind(this);
    options = options || {};
    const query = urlQuery({
      i: user?.token,
      // To prevent cache of an HTML such as error screen
      _t: Date.now()
    });
    const wsOrigin = origin.replace("http://", "ws://").replace("https://", "wss://");
    this.stream = new import_reconnecting_websocket.default(
      `${wsOrigin}/streaming?${query}`,
      "",
      {
        minReconnectionDelay: 1,
        // https://github.com/pladaria/reconnecting-websocket/issues/91
        WebSocket: options.WebSocket
      }
    );
    this.stream.addEventListener("open", this.onOpen);
    this.stream.addEventListener("close", this.onClose);
    this.stream.addEventListener("message", this.onMessage);
  }
  genId() {
    return (++this.idCounter).toString();
  }
  useChannel(channel, params, name) {
    if (params) {
      return this.connectToChannel(channel, params);
    } else {
      return this.useSharedConnection(channel, name);
    }
  }
  useSharedConnection(channel, name) {
    let pool = this.sharedConnectionPools.find((p) => p.channel === channel);
    if (pool == null) {
      pool = new Pool(this, channel, this.genId());
      this.sharedConnectionPools.push(pool);
    }
    const connection = new SharedConnection(this, channel, pool, name);
    this.sharedConnections.push(connection);
    return connection;
  }
  removeSharedConnection(connection) {
    this.sharedConnections = this.sharedConnections.filter(
      (c) => c !== connection
    );
  }
  removeSharedConnectionPool(pool) {
    this.sharedConnectionPools = this.sharedConnectionPools.filter(
      (p) => p !== pool
    );
  }
  connectToChannel(channel, params) {
    const connection = new NonSharedConnection(
      this,
      channel,
      this.genId(),
      params
    );
    this.nonSharedConnections.push(connection);
    return connection;
  }
  disconnectToChannel(connection) {
    this.nonSharedConnections = this.nonSharedConnections.filter(
      (c) => c !== connection
    );
  }
  /**
   * Callback of when open connection
   */
  onOpen() {
    const isReconnect = this.state === "reconnecting";
    this.state = "connected";
    this.emit("_connected_");
    if (isReconnect) {
      for (const p of this.sharedConnectionPools) p.connect();
      for (const c of this.nonSharedConnections) c.connect();
    }
  }
  /**
   * Callback of when close connection
   */
  onClose() {
    if (this.state === "connected") {
      this.state = "reconnecting";
      this.emit("_disconnected_");
    }
  }
  /**
   * Callback of when received a message from connection
   */
  onMessage(message) {
    const { type, body } = JSON.parse(message.data);
    if (type === "channel") {
      const id = body.id;
      let connections;
      connections = this.sharedConnections.filter((c) => c.id === id);
      if (connections.length === 0) {
        const found = this.nonSharedConnections.find((c) => c.id === id);
        if (found) {
          connections = [found];
        }
      }
      for (const c of connections) {
        c.emit(body.type, body.body);
        c.inCount++;
      }
    } else {
      this.emit(type, body);
    }
  }
  /**
   * Send a message to connection
   */
  send(typeOrPayload, payload) {
    const data = payload === void 0 ? typeOrPayload : {
      type: typeOrPayload,
      body: payload
    };
    this.stream.send(JSON.stringify(data));
  }
  /**
   * Close this connection
   */
  close() {
    this.stream.close();
  }
};
var Pool = class {
  constructor(stream, channel, id) {
    this.users = 0;
    this.isConnected = false;
    this.channel = channel;
    this.stream = stream;
    this.id = id;
    this.stream.on("_disconnected_", this.onStreamDisconnected);
  }
  onStreamDisconnected() {
    this.isConnected = false;
  }
  inc() {
    if (this.users === 0 && !this.isConnected) {
      this.connect();
    }
    this.users++;
    if (this.disposeTimerId) {
      clearTimeout(this.disposeTimerId);
      this.disposeTimerId = null;
    }
  }
  dec() {
    this.users--;
    if (this.users === 0) {
      this.disposeTimerId = setTimeout(() => {
        this.disconnect();
      }, 3e3);
    }
  }
  connect() {
    if (this.isConnected) return;
    this.isConnected = true;
    this.stream.send("connect", {
      channel: this.channel,
      id: this.id
    });
  }
  disconnect() {
    this.stream.off("_disconnected_", this.onStreamDisconnected);
    this.stream.send("disconnect", { id: this.id });
    this.stream.removeSharedConnectionPool(this);
  }
};
var Connection = class extends import_eventemitter3.EventEmitter {
  // for debug
  constructor(stream, channel, name) {
    super();
    // for debug
    this.inCount = 0;
    // for debug
    this.outCount = 0;
    this.stream = stream;
    this.channel = channel;
    this.name = name;
  }
  send(type, body) {
    this.stream.send("ch", {
      id: this.id,
      type,
      body
    });
    this.outCount++;
  }
};
var SharedConnection = class extends Connection {
  get id() {
    return this.pool.id;
  }
  constructor(stream, channel, pool, name) {
    super(stream, channel, name);
    this.pool = pool;
    this.pool.inc();
  }
  dispose() {
    this.pool.dec();
    this.removeAllListeners();
    this.stream.removeSharedConnection(this);
  }
};
var NonSharedConnection = class extends Connection {
  constructor(stream, channel, id, params) {
    super(stream, channel);
    this.params = params;
    this.id = id;
    this.connect();
  }
  connect() {
    this.stream.send("connect", {
      channel: this.channel,
      id: this.id,
      params: this.params
    });
  }
  dispose() {
    this.removeAllListeners();
    this.stream.send("disconnect", { id: this.id });
    this.stream.disconnectToChannel(this);
  }
};

// src/consts.ts
var notificationTypes = [
  "follow",
  "mention",
  "reply",
  "renote",
  "quote",
  "reaction",
  "pollVote",
  "pollEnded",
  "receiveFollowRequest",
  "followRequestAccepted",
  "groupInvited",
  "app",
  "bite"
];
var noteVisibilities = [
  "public",
  "home",
  "followers",
  "specified"
];
var ffVisibility = ["public", "followers", "private"];
var permissions = [
  "read:account",
  "write:account",
  "read:blocks",
  "write:blocks",
  "read:drive",
  "write:drive",
  "read:favorites",
  "write:favorites",
  "read:following",
  "write:following",
  "read:messaging",
  "write:messaging",
  "read:mutes",
  "write:mutes",
  "write:notes",
  "read:notifications",
  "write:notifications",
  "read:reactions",
  "write:reactions",
  "write:votes",
  "read:pages",
  "write:pages",
  "write:page-likes",
  "read:page-likes",
  "read:user-groups",
  "write:user-groups",
  "read:channels",
  "write:channels",
  "read:gallery",
  "write:gallery",
  "read:gallery-likes",
  "write:gallery-likes"
];

// src/api.ts
var api_exports = {};
__export(api_exports, {
  APIClient: () => APIClient,
  isAPIError: () => isAPIError
});
var MK_API_ERROR = /* @__PURE__ */ Symbol();
function isAPIError(reason) {
  return reason[MK_API_ERROR] === true;
}
var APIClient = class {
  constructor(opts) {
    this.origin = opts.origin;
    this.credential = opts.credential;
    this.fetch = opts.fetch || ((...args) => fetch(...args));
  }
  request(endpoint, params = {}, credential) {
    const promise = new Promise((resolve, reject) => {
      this.fetch(`${this.origin}/api/${endpoint}`, {
        method: "POST",
        body: JSON.stringify({
          ...params,
          i: credential !== void 0 ? credential : this.credential
        }),
        credentials: "omit",
        cache: "no-cache"
      }).then(async (res) => {
        const body = res.status === 204 ? null : await res.json();
        if (res.status === 200) {
          resolve(body);
        } else if (res.status === 204) {
          resolve(null);
        } else {
          reject({
            [MK_API_ERROR]: true,
            ...body.error
          });
        }
      }).catch(reject);
    });
    return promise;
  }
};

// src/entities.ts
var entities_exports = {};

// src/index.ts
var permissions2 = permissions;
var notificationTypes2 = notificationTypes;
var noteVisibilities2 = noteVisibilities;
var ffVisibility2 = ffVisibility;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  ChannelConnection,
  Stream,
  api,
  entities,
  ffVisibility,
  noteVisibilities,
  notificationTypes,
  permissions
});
