export class FocusTracker {
  mount(win: Window) {
    const checkDown = (e: MouseEvent) => {
      if (this.checkModalDown(e)) {
        return;
      }
      const { first } = this;
      if (first && !first.internalCheckInRange(e)) {
        this.internalSuspenseItem(first);
        first.internalTriggerBlur();
      }
    };
    win.document.addEventListener('click', checkDown, true);
    return () => {
      win.document.removeEventListener('click', checkDown, true);
    };
  }

  private actives: Focusable[] = [];

  get first() {
    return this.actives[0];
  }

  private modals: Array<{ checkDown: (e: MouseEvent) => boolean; checkOpen: () => boolean }> = [];

  addModal(checkDown: (e: MouseEvent) => boolean, checkOpen: () => boolean) {
    this.modals.push({
      checkDown,
      checkOpen,
    });
  }

  private checkModalOpen(): boolean {
    return this.modals.some(item => item.checkOpen());
  }

  private checkModalDown(e: MouseEvent): boolean {
    return this.modals.some(item => item.checkDown(e));
  }

  execSave() {
    // has Modal return;
    if (this.checkModalOpen()) {
      return;
    }
    // catch
    if (this.first) {
      this.first.internalTriggerSave();
    }
  }

  execEsc() {
    const { first } = this;
    if (first) {
      this.internalSuspenseItem(first);
      first.internalTriggerEsc();
    }
  }

  create(config: FocusableConfig) {
    return new Focusable(this, config);
  }

  internalActiveItem(item: Focusable) {
    const first = this.actives[0];
    if (first === item) {
      return;
    }
    const i = this.actives.indexOf(item);
    if (i > -1) {
      this.actives.splice(i, 1);
    }
    this.actives.unshift(item);
    if (!item.isModal && first) {
      // trigger Blur
      first.internalTriggerBlur();
    }
    // trigger onActive
    item.internalTriggerActive();
  }

  internalSuspenseItem(item: Focusable) {
    const i = this.actives.indexOf(item);
    if (i > -1) {
      this.actives.splice(i, 1);
      this.first?.internalTriggerActive();
    }
  }
}

export interface FocusableConfig {
  range: HTMLElement | ((e: MouseEvent) => boolean);
  modal?: boolean; // 模态窗口级别
  onEsc?: () => void;
  onBlur?: () => void;
  onSave?: () => void;
  onActive?: () => void;
}

export class Focusable {
  readonly isModal: boolean;

  constructor(private tracker: FocusTracker, private config: FocusableConfig) {
    this.isModal = config.modal == null ? false : config.modal;
  }

  active() {
    this.tracker.internalActiveItem(this);
  }

  suspense() {
    this.tracker.internalSuspenseItem(this);
  }

  purge() {
    this.tracker.internalSuspenseItem(this);
  }

  internalCheckInRange(e: MouseEvent) {
    const { range } = this.config;
    if (!range) {
      return false;
    }
    if (typeof range === 'function') {
      return range(e);
    }
    return range.contains(e.target as HTMLElement);
  }

  internalTriggerBlur() {
    if (this.config.onBlur) {
      this.config.onBlur();
    }
  }

  internalTriggerSave() {
    if (this.config.onSave) {
      this.config.onSave();
      return true;
    }
    return false;
  }

  internalTriggerEsc() {
    if (this.config.onEsc) {
      this.config.onEsc();
    }
  }

  internalTriggerActive() {
    if (this.config.onActive) {
      this.config.onActive();
    }
  }
}

export const focusTracker = new FocusTracker();

focusTracker.mount(window);
