/**
 * An application for configuring data across all installed and active packages.
 */
class PackageConfiguration extends FormApplication {

  static get categoryOrder() {
    return ["all", "core", "system", "module", "unmapped"];
  }

  /**
   * The name of the currently active tab.
   * @type {string}
   */
  get activeCategory() {
    return this._tabs[0].active;
  }

  /* -------------------------------------------- */

  /** @override */
  static get defaultOptions() {
    return foundry.utils.mergeObject(super.defaultOptions, {
      classes: ["package-configuration"],
      template: "templates/sidebar/apps/package-configuration.html",
      categoryTemplate: undefined,
      width: 780,
      height: 680,
      resizable: true,
      scrollY: [".filters", ".categories"],
      tabs: [{navSelector: ".tabs", contentSelector: "form .scrollable", initial: "all"}],
      filters: [{inputSelector: 'input[name="filter"]', contentSelector: ".categories"}],
      submitButton: false
    });
  }

  /* -------------------------------------------- */

  /** @override */
  getData(options={}) {
    const data = this._prepareCategoryData();
    data.categoryTemplate = this.options.categoryTemplate;
    data.submitButton = this.options.submitButton;
    return data;
  }

  /* -------------------------------------------- */

  /**
   * Prepare the structure of category data which is rendered in this configuration form.
   * @abstract
   * @protected
   */
  _prepareCategoryData() {
    return {categories: [], total: 0};
  }

  /* -------------------------------------------- */

  /**
   * Classify what Category an Action belongs to
   * @param {string} namespace                The entry to classify
   * @returns {{id: string, title: string}}   The category the entry belongs to
   * @protected
   */
  _categorizeEntry(namespace) {
    if ( namespace === "core" ) return {
      id: "core",
      title: game.i18n.localize("PACKAGECONFIG.Core")
    };
    else if ( namespace === game.system.id ) return {
      id: "system",
      title: game.system.title
    };
    else {
      const module = game.modules.get(namespace);
      if ( module ) return {
        id: module.id,
        title: module.title
      };
      return {
        id: "unmapped",
        title: game.i18n.localize("PACKAGECONFIG.Unmapped")
      };
    }
  }

  /* -------------------------------------------- */

  /**
   * Reusable logic for how categories are sorted in relation to each other.
   * @param {object} a
   * @param {object} b
   * @protected
   */
  _sortCategories(a, b) {
    const categories = this.constructor.categoryOrder;
    let ia = categories.indexOf(a.id);
    if ( ia === -1 ) ia = categories.length - 2; // Modules second from last
    let ib = this.constructor.categoryOrder.indexOf(b.id);
    if ( ib === -1 ) ib = categories.length - 2; // Modules second from last
    return (ia - ib) || a.title.localeCompare(b.title, game.i18n.lang);
  }

  /* -------------------------------------------- */

  /** @inheritDoc */
  async _render(force, {activeCategory, ...options}={}) {
    await loadTemplates([this.options.categoryTemplate]);
    await super._render(force, options);
    if ( activeCategory ) this._tabs[0].activate(activeCategory);
    const activeTab = this._tabs[0]?.active;
    if ( activeTab ) this.element[0].querySelector(`.tabs [data-tab="${activeTab}"]`)?.scrollIntoView();
  }

  /* -------------------------------------------- */

  /** @inheritDoc */
  activateListeners(html) {
    super.activateListeners(html);
    if ( this.activeCategory === "all" ) {
      this._tabs[0]._content.querySelectorAll(".tab").forEach(tab => tab.classList.add("active"));
    }
    html.find("button.reset-all").click(this._onResetDefaults.bind(this));
    html.find("input[name=filter]").focus();
  }

  /* -------------------------------------------- */

  /** @override */
  _onChangeTab(event, tabs, active) {
    if ( active === "all" ) {
      tabs._content.querySelectorAll(".tab").forEach(tab => tab.classList.add("active"));
    }
  }

  /* -------------------------------------------- */

  /** @override */
  _onSearchFilter(event, query, rgx, html) {
    const visibleCategories = new Set();

    // Hide entries
    for ( const entry of html.querySelectorAll(".form-group") ) {
      if ( !query ) {
        entry.classList.remove("hidden");
        continue;
      }
      const label = entry.querySelector("label")?.textContent;
      const notes = entry.querySelector(".notes")?.textContent;
      const match = (label && rgx.test(SearchFilter.cleanQuery(label)))
        || (notes && rgx.test(SearchFilter.cleanQuery(notes)));
      entry.classList.toggle("hidden", !match);
      if ( match ) visibleCategories.add(entry.parentElement.dataset.category);
    }

    // Hide categories which have no visible children
    for ( const category of html.querySelectorAll(".category") ) {
      category.classList.toggle("hidden", query && !visibleCategories.has(category.dataset.category));
    }
  }

  /* -------------------------------------------- */

  /**
   * Handle button click to reset default settings
   * @param {Event} event   The initial button click event
   * @abstract
   * @protected
   */
  _onResetDefaults(event) {}
}
