import {clipboard} from "electron/common";
import path from "node:path";
import process from "node:process";
import url from "node:url";

import {Menu, app, dialog, session} from "@electron/remote";
import * as remote from "@electron/remote";
import * as Sentry from "@sentry/electron/renderer";

import type {Config} from "../../common/config-util.js";
import * as ConfigUtil from "../../common/config-util.js";
import * as DNDUtil from "../../common/dnd-util.js";
import type {DndSettings} from "../../common/dnd-util.js";
import * as EnterpriseUtil from "../../common/enterprise-util.js";
import {html} from "../../common/html.js";
import * as LinkUtil from "../../common/link-util.js";
import Logger from "../../common/logger-util.js";
import * as Messages from "../../common/messages.js";
import {bundlePath, bundleUrl} from "../../common/paths.js";
import * as t from "../../common/translation-util.js";
import type {
  NavigationItem,
  ServerConfig,
  TabData,
  TabPage,
} from "../../common/types.js";
import defaultIcon from "../img/icon.png";

import FunctionalTab from "./components/functional-tab.js";
import ServerTab from "./components/server-tab.js";
import WebView from "./components/webview.js";
import {AboutView} from "./pages/about.js";
import {PreferenceView} from "./pages/preference/preference.js";
import {initializeTray} from "./tray.js";
import {ipcRenderer} from "./typed-ipc-renderer.js";
import * as DomainUtil from "./utils/domain-util.js";
import ReconnectUtil from "./utils/reconnect-util.js";

Sentry.init({});

type WebviewListener =
  | "webview-reload"
  | "back"
  | "focus"
  | "forward"
  | "zoomIn"
  | "zoomOut"
  | "zoomActualSize"
  | "log-out"
  | "show-keyboard-shortcuts"
  | "tab-devtools";

const logger = new Logger({
  file: "errors.log",
});

type ServerOrFunctionalTab = ServerTab | FunctionalTab;

const rootWebContents = remote.getCurrentWebContents();

const dingSound = new Audio(
  new URL("resources/sounds/ding.ogg", bundleUrl).href,
);

export class ServerManagerView {
  $addServerButton: HTMLButtonElement;
  $tabsContainer: Element;
  $reloadButton: HTMLButtonElement;
  $loadingIndicator: HTMLButtonElement;
  $settingsButton: HTMLButtonElement;
  $webviewsContainer: Element;
  $backButton: HTMLButtonElement;
  $dndButton: HTMLButtonElement;
  $addServerTooltip: HTMLElement;
  $reloadTooltip: HTMLElement;
  $loadingTooltip: HTMLElement;
  $settingsTooltip: HTMLElement;
  $serverIconTooltip: HTMLCollectionOf<HTMLElement>;
  $backTooltip: HTMLElement;
  $dndTooltip: HTMLElement;
  $sidebar: Element;
  $fullscreenPopup: Element;
  $fullscreenEscapeKey: string;
  loading: Set<string>;
  activeTabIndex: number;
  tabs: ServerOrFunctionalTab[];
  functionalTabs: Map<TabPage, number>;
  tabIndex: number;
  presetOrgs: string[];
  preferenceView?: PreferenceView;
  constructor() {
    this.$addServerButton = document.querySelector("#add-tab")!;
    this.$tabsContainer = document.querySelector("#tabs-container")!;

    const $actionsContainer = document.querySelector("#actions-container")!;
    this.$reloadButton = $actionsContainer.querySelector("#reload-action")!;
    this.$loadingIndicator =
      $actionsContainer.querySelector("#loading-action")!;
    this.$settingsButton = $actionsContainer.querySelector("#settings-action")!;
    this.$webviewsContainer = document.querySelector("#webviews-container")!;
    this.$backButton = $actionsContainer.querySelector("#back-action")!;
    this.$dndButton = $actionsContainer.querySelector("#dnd-action")!;

    this.$addServerTooltip = document.querySelector("#add-server-tooltip")!;
    this.$reloadTooltip = $actionsContainer.querySelector("#reload-tooltip")!;
    this.$loadingTooltip = $actionsContainer.querySelector("#loading-tooltip")!;
    this.$settingsTooltip =
      $actionsContainer.querySelector("#setting-tooltip")!;

    // TODO: This should have been querySelector but the problem is that
    // querySelector doesn't return elements not present in dom whereas somehow
    // getElementsByClassName does. To fix this we need to call this after this.initTabs
    // is called in this.init.
    // eslint-disable-next-line unicorn/prefer-query-selector
    this.$serverIconTooltip = document.getElementsByClassName(
      "server-tooltip",
    ) as HTMLCollectionOf<HTMLElement>;
    this.$backTooltip = $actionsContainer.querySelector("#back-tooltip")!;
    this.$dndTooltip = $actionsContainer.querySelector("#dnd-tooltip")!;

    this.$sidebar = document.querySelector("#sidebar")!;

    this.$fullscreenPopup = document.querySelector("#fullscreen-popup")!;
    this.$fullscreenEscapeKey = process.platform === "darwin" ? "^⌘F" : "F11";
    this.$fullscreenPopup.textContent = `Press ${this.$fullscreenEscapeKey} to exit full screen`;

    this.loading = new Set();
    this.activeTabIndex = -1;
    this.tabs = [];
    this.presetOrgs = [];
    this.functionalTabs = new Map();
    this.tabIndex = 0;
  }

