/**
 * @fileoverview
 * A utility class that aides with computational focus.
 *
 * The main motivation for creating such a module comes from `inert` and its inconvenient way
 *    of intentional "focus-trapping" for e.g a dialog or drawer. On several projects I found
 *    myself creating an array of different sections of the site only to give them an attribute.
 *    Hopefully this class helps with that. Alongside that it has some common a11y functions.
 *
 * @example
 * HTML:
 * <nav data-focus-section="topbar"></nav>
 * <main   data-focus-section="main"></main>
 * <dialog data-focus-section="mydialog"></dialog>
 *
 * JS:
 * const fm = new FocusManager();
 * fm.rejectFocusToAllSections();
 * fm.allowFocusToSection('mydialog');
 *
 */

import {
  ATTRIBUTES,
  focusable,
  isHidden,
} from './utilities';


/**
 * A utility class that aides with computational focus.
 */
class FocusManager {
  /**
   * Set up all members.
   */
  constructor() {
    /**
     * This object will store all top-level sections of the site for easy access.
     * @type {?NodeListOf<HTMLElement> | {}}
     * @private
     **/
    this._sections = {};

    /** @type {?NodeListOf<HTMLElement>} */
    const sections = document.querySelectorAll(ATTRIBUTES.SECTION_SELECTOR);

    sections.forEach((element) => {
      /** @type {string} */
      const key = element.dataset.focusSection;

      this._sections[key] = {
        element,
        focusable: !isHidden(element),
      };
    });
  }


  /**
   * Return all pre-defined sections of importance.
   * @return {?NodeListOf<HTMLElement>}
   */
  get sections() {
    return this._sections;
  }


  /**
   * Add a section.
   * This should only be used when an important section gets programmatically added to the DOM.
   * Otherwise you should probably do it through the data attributes.
   * @param {string} key
   * @param {HTMLElement} element
   */
  addSection(key, element) {
    this._sections[key] = {
      element,
      focusable: !isHidden(element),
    };
  }

  /**
   * Remove a section.
   * This means that this section will no longer be accessible through the key format.
   * This could potentially lead to buggy focus-behaviour and should only be used if
   *    the given section gets removed from the DOM, or changes hierarchically so it isn't
   *    as important anymore.
   * @param {string} key
   */
  removeSection(key) {
    delete this._sections[key];
  }


  /**
   * Reject focus from all important sections.
   * Useful if you want to give focus to one particular section, e.g a dialog.
   */
  rejectFocusToAllSections() {
    Object.keys(this._sections).forEach((key) => this.rejectFocusToSection(key));
  }


  /**
   * Reject focus from a section in particular.
   * @param {string} key
   */
  rejectFocusToSection(key) {
    const {element} = this._sections[key];

    focusable(element, false);

    this._sections[key].focusable = false;
  }


  /**
   * Allow focus to a specific section.
   * @param {string} key
   */
  allowFocusToSection(key) {
    const {element} = this._sections[key];

    focusable(element, true);

    this._sections[key].focusable = true;
  }
}

export default FocusManager;