import { isArray } from 'lodash';
import { useSyncExternalStore } from 'react';

export type Control = string | number;
export type ControlMode = 'one' | 'all';

export class ACL {
  private _full = false;
  private _controls = new Set<Control>();

  public get full(): boolean {
    return this._full;
  }

  public get controls(): Control[] {
    return Array.from(this._controls);
  }

  public setFull(full: boolean) {
    this._full = full;
    emitChange();
  }

  public set(control: Control[]): void {
    this._controls = new Set(control);
    emitChange();
  }

  public add(control: Control | Control[]): void {
    for (const v of isArray(control) ? control : [control]) {
      this._controls.add(v);
    }
    emitChange();
  }

  public remove(control: Control | Control[]): void {
    for (const v of isArray(control) ? control : [control]) {
      this._controls.delete(v);
    }
    emitChange();
  }

  public can(control: Control | Control[], mode: ControlMode = 'one'): boolean {
    if (this._full) {
      return true;
    }

    const arr = isArray(control) ? control : [control];

    let n = 0;
    for (const v of arr) {
      if (this._controls.has(v)) {
        n += 1;
      }
    }

    if (n > 0 && (mode === 'one' || (mode === 'all' && n === arr.length))) {
      return true;
    }
    return false;
  }
}

const acl = new ACL();

let listeners: (() => void)[] = [];

function subscribe(onStoreChange: () => void) {
  listeners = listeners.concat([onStoreChange]);
  return () => {
    listeners = listeners.filter((f) => f !== onStoreChange);
  };
}

function getSnapshot() {
  return acl;
}

function emitChange() {
  for (const listener of listeners) {
    listener();
  }
}

export function useACL() {
  return useSyncExternalStore(subscribe, getSnapshot);
}