/**
 * The UI element which displays the Scene documents which are currently enabled for quick navigation.
 */
class SceneNavigation extends Application {
  constructor(options) {
    super(options);
    game.scenes.apps.push(this);

    /**
     * Navigation collapsed state
     * @type {boolean}
     */
    this._collapsed = false;
  }

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

  /** @inheritdoc */
  static get defaultOptions() {
    return foundry.utils.mergeObject(super.defaultOptions, {
      id: "navigation",
      template: "templates/hud/navigation.html",
      popOut: false,
      dragDrop: [{dragSelector: ".scene"}]
    });
  }

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

  /**
   * Return an Array of Scenes which are displayed in the Navigation bar
   * @returns {Scene[]}
   */
  get scenes() {
    const scenes = game.scenes.filter(s => {
      return (s.navigation && s.visible) || s.active || s.isView;
    });
    scenes.sort((a, b) => a.navOrder - b.navOrder);
    return scenes;
  }

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

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

  /** @inheritdoc */
  render(force, context = {}) {
    let {renderContext, renderData} = context;
    if ( renderContext ) {
      const events = ["createScene", "updateScene", "deleteScene"];
      if ( !events.includes(renderContext) ) return this;
      const updateKeys = ["name", "ownership", "active", "navigation", "navName", "navOrder"];
      if ( (renderContext === "updateScene") && !renderData.some(d => updateKeys.some(k => k in d)) ) return this;
    }
    return super.render(force, context);
  }

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

  /** @inheritdoc */
  async _render(force, options) {
    await super._render(force, options);
    const loading = document.getElementById("loading");
    const nav = this.element[0];
    loading.style.top = `${nav.offsetTop + nav.offsetHeight}px`;
  }

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

  /** @inheritdoc */
  getData(options={}) {
    const scenes = this.scenes.map(scene => {
      return {
        id: scene.id,
        active: scene.active,
        name: TextEditor.truncateText(scene.navName || scene.name, {maxLength: 32}),
        tooltip: scene.navName && game.user.isGM ? scene.name : null,
        users: game.users.reduce((arr, u) => {
          if ( u.active && ( u.viewedScene === scene.id) ) arr.push({letter: u.name[0], color: u.color.css});
          return arr;
        }, []),
        visible: game.user.isGM || scene.isOwner || scene.active,
        css: [
          scene.isView ? "view" : null,
          scene.active ? "active" : null,
          scene.ownership.default === 0 ? "gm" : null
        ].filterJoin(" ")
      };
    });
    return {collapsed: this._collapsed, scenes: scenes};
  }

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

  /**
   * A hook event that fires when the SceneNavigation menu is expanded or collapsed.
   * @function collapseSceneNavigation
   * @memberof hookEvents
   * @param {SceneNavigation} sceneNavigation The SceneNavigation application
   * @param {boolean} collapsed               Whether the SceneNavigation is now collapsed or not
   */

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

  /**
   * Expand the SceneNavigation menu, sliding it down if it is currently collapsed
   */
  expand() {
    if ( !this._collapsed ) return true;
    const nav = this.element;
    const icon = nav.find("#nav-toggle i.fas");
    const ul = nav.children("#scene-list");
    return new Promise(resolve => {
      ul.slideDown(200, () => {
        nav.removeClass("collapsed");
        icon.removeClass("fa-caret-down").addClass("fa-caret-up");
        this._collapsed = false;
        Hooks.callAll("collapseSceneNavigation", this, this._collapsed);
        return resolve(true);
      });
    });
  }

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

  /**
   * Collapse the SceneNavigation menu, sliding it up if it is currently expanded
   * @returns {Promise<boolean>}
   */
  async collapse() {
    if ( this._collapsed ) return true;
    const nav = this.element;
    const icon = nav.find("#nav-toggle i.fas");
    const ul = nav.children("#scene-list");
    return new Promise(resolve => {
      ul.slideUp(200, () => {
        nav.addClass("collapsed");
        icon.removeClass("fa-caret-up").addClass("fa-caret-down");
        this._collapsed = true;
        Hooks.callAll("collapseSceneNavigation", this, this._collapsed);
        return resolve(true);
      });
    });
  }

  /* -------------------------------------------- */
  /*  Event Listeners and Handlers
  /* -------------------------------------------- */

