import HandlebarsApplicationMixin from "../../api/handlebars-application.mjs";
import AbstractSidebarTab from "../sidebar-tab.mjs";

/**
 * @import {ApplicationRenderContext, ApplicationRenderOptions} from "../../_types.mjs";
 * @import {HandlebarsRenderOptions} from "../../api/handlebars-applications.mjs";
 * @import PlaceableTab from "./placeable-tab.mjs";
 */

/**
 * @typedef {ApplicationRenderOptions & HandlebarsRenderOptions} PlaceableDirectoryRenderOptions
 */

/**
 * The sidebar placeables tab.
 * @extends {AbstractSidebarTab}
 * @mixes HandlebarsApplication
 */
export default class PlaceableDirectory extends HandlebarsApplicationMixin(AbstractSidebarTab) {
  /** @override */
  static DEFAULT_OPTIONS = {
    classes: ["flexcol"],
    window: {
      title: "SIDEBAR.TabPlaceables"
    }
  };

  /** @override */
  static PARTS = {
    tabs: { template: "templates/sidebar/tabs/placeable/tabs.hbs" },
    tab: { template: "templates/sidebar/tabs/placeable/tab.hbs" }
  };

  /** @override */
  static TABS = {
    sheet: {
      tabs: [],
      initial: ""
    }
  };

  /** @override */
  static tabName = "placeables";

  /* -------------------------------------------- */
  /*  Properties                                  */
  /* -------------------------------------------- */

  /**
   * The currently rendered tab.
   * @returns {PlaceableTab}
   */
  get tab() {
    return this.#tab;
  }

  #tab;

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

  /**
   * Tab application cache.
   * @type {Record<string, PlaceableTab>}
   */
  #tabs = {};

  /* -------------------------------------------- */
  /*  Rendering                                   */
  /* -------------------------------------------- */

  /** @override */
  _canRender(options) {
    if ( game.settings.get("core", "noCanvas") ) return false;
    const { renderContext } = options;
    if ( !renderContext ) return true;
    const layerName = this.tabGroups.sheet;
    if ( !layerName ) return true;
    const layerClass = CONFIG.Canvas.layers[layerName].layerClass;
    const collection = foundry.utils.getDocumentClass(layerClass.documentName).metadata.collection;
    return [`create${collection}`, `update${collection}`, `delete${collection}`].includes(renderContext);
  }

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

  /** @override */
  async close(options) {
    if ( options?.renderContext !== "deleteScene" ) return super.close(options);
  }

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

  /** @inheritDoc */
  _configureRenderParts(options) {
    const parts = super._configureRenderParts(options);
    if ( !options.isFirstRender && !options.tabs ) delete parts.tab;
    return parts;
  }

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

  /** @inheritDoc */
  _getTabsConfig(group) {
    const config = foundry.utils.deepClone(super._getTabsConfig(group));
    if ( group === "sheet" ) config.tabs = Object.values(CONFIG.Canvas.layers).reduce((tabs, config) => {
      const { layerClass } = config;
      const { documentClass, sidebar } = CONFIG[layerClass.documentName] ?? {};
      if ( !sidebar ) return tabs;
      const control = layerClass.prepareSceneControls();
      const { name: id, icon, visible } = control;
      if ( visible === false ) return tabs;
      const { labelPlural: label } = documentClass.metadata;
      tabs.push({ id, icon, label, disabled: !canvas.scene, order: sidebar?.order ?? 1000 });
      return tabs;
    }, []).sort((a, b) => a.order - b.order);
    return config;
  }

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

  /** @inheritDoc */
  async _onRender(context, options) {
    await super._onRender(context, options);
    await this._renderTab(context, options);
    if ( canvas.scene ) canvas.scene.apps[this.id] = this;
  }

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

  /** @inheritDoc */
  async _prepareContext(options) {
    await canvas.initializing;
    const activeLayer = canvas.activeLayer?.options.name;
    const layerClass = CONFIG.Canvas.layers[activeLayer]?.layerClass;
    if ( CONFIG[layerClass?.documentName]?.sidebar ) this.tabGroups.sheet = activeLayer;
    this.tabGroups.sheet ??= "tokens";
    return super._prepareContext(options);
  }

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

  /**
   * Render the directory for a given placeable type.
   * @param {ApplicationRenderContext} context          Render context.
   * @param {PlaceableDirectoryRenderOptions} options   Render options.
   * @returns {Promise<void>}
   * @protected
   */
  async _renderTab(context, options) {
    this.#tab?._onDeactivateTab();
    const tab = this.tabGroups.sheet;
    let app = this.#tabs[tab];
    if ( !app ) {
      const { layerClass } = CONFIG.Canvas.layers[tab] ?? {};
      const { sidebar } = CONFIG[layerClass?.documentName] ?? {};
      if ( !sidebar ) return;
      const collectionName = foundry.documents.Scene.getCollectionName(layerClass.documentName);
      app = new sidebar.applicationClass({ collectionName, directory: this });
      this.#tabs[tab] = app;
    }
    this.#tab = app;
    await app.render({ force: true });
    app.element.dataset.applicationPart = "tab";
    const prior = this.element.querySelector("[data-application-part=tab]");
    if ( app.element !== prior ) prior.replaceWith(app.element);
  }

  /* -------------------------------------------- */
  /*  Event Listeners & Handlers                  */
  /* -------------------------------------------- */

  /** @inheritDoc */
  changeTab(tab, group, options) {
    super.changeTab(tab, group, options);
    ui.controls.activate({ control: tab });
  }

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

  /** @override */
  _onActivate() {
    this._updateFilterPip();
  }

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

  /** @override */
  _onDeactivate() {
    for ( const tab of Object.values(this.#tabs) ) tab._onDeactivateDirectory();
    this._updateFilterPip();
  }

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

  /**
   * Update the notification pip on the placeables sidebar tab button.
   * The pip is shown when the placeables tab is not active and the current sub-tab has an active filter.
   */
  _updateFilterPip() {
    const pip = document.querySelector("#sidebar .tabs [data-tab=placeables] + .notification-pip");
    pip?.classList.toggle("active", !this.active && !!this.#tab?._isFiltered());
  }

  /* -------------------------------------------- */
  /*  Delegation                                  */
  /* -------------------------------------------- */

  /**
   * Highlight a hovered entry in the sidebar.
   * @param {PlaceableObject} object  The object being hovered on canvas.
   * @param {boolean} hover           The hover state.
   */
  hoverEntry(object, hover) {
    return this.#tab?.hoverEntry(object, hover);
  }

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

  /**
   * Determine if a placeable is visible in the sidebar.
   * @param {PlaceableObject} object  The placeable.
   * @returns {boolean}
   */
  isEntryVisible(object) {
    if ( !this.#tab || !this.rendered ) return true;
    return this.#tab.isEntryVisible(object);
  }
}
