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

// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-button-group.mjs";
import { MigrationWizardConstants } from "chrome://browser/content/migration/migration-wizard-constants.mjs";

/**
 * This component contains the UI that steps users through migrating their
 * data from other browsers to this one. This component only contains very
 * basic logic and structure for the UI, and most of the state management
 * occurs in the MigrationWizardChild JSWindowActor.
 */
export class MigrationWizard extends HTMLElement {
  static #template = null;

  #deck = null;
  #browserProfileSelector = null;
  #browserProfileSelectorList = null;
  #resourceTypeList = null;
  #shadowRoot = null;
  #importButton = null;
  #importFromFileButton = null;
  #chooseImportFromFile = null;
  #getPermissionsButton = null;
  #safariPermissionButton = null;
  #selectAllCheckbox = null;
  #resourceSummary = null;
  #expandedDetails = false;
  #extensionsSuccessLink = null;
  #supportTextLinks = null;
  #safariPasswordImportInstructions = null;

  static get markup() {
    return `
      <template>
        <link rel="stylesheet" href="chrome://browser/skin/migration/migration-wizard.css">
        <named-deck id="wizard-deck" selected-view="page-loading" aria-busy="true" part="deck">
          <div name="page-loading">
            <h1 class="migration-wizard-header" data-l10n-id="migration-wizard-selection-header" part="header"></h1>
            <div class="loading-block large"></div>
            <div class="loading-block small"></div>
            <div class="loading-block small"></div>
            <moz-button-group class="buttons" part="buttons">
              <!-- If possible, use the same button labels as the SELECTION page with the same strings.
                   That'll prevent flicker when the load state exits if we then enter the SELECTION page. -->
              <button class="cancel-close" data-l10n-id="migration-cancel-button-label" disabled></button>
              <button class="migration-import-button" data-l10n-id="migration-import-button-label" disabled></button>
            </moz-button-group>
          </div>

          <div name="page-selection">
            <h1 class="migration-wizard-header" data-l10n-id="migration-wizard-selection-header" part="header"></h1>
            <p class="migration-wizard-subheader" part="subheader" hidden=""></p>
            <button id="browser-profile-selector" aria-haspopup="menu" aria-labelledby="migrator-name profile-name">
              <span class="migrator-icon" role="presentation"></span>
              <div class="migrator-description" role="presentation">
                <div id="migrator-name">&nbsp;</div>
                <div id="profile-name" class="text-deemphasized"></div>
              </div>
              <span class="dropdown-icon" role="presentation"></span>
            </button>
            <div class="no-resources-found error-message">
              <span class="error-icon" role="img"></span>
              <div data-l10n-id="migration-wizard-import-browser-no-resources"></div>
            </div>

            <div class="no-permissions-message">
              <p data-l10n-id="migration-no-permissions-message">
              </p>
              <p data-l10n-id="migration-no-permissions-instructions">
              </p>
              <ol>
                <li data-l10n-id="migration-no-permissions-instructions-step1"></li>
                <li class="migration-no-permissions-instructions-step2" data-l10n-id="migration-no-permissions-instructions-step2" data-l10n-args='{"permissionsPath": "" }'>
                  <code></code>
                </li>
              </ol>
            </div>

            <div data-l10n-id="migration-wizard-selection-list" class="resource-selection-preamble text-deemphasized hide-on-error"></div>
            <details class="resource-selection-details hide-on-error">
              <summary id="resource-selection-summary">
                <div class="selected-data-header" data-l10n-id="migration-all-available-data-label"></div>
                <div class="selected-data text-deemphasized">&nbsp;</div>
                <span class="expand-collapse-icon" role="img"></span>
              </summary>
              <fieldset id="resource-type-list">
                <label id="select-all">
                  <input type="checkbox" class="select-all-checkbox"/><span data-l10n-id="migration-select-all-option-label"></span>
                </label>
                <label id="bookmarks" class="resource-type-label" data-resource-type="BOOKMARKS"/>
                  <input type="checkbox"/><span default-data-l10n-id="migration-bookmarks-option-label" ie-edge-data-l10n-id="migration-favorites-option-label"></span>
                </label>
                <label id="logins-and-passwords" class="resource-type-label" data-resource-type="PASSWORDS">
                  <input type="checkbox"/><span data-l10n-id="migration-passwords-option-label"></span>
                </label>
                <label id="history" class="resource-type-label" data-resource-type="HISTORY">
                  <input type="checkbox"/><span data-l10n-id="migration-history-option-label"></span>
                </label>
                <label id="extensions" class="resource-type-label" data-resource-type="EXTENSIONS">
                  <input type="checkbox"/><span data-l10n-id="migration-extensions-option-label"></span>
                </label>
                <label id="form-autofill" class="resource-type-label" data-resource-type="FORMDATA">
                  <input type="checkbox"/><span data-l10n-id="migration-form-autofill-option-label"></span>
                </label>
                <label id="payment-methods" class="resource-type-label" data-resource-type="PAYMENT_METHODS">
                  <input type="checkbox"/><span data-l10n-id="migration-payment-methods-option-label"></span>
                </label>
              </fieldset>
            </details>

            <div class="file-import-error error-message">
              <span class="error-icon" role="img"></span>
              <div id="file-import-error-message"></div>
            </div>

            <moz-button-group class="buttons" part="buttons">
              <button class="cancel-close" data-l10n-id="migration-cancel-button-label"></button>
              <button id="import-from-file" class="primary" data-l10n-id="migration-import-from-file-button-label"></button>
              <button id="import" class="primary migration-import-button" data-l10n-id="migration-import-button-label"></button>
              <button id="get-permissions" class="primary" data-l10n-id="migration-continue-button-label"></button>
            </moz-button-group>
          </div>

          <div name="page-progress">
            <h1 id="progress-header" data-l10n-id="migration-wizard-progress-header" part="header"></h1>
            <div class="resource-progress">
              <div data-resource-type="BOOKMARKS" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span default-data-l10n-id="migration-bookmarks-option-label" ie-edge-data-l10n-id="migration-favorites-option-label"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
                <a class="support-text text-deemphasized"></a>
              </div>

              <div data-resource-type="PASSWORDS" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-passwords-option-label"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
                <a class="support-text text-deemphasized"></a>
              </div>

              <div data-resource-type="HISTORY" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-history-option-label"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
                <a class="support-text text-deemphasized"></a>
              </div>

              <div data-resource-type="EXTENSIONS" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-extensions-option-label"></span>
                <a id="extensions-success-link" href="about:addons" class="message-text text-deemphasized"></a>
                <span class="message-text text-deemphasized"></span>
                <a class="support-text text-deemphasized"></a>
              </div>

              <div data-resource-type="FORMDATA" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-form-autofill-option-label"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
                <a class="support-text text-deemphasized"></a>
              </div>

              <div data-resource-type="PAYMENT_METHODS" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-payment-methods-option-label"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
                <a class="support-text text-deemphasized"></a>
              </div>

              <div data-resource-type="COOKIES" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-cookies-option-label"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
                <a class="support-text text-deemphasized"></a>
              </div>

              <div data-resource-type="SESSION" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-session-option-label"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
                <a class="support-text text-deemphasized"></a>
              </div>

              <div data-resource-type="OTHERDATA" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-otherdata-option-label"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
                <a class="support-text text-deemphasized"></a>
              </div>
            </div>
            <moz-button-group class="buttons" part="buttons">
              <button class="cancel-close" data-l10n-id="migration-cancel-button-label" disabled></button>
              <button class="primary finish-button done-button" data-l10n-id="migration-done-button-label"></button>
              <button class="primary finish-button continue-button" data-l10n-id="migration-continue-button-label"></button>
            </moz-button-group>
          </div>

          <div name="page-file-import-progress">
            <h1 id="file-import-progress-header"part="header"></h1>
            <div class="resource-progress">
              <div data-resource-type="PASSWORDS_FROM_FILE" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-passwords-from-file"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
              </div>

              <div data-resource-type="PASSWORDS_NEW" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-passwords-new"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
              </div>

              <div data-resource-type="PASSWORDS_UPDATED" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-passwords-updated"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
              </div>

              <div data-resource-type="BOOKMARKS_FROM_FILE" class="resource-progress-group">
                <span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
                <span data-l10n-id="migration-bookmarks-from-file"></span>
                <span class="message-text text-deemphasized">&nbsp;</span>
              </div>
            </div>
            <moz-button-group class="buttons" part="buttons">
              <button class="cancel-close" data-l10n-id="migration-cancel-button-label" disabled></button>
              <button class="primary finish-button done-button" data-l10n-id="migration-done-button-label"></button>
              <button class="primary finish-button continue-button" data-l10n-id="migration-continue-button-label"></button>
            </moz-button-group>
          </div>

          <div name="page-safari-password-permission-pre-sequoia">
            <h1 data-l10n-id="migration-safari-password-import-header" part="header"></h1>
            <span data-l10n-id="migration-safari-password-import-steps-header"></span>
            <ol>
              <li data-l10n-id="migration-safari-password-import-step1"></li>
              <li data-l10n-id="migration-safari-password-import-step2"><img class="safari-icon-3dots" data-l10n-name="safari-icon-3dots"/></li>
              <li data-l10n-id="migration-safari-password-import-step3"></li>
              <li class="safari-icons-group">
                <span data-l10n-id="migration-safari-password-import-step4"></span>
                <span class="page-portrait-icon"></span>
              </li>
            </ol>
            <moz-button-group class="buttons" part="buttons">
              <button class="manual-password-import-skip" data-l10n-id="migration-manual-password-import-skip-button"></button>
              <button class="manual-password-import-select primary" data-l10n-id="migration-manual-password-import-select-button"></button>
            </moz-button-group>
          </div>

          <div name="page-safari-password-permission">
            <h1 data-l10n-id="migration-safari-password-import-header" part="header"></h1>
            <ol id="migration-safari-password-import-instructions">
              <li data-l10n-id="migration-safari-password-import-post-sequoia-step1"><a id="launch-macos-passwords-app" data-l10n-name="macos-passwords-app"></a></li>
              <li data-l10n-id="migration-safari-password-import-post-sequoia-step2"></li>
              <li data-l10n-id="migration-safari-password-import-post-sequoia-step3"></li>
              <li data-l10n-id="migration-safari-password-import-post-sequoia-step4"></li>
            </ol>
            <moz-button-group class="buttons" part="buttons">
              <button class="manual-password-import-skip" data-l10n-id="migration-manual-password-import-cancel-button"></button>
              <button class="manual-password-import-select primary" data-l10n-id="migration-manual-password-import-select-button"></button>
            </moz-button-group>
          </div>

          <div name="page-chrome-windows-password-permission">
            <h1 data-l10n-id="migration-chrome-windows-password-import-header" part="header"></h1>
            <span data-l10n-id="migration-chrome-windows-password-import-steps-header"></span>
            <ol>
              <li data-l10n-id="migration-chrome-windows-password-import-step1"><img class="chrome-icon-3dots" data-l10n-name="chrome-icon-3dots"/></li>
              <li data-l10n-id="migration-chrome-windows-password-import-step2"></li>
              <li data-l10n-id="migration-chrome-windows-password-import-step3"></li>
            </ol>
            <p>
              <span data-l10n-id="migration-chrome-windows-password-import-step4"></span>
            </p>
            <moz-button-group class="buttons" part="buttons">
              <button class="manual-password-import-skip" data-l10n-id="migration-manual-password-import-skip-button"></button>
              <button class="manual-password-import-select primary" data-l10n-id="migration-manual-password-import-select-button"></button>
            </moz-button-group>
          </div>

          <div name="page-safari-permission">
            <h1 data-l10n-id="migration-wizard-selection-header" part="header"></h1>
            <div data-l10n-id="migration-wizard-safari-permissions-sub-header"></div>
            <ol>
              <li data-l10n-id="migration-wizard-safari-instructions-continue"></li>
              <li data-l10n-id="migration-wizard-safari-instructions-folder"></li>
            </ol>
            <moz-button-group class="buttons" part="buttons">
              <button class="cancel-close" data-l10n-id="migration-cancel-button-label"></button>
              <button id="safari-request-permissions" class="primary" data-l10n-id="migration-continue-button-label"></button>
            </moz-button-group>
          </div>

          <div name="page-no-browsers-found">
            <h1 data-l10n-id="migration-wizard-selection-header" part="header"></h1>
            <div class="no-browsers-found error-message">
              <span class="error-icon" role="img"></span>
              <div class="no-browsers-found-message" data-l10n-id="migration-wizard-import-browser-no-browsers"></div>
            </div>
            <moz-button-group class="buttons" part="buttons">
              <button class="cancel-close" data-l10n-id="migration-cancel-button-label"></button>
              <button id="choose-import-from-file" class="primary" data-l10n-id="migration-choose-to-import-from-file-button-label"></button>
            </moz-button-group>
          </div>
        </named-deck>
        <slot></slot>
      </template>
    `;
  }