  /** @inheritdoc */
  activateListeners(html) {
    super.activateListeners(html);

    // Click event listener
    const scenes = html.find(".scene");
    scenes.click(this._onClickScene.bind(this));
    html.find("#nav-toggle").click(this._onToggleNav.bind(this));

    // Activate Context Menu
    const contextOptions = this._getContextMenuOptions();
    Hooks.call("getSceneNavigationContext", html, contextOptions);
    if ( contextOptions ) new ContextMenu(html, ".scene", contextOptions);
  }

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

  /**
   * Get the set of ContextMenu options which should be applied for Scenes in the menu
   * @returns {object[]}   The Array of context options passed to the ContextMenu instance
   * @private
   */
  _getContextMenuOptions() {
    return [
      {
        name: "SCENES.Activate",
        icon: '<i class="fas fa-bullseye"></i>',
        condition: li => game.user.isGM && !game.scenes.get(li.data("sceneId")).active,
        callback: li => {
          let scene = game.scenes.get(li.data("sceneId"));
          scene.activate();
        }
      },
      {
        name: "SCENES.Configure",
        icon: '<i class="fas fa-cogs"></i>',
        condition: game.user.isGM,
        callback: li => {
          let scene = game.scenes.get(li.data("sceneId"));
          scene.sheet.render(true);
        }
      },
      {
        name: "SCENES.Notes",
        icon: '<i class="fas fa-scroll"></i>',
        condition: li => {
          if ( !game.user.isGM ) return false;
          const scene = game.scenes.get(li.data("sceneId"));
          return !!scene.journal;
        },
        callback: li => {
          const scene = game.scenes.get(li.data("sceneId"));
          const entry = scene.journal;
          if ( entry ) {
            const sheet = entry.sheet;
            const options = {};
            if ( scene.journalEntryPage ) options.pageId = scene.journalEntryPage;
            sheet.render(true, options);
          }
        }
      },
      {
        name: "SCENES.Preload",
        icon: '<i class="fas fa-download"></i>',
        condition: game.user.isGM,
        callback: li => {
          let sceneId = li.attr("data-scene-id");
          game.scenes.preload(sceneId, true);
        }
      },
      {
        name: "SCENES.ToggleNav",
        icon: '<i class="fas fa-compass"></i>',
        condition: li => {
          const scene = game.scenes.get(li.data("sceneId"));
          return game.user.isGM && (!scene.active);
        },
        callback: li => {
          const scene = game.scenes.get(li.data("sceneId"));
          scene.update({navigation: !scene.navigation});
        }
      }
    ];
  }

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

  /**
   * Handle left-click events on the scenes in the navigation menu
   * @param {PointerEvent} event
   * @private
   */
  _onClickScene(event) {
    event.preventDefault();
    let sceneId = event.currentTarget.dataset.sceneId;
    game.scenes.get(sceneId).view();
  }

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

  /** @override */
  _onDragStart(event) {
    const sceneId = event.currentTarget.dataset.sceneId;
    const scene = game.scenes.get(sceneId);
    event.dataTransfer.setData("text/plain", JSON.stringify(scene.toDragData()));
  }

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

  /** @override */
  async _onDrop(event) {
    const data = TextEditor.getDragEventData(event);
    if ( data.type !== "Scene" ) return;

    // Identify the document, the drop target, and the set of siblings
    const scene = await Scene.implementation.fromDropData(data);
    const dropTarget = event.target.closest(".scene") || null;
    const sibling = dropTarget ? game.scenes.get(dropTarget.dataset.sceneId) : null;
    if ( sibling && (sibling.id === scene.id) ) return;
    const siblings = this.scenes.filter(s => s.id !== scene.id);

    // Update the navigation sorting for each Scene
    return scene.sortRelative({
      target: sibling,
      siblings: siblings,
      sortKey: "navOrder"
    });
  }

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

  /**
   * Handle navigation menu toggle click events
   * @param {Event} event
   * @private
   */
  _onToggleNav(event) {
    event.preventDefault();
    if ( this._collapsed ) return this.expand();
    else return this.collapse();
  }

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

  /**
   * Display progress of some major operation like loading Scene textures.
   * @param {object} options    Options for how the progress bar is displayed
   * @param {string} options.label  A text label to display
   * @param {number} options.pct    A percentage of progress between 0 and 100
   */
  static displayProgressBar({label, pct} = {}) {
    const loader = document.getElementById("loading");
    pct = Math.clamp(pct, 0, 100);
    loader.querySelector("#context").textContent = label;
    loader.querySelector("#loading-bar").style.width = `${pct}%`;
    loader.querySelector("#progress").textContent = `${pct}%`;
    loader.style.display = "block";
    if ( (pct === 100) && !loader.hidden ) $(loader).fadeOut(2000);
  }
}
