/* 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";

/* global MozElements, MozXULElement, gAccountManager */

// Wrap in a block to prevent leaking to window scope.
{
  const { DownloadUtils } = ChromeUtils.importESModule(
    "resource://gre/modules/DownloadUtils.sys.mjs"
  );
  const { ChatIcons } = ChromeUtils.importESModule(
    "resource:///modules/chatIcons.sys.mjs"
  );

  /**
   * The MozChatAccountRichlistitem widget displays the information about the
   * configured account: i.e. icon, state, name, error, checkbox for
   * auto sign in and buttons for disconnect and properties.
   *
   * @augments {MozElements.MozRichlistitem}
   */
  class MozChatAccountRichlistitem extends MozElements.MozRichlistitem {
    static get inheritedAttributes() {
      return {
        stack: "tooltiptext=protocol",
        ".accountName": "value=name",
        ".autoSignOn": "checked=autologin",
        ".account-buttons": "autologin,name",
      };
    }

    connectedCallback() {
      if (this.delayConnectedCallback() || this.hasChildNodes()) {
        return;
      }

      this.setAttribute("is", "chat-account-richlistitem");
      this.addEventListener("dblclick", event => {
        if (event.button == 0) {
          // If we double clicked on a widget that has already done
          // something with the first click, we should ignore the event
          let localName = event.target.localName;
          if (localName != "button" && localName != "checkbox") {
            this.proceedDefaultAction();
          }
        }
        // Prevent from loading an account wizard
        event.stopPropagation();
      });

      this.appendChild(
        MozXULElement.parseXULToFragment(
          `
          <vbox flex="1">
            <hbox flex="1" align="start">
              <vbox>
                <stack>
                  <html:img class="accountIcon" alt="" />
                  <html:img class="statusTypeIcon" alt="" />
                </stack>
                <spacer flex="1"></spacer>
              </vbox>
              <vbox flex="1" align="start">
                <label crop="end" class="accountName"></label>
                <label class="connecting" crop="end" value="&account.connecting;"></label>
                <label class="connected" crop="end"></label>
                <label class="disconnecting" crop="end" value="&account.disconnecting;"></label>
                <label class="disconnected" crop="end" value="&account.disconnected;"></label>
                <description class="error error-description"></description>
                <description class="error error-reconnect"></description>
                <spacer flex="1"></spacer>
              </vbox>
              <checkbox label="&account.autoSignOn.label;"
                        class="autoSignOn"
                        accesskey="&account.autoSignOn.accesskey;"
                        oncommand="gAccountManager.autologin()"></checkbox>
            </hbox>
            <hbox flex="1" class="account-buttons">
              <button class="disconnectButton" command="cmd_disconnect"></button>
              <button class="connectButton" command="cmd_connect"></button>
              <spacer flex="1"></spacer>
              <button command="cmd_edit"></button>
            </hbox>
          </vbox>
          `,
          ["chrome://chat/locale/accounts.dtd"]
        )
      );
      this._buttons = this.querySelector(".account-buttons");
      this._connectedLabel = this.querySelector(".connected");
      this._stateIcon = this.querySelector(".statusTypeIcon");
      this.initializeAttributeInheritance();
    }

    set autoLogin(val) {
      if (val) {
        this.setAttribute("autologin", "true");
      } else {
        this.removeAttribute("autologin");
      }
      if (this._account.autoLogin != val) {
        this._account.autoLogin = val;
      }
    }

    get autoLogin() {
      return this.hasAttribute("autologin");
    }

    /**
     * override the default accessible name
     */
    get label() {
      return this.getAttribute("name");
    }

    get account() {
      return this._account;
    }

    get buttons() {
      return this._buttons;
    }

    build(aAccount) {
      this._account = aAccount;
      this.setAttribute("name", aAccount.name);
      this.setAttribute("id", aAccount.id);
      let proto = aAccount.protocol;
      this.setAttribute("protocol", proto.name);
      this.querySelector(".accountIcon").setAttribute(
        "src",
        ChatIcons.getProtocolIconURI(proto, 32)
      );
      this.refreshState();
      this.autoLogin = aAccount.autoLogin;
    }

    /**
     * Refresh the shown connection state.
     *
     * @param {"connected"|"connecting"|"disconnected"|"disconnecting"}
     *   [forceState] - The connection state to show. Otherwise, determined
     *   through the account status.
     */
    refreshState(forceState) {
      let account = this._account;
      let state = "unknown";
      if (forceState) {
        state = forceState;
      } else if (account.connected) {
        state = "connected";
      } else if (account.disconnected) {
        state = "disconnected";
      } else if (this._account.connecting) {
        state = "connecting";
      } else if (this._account.disconnecting) {
        state = "disconnecting";
      }

      switch (state) {
        case "connected":
          this.refreshConnectedLabel();
          break;
        case "connecting":
          this.updateConnectingProgress();
          break;
      }

      /* "state" and "error" attributes are needed for CSS styling of the
       * accountIcon and the connection buttons. */
      this.setAttribute("state", state);

      if (account.connectionErrorReason !== Ci.prplIAccount.NO_ERROR) {
        /* Icon and error attribute set in other method. */
        this.updateConnectionError();
        return;
      }

      this.removeAttribute("error");

      this._stateIcon.setAttribute("src", ChatIcons.getStatusIconURI(state));
    }

    updateConnectingProgress() {
      let bundle = Services.strings.createBundle(
        "chrome://messenger/locale/imAccounts.properties"
      );
      const key = "account.connection.progress";
      let text = this._account.connectionStateMsg;
      text = text
        ? bundle.formatStringFromName(key, [text])
        : bundle.GetStringFromName("account.connecting");

      let progress = this.querySelector(".connecting");
      progress.setAttribute("value", text);
      if (this.reconnectUpdateInterval) {
        this._cancelReconnectTimer();
      }
    }

    updateConnectionError() {
      let bundle = Services.strings.createBundle(
        "chrome://messenger/locale/imAccounts.properties"
      );
      const key = "account.connection.error";
      let account = this._account;
      let text;
      let errorReason = account.connectionErrorReason;
      if (errorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL) {
        text = bundle.formatStringFromName(key + "UnknownPrpl", [
          account.protocol.id,
        ]);
      } else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) {
        text = bundle.GetStringFromName(key + "EnteringPasswordRequired");
      } else if (errorReason == Ci.imIAccount.ERROR_CRASHED) {
        text = bundle.GetStringFromName(key + "CrashedAccount");
      } else {
        text = account.connectionErrorMessage;
      }

      if (errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD) {
        text = bundle.formatStringFromName(key, [text]);
      }

      /* "error" attribute is needed for CSS styling of the accountIcon and the
       * connection buttons. */
      this.setAttribute("error", "true");
      this._stateIcon.setAttribute(
        "src",
        "chrome://global/skin/icons/warning.svg"
      );
      let error = this.querySelector(".error-description");
      error.textContent = text;

      let updateReconnect = () => {
        let date = Math.round(
          (account.timeOfNextReconnect - Date.now()) / 1000
        );
        let reconnect = "";
        if (date > 0) {
          let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
          if (!val2) {
            reconnect = bundle.formatStringFromName(
              "account.reconnectInSingle",
              [val1, unit1]
            );
          } else {
            reconnect = bundle.formatStringFromName(
              "account.reconnectInDouble",
              [val1, unit1, val2, unit2]
            );
          }
        }
        this.querySelector(".error-reconnect").textContent = reconnect;
        return reconnect;
      };
      if (updateReconnect() && !this.reconnectUpdateInterval) {
        this.setAttribute("reconnectPending", "true");
        this.reconnectUpdateInterval = setInterval(updateReconnect, 1000);
        gAccountManager.disableCommandItems();
      }
    }

    refreshConnectedLabel() {
      let bundle = Services.strings.createBundle(
        "chrome://messenger/locale/imAccounts.properties"
      );
      let date =
        60 * Math.floor((Date.now() - this._account.timeOfLastConnect) / 60000);
      let value;
      if (date > 0) {
        let [val1, unit1, val2, unit2] = DownloadUtils.convertTimeUnits(date);
        if (!val2) {
          value = bundle.formatStringFromName("account.connectedForSingle", [
            val1,
            unit1,
          ]);
        } else {
          value = bundle.formatStringFromName("account.connectedForDouble", [
            val1,
            unit1,
            val2,
            unit2,
          ]);
        }
      } else {
        value = bundle.GetStringFromName("account.connectedForSeconds");
      }
      this._connectedLabel.value = value;
    }

    _cancelReconnectTimer() {
      this.removeAttribute("reconnectPending");
      clearInterval(this.reconnectUpdateInterval);
      delete this.reconnectUpdateInterval;
      gAccountManager.disableCommandItems();
    }

    cancelReconnection() {
      if (this.reconnectUpdateInterval) {
        this._cancelReconnectTimer();
        this._account.cancelReconnection();
      }
    }

    destroy() {
      // If we have a reconnect timer, stop it:
      // it will throw errors otherwise (see bug 480).
      if (!this.reconnectUpdateInterval) {
        return;
      }
      clearInterval(this.reconnectUpdateInterval);
      delete this.reconnectUpdateInterval;
    }

    get activeButton() {
      let action = this.account.disconnected
        ? ".connectButton"
        : ".disconnectButton";
      return this.querySelector(action);
    }

    setFocus() {
      let focusTarget = this.activeButton;
      let accountName = this.getAttribute("name");
      focusTarget.setAttribute(
        "aria-label",
        focusTarget.label + " " + accountName
      );
      if (focusTarget.disabled) {
        focusTarget = document.getElementById("accountlist");
      }
      focusTarget.focus();
    }

    proceedDefaultAction() {
      this.activeButton.click();
    }
  }

  MozXULElement.implementCustomInterface(MozChatAccountRichlistitem, [
    Ci.nsIDOMXULSelectControlItemElement,
  ]);

  customElements.define(
    "chat-account-richlistitem",
    MozChatAccountRichlistitem,
    { extends: "richlistitem" }
  );
}
