/* 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/. */

/**
 * This module exports the SyncedTabsErrorHandler singleton, which handles
 * error states for synced tabs.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  UIState: "resource://services-sync/UIState.sys.mjs",
});

XPCOMUtils.defineLazyGetter(lazy, "syncUtils", () => {
  return ChromeUtils.importESModule("resource://services-sync/util.sys.mjs")
    .Utils;
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gNetworkLinkService",
  "@mozilla.org/network/network-link-service;1",
  "nsINetworkLinkService"
);

const FXA_ENABLED = "identity.fxaccounts.enabled";
const NETWORK_STATUS_CHANGED = "network:offline-status-changed";
const SYNC_SERVICE_ERROR = "weave:service:sync:error";
const SYNC_SERVICE_FINISHED = "weave:service:sync:finish";
const TOPIC_DEVICESTATE_CHANGED = "firefox-view.devicestate.changed";

const ErrorType = Object.freeze({
  SYNC_ERROR: "sync-error",
  FXA_ADMIN_DISABLED: "fxa-admin-disabled",
  NETWORK_OFFLINE: "network-offline",
  SYNC_DISCONNECTED: "sync-disconnected",
  PASSWORD_LOCKED: "password-locked",
  SIGNED_OUT: "signed-out",
});

export const SyncedTabsErrorHandler = {
  init() {
    this.networkIsOnline =
      lazy.gNetworkLinkService.linkStatusKnown &&
      lazy.gNetworkLinkService.isLinkUp;
    this.syncIsConnected = lazy.UIState.get().syncEnabled;
    this.syncIsWorking = true;

    Services.obs.addObserver(this, NETWORK_STATUS_CHANGED);
    Services.obs.addObserver(this, lazy.UIState.ON_UPDATE);
    Services.obs.addObserver(this, SYNC_SERVICE_ERROR);
    Services.obs.addObserver(this, SYNC_SERVICE_FINISHED);
    Services.obs.addObserver(this, TOPIC_DEVICESTATE_CHANGED);

    return this;
  },

  get fxaSignedIn() {
    let { UIState } = lazy;
    let syncState = UIState.get();
    return (
      UIState.isReady() &&
      syncState.status === UIState.STATUS_SIGNED_IN &&
      // syncEnabled just checks the "services.sync.username" pref has a value
      syncState.syncEnabled
    );
  },

  getErrorType() {
    // this ordering is important for dealing with multiple errors at once
    const errorStates = {
      [ErrorType.NETWORK_OFFLINE]: !this.networkIsOnline,
      [ErrorType.FXA_ADMIN_DISABLED]: Services.prefs.prefIsLocked(FXA_ENABLED),
      [ErrorType.PASSWORD_LOCKED]: this.isPrimaryPasswordLocked,
      [ErrorType.SIGNED_OUT]:
        lazy.UIState.get().status === lazy.UIState.STATUS_LOGIN_FAILED,
      [ErrorType.SYNC_DISCONNECTED]: !this.syncIsConnected,
      [ErrorType.SYNC_ERROR]: !this.syncIsWorking && !this.syncHasWorked,
    };

    for (let [type, value] of Object.entries(errorStates)) {
      if (value) {
        return type;
      }
    }
    return null;
  },

  getFluentStringsForErrorType(type) {
    return Object.freeze(this._errorStateStringMappings[type]);
  },

  get isPrimaryPasswordLocked() {
    return lazy.syncUtils.mpLocked();
  },

  isSyncReady() {
    const fxaStatus = lazy.UIState.get().status;
    return (
      this.networkIsOnline &&
      (this.syncIsWorking || this.syncHasWorked) &&
      !Services.prefs.prefIsLocked(FXA_ENABLED) &&
      // it's an error for sync to not be connected if we are signed-in,
      // or for sync to be connected if the FxA status is "login_failed",
      // which can happen if a user updates their password on another device
      ((!this.syncIsConnected && fxaStatus !== lazy.UIState.STATUS_SIGNED_IN) ||
        (this.syncIsConnected &&
          fxaStatus !== lazy.UIState.STATUS_LOGIN_FAILED)) &&
      // We treat a locked primary password as an error if we are signed-in.
      // If the user dismisses the prompt to unlock, they can use the "Try again" button to prompt again
      (!this.isPrimaryPasswordLocked || !this.fxaSignedIn)
    );
  },

  observe(_, topic, data) {
    switch (topic) {
      case NETWORK_STATUS_CHANGED:
        this.networkIsOnline = data == "online";
        break;
      case lazy.UIState.ON_UPDATE:
        this.syncIsConnected = lazy.UIState.get().syncEnabled;
        break;
      case SYNC_SERVICE_ERROR:
        if (lazy.UIState.get().status == lazy.UIState.STATUS_SIGNED_IN) {
          this.syncIsWorking = false;
        }
        break;
      case SYNC_SERVICE_FINISHED:
        if (!this.syncIsWorking) {
          this.syncIsWorking = true;
          this.syncHasWorked = true;
        }
        break;
      case TOPIC_DEVICESTATE_CHANGED:
        this.syncHasWorked = false;
    }
  },

  ErrorType,

  // We map the error state strings to Fluent string IDs so that it's easier
  // to change strings in the future without having to update all of the
  // error state strings.
  _errorStateStringMappings: {
    [ErrorType.SYNC_ERROR]: {
      header: "firefoxview-tabpickup-sync-error-header",
      description: "firefoxview-tabpickup-generic-sync-error-description",
      buttonLabel: "firefoxview-tabpickup-sync-error-primarybutton",
    },
    [ErrorType.FXA_ADMIN_DISABLED]: {
      header: "firefoxview-tabpickup-fxa-admin-disabled-header",
      description: "firefoxview-tabpickup-fxa-admin-disabled-description",
      // The button is hidden for this errorState, so we don't include the
      // buttonLabel property.
    },
    [ErrorType.NETWORK_OFFLINE]: {
      header: "firefoxview-tabpickup-network-offline-header",
      description: "firefoxview-tabpickup-network-offline-description",
      buttonLabel: "firefoxview-tabpickup-network-offline-primarybutton",
    },
    [ErrorType.SYNC_DISCONNECTED]: {
      header: "firefoxview-tabpickup-sync-disconnected-header",
      description: "firefoxview-tabpickup-sync-disconnected-description",
      buttonLabel: "firefoxview-tabpickup-sync-disconnected-primarybutton",
    },
    [ErrorType.PASSWORD_LOCKED]: {
      header: "firefoxview-tabpickup-password-locked-header",
      description: "firefoxview-tabpickup-password-locked-description",
      buttonLabel: "firefoxview-tabpickup-password-locked-primarybutton",
      link: {
        label: "firefoxview-tabpickup-password-locked-link",
        href:
          Services.urlFormatter.formatURLPref("app.support.baseURL") +
          "primary-password-stored-logins",
      },
    },
    [ErrorType.SIGNED_OUT]: {
      header: "firefoxview-tabpickup-signed-out-header",
      description: "firefoxview-tabpickup-signed-out-description",
      buttonLabel: "firefoxview-tabpickup-signed-out-primarybutton",
    },
  },
}.init();
