import _defineProperty from "@babel/runtime/helpers/defineProperty";
import throttle from 'lodash/throttle';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
const DEFAULT_INITIAL_BUCKETS = 50;
const DEFAULT_MAX_BUCKET_CAPACITY = 50;
const DEFAULT_SCALE_RATIO = 0.5;
const DEFAULT_THROTTLE_DELAY = 16; // ~60fps for smooth updates

/**
 * 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,
    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.
 * @property {Map<number, ReturnType<typeof throttle>>} throttledBucketUpdaters - A map of bucket IDs to their throttled update functions.
 * @property {number} throttleDelay - The delay in milliseconds for throttling bucket updates.
 *
 * @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.
 * @param {number} [throttleDelay=DEFAULT_THROTTLE_DELAY] - The delay in milliseconds for throttling updates.
 */
export class PortalManager {
  /**
   * RequestAnimationFrame callback ID used to transition from synchronous to throttled update mode.
   * This ensures we switch to throttled mode after the initial render cycle completes.
   */

  constructor(initialBuckets = DEFAULT_INITIAL_BUCKETS, maxBucketCapacity = DEFAULT_MAX_BUCKET_CAPACITY, scaleRatio = DEFAULT_SCALE_RATIO, throttleDelay = DEFAULT_THROTTLE_DELAY) {
    /**
     * Controls whether to use throttled updates or immediate synchronous updates.
     * During initial load, we use synchronous updates to avoid delays and ensure immediate responsiveness.
     * After the initial load period (one RAF cycle), we switch to throttled updates for better performance.
     */
    _defineProperty(this, "shouldUseThrottledUpdates", false);
    this.maxBucketCapacity = maxBucketCapacity;
    this.scaleRatio = scaleRatio;
    this.throttleDelay = throttleDelay;

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

    //If the current bucket has capacity, skip this logic
    // @ts-ignore - TS2538 TypeScript 5.9.2 upgrade
    if (this.buckets[currentBucket].capacity > 0) {
      return;
    } else {
      // The current bucket is full, delete the bucket from the list of available buckets
      // @ts-ignore - TS2345 TypeScript 5.9.2 upgrade
      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
    const numBucketsToAdd = Math.floor(this.buckets.length * this.scaleRatio);
    this.buckets = [...this.buckets];
    for (let 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 ? void 0 : _this$portalRendererU.call(this, this.buckets);
  }
  getOrCreateThrottledUpdater(id) {
    if (!this.throttledBucketUpdaters.has(id)) {
      const throttledUpdater = throttle(() => {
        var _this$buckets$id, _this$buckets$id$upda;
        (_this$buckets$id = this.buckets[id]) === null || _this$buckets$id === void 0 ? void 0 : (_this$buckets$id$upda = _this$buckets$id.updater) === null || _this$buckets$id$upda === void 0 ? void 0 : _this$buckets$id$upda.call(_this$buckets$id, () => ({
          ...this.buckets[id].portals
        }));
      }, this.throttleDelay);
      this.throttledBucketUpdaters.set(id, throttledUpdater);
    }
    return this.throttledBucketUpdaters.get(id);
  }
  getBuckets() {
    return this.buckets;
  }
  registerBucket(id, updater) {
    var _this$buckets$id$upda2, _this$buckets$id2;
    this.buckets[id].updater = updater;
    (_this$buckets$id$upda2 = (_this$buckets$id2 = this.buckets[id]).updater) === null || _this$buckets$id$upda2 === void 0 ? void 0 : _this$buckets$id$upda2.call(_this$buckets$id2, () => ({
      ...this.buckets[id].portals
    }));
  }
  unregisterBucket(id) {
    this.buckets[id].updater = null;
    // Clean up throttled updater when bucket is unregistered
    if (this.throttledBucketUpdaters.has(id)) {
      var _this$throttledBucket;
      (_this$throttledBucket = this.throttledBucketUpdaters.get(id)) === null || _this$throttledBucket === void 0 ? void 0 : _this$throttledBucket.cancel();
      this.throttledBucketUpdaters.delete(id);
    }
  }
  updateBuckets(id, immediate = false) {
    if (!this.throttleActivationRAFId && expValEquals('platform_editor_debounce_portal_provider', 'isEnabled', true)) {
      this.throttleActivationRAFId = requestAnimationFrame(() => {
        this.shouldUseThrottledUpdates = true;
      });
    }
    if (immediate || !this.shouldUseThrottledUpdates || !expValEquals('platform_editor_debounce_portal_provider', 'isEnabled', true)) {
      var _this$buckets$id$upda3, _this$buckets$id3;
      // Cancel any pending throttled update and update immediately
      if (this.throttledBucketUpdaters.has(id)) {
        var _this$throttledBucket2;
        (_this$throttledBucket2 = this.throttledBucketUpdaters.get(id)) === null || _this$throttledBucket2 === void 0 ? void 0 : _this$throttledBucket2.cancel();
      }
      (_this$buckets$id$upda3 = (_this$buckets$id3 = this.buckets[id]).updater) === null || _this$buckets$id$upda3 === void 0 ? void 0 : _this$buckets$id$upda3.call(_this$buckets$id3, () => ({
        ...this.buckets[id].portals
      }));
    } else {
      var _this$getOrCreateThro;
      // Use throttled update for smooth, regular updates
      (_this$getOrCreateThro = this.getOrCreateThrottledUpdater(id)) === null || _this$getOrCreateThro === void 0 ? void 0 : _this$getOrCreateThro();
    }
  }
  registerPortal(key, portal, immediate = false) {
    var _this$portalToBucketM;
    this.createBucket();
    // @ts-ignore - TS2538 TypeScript 5.9.2 upgrade
    this.buckets[this.getCurrentBucket()].capacity -= 1;
    const id = (_this$portalToBucketM = this.portalToBucketMap.get(key)) !== null && _this$portalToBucketM !== void 0 ? _this$portalToBucketM : this.getCurrentBucket();
    // @ts-ignore - TS2345 TypeScript 5.9.2 upgrade
    this.portalToBucketMap.set(key, id);
    // @ts-ignore - TS2538 TypeScript 5.9.2 upgrade
    if (this.buckets[id].portals[key] !== portal) {
      // @ts-ignore - TS2538 TypeScript 5.9.2 upgrade
      this.buckets[id].portals[key] = portal;
      // @ts-ignore - TS2345 TypeScript 5.9.2 upgrade
      this.updateBuckets(id, immediate);
    }

    //returns a function to unregister the portal
    return () => {
      // @ts-ignore - TS2538 TypeScript 5.9.2 upgrade
      delete this.buckets[id].portals[key];
      this.portalToBucketMap.delete(key);
      // @ts-ignore - TS2538 TypeScript 5.9.2 upgrade
      this.buckets[id].capacity += 1;
      // @ts-ignore - TS2538 TypeScript 5.9.2 upgrade
      if (this.buckets[id].capacity > this.scaleCapacityThreshold) {
        // @ts-ignore - TS2345 TypeScript 5.9.2 upgrade
        this.availableBuckets.add(id);
      }
      // @ts-ignore - TS2345 TypeScript 5.9.2 upgrade
      this.updateBuckets(id, immediate);
    };
  }
  registerPortalRenderer(updater) {
    if (!this.portalRendererUpdater) {
      updater(() => this.buckets);
    }
    this.portalRendererUpdater = updater;
  }
  unregisterPortalRenderer() {
    this.portalRendererUpdater = null;
  }

  /**
   * Cleans up resources used by the PortalManager. This includes clearing all portals,
   * unregistering all buckets, and resetting internal state.
   */
  destroy() {
    // Cancel all pending throttled updates
    this.throttledBucketUpdaters.forEach(updater => {
      updater.cancel();
    });
    this.throttledBucketUpdaters.clear();

    // Iterate through each bucket and clear its portals and unset the updater function
    this.buckets.forEach((bucket, id) => {
      bucket.portals = {}; // Clearing all portals from the bucket
      bucket.updater = null; // Unsetting the bucket's updater function
      this.availableBuckets.add(id); // Mark all buckets as available
    });
    this.portalToBucketMap.clear();
    this.portalRendererUpdater = null;
    this.availableBuckets = new Set(this.buckets.map((_, index) => index));
  }
}