  static get fragment() {
    if (!MigrationWizard.#template) {
      let parser = new DOMParser();
      let doc = parser.parseFromString(MigrationWizard.markup, "text/html");
      MigrationWizard.#template = document.importNode(
        doc.querySelector("template"),
        true
      );
    }
    return MigrationWizard.#template.content.cloneNode(true);
  }

  constructor() {
    super();
    const shadow = this.attachShadow({ mode: "open" });

    if (window.MozXULElement) {
      window.MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
      window.MozXULElement.insertFTLIfNeeded("browser/migrationWizard.ftl");
    }
    document.l10n.connectRoot(shadow);

    shadow.appendChild(MigrationWizard.fragment);

    this.#deck = shadow.querySelector("#wizard-deck");
    this.#browserProfileSelector = shadow.querySelector(
      "#browser-profile-selector"
    );
    this.#resourceSummary = shadow.querySelector("#resource-selection-summary");
    this.#resourceSummary.addEventListener("click", this);

    let cancelCloseButtons = shadow.querySelectorAll(".cancel-close");
    for (let button of cancelCloseButtons) {
      button.addEventListener("click", this);
    }

    let finishButtons = shadow.querySelectorAll(".finish-button");
    for (let button of finishButtons) {
      button.addEventListener("click", this);
    }

    this.#importButton = shadow.querySelector("#import");
    this.#importButton.addEventListener("click", this);
    this.#importFromFileButton = shadow.querySelector("#import-from-file");
    this.#importFromFileButton.addEventListener("click", this);
    this.#chooseImportFromFile = shadow.querySelector(
      "#choose-import-from-file"
    );
    this.#chooseImportFromFile.addEventListener("click", this);
    this.#getPermissionsButton = shadow.querySelector("#get-permissions");
    this.#getPermissionsButton.addEventListener("click", this);

    this.#browserProfileSelector.addEventListener("click", this);
    this.#browserProfileSelector.addEventListener("mousedown", this);
    this.#resourceTypeList = shadow.querySelector("#resource-type-list");
    this.#resourceTypeList.addEventListener("change", this);

    this.#safariPermissionButton = shadow.querySelector(
      "#safari-request-permissions"
    );
    this.#safariPermissionButton.addEventListener("click", this);

    this.#selectAllCheckbox = shadow.querySelector("#select-all").control;

    let manualPasswordImportSkipButtons = shadow.querySelectorAll(
      ".manual-password-import-skip"
    );
    for (let button of manualPasswordImportSkipButtons) {
      button.addEventListener("click", this);
    }

    let manualPasswordImportSelectButtons = shadow.querySelectorAll(
      ".manual-password-import-select"
    );
    for (let button of manualPasswordImportSelectButtons) {
      button.addEventListener("click", this);
    }

    this.#extensionsSuccessLink = shadow.querySelector(
      "#extensions-success-link"
    );
    this.#extensionsSuccessLink.addEventListener("click", this);

    this.#supportTextLinks = shadow.querySelectorAll(".support-text");
    this.#supportTextLinks.forEach(link =>
      link.addEventListener("click", this)
    );

    // We put the click event listener on the instructions list rather than
    // directly on the launch-macos-passwords-app node, since Fluent will
    // replace that node after this constructor has run.
    this.#safariPasswordImportInstructions = shadow.querySelector(
      "#migration-safari-password-import-instructions"
    );
    this.#safariPasswordImportInstructions.addEventListener("click", this);

    this.#shadowRoot = shadow;
  }

  connectedCallback() {
    if (this.hasAttribute("auto-request-state")) {
      this.requestState();
    }
  }

  requestState() {
    this.dispatchEvent(
      new CustomEvent("MigrationWizard:RequestState", { bubbles: true })
    );
  }

  /**
   * This setter can be used in the event that the MigrationWizard is being
   * inserted via Lit, and the caller wants to set state declaratively using
   * a property expression.
   *
   * @param {object} state
   *   The state object to pass to setState.
   * @see MigrationWizard.setState.
   */
  set state(state) {
    this.setState(state);
  }

  /**
   * This is the main entrypoint for updating the state and appearance of
   * the wizard.
   *
   * @param {object} state The state to be represented by the component.
   * @param {string} state.page The page of the wizard to display. This should
   *   be one of the MigrationWizardConstants.PAGES constants.
   */
  setState(state) {
    switch (state.page) {
      case MigrationWizardConstants.PAGES.SELECTION: {
        this.#onShowingSelection(state);
        break;
      }
      case MigrationWizardConstants.PAGES.PROGRESS: {
        this.#onShowingProgress(state);
        break;
      }
      case MigrationWizardConstants.PAGES.FILE_IMPORT_PROGRESS: {
        this.#onShowingFileImportProgress(state);
        break;
      }
      case MigrationWizardConstants.PAGES.NO_BROWSERS_FOUND: {
        this.#onShowingNoBrowsersFound(state);
        break;
      }
    }

    this.#deck.toggleAttribute(
      "aria-busy",
      state.page == MigrationWizardConstants.PAGES.LOADING
    );
    this.#deck.setAttribute("selected-view", `page-${state.page}`);

    if (window.IS_STORYBOOK) {
      this.#updateForStorybook();
    }
  }

  get #dialogMode() {
    return this.hasAttribute("dialog-mode");
  }

  #ensureSelectionDropdown() {
    if (this.#browserProfileSelectorList) {
      return;
    }
    this.#browserProfileSelectorList = document.createElement("panel-list");
    this.#browserProfileSelectorList.toggleAttribute(
      "min-width-from-anchor",
      true
    );
    this.#browserProfileSelectorList.addEventListener("click", this);

    if (document.createXULElement) {
      let panel = document.createXULElement("panel");
      panel.appendChild(this.#browserProfileSelectorList);
      this.#shadowRoot.appendChild(panel);
    } else {
      this.#shadowRoot.appendChild(this.#browserProfileSelectorList);
    }
  }

  /**
   * Reacts to changes to the browser / profile selector dropdown. This
   * should update the list of resource types to match what's supported
   * by the selected migrator and profile.
   *
   *  @param {Element} panelItem the selected <panel-item>
   */
  #onBrowserProfileSelectionChanged(panelItem) {
    this.#browserProfileSelector.selectedPanelItem = panelItem;
    if (this.#browserProfileSelectorList.selectedPanelItem) {
      this.#browserProfileSelectorList.selectedPanelItem.classList.remove(
        "selected"
      );
    }
    this.#browserProfileSelectorList.selectedPanelItem = panelItem;
    this.#browserProfileSelectorList.selectedPanelItem.classList.add(
      "selected"
    );

    this.#browserProfileSelector.querySelector("#migrator-name").textContent =
      panelItem.displayName;
    this.#browserProfileSelector.querySelector("#profile-name").textContent =
      panelItem.profile?.name || "";

    if (panelItem.brandImage) {
      this.#browserProfileSelector.querySelector(
        ".migrator-icon"
      ).style.content = `url(${panelItem.brandImage})`;
    } else {
      this.#browserProfileSelector.querySelector(
        ".migrator-icon"
      ).style.content = "url(chrome://global/skin/icons/defaultFavicon.svg)";
    }

    let key = panelItem.getAttribute("key");
    const allowedTypes = ["BOOKMARKS"];
    let resourceTypes = panelItem.resourceTypes.filter(t =>
      allowedTypes.includes(t)
    );

    for (let child of this.#resourceTypeList.querySelectorAll(
      "label[data-resource-type]"
    )) {
      child.hidden = true;
      child.control.checked = false;
    }

    for (let resourceType of resourceTypes) {
      let resourceLabel = this.#resourceTypeList.querySelector(
        `label[data-resource-type="${resourceType}"]`
      );
      if (resourceLabel) {
        resourceLabel.hidden = false;
        resourceLabel.control.checked = true;

        let labelSpan = resourceLabel.querySelector(
          "span[default-data-l10n-id]"
        );
        if (labelSpan) {
          if (MigrationWizardConstants.USES_FAVORITES.includes(key)) {
            document.l10n.setAttributes(
              labelSpan,
              labelSpan.getAttribute("ie-edge-data-l10n-id")
            );
          } else {
            document.l10n.setAttributes(
              labelSpan,
              labelSpan.getAttribute("default-data-l10n-id")
            );
          }
        }
      }
    }
    let selectAll = this.#shadowRoot.querySelector("#select-all").control;
    selectAll.checked = true;

    this.#displaySelectedResources();
    this.#browserProfileSelector.selectedPanelItem = panelItem;

    let selectionPage = this.#shadowRoot.querySelector(
      "div[name='page-selection']"
    );
    selectionPage.setAttribute("migrator-type", panelItem.getAttribute("type"));

    // Safari currently has a special flow for requesting permissions that
    // occurs _after_ resource selection, so we don't show this message
    // for that migrator.
    let showNoPermissionsMessage =
      panelItem.getAttribute("type") ==
        MigrationWizardConstants.MIGRATOR_TYPES.BROWSER &&
      !panelItem.hasPermissions &&
      panelItem.getAttribute("key") != "safari";

    selectionPage.toggleAttribute("no-permissions", showNoPermissionsMessage);
    if (showNoPermissionsMessage) {
      let step2 = selectionPage.querySelector(
        ".migration-no-permissions-instructions-step2"
      );
      step2.setAttribute(
        "data-l10n-args",
        JSON.stringify({ permissionsPath: panelItem.permissionsPath })
      );

      this.dispatchEvent(
        new CustomEvent("MigrationWizard:PermissionsNeeded", {
          bubbles: true,
          detail: {
            key,
          },
        })
      );
    }

    selectionPage.toggleAttribute(
      "no-resources",
      panelItem.getAttribute("type") ==
        MigrationWizardConstants.MIGRATOR_TYPES.BROWSER &&
        !resourceTypes.length &&
        panelItem.hasPermissions
    );
  }

  /**
   * Called when showing the browser/profile selection page of the wizard.
   *
   * @param {object} state
   *   The state object passed into setState. The following properties are
   *   used:
   * @param {string[]} state.migrators
   *   An array of source browser names that can be migrated from.
   * @param {string} [state.migratorKey=null]
   *   The key for a migrator to automatically select in the migrators array.
   *   If not defined, the first item in the array will be selected.
   * @param {string} [state.fileImportErrorMessage=null]
   *   An error message to display in the event that an attempt at doing a
   *   file import failed. File import failures are special in that they send
   *   the wizard back to the selection page with an error message. If not
   *   defined, it is presumed that a file import error has not occurred.
   */
  #onShowingSelection(state) {
    this.#ensureSelectionDropdown();
    this.#browserProfileSelectorList.textContent = "";

    let selectionPage = this.#shadowRoot.querySelector(
      "div[name='page-selection']"
    );

    let header = selectionPage.querySelector(".migration-wizard-header");
    let selectionHeaderString = this.getAttribute("selection-header-string");

    if (this.hasAttribute("selection-header-string")) {
      header.textContent = selectionHeaderString;
      header.toggleAttribute("hidden", !selectionHeaderString);
    } else {
      header.removeAttribute("hidden");
    }

    let selectionSubheaderString = this.getAttribute(
      "selection-subheader-string"
    );
    let subheader = selectionPage.querySelector(".migration-wizard-subheader");
    subheader.textContent = selectionSubheaderString;
    subheader.toggleAttribute("hidden", !selectionSubheaderString);

    let details = this.#shadowRoot.querySelector("details");

    if (this.hasAttribute("force-show-import-all")) {
      let forceShowImportAll =
        this.getAttribute("force-show-import-all") == "true";
      selectionPage.toggleAttribute("show-import-all", forceShowImportAll);
      details.open = !forceShowImportAll;
    } else {
      selectionPage.toggleAttribute("show-import-all", state.showImportAll);
      details.open = !state.showImportAll;
    }

    this.#expandedDetails = false;

    this.#applyContentCustomizations();

    for (let migrator of state.migrators) {
      let opt = document.createElement("panel-item");
      opt.setAttribute("key", migrator.key);
      opt.setAttribute("type", migrator.type);
      opt.profile = migrator.profile;
      opt.displayName = migrator.displayName;
      opt.resourceTypes = migrator.resourceTypes;
      opt.hasPermissions = migrator.hasPermissions;
      opt.permissionsPath = migrator.permissionsPath;
      opt.brandImage = migrator.brandImage;

      let button = opt.shadowRoot.querySelector("button");
      if (migrator.brandImage) {
        button.style.backgroundImage = `url(${migrator.brandImage})`;
      }

      if (migrator.profile) {
        document.l10n.setAttributes(
          opt,
          "migration-wizard-selection-option-with-profile",
          {
            sourceBrowser: migrator.displayName,
            profileName: migrator.profile.name,
          }
        );
      } else {
        document.l10n.setAttributes(
          opt,
          "migration-wizard-selection-option-without-profile",
          {
            sourceBrowser: migrator.displayName,
          }
        );
      }

      this.#browserProfileSelectorList.appendChild(opt);
    }

    if (state.migrators.length) {
      this.#onBrowserProfileSelectionChanged(
        this.#browserProfileSelectorList.firstElementChild
      );
    }

    if (state.migratorKey) {
      let panelItem = this.#browserProfileSelectorList.querySelector(
        `panel-item[key="${state.migratorKey}"]`
      );
      this.#onBrowserProfileSelectionChanged(panelItem);
    }

    let fileImportErrorMessageEl = selectionPage.querySelector(
      "#file-import-error-message"
    );

    if (state.fileImportErrorMessage) {
      fileImportErrorMessageEl.textContent = state.fileImportErrorMessage;
      selectionPage.toggleAttribute("file-import-error", true);
    } else {
      fileImportErrorMessageEl.textContent = "";
      selectionPage.toggleAttribute("file-import-error", false);
    }

    // Since this is called before the named-deck actually switches to
    // show the selection page, we cannot focus this button immediately.
    // Instead, we use a rAF to queue this up for focusing before the
    // next paint.
    requestAnimationFrame(() => {
      this.#browserProfileSelector.focus({ focusVisible: false });
    });
  }

  /**
   * @typedef {object} ProgressState
   *  The migration progress state for a resource.
   * @property {number} value
   *  One of the values from MigrationWizardConstants.PROGRESS_VALUE.
   * @property {string} [message=undefined]
   *  An optional message to display underneath the resource in
   *  the progress dialog. This message is only shown when value
   *  is not LOADING.
   * @property {string} [linkURL=undefined]
   *  The URL for an optional link to appear after the status message.
   *  This will only be shown if linkText is also not-empty.
   * @property {string} [linkText=undefined]
   *  The text for an optional link to appear after the status message.
   *  This will only be shown if linkURL is also not-empty.
   */

  /**
   * @typedef {
   *   keyof typeof MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES
   * } DISPLAYED_RESOURCE_TYPES_KEYS
   */

  /**
   * Called when showing the progress / success page of the wizard.
   *
   * @param {object} state
   *   The state object passed into setState. The following properties are
   *   used:
   * @param {string} state.key
   *   The key of the migrator being used.
   * @param {Record<DISPLAYED_RESOURCE_TYPES_KEYS, ProgressState>} state.progress
   *   An object whose keys match one of DISPLAYED_RESOURCE_TYPES.
   *
   *   Any resource type not included in state.progress will be hidden.
   */
  #onShowingProgress(state) {
    // Any resource progress group not included in state.progress is hidden.
    let progressPage = this.#shadowRoot.querySelector(
      "div[name='page-progress']"
    );
    let resourceGroups = progressPage.querySelectorAll(
      ".resource-progress-group"
    );
    this.#extensionsSuccessLink.textContent = "";

    let totalProgressGroups = Object.keys(state.progress).length;
    let remainingProgressGroups = totalProgressGroups;
    let totalWarnings = 0;

    for (let group of resourceGroups) {
      let resourceType = group.dataset.resourceType;
      if (!state.progress.hasOwnProperty(resourceType)) {
        group.hidden = true;
        continue;
      }
      group.hidden = false;

      let progressIcon = group.querySelector(".progress-icon");
      let messageText = group.querySelector("span.message-text");
      let supportLink = group.querySelector(".support-text");

      let labelSpan = group.querySelector("span[default-data-l10n-id]");
      if (labelSpan) {
        if (MigrationWizardConstants.USES_FAVORITES.includes(state.key)) {
          document.l10n.setAttributes(
            labelSpan,
            labelSpan.getAttribute("ie-edge-data-l10n-id")
          );
        } else {
          document.l10n.setAttributes(
            labelSpan,
            labelSpan.getAttribute("default-data-l10n-id")
          );
        }
      }
      messageText.textContent = "";

      if (supportLink) {
        supportLink.textContent = "";
        supportLink.removeAttribute("href");
      }
      let progressValue = state.progress[resourceType].value;
      switch (progressValue) {
        case MigrationWizardConstants.PROGRESS_VALUE.LOADING: {
          document.l10n.setAttributes(
            progressIcon,
            "migration-wizard-progress-icon-in-progress"
          );
          progressIcon.setAttribute("state", "loading");
          messageText.textContent = "";
          supportLink.textContent = "";
          supportLink.removeAttribute("href");
          // With no status text, we re-insert the &nbsp; so that the status
          // text area does not fully collapse.
          messageText.appendChild(document.createTextNode("\u00A0"));
          break;
        }
        case MigrationWizardConstants.PROGRESS_VALUE.SUCCESS: {
          document.l10n.setAttributes(
            progressIcon,
            "migration-wizard-progress-icon-completed"
          );
          progressIcon.setAttribute("state", "success");
          messageText.textContent = state.progress[resourceType].message;
          if (
            resourceType ==
            MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS
          ) {
            messageText.textContent = "";
            this.#extensionsSuccessLink.target = "_blank";
            this.#extensionsSuccessLink.textContent =
              state.progress[resourceType].message;
          }
          remainingProgressGroups--;
          break;
        }
        case MigrationWizardConstants.PROGRESS_VALUE.WARNING: {
          document.l10n.setAttributes(
            progressIcon,
            "migration-wizard-progress-icon-completed"
          );
          progressIcon.setAttribute("state", "warning");
          messageText.textContent = state.progress[resourceType].message;
          supportLink.textContent = state.progress[resourceType].linkText;
          supportLink.href = state.progress[resourceType].linkURL;
          supportLink.target = "_blank";
          remainingProgressGroups--;
          totalWarnings++;
          break;
        }
        case MigrationWizardConstants.PROGRESS_VALUE.INFO: {
          document.l10n.setAttributes(
            progressIcon,
            "migration-wizard-progress-icon-completed"
          );
          progressIcon.setAttribute("state", "info");
          messageText.textContent = state.progress[resourceType].message;
          supportLink.textContent = state.progress[resourceType].linkText;
          supportLink.href = state.progress[resourceType].linkURL;
          supportLink.target = "_blank";
          if (
            resourceType ==
            MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS
          ) {
            messageText.textContent = "";
            this.#extensionsSuccessLink.target = "_blank";
            this.#extensionsSuccessLink.textContent =
              state.progress[resourceType].message;
          }
          remainingProgressGroups--;
          break;
        }
      }
    }

    let migrationDone = remainingProgressGroups == 0;
    let headerL10nID = "migration-wizard-progress-header";
    let header = this.#shadowRoot.getElementById("progress-header");

    if (migrationDone) {
      if (totalWarnings) {
        headerL10nID = "migration-wizard-progress-done-with-warnings-header";
      } else if (this.getAttribute("data-import-complete-success-string")) {
        header.textContent = this.getAttribute(
          "data-import-complete-success-string"
        );
      } else {
        headerL10nID = "migration-wizard-progress-done-header";
      }
    }

    document.l10n.setAttributes(header, headerL10nID);

    let finishButtons = progressPage.querySelectorAll(".finish-button");
    let cancelButton = progressPage.querySelector(".cancel-close");

    for (let finishButton of finishButtons) {
      finishButton.hidden = !migrationDone;
    }

    cancelButton.hidden = migrationDone;

    if (migrationDone) {
      // Since this might be called before the named-deck actually switches to
      // show the progress page, we cannot focus this button immediately.
      // Instead, we use a rAF to queue this up for focusing before the
      // next paint.
      requestAnimationFrame(() => {
        let button = this.#dialogMode
          ? progressPage.querySelector(".done-button")
          : progressPage.querySelector(".continue-button");
        button.focus({ focusVisible: false });
      });
    }
  }

  /**
   * @typedef {
   *   keyof typeof MigrationWizardConstants.DISPLAYED_FILE_RESOURCE_TYPES
   * } DISPLAYED_FILE_RESOURCE_TYPES_KEYS
   */

  /**
   * Called when showing the progress / success page of the wizard for
   * files.
   *
   * @param {object} state
   *   The state object passed into setState. The following properties are
   *   used:
   * @param {string} state.title
   *   The string to display in the header.
   * @param {Record<DISPLAYED_FILE_RESOURCE_TYPES_KEYS, ProgressState>} state.progress
   *   An object whose keys match one of DISPLAYED_FILE_RESOURCE_TYPES.
   *
   *   Any resource type not included in state.progress will be hidden.
   */
  #onShowingFileImportProgress(state) {
    // Any resource progress group not included in state.progress is hidden.
    let progressPage = this.#shadowRoot.querySelector(
      "div[name='page-file-import-progress']"
    );
    let resourceGroups = progressPage.querySelectorAll(
      ".resource-progress-group"
    );
    let totalProgressGroups = Object.keys(state.progress).length;
    let remainingProgressGroups = totalProgressGroups;

    for (let group of resourceGroups) {
      let resourceType = group.dataset.resourceType;
      if (!state.progress.hasOwnProperty(resourceType)) {
        group.hidden = true;
        continue;
      }
      group.hidden = false;

      let progressIcon = group.querySelector(".progress-icon");
      let messageText = group.querySelector(".message-text");

      let progressValue = state.progress[resourceType].value;
      switch (progressValue) {
        case MigrationWizardConstants.PROGRESS_VALUE.LOADING: {
          document.l10n.setAttributes(
            progressIcon,
            "migration-wizard-progress-icon-in-progress"
          );
          progressIcon.setAttribute("state", "loading");
          messageText.textContent = "";
          // With no status text, we re-insert the &nbsp; so that the status
          // text area does not fully collapse.
          messageText.appendChild(document.createTextNode("\u00A0"));
          break;
        }
        case MigrationWizardConstants.PROGRESS_VALUE.SUCCESS: {
          document.l10n.setAttributes(
            progressIcon,
            "migration-wizard-progress-icon-completed"
          );
          progressIcon.setAttribute("state", "success");
          messageText.textContent = state.progress[resourceType].message;
          remainingProgressGroups--;
          break;
        }
        case MigrationWizardConstants.PROGRESS_VALUE.WARNING: {
          document.l10n.setAttributes(
            progressIcon,
            "migration-wizard-progress-icon-completed"
          );
          progressIcon.setAttribute("state", "warning");
          messageText.textContent = state.progress[resourceType].message;
          remainingProgressGroups--;
          break;
        }
        default: {
          console.error(
            "Unrecognized state for file migration: ",
            progressValue
          );
        }
      }
    }

    let migrationDone = remainingProgressGroups == 0;
    let header = this.#shadowRoot.getElementById("file-import-progress-header");
    header.textContent = state.title;

    let doneButton = progressPage.querySelector(".primary");
    let cancelButton = progressPage.querySelector(".cancel-close");
    doneButton.hidden = !migrationDone;
    cancelButton.hidden = migrationDone;

    if (migrationDone) {
      // Since this might be called before the named-deck actually switches to
      // show the progress page, we cannot focus this button immediately.
      // Instead, we use a rAF to queue this up for focusing before the
      // next paint.
      requestAnimationFrame(() => {
        doneButton.focus({ focusVisible: false });
      });
    }
  }

  /**
   * Called when showing the "no browsers found" page of the wizard.
   *
   * @param {object} state
   *   The state object passed into setState. The following properties are
   *   used:
   * @param {string} state.hasFileMigrators
   *   True if at least one FileMigrator is available for use.
   */
  #onShowingNoBrowsersFound(state) {
    this.#chooseImportFromFile.hidden = !state.hasFileMigrators;
  }

  /**
   * Certain parts of the MigrationWizard need to be modified slightly
   * in order to work properly with Storybook. This method should be called
   * to apply those changes after changing state.
   */
  #updateForStorybook() {
    // The CSS mask used for the progress spinner cannot be loaded via
    // chrome:// URIs in Storybook. We work around this by exposing the
    // progress elements as custom parts that the MigrationWizard story
    // can style on its own.
    this.#shadowRoot.querySelectorAll(".progress-icon").forEach(progressEl => {
      if (progressEl.getAttribute("state") == "loading") {
        progressEl.setAttribute("part", "progress-spinner");
      } else {
        progressEl.removeAttribute("part");
      }
    });
  }

  /**
   * A public method for starting a migration without the user needing
   * to choose a browser, profile or resource types. This is typically
   * done only for doing a profile reset.
   *
   * @param {string} migratorKey
   *   The key associated with the migrator to use.
   * @param {object|null} profile
   *   A representation of a browser profile. When not null, this is an
   *   object with a string "id" property, and a string "name" property.
   * @param {string[]} resourceTypes
   *   An array of resource types that import should occur for. These
   *   strings should be from MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
   */
  doAutoImport(migratorKey, profile, resourceTypes) {
    let migrationEventDetail = this.#gatherMigrationEventDetails({
      migratorKey,
      profile,
      resourceTypes,
    });

    this.dispatchEvent(
      new CustomEvent("MigrationWizard:BeginMigration", {
        bubbles: true,
        detail: migrationEventDetail,
      })
    );
  }

  /**
   * Takes the current state of the selections page and bundles them
   * up into a MigrationWizard:BeginMigration event that can be handled
   * externally to perform the actual migration.
   */
  #doImport() {
    let migrationEventDetail = this.#gatherMigrationEventDetails();

    this.dispatchEvent(
      new CustomEvent("MigrationWizard:BeginMigration", {
        bubbles: true,
        detail: migrationEventDetail,
      })
    );
  }

  /**
   * @typedef {object} MigrationDetails
   * @property {string} key
   *   The key for a MigratorBase subclass.
   * @property {object|null} profile
   *   A representation of a browser profile. This is serialized and originally
   *   sent down from the parent via the GetAvailableMigrators message.
   * @property {string[]} resourceTypes
   *   An array of resource types that the user is attempted to import. These
   *   strings should be from MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
   * @property {boolean} hasPermissions
   *   True if this MigrationWizardChild told us that the associated
   *   MigratorBase subclass for the key has enough permission to read
   *   the requested resources.
   * @property {boolean} expandedDetails
   *   True if the user clicked on the <summary> element to expand the resource
   *   type list.
   * @property {boolean} autoMigration
   *   True if the migration is occurring automatically, without the user
   *   having selected any items explicitly from the wizard.
   * @property {string} [manualPasswordFilePath=null]
   *   An optional string argument that points to the path of a passwords
   *   export file from another browser. This file will have password imported
   *   from if supplied. This argument is ignored if the key is not for the
   *   Safari browser or the Chrome browser on Windows.
   */

  /**
   * Pulls information from the DOM state of the MigrationWizard and constructs
   * and returns an object that can be used to begin migration via and event
   * sent to the MigrationWizardChild. If autoMigrationDetails is provided,
   * this information is used to construct the object instead of the DOM state.
   *
   * @param {object} [autoMigrationDetails=null]
   *   Provided iff an automatic migration is being invoked. In that case, the
   *   details are constructed from this object rather than the wizard DOM state.
   * @param {string} autoMigrationDetails.migratorKey
   *   The key of the migrator to do automatic migration from.
   * @param {object|null} autoMigrationDetails.profile
   *   A representation of a browser profile. When not null, this is an
   *   object with a string "id" property, and a string "name" property.
   * @param {string[]} autoMigrationDetails.resourceTypes
   *   An array of resource types that import should occur for. These
   *   strings should be from MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.
   * @returns {MigrationDetails} details
   */
  #gatherMigrationEventDetails(autoMigrationDetails) {
    if (autoMigrationDetails?.migratorKey) {
      let { migratorKey, profile, resourceTypes } = autoMigrationDetails;

      return {
        key: migratorKey,
        type: MigrationWizardConstants.MIGRATOR_TYPES.BROWSER,
        profile,
        resourceTypes,
        hasPermissions: true,
        expandedDetails: this.#expandedDetails,
        autoMigration: true,
      };
    }

    let panelItem = this.#browserProfileSelector.selectedPanelItem;
    let key = panelItem.getAttribute("key");
    let type = panelItem.getAttribute("type");
    let profile = panelItem.profile;
    let hasPermissions = panelItem.hasPermissions;

    let resourceTypeFields = this.#resourceTypeList.querySelectorAll(
      "label[data-resource-type]"
    );
    let resourceTypes = [];
    for (let resourceTypeField of resourceTypeFields) {
      if (resourceTypeField.control.checked) {
        resourceTypes.push(resourceTypeField.dataset.resourceType);
      }
    }

    return {
      key,
      type,
      profile,
      resourceTypes,
      hasPermissions,
      expandedDetails: this.#expandedDetails,
      autoMigration: false,
    };
  }

  /**
   * Sends a request to gain read access to the Safari profile folder on
   * macOS, and upon gaining access, performs a migration using the current
   * settings as gathered by #gatherMigrationEventDetails
   */
  #requestSafariPermissions() {
    let migrationEventDetail = this.#gatherMigrationEventDetails();
    this.dispatchEvent(
      new CustomEvent("MigrationWizard:RequestSafariPermissions", {
        bubbles: true,
        detail: migrationEventDetail,
      })
    );
  }

  /**
   * Sends a request to get a string path for a passwords file exported
   * from another browser (like Safari on macOS, or Chrome on Windows)
   * where we cannot currently import automatically.
   */
  #selectManualPasswordFile() {
    let migrationEventDetail = this.#gatherMigrationEventDetails();
    this.dispatchEvent(
      new CustomEvent("MigrationWizard:SelectManualPasswordFile", {
        bubbles: true,
        detail: migrationEventDetail,
      })
    );
  }

  /**
   * Sends a request to get read permissions for the data associated
   * with the selected browser.
   */
  #getPermissions() {
    let migrationEventDetail = this.#gatherMigrationEventDetails();
    this.dispatchEvent(
      new CustomEvent("MigrationWizard:GetPermissions", {
        bubbles: true,
        detail: migrationEventDetail,
      })
    );
  }

  /**
   * Changes selected-data-header text and selected-data text based on
   * how many resources are checked
   */
  async #displaySelectedResources() {
    let resourceTypeLabels = this.#resourceTypeList.querySelectorAll(
      "label:not([hidden])[data-resource-type]"
    );
    let panelItem = this.#browserProfileSelector.selectedPanelItem;
    let key = panelItem.getAttribute("key");

    let totalResources = resourceTypeLabels.length;
    let checkedResources = 0;

    let selectedData = this.#shadowRoot.querySelector(".selected-data");
    let selectedDataArray = [];
    let resourceTypeToLabelIDs = {
      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS]:
        "migration-list-bookmark-label",
      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS]:
        "migration-list-password-label",
      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.HISTORY]:
        "migration-list-history-label",
      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS]:
        "migration-list-extensions-label",
      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.FORMDATA]:
        "migration-list-autofill-label",
      [MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PAYMENT_METHODS]:
        "migration-list-payment-methods-label",
    };

    if (MigrationWizardConstants.USES_FAVORITES.includes(key)) {
      resourceTypeToLabelIDs[
        MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.BOOKMARKS
      ] = "migration-list-favorites-label";
    }

    let resourceTypes = Object.keys(resourceTypeToLabelIDs);
    let labelIds = Object.values(resourceTypeToLabelIDs).map(id => {
      return { id };
    });
    let labels = await document.l10n.formatValues(labelIds);
    let resourceTypeLabelMapping = new Map();
    for (let i = 0; i < resourceTypes.length; ++i) {
      let resourceType = resourceTypes[i];
      resourceTypeLabelMapping.set(resourceType, labels[i]);
    }
    let formatter = new Intl.ListFormat(undefined, {
      style: "long",
      type: "conjunction",
    });
    for (let resourceTypeLabel of resourceTypeLabels) {
      if (resourceTypeLabel.control.checked) {
        selectedDataArray.push(
          resourceTypeLabelMapping.get(resourceTypeLabel.dataset.resourceType)
        );
        checkedResources++;
      }
    }
    if (selectedDataArray.length) {
      selectedDataArray[0] =
        selectedDataArray[0].charAt(0).toLocaleUpperCase() +
        selectedDataArray[0].slice(1);
      selectedData.textContent = formatter.format(selectedDataArray);
    } else {
      selectedData.textContent = "\u00A0";
    }

    let selectedDataHeader = this.#shadowRoot.querySelector(
      ".selected-data-header"
    );

    let importButton = this.#shadowRoot.querySelector("#import");
    importButton.disabled = checkedResources == 0;

    if (this.hasAttribute("option-expander-title-string")) {
      let optionString = this.getAttribute("option-expander-title-string");
      selectedDataHeader.textContent = optionString;
    } else if (checkedResources == 0) {
      document.l10n.setAttributes(
        selectedDataHeader,
        "migration-no-selected-data-label"
      );
    } else if (checkedResources < totalResources) {
      document.l10n.setAttributes(
        selectedDataHeader,
        "migration-selected-data-label"
      );
    } else {
      document.l10n.setAttributes(
        selectedDataHeader,
        "migration-all-available-data-label"
      );
    }

    let selectionPage = this.#shadowRoot.querySelector(
      "div[name='page-selection']"
    );
    selectionPage.toggleAttribute("single-item", totalResources == 1);

    this.dispatchEvent(
      new CustomEvent("MigrationWizard:ResourcesUpdated", { bubbles: true })
    );
  }

  /**
   * Updates content and layout to apply changes that are
   * informed through element attributes
   */
  #applyContentCustomizations() {
    let selectionPage = this.#shadowRoot.querySelector(
      "div[name='page-selection']"
    );
    if (this.hasAttribute("hide-select-all")) {
      let hideSelectAll = this.getAttribute("hide-select-all");

      selectionPage.toggleAttribute("hide-select-all", hideSelectAll);
    } else {
      selectionPage.removeAttribute("hide-select-all");
    }

    if (this.hasAttribute("import-button-string")) {
      if (this.getAttribute("import-button-string")) {
        this.#importButton.textContent = this.getAttribute(
          "import-button-string"
        );
      }
    }

    if (this.hasAttribute("checkbox-margin-inline")) {
      let inlineMargin = this.getAttribute("checkbox-margin-inline");
      this.style.setProperty(
        "--resource-type-label-margin-inline",
        inlineMargin
      );
    }

    if (this.hasAttribute("checkbox-margin-block")) {
      let blockMargin = this.getAttribute("checkbox-margin-block");
      this.style.setProperty("--resource-type-label-margin-block", blockMargin);
    }

    if (this.hasAttribute("import-button-class")) {
      let importButtonClass = this.getAttribute("import-button-class");
      if (importButtonClass) {
        this.#importButton.classList.add(importButtonClass);
      }
    }

    if (this.hasAttribute("header-font-size")) {
      let headerFontSize = this.getAttribute("header-font-size");
      if (headerFontSize) {
        this.style.setProperty(
          "--embedded-wizard-header-font-size",
          headerFontSize
        );
      }
    }

    if (this.hasAttribute("header-font-weight")) {
      let headerFontWeight = this.getAttribute("header-font-weight");
      if (headerFontWeight) {
        this.style.setProperty(
          "--embedded-wizard-header-font-weight",
          headerFontWeight
        );
      }
    }

    if (this.hasAttribute("header-margin-block")) {
      let headerMarginBlock = this.getAttribute("header-margin-block");
      if (headerMarginBlock) {
        this.style.setProperty(
          "--embedded-wizard-header-margin-block",
          headerMarginBlock
        );
      }
    }

    if (this.hasAttribute("subheader-font-size")) {
      let subheaderFontSize = this.getAttribute("subheader-font-size");
      if (subheaderFontSize) {
        this.style.setProperty(
          "--embedded-wizard-subheader-font-size",
          subheaderFontSize
        );
      }
    }

    if (this.hasAttribute("subheader-font-weight")) {
      let subheaderFontWeight = this.getAttribute("subheader-font-weight");
      if (subheaderFontWeight) {
        this.style.setProperty(
          "--embedded-wizard-subheader-font-weight",
          subheaderFontWeight
        );
      }
    }

    if (this.hasAttribute("subheader-margin-block")) {
      let subheaderMarginBlock = this.getAttribute("subheader-margin-block");
      if (subheaderMarginBlock) {
        this.style.setProperty(
          "--embedded-wizard-subheader-margin-block",
          subheaderMarginBlock
        );
      }
    }
  }

  #handleClickEvent(event) {
    if (
      event.target == this.#importButton ||
      event.target == this.#importFromFileButton
    ) {
      this.#doImport();
    } else if (
      event.target.classList.contains("cancel-close") ||
      event.target.classList.contains("finish-button")
    ) {
      this.dispatchEvent(
        new CustomEvent("MigrationWizard:Close", { bubbles: true })
      );
    } else if (
      event.currentTarget == this.#browserProfileSelectorList &&
      event.target != this.#browserProfileSelectorList
    ) {
      this.#onBrowserProfileSelectionChanged(event.target);
      // If the user selected a file migration type from the selector, we'll
      // help the user out by immediately starting the file migration flow,
      // rather than waiting for them to click the "Select File".
      if (
        event.target.getAttribute("type") ==
        MigrationWizardConstants.MIGRATOR_TYPES.FILE
      ) {
        this.#doImport();
      }
    } else if (event.target == this.#safariPermissionButton) {
      this.#requestSafariPermissions();
    } else if (event.currentTarget == this.#resourceSummary) {
      this.#expandedDetails = true;
    } else if (event.target == this.#chooseImportFromFile) {
      this.dispatchEvent(
        new CustomEvent("MigrationWizard:RequestState", {
          bubbles: true,
          detail: {
            allowOnlyFileMigrators: true,
          },
        })
      );
    } else if (event.target.id == "launch-macos-passwords-app") {
      this.dispatchEvent(
        new CustomEvent("MigrationWizard:LaunchMacOSPasswordsApp", {
          bubbles: true,
        })
      );
    } else if (event.target.classList.contains("manual-password-import-skip")) {
      // If the user chose to skip importing passwords manually from a CSV, we
      // programmatically uncheck the PASSWORDS resource type and re-request
      // import.
      let checkbox = this.#shadowRoot.querySelector(
        `label[data-resource-type="${MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.PASSWORDS}"]`
      ).control;
      checkbox.checked = false;

      // If there are no other checked checkboxes, go back to the selection
      // screen.
      let checked = this.#shadowRoot.querySelectorAll(
        `label[data-resource-type] > input:checked`
      ).length;

      if (!checked) {
        this.requestState();
      } else {
        this.#doImport();
      }
    } else if (
      event.target.classList.contains("manual-password-import-select")
    ) {
      this.#selectManualPasswordFile();
    } else if (event.target == this.#extensionsSuccessLink) {
      this.dispatchEvent(
        new CustomEvent("MigrationWizard:OpenAboutAddons", {
          bubbles: true,
        })
      );
      event.preventDefault();
    } else if (
      [...this.#supportTextLinks].includes(event.target) &&
      this.hasAttribute("in-aboutwelcome-bundle")
    ) {
      // When we're running in the context of a spotlight
      // the click events for standard anchors are being gobbled up by spotlight,
      // so we're also firing a custom event to handle those clicks when in that context
      this.dispatchEvent(
        new CustomEvent("MigrationWizard:OpenURL", {
          bubbles: true,
          detail: {
            url: event.target.href,
            where: "tabshifted",
          },
        })
      );
      event.preventDefault();
    } else if (event.target == this.#getPermissionsButton) {
      this.#getPermissions();
    }
  }

  #handleChangeEvent(event) {
    if (event.target == this.#browserProfileSelector) {
      this.#onBrowserProfileSelectionChanged();
    } else if (event.target == this.#selectAllCheckbox) {
      let checkboxes = this.#shadowRoot.querySelectorAll(
        'label[data-resource-type]:not([hidden]) > input[type="checkbox"]'
      );
      for (let checkbox of checkboxes) {
        checkbox.checked = this.#selectAllCheckbox.checked;
      }
      this.#displaySelectedResources();
    } else {
      let checkboxes = this.#shadowRoot.querySelectorAll(
        'label[data-resource-type]:not([hidden]) > input[type="checkbox"]'
      );

      let allVisibleChecked = Array.from(checkboxes).every(checkbox => {
        return checkbox.checked;
      });

      this.#selectAllCheckbox.checked = allVisibleChecked;
      this.#displaySelectedResources();
    }
  }

  handleEvent(event) {
    if (
      event.target == this.#browserProfileSelector &&
      (event.type == "mousedown" ||
        (event.type == "click" &&
          event.mozInputSource == MouseEvent.MOZ_SOURCE_KEYBOARD))
    ) {
      this.#browserProfileSelectorList.toggle(event);
      return;
    }
    switch (event.type) {
      case "click": {
        this.#handleClickEvent(event);
        break;
      }
      case "change": {
        this.#handleChangeEvent(event);
        break;
      }
    }
  }
}

if (globalThis.customElements) {
  customElements.define("migration-wizard", MigrationWizard);
}
