import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
var DEFAULT_INITIAL_BUCKETS = 50;
var DEFAULT_MAX_BUCKET_CAPACITY = 50;
var DEFAULT_SCALE_RATIO = 0.5;

/**
 * Creates an empty bucket object with a specified capacity. Each bucket is designed
 * to hold a certain number of React portals and has an associated updater function
 * which can be null initially.
 *
 * @function createEmptyBucket
 * @param {number} capacity - The maximum capacity of the bucket.
 * @returns {PortalBucketType} An object representing an empty bucket with the specified capacity.
 */
function createEmptyBucket(capacity) {
  return {
    portals: {},
    capacity: capacity,
    updater: null
  };
}

/**
 * A utility class to manage and dynamically scale React portals across multiple buckets.
 * It allows for efficient rendering of large numbers of React portals by distributing them
 * across "buckets" and updating these buckets as necessary to balance load and performance.
 *
 * @class PortalManager
 * @typedef {Object} PortalManager
 *
 * @property {number} maxBucketCapacity - The maximum capacity of each bucket before a new bucket is created.
 * @property {number} scaleRatio - The ratio to determine the number of new buckets to add when scaling up.
 * @property {Array<PortalBucketType>} buckets - An array of bucket objects where each bucket holds a record of React portals.
 * @property {Set<number>} availableBuckets - A set of indices representing buckets that have available capacity.
 * @property {Map<React.Key, number>} portalToBucketMap - A map of React portal keys to their corresponding bucket indices.
 * @property {PortalRendererUpdater|null} portalRendererUpdater - A function to trigger updates to the rendering of portals.
 * @property {number} scaleCapacityThreshold - The threshold at which the buckets are scaled up to accommodate more portals.
 *
 * @param {number} [initialBuckets=DEFAULT_INITIAL_BUCKETS] - The initial number of buckets to create.
 * @param {number} [maxBucketCapacity=DEFAULT_MAX_BUCKET_CAPACITY] - The maximum number of portals a single bucket can hold.
 * @param {number} [scaleRatio=DEFAULT_SCALE_RATIO] - The ratio used to calculate the number of new buckets to add when scaling.
 */
