/**
 * Immutable Set cannot be changed once created
 */
export class ImmutableSet {
  /**
   * The constructor of ImmutableSet
   *
   * @typedef T generic type parameter
   * @param {Set<T>} initialSet - The initial set to use. If not provided, the set will be empty.
   * @param {() => void} onMutate - A callback function that will be called whenever the set is mutated.
   */
  constructor(initialSet, onMutate) {
    this.size = 0;
    this.set = new Set();
    if (initialSet) {
      this.set = initialSet;
      this.size = initialSet.size;
    }
    if (onMutate) {
      this.onMutate = onMutate;
    }
  }
  /**
   * The function is called when the set is mutated
   *
   * @param {Immutable<Set<T>>} _newSet - The new set that was created.
   */
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  onMutate(_newSet) {
    return;
  }
  /**
   * Add new items to the set, and then call the onMutate with the new ImmutableSet
   *
   * @typedef T generic type parameter
   * @param {T[]} value  - The items to add to the set.
   * @returns {ImmutableSet<T>} A new ImmutableSet with the new items added.
   */
  add(...value) {
    return this.withMutation((set) => {
      value.forEach((value) => set.add(value));
    });
  }
  /**
   * clear the set values
   *
   * @typedef T generic type parameter
   */
  clear() {
    this.withMutation((set) => set.clear());
  }
  /**
   * delete item from set
   *
   * @typedef T generic type parameter
   * @param {T} value - The key to delete from the set.
   * @returns {ImmutableSet<T>} A new ImmutableSet
   */
  delete(value) {
    return this.withMutation((set) => {
      set.delete(value);
    });
  }
  /**
   * It takes a function that mutates a set, and returns a new immutable set that is the result of that mutation
   *
   * @param {(set: Set<T>) => void} fn - (set: Set<T>) => void
   * @returns {ImmutableSet<T>} A new ImmutableSet
   */
  withMutation(fn) {
    const newSet = new Set(this.set);
    fn(newSet);
    const newImmutableSet = new ImmutableSet(newSet, this.onMutate);
    this.onMutate(newImmutableSet);
    return newImmutableSet;
  }
  /**
   * It returns true if the value is in the set, and false otherwise
   *
   * @typedef T generic type parameter
   * @param {T} value - The value to check for.
   * @returns {boolean} A boolean value.
   */
  has(value) {
    return this.set.has(value);
  }
  /**
   * It returns an array of the values in the set
   *
   * @returns {T[]} An array of the values in the set.
   */
  toArray() {
    return [...this.set];
  }
}