  async init(): Promise<void> {
    initializeTray(this);
    await this.loadProxy();
    this.initDefaultSettings();
    this.initSidebar();
    this.removeUaFromDisk();
    if (EnterpriseUtil.hasConfigFile()) {
      await this.initPresetOrgs();
    }

    await this.initTabs();
    this.initActions();
    this.registerIpcs();
  }

  async loadProxy(): Promise<void> {
    // To change proxyEnable to useManualProxy in older versions
    const proxyEnabledOld = ConfigUtil.isConfigItemExists("useProxy");
    if (proxyEnabledOld) {
      const proxyEnableOldState = ConfigUtil.getConfigItem("useProxy", false);
      if (proxyEnableOldState) {
        ConfigUtil.setConfigItem("useManualProxy", true);
      }

      ConfigUtil.removeConfigItem("useProxy");
    }

    await session.fromPartition("persist:webviewsession").setProxy(
      ConfigUtil.getConfigItem("useSystemProxy", false)
        ? {mode: "system"}
        : ConfigUtil.getConfigItem("useManualProxy", false)
          ? {
              pacScript: ConfigUtil.getConfigItem("proxyPAC", ""),
              proxyRules: ConfigUtil.getConfigItem("proxyRules", ""),
              proxyBypassRules: ConfigUtil.getConfigItem("proxyBypass", ""),
            }
          : {mode: "direct"},
    );
  }

  // Settings are initialized only when user clicks on General/Server/Network section settings
  // In case, user doesn't visit these section, those values set to be null automatically
  // This will make sure the default settings are correctly set to either true or false
  initDefaultSettings(): void {
    // Default settings which should be respected
    const settingOptions: Partial<Config> = {
      autoHideMenubar: false,
      trayIcon: true,
      useManualProxy: false,
      useSystemProxy: false,
      showSidebar: true,
      badgeOption: true,
      startAtLogin: false,
      startMinimized: false,
      enableSpellchecker: true,
      showNotification: true,
      autoUpdate: true,
      betaUpdate: false,
      errorReporting: true,
      // eslint-disable-next-line @typescript-eslint/naming-convention
      customCSS: false,
      silent: false,
      lastActiveTab: 0,
      dnd: false,
      dndPreviousSettings: {
        showNotification: true,
        silent: false,
      },
      downloadsPath: `${app.getPath("downloads")}`,
      quitOnClose: false,
      promptDownload: false,
    };

    // Platform specific settings

    if (process.platform === "win32") {
      // Only available on Windows
      settingOptions.flashTaskbarOnMessage = true;
      settingOptions.dndPreviousSettings!.flashTaskbarOnMessage = true;
    }

    if (process.platform === "darwin") {
      // Only available on macOS
      settingOptions.dockBouncing = true;
    }

    if (process.platform !== "darwin") {
      settingOptions.autoHideMenubar = false;
      settingOptions.spellcheckerLanguages = ["en-US"];
    }

    for (const [setting, value] of Object.entries(settingOptions) as Array<
      {[Key in keyof Config]: [Key, Config[Key]]}[keyof Config]
    >) {
      // Give preference to defaults defined in global_config.json
      if (EnterpriseUtil.configItemExists(setting)) {
        ConfigUtil.setConfigItem(
          setting,
          EnterpriseUtil.getConfigItem(setting, value),
          true,
        );
      } else if (!ConfigUtil.isConfigItemExists(setting)) {
        ConfigUtil.setConfigItem(setting, value);
      }
    }
  }

  initSidebar(): void {
    const showSidebar = ConfigUtil.getConfigItem("showSidebar", true);
    this.toggleSidebar(showSidebar);
  }

  // Remove the stale UA string from the disk if the app is not freshly
  // installed.  This should be removed in a further release.
  removeUaFromDisk(): void {
    ConfigUtil.removeConfigItem("userAgent");
  }

  async queueDomain(domain: string): Promise<boolean> {
    // Allows us to start adding multiple domains to the app simultaneously
    // promise of addition resolves in both cases, but we consider it rejected
    // if the resolved value is false
    try {
      const serverConfig = await DomainUtil.checkDomain(domain);
      await DomainUtil.addDomain(serverConfig);
      return true;
    } catch (error: unknown) {
      logger.error(error);
      logger.error(
        `Could not add ${domain}. Please contact your system administrator.`,
      );
      return false;
    }
  }