export var PortalManager = /*#__PURE__*/function () {
  function PortalManager() {
    var initialBuckets = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : DEFAULT_INITIAL_BUCKETS;
    var maxBucketCapacity = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : DEFAULT_MAX_BUCKET_CAPACITY;
    var scaleRatio = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : DEFAULT_SCALE_RATIO;
    _classCallCheck(this, PortalManager);
    this.maxBucketCapacity = maxBucketCapacity;
    this.scaleRatio = scaleRatio;

    // Initialise buckets array by creating an array of length `initialBuckets` containing empty buckets
    this.buckets = Array.from({
      length: initialBuckets
    }, function () {
      return createEmptyBucket(maxBucketCapacity);
    });
    this.portalToBucketMap = new Map();
    this.availableBuckets = new Set(Array.from({
      length: initialBuckets
    }, function (_, i) {
      return i;
    }));
    this.portalRendererUpdater = null;
    this.scaleCapacityThreshold = maxBucketCapacity / 2;
  }
  _createClass(PortalManager, [{
    key: "getCurrentBucket",
    value: function getCurrentBucket() {
      return this.availableBuckets.values().next().value;
    }
  }, {
    key: "createBucket",
    value: function createBucket() {
      var _this$portalRendererU;
      var currentBucket = this.getCurrentBucket();

      //If the current bucket has capacity, skip this logic
      if (this.buckets[currentBucket].capacity > 0) {
        return;
      } else {
        // The current bucket is full, delete the bucket from the list of available buckets
        this.availableBuckets.delete(currentBucket);
      }

      // Skip creating new bucket if there are buckets still available
      if (this.availableBuckets.size > 0) {
        return;
      }

      // Scale the buckets up only if there are no available buckets left
      // Calculate how many new buckets need to be added
      var numBucketsToAdd = Math.floor(this.buckets.length * this.scaleRatio);
      this.buckets = _toConsumableArray(this.buckets);
      for (var i = 0; i < numBucketsToAdd; i++) {
        this.buckets.push(createEmptyBucket(this.maxBucketCapacity));
        this.availableBuckets.add(this.buckets.length - 1);
      }
      (_this$portalRendererU = this.portalRendererUpdater) === null || _this$portalRendererU === void 0 || _this$portalRendererU.call(this, this.buckets);
    }
  }, {
    key: "getBuckets",
    value: function getBuckets() {
      return this.buckets;
    }
  }, {
    key: "registerBucket",
    value: function registerBucket(id, updater) {
      var _this$buckets$id$upda,
        _this$buckets$id,
        _this = this;
      this.buckets[id].updater = updater;
      (_this$buckets$id$upda = (_this$buckets$id = this.buckets[id]).updater) === null || _this$buckets$id$upda === void 0 || _this$buckets$id$upda.call(_this$buckets$id, function () {
        return _objectSpread({}, _this.buckets[id].portals);
      });
    }
  }, {
    key: "unregisterBucket",
    value: function unregisterBucket(id) {
      this.buckets[id].updater = null;
    }
  }, {
    key: "updateBuckets",
    value: function updateBuckets(id) {
      var _this$buckets$id$upda2,
        _this$buckets$id2,
        _this2 = this;
      (_this$buckets$id$upda2 = (_this$buckets$id2 = this.buckets[id]).updater) === null || _this$buckets$id$upda2 === void 0 || _this$buckets$id$upda2.call(_this$buckets$id2, function () {
        // new object is required to trigger react updates
        return _objectSpread({}, _this2.buckets[id].portals);
      });
    }
  }, {
    key: "registerPortal",
    value: function registerPortal(key, portal) {
      var _this$portalToBucketM,
        _this3 = this;
      this.createBucket();
      this.buckets[this.getCurrentBucket()].capacity -= 1;
      var id = (_this$portalToBucketM = this.portalToBucketMap.get(key)) !== null && _this$portalToBucketM !== void 0 ? _this$portalToBucketM : this.getCurrentBucket();
      this.portalToBucketMap.set(key, id);
      if (this.buckets[id].portals[key] !== portal) {
        this.buckets[id].portals[key] = portal;
        this.updateBuckets(id);
      }

      //returns a function to unregister the portal
      return function () {
        delete _this3.buckets[id].portals[key];
        _this3.portalToBucketMap.delete(key);
        _this3.buckets[id].capacity += 1;
        if (_this3.buckets[id].capacity > _this3.scaleCapacityThreshold) {
          _this3.availableBuckets.add(id);
        }
        _this3.updateBuckets(id);
      };
    }
  }, {
    key: "registerPortalRenderer",
    value: function registerPortalRenderer(updater) {
      var _this4 = this;
      if (!this.portalRendererUpdater) {
        updater(function () {
          return _this4.buckets;
        });
      }
      this.portalRendererUpdater = updater;
    }
  }, {
    key: "unregisterPortalRenderer",
    value: function unregisterPortalRenderer() {
      this.portalRendererUpdater = null;
    }

    /**
     * Cleans up resources used by the PortalManager. This includes clearing all portals,
     * unregistering all buckets, and resetting internal state.
     */
  }, {
    key: "destroy",
    value: function destroy() {
      var _this5 = this;
      // Iterate through each bucket and clear its portals and unset the updater function
      this.buckets.forEach(function (bucket, id) {
        bucket.portals = {}; // Clearing all portals from the bucket
        bucket.updater = null; // Unsetting the bucket's updater function
        _this5.availableBuckets.add(id); // Mark all buckets as available
      });
      this.portalToBucketMap.clear();
      this.portalRendererUpdater = null;
      this.availableBuckets = new Set(this.buckets.map(function (_, index) {
        return index;
      }));
    }
  }]);
  return PortalManager;
}();