/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* 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/. */

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

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AboutReaderParent: "resource:///actors/AboutReaderParent.sys.mjs",
  CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
  Pocket: "chrome://pocket/content/Pocket.sys.mjs",
});

var PocketCustomizableWidget = {
  init() {
    lazy.CustomizableUI.createWidget({
      id: "save-to-pocket-button",
      l10nId: "save-to-pocket-button",
      type: "view",
      viewId: "PanelUI-savetopocket",
      // This closes any open Pocket panels if you change location.
      locationSpecific: true,
      onViewShowing(aEvent) {
        let panelView = aEvent.target;
        let panelNode = panelView.querySelector(
          ".PanelUI-savetopocket-container"
        );
        let doc = panelNode.ownerDocument;
        let frame = doc.createXULElement("browser");

        frame.setAttribute("type", "content");
        frame.setAttribute("remote", "true");
        frame.setAttribute("remoteType", "privilegedabout");
        frame.setAttribute("maychangeremoteness", "true");
        frame.setAttribute("autocompletepopup", "PopupAutoComplete");
        panelNode.appendChild(frame);

        SaveToPocket.onShownInToolbarPanel(panelNode, frame);
      },
      onViewHiding(aEvent) {
        let panelView = aEvent.target;
        let panelNode = panelView.querySelector(
          ".PanelUI-savetopocket-container"
        );

        panelNode.textContent = "";
        SaveToPocket.updateToolbarNodeState(panelNode.ownerGlobal);
      },
    });
  },
  shutdown() {
    lazy.CustomizableUI.destroyWidget("save-to-pocket-button");
  },
};

var PocketOverlay = {
  startup() {
    PocketCustomizableWidget.init();
  },
  shutdown() {
    PocketCustomizableWidget.shutdown();
  },
};

function browserWindows() {
  return Services.wm.getEnumerator("navigator:browser");
}

export var SaveToPocket = {
  init() {
    // migrate enabled pref
    if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) {
      Services.prefs.setBoolPref(
        "extensions.pocket.enabled",
        Services.prefs.getBoolPref("browser.pocket.enabled")
      );
      Services.prefs.clearUserPref("browser.pocket.enabled");
    }
    // Only define the pref getter now, so we don't get notified for the
    // migrated pref above.
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "prefEnabled",
      "extensions.pocket.enabled",
      true,
      this.onPrefChange.bind(this)
    );
    if (this.prefEnabled) {
      PocketOverlay.startup();
    } else {
      // We avoid calling onPrefChange or similar here, because we don't want to
      // shut down things that haven't started up, or broadcast unnecessary messages.
      this.updateElements(false);
      Services.obs.addObserver(this, "browser-delayed-startup-finished");
    }
    lazy.AboutReaderParent.addMessageListener("Reader:OnSetup", this);
    lazy.AboutReaderParent.addMessageListener(
      "Reader:Clicked-pocket-button",
      this
    );
  },

  observe(subject, topic, data) {
    if (topic == "browser-delayed-startup-finished") {
      // We only get here if pocket is disabled; the observer is removed when
      // we're enabled.
      this.updateElementsInWindow(subject, false);
    }
  },

  _readerButtonData: {
    id: "pocket-button",
    l10nId: "about-reader-toolbar-savetopocket",
    telemetryId: "save-to-pocket",
    image: "chrome://global/skin/icons/pocket.svg",
  },

  onPrefChange(pref, oldValue, newValue) {
    if (!newValue) {
      lazy.AboutReaderParent.broadcastAsyncMessage("Reader:RemoveButton", {
        id: "pocket-button",
      });
      PocketOverlay.shutdown();
      Services.obs.addObserver(this, "browser-delayed-startup-finished");
    } else {
      lazy.AboutReaderParent.broadcastAsyncMessage(
        "Reader:AddButton",
        this._readerButtonData
      );
      Services.obs.removeObserver(this, "browser-delayed-startup-finished");
      PocketOverlay.startup();
    }
    this.updateElements(newValue);
  },

  // Sets or removes the "pocketed" attribute on the Pocket urlbar button as
  // necessary.
  updateToolbarNodeState(browserWindow) {
    const toolbarNode = browserWindow.document.getElementById(
      "save-to-pocket-button"
    );
    if (!toolbarNode || toolbarNode.hidden) {
      return;
    }

    let browser = browserWindow.gBrowser.selectedBrowser;

    let pocketedInnerWindowID = this.innerWindowIDsByBrowser.get(browser);
    if (pocketedInnerWindowID == browser.innerWindowID) {
      // The current window in this browser is pocketed.
      toolbarNode.setAttribute("pocketed", "true");
    } else {
      // The window isn't pocketed.
      toolbarNode.removeAttribute("pocketed");
    }
  },

  // For pocketed inner windows, this maps their <browser>s to those inner
  // window IDs.  If a browser's inner window changes, then the mapped ID will
  // be out of date, meaning that the new inner window has not been pocketed.
  // If a browser goes away, then it'll be gone from this map too since it's
  // weak.  To tell whether a window has been pocketed then, look up its browser
  // in this map and compare the mapped inner window ID to the ID of the current
  // inner window.
  get innerWindowIDsByBrowser() {
    delete this.innerWindowIDsByBrowser;
    return (this.innerWindowIDsByBrowser = new WeakMap());
  },

  onLocationChange(browserWindow) {
    this.updateToolbarNodeState(browserWindow);
  },

  /**
   * Functions related to the Pocket panel UI.
   */
  onShownInToolbarPanel(panel, frame) {
    let window = panel.ownerGlobal;
    window.pktUI.setToolbarPanelFrame(frame);
    lazy.Pocket._initPanelView(window);
  },

  // If an item is saved to Pocket, we cache the browser's inner window ID,
  // so if you navigate to that tab again, we can check the ID
  // and see if we need to update the toolbar icon.
  itemSaved() {
    const browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
    const browser = browserWindow.gBrowser.selectedBrowser;
    SaveToPocket.innerWindowIDsByBrowser.set(browser, browser.innerWindowID);
  },

  // If an item is removed from Pocket, we remove that browser's inner window ID,
  // so if you navigate to that tab again, we can check the ID
  // and see if we need to update the toolbar icon.
  itemDeleted() {
    const browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
    const browser = browserWindow.gBrowser.selectedBrowser;
    SaveToPocket.innerWindowIDsByBrowser.delete(browser);
  },

  updateElements(enabled) {
    // loop through windows and show/hide all our elements.
    for (let win of browserWindows()) {
      this.updateElementsInWindow(win, enabled);
    }
  },

  updateElementsInWindow(win, enabled) {
    if (enabled) {
      win.document.documentElement.removeAttribute("pocketdisabled");
    } else {
      win.document.documentElement.setAttribute("pocketdisabled", "true");
    }
  },

  receiveMessage(message) {
    if (!this.prefEnabled) {
      return;
    }
    switch (message.name) {
      case "Reader:OnSetup": {
        // Tell the reader about our button.
        message.target.sendMessageToActor(
          "Reader:AddButton",
          this._readerButtonData,
          "AboutReader"
        );
        break;
      }
      case "Reader:Clicked-pocket-button": {
        let pocketPanel = message.target.ownerDocument.querySelector(
          "#customizationui-widget-panel"
        );
        if (pocketPanel?.getAttribute("panelopen")) {
          pocketPanel.hidePopup();
        } else {
          // Saves the currently viewed page.
          lazy.Pocket.savePage(message.target);
        }
        break;
      }
    }
  },
};
