et the entitlement if not available.
        this.#setEntitlement(null);
        return { isEnrolledAndEntitled: false, error: data.error };
      }

      this.#setEntitlement(data.entitlement);
      return { isEnrolledAndEntitled: true };
    };

    this.#runningPromise = enrollAndEntitle().finally(() => {
      this.#runningPromise = null;
    });

    return this.#runningPromise;
  }

  // This method is static because we don't want to change the internal state
  // of the singleton.
  static async #maybeEnrollAndEntitle() {
    let isLinked = false;
    try {
      isLinked = await lazy.IPProtectionService.guardian.isLinkedToGuardian(
        /* only cache: */ false
      );
    } catch (error) {
      // If not linked, it's not an issue.
    }

    if (isLinked) {
      // Linked does not mean enrolled: it could be that the link comes from a
      // previous MozillaVPN subscription. Let's see if `fetchUserInfo` is able
      // to obtain the entitlement.
      const { status, entitlement } =
        await lazy.IPProtectionService.guardian.fetchUserInfo();
      if (status === 200) {
        return { entitlement };
      }
    }

    try {
      const enrollment = await lazy.IPProtectionService.guardian.enroll();
      if (!enrollment?.ok) {
        return { entitlement: null, error: enrollment?.error };
      }
    } catch (error) {
      return { enrollment: null, error: error?.message };
    }

    const { status, entitlement, error } =
      await lazy.IPProtectionService.guardian.fetchUserInfo();
    lazy.logConsole.debug("Entitlement:", { status, entitlement, error });

    // If we see an error during the READY state, let's trigger an error state.
    if (error || !entitlement || status != 200) {
      return { entitlement: null, error: error || `Status: ${status}` };
    }

    return { entitlement };
  }

  #setEntitlement(entitlement) {
    this.#entitlement = entitlement;
    lazy.IPPStartupCache.storeEntitlement(this.#entitlement);

    lazy.IPProtectionService.updateState();

    this.dispatchEvent(
      new CustomEvent("IPPEnrollAndEntitleManager:StateChanged", {
        bubbles: true,
        composed: true,
      })
    );
  }

  get isEnrolledAndEntitled() {
    return !!this.#entitlement;
  }

  /**
   * Checks if a user has upgraded.
   *
   * @returns {boolean}
   */
  get hasUpgraded() {
    return this.#entitlement?.subscribed;
  }

  /**
   * Checks if the entitlement exists and it contains a UUID
   */
  get hasEntitlementUid() {
    return !!this.#entitlement?.uid;
  }

  /**
   * Checks if we have the entitlement
   */
  get hasEntitlement() {
    return !!this.#entitlement;
  }

  /**
   * Checks if we're running the Alpha variant based on
   * available features
   */
  get isAlpha() {
    return (
      !this.#entitlement?.autostart &&
      !this.#entitlement?.website_inclusion &&
      !this.#entitlement?.location_controls
    );
  }

  async refetchEntitlement() {
    await this.maybeEnrollAndEntitle(true);
  }

  resetEntitlement() {
    this.#setEntitlement(null);
  }
}

const IPPEnrollAndEntitleManager = new IPPEnrollAndEntitleManagerSingleton();

export { IPPEnrollAndEntitleManager };
PK