= "open" || panelParent.state == "showing";
  }

  /**
   * Creates an instance of IPProtectionPanel for a specific browser window.
   *
   * Inserts the panel component customElements registry script.
   *
   * @param {Window} window
   *   Window containing the panelView to manage.
   */
  constructor(window) {
    this.handleEvent = this.#handleEvent.bind(this);

    this.state = {
      isSignedOut: !lazy.IPPSignInWatcher.isSignedIn,
      isProtectionEnabled:
        lazy.IPPProxyManager.state === lazy.IPPProxyStates.ACTIVE,
      location: {
        name: "United States",
        code: "us",
      },
      error: "",
      isAlpha: lazy.IPPEnrollAndEntitleManager.isAlpha,
      hasUpgraded: lazy.IPPEnrollAndEntitleManager.hasUpgraded,
      onboardingMessage: "",
      bandwidthWarning: "",
      paused: false,
    };

    if (window) {
      IPProtectionPanel.loadCustomElements(window);
    }

    this.#addProxyListeners();
  }

  /**
   * Set the state for this panel.
   *
   * Updates the current panel component state,
   * if the panel is currently active (showing or not hiding).
   *
   * @example
   * panel.setState({
   *  isSomething: true,
   * });
   *
   * @param {object} state
   *    The state object from IPProtectionPanel.
   */
  setState(state) {
    Object.assign(this.state, state);

    if (this.active) {
      this.updateState();
    }
  }

  /**
   * Updates the state of the panel component.
   *
   * @param {object} state
   *   The state object from IPProtectionPanel.
   * @param {Element} panelEl
   *   The panelEl element to update the state on.
   */
  updateState(state = this.state, panelEl = this.panel) {
    if (!panelEl?.isConnected || !panelEl.state) {
      return;
    }

    panelEl.state = state;
    panelEl.requestUpdate();
  }

  #startProxy() {
    lazy.IPPProxyManager.start();
  }

  #stopProxy() {
    lazy.IPPProxyManager.stop();
  }

  /**
   * Opens the help page in a new tab and closes the panel.
   *
   * @param {Event} e
   */
  static showHelpPage(e) {
    let win = e.target?.ownerGlobal;
    if (win) {
      win.openWebLinkIn(LINKS.SUPPORT_URL, "tab");
    }

    let panelParent = e.target?.closest("panel");
    if (panelParent) {
      panelParent.hidePopup();
    }
  }

  /**
   * Updates the visibility of the panel components before they will shown.
   *
   * - If the panel component has already been created, updates the state.
   * - Creates a panel component if need, state will be updated on once it has
   *   been connected.
   *
   * @param {XULElement} panelView
   *   The panelView element from the CustomizableUI widget callback.
   */
  showing(panelView) {
    if (this.initiatedUpgrade) {
      lazy.IPPEnrollAndEntitleManager.refetchEntitlement();
      this.initiatedUpgrade = false;
    }

    if (this.panel) {
      this.updateState();
    } else {
      this.#createPanel(panelView);
    }

    // TODO: Stop counting after all onboarding messages have been shown - Bug 1997332
    let currentCount = Services.prefs.getIntPref(
      "browser.ipProtection.panelOpenCount"
    );
    let updatedCount = currentCount + 1;
    Services.prefs.setIntPref(
      "browser.ipProtection.panelOpenCount",
      updatedCount
    );
  }

  /**
   * Called when the panel elements will be hidden.
   *
   * Disables updates to the panel.
   */
  hiding() {
    this.destroy();
  }

  /**
   * Creates a panel component in a panelView.
   *
   * @param {MozBrowser} panelView
   */
  #createPanel(panelView) {
    let { ownerDocument } = panelView;

    let headerArea = panelView.querySelector(
      `#${IPProtectionPanel.HEADER_AREA_ID}`
    );
    let headerButton = headerArea.querySelector(
      `#${IPProtectionPanel.HEADER_BUTTON_ID}`
    );
    if (!headerButton) {
      headerButton = this.#createHeaderButton(ownerDocument);
      headerArea.appendChild(headerButton);
    }
    // Reset the tab index to ensure it is focusable.
    headerButton.setAttribute("tabindex", "0");

    let contentEl = ownerDocument.createElement(
      IPProtectionPanel.CONTENT_TAGNAME
    );
    this.panel = contentEl;

    contentEl.dataset.capturesFocus = "true";

    this.#addPanelListeners(ownerDocument);

    let contentArea = panelView.querySelector(
      `#${IPProtectionPanel.CONTENT_AREA_ID}`
    );
    contentArea.appendChild(contentEl);
  }

  #createHeaderButton(ownerDocument) {
    const headerButton = ownerDocument.createXULElement("toolbarbutton");

    headerButton.id = IPProtectionPanel.HEADER_BUTTON_ID;
    headerButton.className = "panel-info-button";
    headerButton.dataset.capturesFocus = "true";

    ownerDocument.l10n.setAttributes(headerButton, "ipprotection-help-button");
    headerButton.addEventListener("click", IPProtectionPanel.showHelpPage);
    headerButton.addEventListener("keypress", e => {
      if (e.code == "Space" || e.code == "Enter") {
        IPProtectionPanel.showHelpPage(e);
      }
    });
    return headerButton;
  }

  /**
   * Open the IP Protection panel in the given window.
   *
   * @param {Window} window - which window to open the panel in.
   * @returns {Promise<void>}
   */
  async open(window) {
    if (!lazy.IPProtection.created || !window?.PanelUI) {
      return;
    }

    let widget = lazy.CustomizableUI.getWidget(IPProtectionPanel.WIDGET_ID);
    let anchor = widget.forWindow(window).anchor;
    await window.PanelUI.showSubView(IPProtectionPanel.PANEL_ID, anchor);
  }

  /**
   * Close the containing panel popup.
   */
  close() {
    let panelParent = this.panel?.closest("panel");
    if (!panelParent) {
      return;
    }
    panelParent.hidePopup();
  }

  /**
   * Start flow for signing in and then opening the panel on success
   */
  async startLoginFlow() {
    let window = this.panel.ownerGlobal;
    let browser = window.gBrowser;
    this.close();
    let isSignedIn = await lazy.IPProtectionService.startLoginFlow(browser);
    if (isSignedIn) {
      await this.open(window);
    }
  }

  /**
   * Remove added elements and listeners.
   */
  destroy() {
    if (this.panel) {
      this.panel.remove();
      this.#removePanelListeners(this.panel.ownerDocument);
      this.panel = null;
      if (this.state.error) {
        this.setState({
          error: "",
        });
      }
    }
  }

  uninit() {
    this.destroy();
    this.#removeProxyListeners();
  }

  #addPanelListeners(doc) {
    doc.addEventListener("IPProtection:Init", this.handleEvent);
    doc.addEventListener("IPProtection:ClickUpgrade", this.handleEvent);
    doc.addEventListener("IPProtection:Close", this.handleEvent);
    doc.addEventListener("IPProtection:UserEnable", this.handleEvent);
    doc.addEventListener("IPProtection:UserDisable", this.handleEvent);
    doc.addEventListener("IPProtection:SignIn", this.handleEvent);
    doc.addEventListener("IPProtection:UserShowSiteSettings", this.handleEvent);
  }

  #removePanelListeners(doc) {
    doc.removeEventListener("IPProtection:Init", this.handleEvent);
    doc.removeEventListener("IPProtection:ClickUpgrade", this.handleEvent);
    doc.removeEventListener("IPProtection:Close", this.handleEvent);
    doc.removeEventListener("IPProtection:UserEnable", this.handleEvent);
    doc.removeEventListener("IPProtection:UserDisable", this.handleEvent);
    doc.removeEventListener("IPProtection:SignIn", this.handleEvent);
    doc.removeEventListener(
      "IPProtection:UserShowSiteSettings",
      this.handleEvent
    );
  }

  #addProxyListeners() {
    lazy.IPProtectionService.addEventListener(
      "IPProtectionService:StateChanged",
      this.handleEvent
    );
    lazy.IPPProxyManager.addEventListener(
      "IPPProxyManager:StateChanged",
      this.handleEvent
    );
    lazy.IPPEnrollAndEntitleManager.addEventListener(
      "IPPEnrollAndEntitleManager:StateChanged",
      this.handleEvent
    );
  }

  #removeProxyListeners() {
    lazy.IPPEnrollAndEntitleManager.removeEventListener(
      "IPPEnrollAndEntitleManager:StateChanged",
      this.handleEvent
    );
    lazy.IPPProxyManager.removeEventListener(
      "IPPProxyManager:StateChanged",
      this.handleEvent
    );
    lazy.IPProtectionService.removeEventListener(
      "IPProtectionService:StateChanged",
      this.handleEvent
    );
  }

  #handleEvent(event) {
    if (event.type == "IPProtection:Init") {
      this.updateState();
    } else if (event.type == "IPProtection:Close") {
      this.close();
    } else if (event.type == "IPProtection:UserEnable") {
      this.#startProxy();
      Services.prefs.setBoolPref("browser.ipProtection.userEnabled", true);
    } else if (event.type == "IPProtection:UserDisable") {
      this.#stopProxy();
      Services.prefs.setBoolPref("browser.ipProtection.userEnabled", false);
    } else if (event.type == "IPProtection:ClickUpgrade") {
      // Let the service know that we tried upgrading at least once
      this.initiatedUpgrade = true;
      this.close();
    } else if (event.type == "IPProtection:SignIn") {
      this.startLoginFlow();
    } else if (
      event.type == "IPPProxyManager:StateChanged" ||
      event.type == "IPProtectionService:StateChanged" ||
      event.type === "IPPEnrollAndEntitleManager:StateChanged"
    ) {
      let hasError =
        lazy.IPPProxyManager.state === lazy.IPPProxyStates.ERROR &&
        lazy.IPPProxyManager.errors.includes(ERRORS.GENERIC);

      this.setState({
        isSignedOut: !lazy.IPPSignInWatcher.isSignedIn,
        isProtectionEnabled:
          lazy.IPPProxyManager.state === lazy.IPPProxyStates.ACTIVE,
        hasUpgraded: lazy.IPPEnrollAndEntitleManager.hasUpgraded,
        error: hasError ? ERRORS.GENERIC : "",
      });
    } else if (event.type == "IPProtection:UserShowSiteSettings") {
      // TODO: show subview for site settings (Bug 1997413)
    }
  }
}
PK