  async initPresetOrgs(): Promise<void> {
    // Read preset organizations from global_config.json and queues them
    // for addition to the app's domains
    const preAddedDomains = DomainUtil.getDomains();
    this.presetOrgs = EnterpriseUtil.getConfigItem("presetOrganizations", []);
    // Set to true if at least one new domain is added
    const domainPromises = [];
    for (const url of this.presetOrgs) {
      if (DomainUtil.duplicateDomain(url)) {
        continue;
      }

      domainPromises.push(this.queueDomain(url));
    }

    const domainsAdded = await Promise.all(domainPromises);
    if (domainsAdded.includes(true)) {
      // At least one domain was resolved
      if (preAddedDomains.length > 0) {
        // User already has servers added
        // ask them before reloading the app
        const {response} = await dialog.showMessageBox({
          type: "question",
          buttons: [t.__("Yes"), t.__("Later")],
          defaultId: 0,
          message: t.__("New servers added. Reload app now?"),
        });
        if (response === 0) {
          ipcRenderer.send("reload-full-app");
        }
      } else {
        ipcRenderer.send("reload-full-app");
      }
    } else if (domainsAdded.length > 0) {
      // Find all orgs that failed
      const failedDomains: string[] = [];
      for (const org of this.presetOrgs) {
        if (DomainUtil.duplicateDomain(org)) {
          continue;
        }

        failedDomains.push(org);
      }

      const {title, content} = Messages.enterpriseOrgError(
        domainsAdded.length,
        failedDomains,
      );
      dialog.showErrorBox(title, content);
      if (DomainUtil.getDomains().length === 0) {
        // No orgs present, stop showing loading gif
        await this.openSettings("AddServer");
      }
    }
  }

  async initTabs(): Promise<void> {
    const servers = DomainUtil.getDomains();
    if (servers.length > 0) {
      for (const [i, server] of servers.entries()) {
        const tab = this.initServer(server, i);
        (async () => {
          const serverConfig = await DomainUtil.updateSavedServer(
            server.url,
            i,
          );
          tab.setLabel(serverConfig.alias);
          tab.setIcon(DomainUtil.iconAsUrl(serverConfig.icon));
          (await tab.webview).setUnsupportedMessage(
            DomainUtil.getUnsupportedMessage(serverConfig),
          );
        })();
      }

      // Open last active tab
      let lastActiveTab = ConfigUtil.getConfigItem("lastActiveTab", 0);
      if (lastActiveTab >= servers.length) {
        lastActiveTab = 0;
      }

      // `webview.load()` for lastActiveTab before the others
      await this.activateTab(lastActiveTab);
      await Promise.all(
        servers.map(async (server, i) => {
          // After the lastActiveTab is activated, we load the others in the background
          // without activating them, to prevent flashing of server icons
          if (i === lastActiveTab) {
            return;
          }

          const tab = this.tabs[i];
          if (tab instanceof ServerTab) (await tab.webview).load();
        }),
      );
      // Remove focus from the settings icon at sidebar bottom
      this.$settingsButton.classList.remove("active");
    } else if (this.presetOrgs.length === 0) {
      // Not attempting to add organisations in the background
      await this.openSettings("AddServer");
    } else {
      this.showLoading(true);
    }
  }

  initServer(server: ServerConfig, index: number): ServerTab {
    const tabIndex = this.getTabIndex();
    const tab = new ServerTab({
      role: "server",
      icon: DomainUtil.iconAsUrl(server.icon),
      label: server.alias,
      $root: this.$tabsContainer,
      onClick: this.activateLastTab.bind(this, index),
      index,
      tabIndex,
      onHover: this.onHover.bind(this, index),
      onHoverOut: this.onHoverOut.bind(this, index),
      webview: WebView.create({
        $root: this.$webviewsContainer,
        rootWebContents,
        index,
        tabIndex,
        url: server.url,
        role: "server",
        hasPermission: (origin: string, permission: string) =>
          origin === server.url &&
          permission === "notifications" &&
          ConfigUtil.getConfigItem("showNotification", true),
        isActive: () => index === this.activeTabIndex,
        switchLoading: async (loading: boolean, url: string) => {
          if (loading) {
            this.loading.add(url);
          } else {
            this.loading.delete(url);
          }

          const tab = this.tabs[this.activeTabIndex];
          this.showLoading(
            tab instanceof ServerTab &&
              this.loading.has((await tab.webview).properties.url),
          );
        },
        onNetworkError: async (index: number) => {
          await this.openNetworkTroubleshooting(index);
        },
        onTitleChange: this.updateBadge.bind(this),
        preload: url.pathToFileURL(path.join(bundlePath, "preload.js")).href,
        unsupportedMessage: DomainUtil.getUnsupportedMessage(server),
      }),
    });
    this.tabs.push(tab);
    this.loading.add(server.url);
    return tab;
  }

