/* 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.EXPORTED_SYMBOLS = ["Accounts"];

const { utils: Cu } = Components;

Cu.import("resource://gre/modules/Deprecated.jsm"); /*global Deprecated */
Cu.import("resource://gre/modules/Messaging.jsm"); /*global Messaging */
Cu.import("resource://gre/modules/Promise.jsm"); /*global Promise */
Cu.import("resource://gre/modules/Services.jsm"); /*global Services */

/**
 * A promise-based API for querying the existence of Sync accounts,
 * and accessing the Sync setup wizard.
 *
 * Usage:
 *
 *   Cu.import("resource://gre/modules/Accounts.jsm");
 *   Accounts.anySyncAccountsExist().then(
 *     (exist) => {
 *       console.log("Accounts exist? " + exist);
 *       if (!exist) {
 *         Accounts.launchSetup();
 *       }
 *     },
 *     (err) => {
 *       console.log("We failed so hard.");
 *     }
 *   );
 */
var Accounts = Object.freeze({
  _accountsExist: function (kind) {
    return Messaging.sendRequestForResult({
      type: "Accounts:Exist",
      kind: kind
    }).then(data => data.exists);
  },

  firefoxAccountsExist: function () {
    return this._accountsExist("fxa");
  },

  syncAccountsExist: function () {
    Deprecated.warning("The legacy Sync account type has been removed from Firefox for Android. " +
                       "Please use `firefoxAccountsExist` instead.",
                       "https://developer.mozilla.org/en-US/Add-ons/Firefox_for_Android/API/Accounts.jsm");
    return Promise.resolve(false);
  },

  anySyncAccountsExist: function () {
    return this._accountsExist("any");
  },

  /**
   * Fire-and-forget: open the Firefox accounts activity, which
   * will be the Getting Started screen if FxA isn't yet set up.
   *
   * Optional extras are passed, as a JSON string, to the Firefox
   * Account Getting Started activity in the extras bundle of the
   * activity launch intent, under the key "extras".
   *
   * There is no return value from this method.
   */
  launchSetup: function (extras) {
    Messaging.sendRequest({
      type: "Accounts:Create",
      extras: extras
    });
  },

  _addDefaultEndpoints: function (json) {
    let newData = Cu.cloneInto(json, {}, { cloneFunctions: false });
    let associations = {
      authServerEndpoint: 'identity.fxaccounts.auth.uri',
      profileServerEndpoint: 'identity.fxaccounts.remote.profile.uri',
      tokenServerEndpoint: 'identity.sync.tokenserver.uri'
    };
    for (let key in associations) {
      newData[key] = newData[key] || Services.urlFormatter.formatURLPref(associations[key]);
    }
    return newData;
  },

  /**
   * Create a new Android Account corresponding to the given
   * fxa-content-server "login" JSON datum.  The new account will be
   * in the "Engaged" state, and will start syncing immediately.
   *
   * It is an error if an Android Account already exists.
   *
   * Returns a Promise that resolves to a boolean indicating success.
   */
  createFirefoxAccountFromJSON: function (json) {
    return Messaging.sendRequestForResult({
      type: "Accounts:CreateFirefoxAccountFromJSON",
      json: this._addDefaultEndpoints(json)
    });
  },

  /**
   * Move an existing Android Account to the "Engaged" state with the given
   * fxa-content-server "login" JSON datum.  The account will (re)start
   * syncing immediately, unless the user has manually configured the account
   * to not Sync.
   *
   * It is an error if no Android Account exists.
   *
   * Returns a Promise that resolves to a boolean indicating success.
   */
  updateFirefoxAccountFromJSON: function (json) {
    return Messaging.sendRequestForResult({
      type: "Accounts:UpdateFirefoxAccountFromJSON",
      json: this._addDefaultEndpoints(json)
    });
  },

  /**
   * Notify that profile for Android Account has updated.
   * The account will re-fetch the profile image.
   *
   * It is an error if no Android Account exists.
   *
   * There is no return value from this method.
   */
  notifyFirefoxAccountProfileChanged: function () {
    Messaging.sendRequest({
      type: "Accounts:ProfileUpdated",
    });
  },

  /**
   * Fetch information about an existing Android Firefox Account.
   *
   * Returns a Promise that resolves to null if no Android Firefox Account
   * exists, or an object including at least a string-valued 'email' key.
   */
  getFirefoxAccount: function () {
    return Messaging.sendRequestForResult({
      type: "Accounts:Exist",
      kind: "fxa",
    }).then(data => {
      if (!data || !data.exists) {
        return null;
      }
      delete data.exists;
      return data;
    });
  },

  /**
   * Delete an existing Android Firefox Account.
   *
   * It is an error if no Android Account exists.
   *
   * Returns a Promise that resolves to a boolean indicating success.
   */
  deleteFirefoxAccount: function () {
    return Messaging.sendRequestForResult({
      type: "Accounts:DeleteFirefoxAccount",
    });
  },

  showSyncPreferences: function () {
    // Only show Sync preferences of an existing Android Account.
    return Accounts.getFirefoxAccount().then(account => {
      if (!account) {
        throw new Error("Can't show Sync preferences of non-existent Firefox Account!");
      }
      return Messaging.sendRequestForResult({
        type: "Accounts:ShowSyncPreferences"
      });
    });
  }
});