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

/* globals browser, getStrings, selectorLoader, analytics, communication, catcher, log, senderror, startBackground, blobConverters, startSelectionWithOnboarding */

"use strict";

this.main = (function () {
  const exports = {};

  const { incrementCount } = analytics;

  const manifest = browser.runtime.getManifest();
  let backend;

  exports.setBackend = function (newBackend) {
    backend = newBackend;
    backend = backend.replace(/\/*$/, "");
  };

  exports.getBackend = function () {
    return backend;
  };

  communication.register("getBackend", () => {
    return backend;
  });

  for (const permission of manifest.permissions) {
    if (/^https?:\/\//.test(permission)) {
      exports.setBackend(permission);
      break;
    }
  }

  function toggleSelector(tab) {
    return analytics
      .refreshTelemetryPref()
      .then(() => selectorLoader.toggle(tab.id))
      .catch(error => {
        if (
          error.message &&
          /Missing host permission for the tab/.test(error.message)
        ) {
          error.noReport = true;
        }
        error.popupMessage = "UNSHOOTABLE_PAGE";
        throw error;
      });
  }

  // This is called by startBackground.js, where is registered as a click
  // handler for the webextension page action.
  exports.onClicked = catcher.watchFunction(tab => {
    _startShotFlow(tab, "toolbar-button");
  });

  exports.onClickedContextMenu = catcher.watchFunction(tab => {
    _startShotFlow(tab, "context-menu");
  });

  exports.onShortcut = catcher.watchFunction(tab => {
    _startShotFlow(tab, "keyboard-shortcut");
  });

  const _startShotFlow = (tab, inputType) => {
    if (!tab) {
      // Not in a page/tab context, ignore
      return;
    }
    if (!urlEnabled(tab.url)) {
      senderror.showError({
        popupMessage: "UNSHOOTABLE_PAGE",
      });
      return;
    }

    catcher.watchPromise(
      toggleSelector(tab).catch(error => {
        throw error;
      })
    );
  };

  function urlEnabled(url) {
    // Allow screenshots on urls related to web pages in reader mode.
    if (url && url.startsWith("about:reader?url=")) {
      return true;
    }
    if (
      isShotOrMyShotPage(url) ||
      /^(?:about|data|moz-extension):/i.test(url) ||
      isBlacklistedUrl(url)
    ) {
      return false;
    }
    return true;
  }

  function isShotOrMyShotPage(url) {
    // It's okay to take a shot of any pages except shot pages and My Shots
    if (!url.startsWith(backend)) {
      return false;
    }
    const path = url
      .substr(backend.length)
      .replace(/^\/*/, "")
      .replace(/[?#].*/, "");
    if (path === "shots") {
      return true;
    }
    if (/^[^/]{1,4000}\/[^/]{1,4000}$/.test(path)) {
      // Blocks {:id}/{:domain}, but not /, /privacy, etc
      return true;
    }
    return false;
  }

  function isBlacklistedUrl(url) {
    // These specific domains are not allowed for general WebExtension permission reasons
    // Discussion: https://bugzilla.mozilla.org/show_bug.cgi?id=1310082
    // List of domains copied from: https://searchfox.org/mozilla-central/source/browser/app/permissions#18-19
    // Note we disable it here to be informative, the security check is done in WebExtension code
    const badDomains = ["testpilot.firefox.com"];
    let domain = url.replace(/^https?:\/\//i, "");
    domain = domain.replace(/\/.*/, "").replace(/:.*/, "");
    domain = domain.toLowerCase();
    return badDomains.includes(domain);
  }

  communication.register("getStrings", (sender, ids) => {
    return getStrings(ids.map(id => ({ id })));
  });

  communication.register("captureTelemetry", (sender, ...args) => {
    catcher.watchPromise(incrementCount(...args));
  });

  communication.register("openShot", async (sender, { url, copied }) => {
    if (copied) {
      const id = crypto.randomUUID();
      const [title, message] = await getStrings([
        { id: "screenshots-notification-link-copied-title" },
        { id: "screenshots-notification-link-copied-details" },
      ]);
      return browser.notifications.create(id, {
        type: "basic",
        iconUrl: "chrome://browser/content/screenshots/copied-notification.svg",
        title,
        message,
      });
    }
    return null;
  });

  communication.register("copyShotToClipboard", async (sender, blob) => {
    let buffer = await blobConverters.blobToArray(blob);
    await browser.clipboard.setImageData(buffer, blob.type.split("/", 2)[1]);

    const [title, message] = await getStrings([
      { id: "screenshots-notification-image-copied-title" },
      { id: "screenshots-notification-image-copied-details" },
    ]);

    catcher.watchPromise(incrementCount("copy"));
    return browser.notifications.create({
      type: "basic",
      iconUrl: "chrome://browser/content/screenshots/copied-notification.svg",
      title,
      message,
    });
  });

  communication.register("downloadShot", (sender, info) => {
    // 'data:' urls don't work directly, let's use a Blob
    // see http://stackoverflow.com/questions/40269862/save-data-uri-as-file-using-downloads-download-api
    const blob = blobConverters.dataUrlToBlob(info.url);
    const url = URL.createObjectURL(blob);
    let downloadId;
    const onChangedCallback = catcher.watchFunction(function (change) {
      if (!downloadId || downloadId !== change.id) {
        return;
      }
      if (change.state && change.state.current !== "in_progress") {
        URL.revokeObjectURL(url);
        browser.downloads.onChanged.removeListener(onChangedCallback);
      }
    });
    browser.downloads.onChanged.addListener(onChangedCallback);
    catcher.watchPromise(incrementCount("download"));
    return browser.windows.getLastFocused().then(windowInfo => {
      return browser.downloads
        .download({
          url,
          incognito: windowInfo.incognito,
          filename: info.filename,
        })
        .catch(error => {
          // We are not logging error message when user cancels download
          if (error && error.message && !error.message.includes("canceled")) {
            log.error(error.message);
          }
        })
        .then(id => {
          downloadId = id;
        });
    });
  });

  communication.register("abortStartShot", () => {
    // Note, we only show the error but don't report it, as we know that we can't
    // take shots of these pages:
    senderror.showError({
      popupMessage: "UNSHOOTABLE_PAGE",
    });
  });

  // A Screenshots page wants us to start/force onboarding
  communication.register("requestOnboarding", sender => {
    return startSelectionWithOnboarding(sender.tab);
  });

  communication.register("getPlatformOs", () => {
    return catcher.watchPromise(
      browser.runtime.getPlatformInfo().then(platformInfo => {
        return platformInfo.os;
      })
    );
  });

  // This allows the web site show notifications through sitehelper.js
  communication.register("showNotification", (sender, notification) => {
    return browser.notifications.create(notification);
  });

  return exports;
})();