  initActions(): void {
    this.initDndButton();
    this.initServerActions();
    this.initLeftSidebarEvents();
  }

  initServerActions(): void {
    const $serverImgs: NodeListOf<HTMLImageElement> =
      document.querySelectorAll(".server-icons");
    for (const [index, $serverImg] of $serverImgs.entries()) {
      this.addContextMenu($serverImg, index);
      if ($serverImg.src === defaultIcon) {
        this.displayInitialCharLogo($serverImg, index);
      }

      $serverImg.addEventListener("error", () => {
        this.displayInitialCharLogo($serverImg, index);
      });
    }
  }

  initLeftSidebarEvents(): void {
    this.$dndButton.addEventListener("click", () => {
      const dndUtil = DNDUtil.toggle();
      ipcRenderer.send(
        "forward-message",
        "toggle-dnd",
        dndUtil.dnd,
        dndUtil.newSettings,
      );
    });
    this.$reloadButton.addEventListener("click", async () => {
      const tab = this.tabs[this.activeTabIndex];
      if (tab instanceof ServerTab) (await tab.webview).reload();
    });
    this.$addServerButton.addEventListener("click", async () => {
      await this.openSettings("AddServer");
    });
    this.$settingsButton.addEventListener("click", async () => {
      await this.openSettings("General");
    });
    this.$backButton.addEventListener("click", async () => {
      const tab = this.tabs[this.activeTabIndex];
      if (tab instanceof ServerTab) (await tab.webview).back();
    });

    this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip, true);
    this.sidebarHoverEvent(this.$loadingIndicator, this.$loadingTooltip);
    this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
    this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
    this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
    this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
  }

  initDndButton(): void {
    const dnd = ConfigUtil.getConfigItem("dnd", false);
    this.toggleDndButton(dnd);
  }

  getTabIndex(): number {
    const currentIndex = this.tabIndex;
    this.tabIndex++;
    return currentIndex;
  }

  async getCurrentActiveServer(): Promise<string> {
    const tab = this.tabs[this.activeTabIndex];
    return tab instanceof ServerTab ? (await tab.webview).properties.url : "";
  }

  displayInitialCharLogo($img: HTMLImageElement, index: number): void {
    // The index parameter is needed because webview[data-tab-id] can
    // increment beyond the size of the sidebar org array and throw an
    // error

    const $altIcon = document.createElement("div");
    const $parent = $img.parentElement!;
    const $container = $parent.parentElement!;
    const webviewId = $container.dataset.tabId!;
    const $webview = document.querySelector(
      `webview[data-tab-id="${CSS.escape(webviewId)}"]`,
    )!;
    const realmName = $webview.getAttribute("name");

    if (realmName === null) {
      $img.src = defaultIcon;
      return;
    }

    $altIcon.textContent = realmName.charAt(0) || "Z";
    $altIcon.classList.add("server-icon");
    $altIcon.classList.add("alt-icon");

    $img.remove();
    $parent.append($altIcon);

    this.addContextMenu($altIcon, index);
  }

  sidebarHoverEvent(
    SidebarButton: HTMLButtonElement,
    SidebarTooltip: HTMLElement,
    addServer = false,
  ): void {
    SidebarButton.addEventListener("mouseover", () => {
      SidebarTooltip.removeAttribute("style");
      // To handle position of add server tooltip due to scrolling of list of organizations
      // This could not be handled using CSS, hence the top of the tooltip is made same
      // as that of its parent element.
      // This needs to handled only for the add server tooltip and not others.
      if (addServer) {
        const {top} = SidebarButton.getBoundingClientRect();
        SidebarTooltip.style.top = `${top}px`;
      }
    });
    SidebarButton.addEventListener("mouseout", () => {
      SidebarTooltip.style.display = "none";
    });
  }

  onHover(index: number): void {
    // `this.$serverIconTooltip[index].textContent` already has realm name, so we are just
    // removing the style.
    this.$serverIconTooltip[index].removeAttribute("style");
    // To handle position of servers' tooltip due to scrolling of list of organizations
    // This could not be handled using CSS, hence the top of the tooltip is made same
    // as that of its parent element.
    const {top} =
      this.$serverIconTooltip[index].parentElement!.getBoundingClientRect();
    this.$serverIconTooltip[index].style.top = `${top}px`;
  }

  onHoverOut(index: number): void {
    this.$serverIconTooltip[index].style.display = "none";
  }

  async openFunctionalTab(tabProperties: {
    label: string;
    page: TabPage;
    materialIcon: string;
    makeView: () => Promise<Element>;
    destroyView: () => void;
  }): Promise<void> {
    if (this.functionalTabs.has(tabProperties.page)) {
      await this.activateTab(this.functionalTabs.get(tabProperties.page)!);
      return;
    }

    const index = this.tabs.length;
    this.functionalTabs.set(tabProperties.page, index);

    const tabIndex = this.getTabIndex();
    const $view = await tabProperties.makeView();
    this.$webviewsContainer.append($view);

    this.tabs.push(
      new FunctionalTab({
        role: "function",
        materialIcon: tabProperties.materialIcon,
        label: tabProperties.label,
        page: tabProperties.page,
        $root: this.$tabsContainer,
        index,
        tabIndex,
        onClick: this.activateTab.bind(this, index),
        onDestroy: async () => {
          await this.destroyFunctionalTab(tabProperties.page, index);
          tabProperties.destroyView();
        },
        $view,
      }),
    );

    // To show loading indicator the first time a functional tab is opened, indicator is
    // closed when the functional tab DOM is ready, handled in webview.js
    this.$webviewsContainer.classList.remove("loaded");

    await this.activateTab(this.functionalTabs.get(tabProperties.page)!);
  }

  async openSettings(
    navigationItem: NavigationItem = "General",
  ): Promise<void> {
    await this.openFunctionalTab({
      page: "Settings",
      label: t.__("Settings"),
      materialIcon: "settings",
      makeView: async () => {
        this.preferenceView = await PreferenceView.create();
        this.preferenceView.$view.classList.add("functional-view");
        return this.preferenceView.$view;
      },
      destroyView: () => {
        this.preferenceView!.destroy();
        this.preferenceView = undefined;
      },
    });
    this.$settingsButton.classList.add("active");
    this.preferenceView!.handleNavigation(navigationItem);
  }

  async openAbout(): Promise<void> {
    let aboutView: AboutView;
    await this.openFunctionalTab({
      page: "About",
      label: t.__("About"),
      materialIcon: "sentiment_very_satisfied",
      async makeView() {
        aboutView = await AboutView.create();
        aboutView.$view.classList.add("functional-view");
        return aboutView.$view;
      },
      destroyView() {
        aboutView.destroy();
      },
    });
  }

  async openNetworkTroubleshooting(index: number): Promise<void> {
    const tab = this.tabs[index];
    if (!(tab instanceof ServerTab)) return;
    const webview = await tab.webview;
    const reconnectUtil = new ReconnectUtil(webview);
    reconnectUtil.pollInternetAndReload();
    await webview
      .getWebContents()
      .loadURL(new URL("app/renderer/network.html", bundleUrl).href);
  }

  async activateLastTab(index: number): Promise<void> {
    // Open all the tabs in background, also activate the tab based on the index
    await this.activateTab(index);
    // Save last active tab via main process to avoid JSON DB errors
    ipcRenderer.send("save-last-tab", index);
  }

  // Returns this.tabs in an way that does
  // not crash app when this.tabs is passed into
  // ipcRenderer. Something about webview, and properties.webview
  // properties in ServerTab causes the app to crash.
  get tabsForIpc(): TabData[] {
    return this.tabs.map((tab) => ({
      role: tab.properties.role,
      page: tab.properties.page,
      label: tab.properties.label,
      index: tab.properties.index,
    }));
  }

  async activateTab(index: number, hideOldTab = true): Promise<void> {
    const tab = this.tabs[index];
    if (!tab) {
      return;
    }

    if (this.activeTabIndex !== -1) {
      if (this.activeTabIndex === index) {
        return;
      }

      if (hideOldTab) {
        // If old tab is functional tab Settings, remove focus from the settings icon at sidebar bottom
        if (
          this.tabs[this.activeTabIndex].properties.role === "function" &&
          this.tabs[this.activeTabIndex].properties.page === "Settings"
        ) {
          this.$settingsButton.classList.remove("active");
        }

        await this.tabs[this.activeTabIndex].deactivate();
      }
    }

    if (tab instanceof ServerTab) {
      try {
        (await tab.webview).canGoBackButton();
      } catch {}
    } else {
      document
        .querySelector("#actions-container #back-action")!
        .classList.add("disable");
    }

    this.activeTabIndex = index;
    await tab.activate();

    this.showLoading(
      tab instanceof ServerTab &&
        this.loading.has((await tab.webview).properties.url),
    );

    ipcRenderer.send("update-menu", {
      // JSON stringify this.tabs to avoid a crash
      // util.inspect is being used to handle circular references
      tabs: this.tabsForIpc,
      activeTabIndex: this.activeTabIndex,
      // Following flag controls whether a menu item should be enabled or not
      enableMenu: tab.properties.role === "server",
    });
  }

  showLoading(loading: boolean): void {
    this.$reloadButton.classList.toggle("hidden", loading);
    this.$loadingIndicator.classList.toggle("hidden", !loading);
  }

  async destroyFunctionalTab(page: TabPage, index: number): Promise<void> {
    const tab = this.tabs[index];
    if (tab instanceof ServerTab && (await tab.webview).loading) {
      return;
    }

    await tab.destroy();

    delete this.tabs[index]; // eslint-disable-line @typescript-eslint/no-array-delete
    this.functionalTabs.delete(page);

    // Issue #188: If the functional tab was not focused, do not activate another tab.
    if (this.activeTabIndex === index) {
      await this.activateTab(0, false);
    }
  }

  destroyView(): void {
    // Show loading indicator
    this.$webviewsContainer.classList.remove("loaded");

    // Clear global variables
    this.activeTabIndex = -1;
    this.tabs = [];
    this.functionalTabs.clear();

    // Clear DOM elements
    this.$tabsContainer.textContent = "";
    this.$webviewsContainer.textContent = "";
  }

  async reloadView(): Promise<void> {
    // Save and remember the index of last active tab so that we can use it later
    const lastActiveTab = this.tabs[this.activeTabIndex].properties.index;
    ConfigUtil.setConfigItem("lastActiveTab", lastActiveTab);

    // Destroy the current view and re-initiate it
    this.destroyView();
    await this.initTabs();
    this.initServerActions();
  }

  // This will trigger when pressed CTRL/CMD + R [WIP]
  // It won't reload the current view properly when you add/delete a server.
  reloadCurrentView(): void {
    this.$reloadButton.click();
  }

  async updateBadge(): Promise<void> {
    let messageCountAll = 0;
    await Promise.all(
      this.tabs.map(async (tab) => {
        if (tab && tab instanceof ServerTab && tab.updateBadge) {
          const count = (await tab.webview).badgeCount;
          messageCountAll += count;
          tab.updateBadge(count);
        }
      }),
    );

    ipcRenderer.send("update-badge", messageCountAll);
  }

  toggleSidebar(show: boolean): void {
    this.$sidebar.classList.toggle("sidebar-hide", !show);
  }

  // Toggles the dnd button icon.
  toggleDndButton(alert: boolean): void {
    this.$dndTooltip.textContent =
      (alert ? "Disable" : "Enable") + " Do Not Disturb";
    this.$dndButton.querySelector("i")!.textContent = alert
      ? "notifications_off"
      : "notifications";
  }

  async isLoggedIn(tabIndex: number): Promise<boolean> {
    const tab = this.tabs[tabIndex];
    if (!(tab instanceof ServerTab)) return false;
    const webview = await tab.webview;
    const url = webview.getWebContents().getURL();
    return !(url.endsWith("/login/") || webview.loading);
  }

  addContextMenu($serverImg: HTMLElement, index: number): void {
    $serverImg.addEventListener("contextmenu", async (event) => {
      event.preventDefault();
      const template = [
        {
          label: t.__("Disconnect organization"),
          async click() {
            const {response} = await dialog.showMessageBox({
              type: "warning",
              buttons: [t.__("Yes"), t.__("No")],
              defaultId: 0,
              message: t.__(
                "Are you sure you want to disconnect this organization?",
              ),
            });
            if (response === 0) {
              if (DomainUtil.removeDomain(index)) {
                ipcRenderer.send("reload-full-app");
              } else {
                const {title, content} = Messages.orgRemovalError(
                  DomainUtil.getDomain(index).url,
                );
                dialog.showErrorBox(title, content);
              }
            }
          },
        },
        {
          label: t.__("Notification settings"),
          enabled: await this.isLoggedIn(index),
          click: async () => {
            // Switch to tab whose icon was right-clicked
            await this.activateTab(index);
            const tab = this.tabs[index];
            if (tab instanceof ServerTab)
              (await tab.webview).showNotificationSettings();
          },
        },
        {
          label: t.__("Copy Zulip URL"),
          click() {
            clipboard.writeText(DomainUtil.getDomain(index).url);
          },
        },
      ];
      const contextMenu = Menu.buildFromTemplate(template);
      contextMenu.popup({window: remote.getCurrentWindow()});
    });
  }

  registerIpcs(): void {
    const webviewListeners: Array<
      [WebviewListener, (webview: WebView) => void]
    > = [
      [
        "webview-reload",
        (webview) => {
          webview.reload();
        },
      ],
      [
        "back",
        (webview) => {
          webview.back();
        },
      ],
      [
        "focus",
        (webview) => {
          webview.focus();
        },
      ],
      [
        "forward",
        (webview) => {
          webview.forward();
        },
      ],
      [
        "zoomIn",
        (webview) => {
          webview.zoomIn();
        },
      ],
      [
        "zoomOut",
        (webview) => {
          webview.zoomOut();
        },
      ],
      [
        "zoomActualSize",
        (webview) => {
          webview.zoomActualSize();
        },
      ],
      [
        "log-out",
        (webview) => {
          webview.logOut();
        },
      ],
      [
        "show-keyboard-shortcuts",
        (webview) => {
          webview.showKeyboardShortcuts();
        },
      ],
      [
        "tab-devtools",
        (webview) => {
          webview.openDevTools();
        },
      ],
    ];

    for (const [channel, listener] of webviewListeners) {
      ipcRenderer.on(channel, async () => {
        const tab = this.tabs[this.activeTabIndex];
        if (tab instanceof ServerTab) {
          const activeWebview = await tab.webview;
          if (activeWebview) listener(activeWebview);
        }
      });
    }

    ipcRenderer.on(
      "permission-request",
      async (
        event,
        {
          webContentsId,
          origin,
          permission,
        }: {
          webContentsId: number | null;
          origin: string;
          permission: string;
        },
        permissionCallbackId: number,
      ) => {
        const grant =
          webContentsId === null
            ? origin === "null" && permission === "notifications"
            : (
                await Promise.all(
                  this.tabs.map(async (tab) => {
                    if (!(tab instanceof ServerTab)) return false;
                    const webview = await tab.webview;
                    return (
                      webview.webContentsId === webContentsId &&
                      webview.properties.hasPermission?.(origin, permission)
                    );
                  }),
                )
              ).some(Boolean);
        console.log(
          grant ? "Granted" : "Denied",
          "permissions request for",
          permission,
          "from",
          origin,
        );
        ipcRenderer.send("permission-callback", permissionCallbackId, grant);
      },
    );

    ipcRenderer.on("open-settings", async () => {
      await this.openSettings();
    });

    ipcRenderer.on("open-about", this.openAbout.bind(this));

    ipcRenderer.on("open-help", async () => {
      // Open help page of current active server
      await LinkUtil.openBrowser(new URL("https://zulip.com/help/"));
    });

    ipcRenderer.on("reload-viewer", this.reloadView.bind(this));

    ipcRenderer.on("reload-current-viewer", this.reloadCurrentView.bind(this));

    ipcRenderer.on("hard-reload", () => {
      ipcRenderer.send("reload-full-app");
    });

    ipcRenderer.on("switch-server-tab", async (event, index: number) => {
      await this.activateLastTab(index);
    });

    ipcRenderer.on("open-org-tab", async () => {
      await this.openSettings("AddServer");
    });

    ipcRenderer.on("reload-proxy", async (event, showAlert: boolean) => {
      await this.loadProxy();
      if (showAlert) {
        await dialog.showMessageBox({
          message: t.__("Proxy settings saved."),
          buttons: [t.__("OK")],
        });
        ipcRenderer.send("reload-full-app");
      }
    });

    ipcRenderer.on("toggle-sidebar", async (event, show: boolean) => {
      // Toggle the left sidebar
      this.toggleSidebar(show);
    });

    ipcRenderer.on("toggle-silent", async (event, state: boolean) =>
      Promise.all(
        this.tabs.map(async (tab) => {
          if (tab instanceof ServerTab)
            (await tab.webview).getWebContents().setAudioMuted(state);
        }),
      ),
    );

    ipcRenderer.on(
      "toggle-autohide-menubar",
      async (event, autoHideMenubar: boolean, updateMenu: boolean) => {
        if (updateMenu) {
          ipcRenderer.send("update-menu", {
            tabs: this.tabsForIpc,
            activeTabIndex: this.activeTabIndex,
          });
        }
      },
    );

    ipcRenderer.on(
      "toggle-dnd",
      async (event, state: boolean, newSettings: Partial<DndSettings>) => {
        this.toggleDndButton(state);
        ipcRenderer.send(
          "forward-message",
          "toggle-silent",
          newSettings.silent ?? false,
        );
      },
    );

    ipcRenderer.on(
      "update-realm-name",
      (event, serverURL: string, realmName: string) => {
        for (const [index, domain] of DomainUtil.getDomains().entries()) {
          if (domain.url === serverURL) {
            const tab = this.tabs[index];
            if (tab instanceof ServerTab) tab.setLabel(realmName);
            domain.alias = realmName;
            DomainUtil.updateDomain(index, domain);
            // Update the realm name also on the Window menu
            ipcRenderer.send("update-menu", {
              tabs: this.tabsForIpc,
              activeTabIndex: this.activeTabIndex,
            });
          }
        }
      },
    );

    ipcRenderer.on(
      "update-realm-icon",
      async (event, serverURL: string, iconURL: string) => {
        await Promise.all(
          DomainUtil.getDomains().map(async (domain, index) => {
            if (domain.url === serverURL) {
              const localIconPath = await DomainUtil.saveServerIcon(iconURL);
              const tab = this.tabs[index];
              if (tab instanceof ServerTab)
                tab.setIcon(DomainUtil.iconAsUrl(localIconPath));
              domain.icon = localIconPath;
              DomainUtil.updateDomain(index, domain);
            }
          }),
        );
      },
    );

    ipcRenderer.on("enter-fullscreen", () => {
      this.$fullscreenPopup.classList.add("show");
      this.$fullscreenPopup.classList.remove("hidden");
    });

    ipcRenderer.on("leave-fullscreen", () => {
      this.$fullscreenPopup.classList.remove("show");
    });

    ipcRenderer.on("focus-webview-with-id", async (event, webviewId: number) =>
      Promise.all(
        this.tabs.map(async (tab) => {
          if (
            tab instanceof ServerTab &&
            (await tab.webview).webContentsId === webviewId
          ) {
            const concurrentTab: HTMLButtonElement = document.querySelector(
              `div[data-tab-id="${CSS.escape(`${tab.properties.tabIndex}`)}"]`,
            )!;
            concurrentTab.click();
          }
        }),
      ),
    );

    ipcRenderer.on("render-taskbar-icon", (event, messageCount: number) => {
      // Create a canvas from unread message counts
      function createOverlayIcon(messageCount: number): HTMLCanvasElement {
        const canvas = document.createElement("canvas");
        canvas.height = 128;
        canvas.width = 128;
        canvas.style.letterSpacing = "-5px";
        const context = canvas.getContext("2d")!;
        context.fillStyle = "#f42020";
        context.beginPath();
        context.ellipse(64, 64, 64, 64, 0, 0, 2 * Math.PI);
        context.fill();
        context.textAlign = "center";
        context.fillStyle = "white";
        if (messageCount > 99) {
          context.font = "65px Helvetica";
          context.fillText("99+", 64, 85);
        } else if (messageCount < 10) {
          context.font = "90px Helvetica";
          context.fillText(String(Math.min(99, messageCount)), 64, 96);
        } else {
          context.font = "85px Helvetica";
          context.fillText(String(Math.min(99, messageCount)), 64, 90);
        }

        return canvas;
      }

      ipcRenderer.send(
        "update-taskbar-icon",
        createOverlayIcon(messageCount).toDataURL(),
        String(messageCount),
      );
    });

    ipcRenderer.on("copy-zulip-url", async () => {
      clipboard.writeText(await this.getCurrentActiveServer());
    });

    ipcRenderer.on("new-server", async () => {
      await this.openSettings("AddServer");
    });

    ipcRenderer.on("set-active", async () =>
      Promise.all(
        this.tabs.map(async (tab) => {
          if (tab instanceof ServerTab) (await tab.webview).send("set-active");
        }),
      ),
    );

    ipcRenderer.on("set-idle", async () =>
      Promise.all(
        this.tabs.map(async (tab) => {
          if (tab instanceof ServerTab) (await tab.webview).send("set-idle");
        }),
      ),
    );

    ipcRenderer.on("open-network-settings", async () => {
      await this.openSettings("Network");
    });

    ipcRenderer.on("play-ding-sound", async () => {
      await dingSound.play();
    });
  }
}

