/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  const cachedFragments = {
    get editMenuItems() {
      return `
      <menuitem data-l10n-id="text-action-undo" cmd="cmd_undo"></menuitem>
      <menuitem data-l10n-id="text-action-redo" cmd="cmd_redo"></menuitem>
      <menuseparator></menuseparator>
      <menuitem data-l10n-id="text-action-cut" cmd="cmd_cut"></menuitem>
      <menuitem data-l10n-id="text-action-copy" cmd="cmd_copy"></menuitem>
      <menuitem data-l10n-id="text-action-paste" cmd="cmd_paste"></menuitem>
      <menuitem data-l10n-id="text-action-delete" cmd="cmd_delete"></menuitem>
      <menuitem data-l10n-id="text-action-select-all" cmd="cmd_selectAll"></menuitem>
    `;
    },
    get normal() {
      delete this.normal;
      this.normal = MozXULElement.parseXULToFragment(
        `
      <menupopup class="textbox-contextmenu" showservicesmenu="true">
        ${this.editMenuItems}
      </menupopup>
    `
      );
      MozXULElement.insertFTLIfNeeded("toolkit/global/textActions.ftl");
      return this.normal;
    },
    get spellcheck() {
      delete this.spellcheck;
      this.spellcheck = MozXULElement.parseXULToFragment(
        `
      <menupopup class="textbox-contextmenu" showservicesmenu="true">
        <menuitem data-l10n-id="text-action-spell-no-suggestions" anonid="spell-no-suggestions" disabled="true"></menuitem>
        <menuitem data-l10n-id="text-action-spell-add-to-dictionary" anonid="spell-add-to-dictionary" oncommand="this.parentNode.parentNode.spellCheckerUI.addToDictionary();"></menuitem>
        <menuitem data-l10n-id="text-action-spell-undo-add-to-dictionary" anonid="spell-undo-add-to-dictionary" oncommand="this.parentNode.parentNode.spellCheckerUI.undoAddToDictionary();"></menuitem>
        <menuseparator anonid="spell-suggestions-separator"></menuseparator>
        ${this.editMenuItems}
        <menuseparator anonid="spell-check-separator"></menuseparator>
        <menuitem data-l10n-id="text-action-spell-check-toggle" type="checkbox" anonid="spell-check-enabled" oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled();"></menuitem>
        <menu data-l10n-id="text-action-spell-dictionaries" anonid="spell-dictionaries">
          <menupopup anonid="spell-dictionaries-menu" onpopupshowing="event.stopPropagation();" onpopuphiding="event.stopPropagation();"></menupopup>
        </menu>
      </menupopup>
    `
      );
      return this.spellcheck;
    },
  };

  class MozInputBox extends MozXULElement {
    static get observedAttributes() {
      return ["spellcheck"];
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (name === "spellcheck" && oldValue != newValue) {
        this._initUI();
      }
    }

    connectedCallback() {
      this._initUI();
    }

    _initUI() {
      this.spellcheck = this.hasAttribute("spellcheck");
      if (this.menupopup) {
        this.menupopup.remove();
      }

      this.setAttribute("context", "_child");
      this.appendChild(
        this.spellcheck
          ? cachedFragments.spellcheck.cloneNode(true)
          : cachedFragments.normal.cloneNode(true)
      );
      this.menupopup = this.querySelector(".textbox-contextmenu");

      this.menupopup.addEventListener("popupshowing", event => {
        let input = this._input;
        if (document.commandDispatcher.focusedElement != input) {
          input.focus();
        }
        this._doPopupItemEnabling(event);
      });

      if (this.spellcheck) {
        this.menupopup.addEventListener("popuphiding", () => {
          if (this.spellCheckerUI) {
            this.spellCheckerUI.clearSuggestionsFromMenu();
            this.spellCheckerUI.clearDictionaryListFromMenu();
          }
        });
      }

      this.menupopup.addEventListener("command", event => {
        var cmd = event.originalTarget.getAttribute("cmd");
        if (cmd) {
          this.doCommand(cmd);
          event.stopPropagation();
        }
      });
    }

    _doPopupItemEnablingSpell(event) {
      var spellui = this.spellCheckerUI;
      if (!spellui || !spellui.canSpellCheck) {
        this._setMenuItemVisibility("spell-no-suggestions", false);
        this._setMenuItemVisibility("spell-check-enabled", false);
        this._setMenuItemVisibility("spell-check-separator", false);
        this._setMenuItemVisibility("spell-add-to-dictionary", false);
        this._setMenuItemVisibility("spell-undo-add-to-dictionary", false);
        this._setMenuItemVisibility("spell-suggestions-separator", false);
        this._setMenuItemVisibility("spell-dictionaries", false);
        return;
      }

      spellui.initFromEvent(event.rangeParent, event.rangeOffset);

      var enabled = spellui.enabled;
      var showUndo = spellui.canSpellCheck && spellui.canUndo();

      var enabledCheckbox = this.getMenuItem("spell-check-enabled");
      enabledCheckbox.setAttribute("checked", enabled);

      var overMisspelling = spellui.overMisspelling;
      this._setMenuItemVisibility("spell-add-to-dictionary", overMisspelling);
      this._setMenuItemVisibility("spell-undo-add-to-dictionary", showUndo);
      this._setMenuItemVisibility(
        "spell-suggestions-separator",
        overMisspelling || showUndo
      );

      // suggestion list
      var suggestionsSeparator = this.getMenuItem("spell-no-suggestions");
      var numsug = spellui.addSuggestionsToMenuOnParent(
        event.target,
        suggestionsSeparator,
        5
      );
      this._setMenuItemVisibility(
        "spell-no-suggestions",
        overMisspelling && numsug == 0
      );

      // dictionary list
      var dictionariesMenu = this.getMenuItem("spell-dictionaries-menu");
      var numdicts = spellui.addDictionaryListToMenu(dictionariesMenu, null);
      this._setMenuItemVisibility(
        "spell-dictionaries",
        enabled && numdicts > 1
      );
    }

    _doPopupItemEnabling(event) {
      if (this.spellcheck) {
        this._doPopupItemEnablingSpell(event);
      }

      let popupNode = event.target;
      var children = popupNode.childNodes;
      for (var i = 0; i < children.length; i++) {
        var command = children[i].getAttribute("cmd");
        if (command) {
          var controller =
            document.commandDispatcher.getControllerForCommand(command);
          var enabled = controller.isCommandEnabled(command);
          if (enabled) {
            children[i].removeAttribute("disabled");
          } else {
            children[i].setAttribute("disabled", "true");
          }
        }
      }
    }

    get spellCheckerUI() {
      if (!this._spellCheckInitialized) {
        this._spellCheckInitialized = true;

        try {
          const { InlineSpellChecker } = ChromeUtils.importESModule(
            "resource://gre/modules/InlineSpellChecker.sys.mjs"
          );
          this.InlineSpellCheckerUI = new InlineSpellChecker(
            this._input.editor
          );
        } catch (ex) {}
      }

      return this.InlineSpellCheckerUI;
    }

    getMenuItem(anonid) {
      return this.querySelector(`[anonid="${anonid}"]`);
    }

    _setMenuItemVisibility(anonid, visible) {
      this.getMenuItem(anonid).hidden = !visible;
    }

    doCommand(command) {
      var controller =
        document.commandDispatcher.getControllerForCommand(command);
      controller.doCommand(command);
    }

    get _input() {
      return (
        this.getElementsByAttribute("anonid", "input")[0] ||
        this.querySelector(".textbox-input")
      );
    }
  }

  customElements.define("moz-input-box", MozInputBox);
}