window.addEventListener("load", async () => {
  document.body.innerHTML = html`
    <div id="content">
      <div class="popup">
        <span class="popuptext hidden" id="fullscreen-popup"></span>
      </div>
      <div id="sidebar" class="toggle-sidebar">
        <div id="view-controls-container">
          <div id="tabs-container"></div>
          <div id="add-tab" class="tab functional-tab">
            <div class="server-tab" id="add-action">
              <i class="material-icons">add</i>
            </div>
            <span id="add-server-tooltip" style="display: none"
              >${t.__("Add Organization")}</span
            >
          </div>
        </div>
        <div id="actions-container">
          <div class="action-button" id="dnd-action">
            <i class="material-icons md-48">notifications</i>
            <span id="dnd-tooltip" style="display: none"
              >${t.__("Do Not Disturb")}</span
            >
          </div>
          <div class="action-button hidden" id="reload-action">
            <i class="material-icons md-48">refresh</i>
            <span id="reload-tooltip" style="display: none"
              >${t.__("Reload")}</span
            >
          </div>
          <div class="action-button disable" id="loading-action">
            <i class="refresh material-icons md-48">loop</i>
            <span id="loading-tooltip" style="display: none"
              >${t.__("Loading")}</span
            >
          </div>
          <div class="action-button disable" id="back-action">
            <i class="material-icons md-48">arrow_back</i>
            <span id="back-tooltip" style="display: none"
              >${t.__("Go Back")}</span
            >
          </div>
          <div class="action-button" id="settings-action">
            <i class="material-icons md-48">settings</i>
            <span id="setting-tooltip" style="display: none"
              >${t.__("Settings")}</span
            >
          </div>
        </div>
      </div>
      <div id="main-container">
        <div id="webviews-container"></div>
      </div>
    </div>
  `.html;

  const serverManagerView = new ServerManagerView();
  await serverManagerView.init();
});
