/*
 This file is part of GNU Taler
 (C) 2022 Taler Systems S.A.
 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.
 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see 
 */
import { CoreApiResponse } from "@gnu-taler/taler-util";
/**
 * This will modify all the pages that the user load when navigating with Web Extension enabled
 *
 * Can't do useful integration since it run in ISOLATED (or equivalent) mode.
 *
 * If taler support is expected, it will inject a script which will complete the integration.
 */
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Chrome_incompatibilities#content_script_environment
// ISOLATED mode in chromium browsers
// https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md#world
// X-Ray vision in Firefox
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts#xray_vision_in_firefox
// *** IMPORTANT ***
// Content script lifecycle during navigation
// In Firefox: Content scripts remain injected in a web page after the user has navigated away,
// however, window object properties are destroyed.
// In Chrome: Content scripts are destroyed when the user navigates away from a web page.
const documentDocTypeIsHTML =
  window.document.doctype && window.document.doctype.name === "html";
const suffixIsNotXMLorPDF =
  !window.location.pathname.endsWith(".xml") &&
  !window.location.pathname.endsWith(".pdf");
const rootElementIsHTML =
  document.documentElement.nodeName &&
  document.documentElement.nodeName.toLowerCase() === "html";
const pageAcceptsTalerSupport = document.head.querySelector(
  "meta[name=taler-support]",
);
// safe check, if one of this is true then taler handler is not useful
// or not expected
const shouldNotInject =
  !documentDocTypeIsHTML ||
  !suffixIsNotXMLorPDF ||
  // !pageAcceptsTalerSupport || FIXME: removing this before release for testing
  !rootElementIsHTML;
const logger = {
  debug: (...msg: any[]) => {},
  info: (...msg: any[]) =>
    console.log(`${new Date().toISOString()} TALER`, ...msg),
  error: (...msg: any[]) =>
    console.error(`${new Date().toISOString()} TALER`, ...msg),
};
async function start() {
  if (shouldNotInject) {
    return;
  }
  const debugEnabled =
    pageAcceptsTalerSupport?.getAttribute("debug") === "true";
  if (debugEnabled) {
    logger.debug = logger.info;
  }
  createBridgeWithExtension();
  logger.debug("bridged created");
  const shouldInject = await callBackground("isInjectionEnabled", undefined);
  if (shouldInject) {
    injectTalerSupportScript(debugEnabled);
    logger.debug("injection completed");
  } else {
    logger.debug("injection is not enabled");
  }
}
/**
 * Create a  element that load the support in the page context.
 * The interaction support script will create the API to send message
 * that will be received by this loader and be redirected to the extension
 * using the bridge.
 */
function injectTalerSupportScript(debugEnabled: boolean) {
  const container = document.head || document.documentElement;
  const scriptTag = document.createElement("script");
  scriptTag.setAttribute("async", "false");
  const url = new URL(
    chrome.runtime.getURL("/dist/taler-wallet-interaction-support.js"),
  );
  url.searchParams.set("id", chrome.runtime.id);
  if (debugEnabled) {
    url.searchParams.set("debug", "true");
  }
  scriptTag.src = url.href;
  try {
    container.insertBefore(scriptTag, container.children[0]);
  } catch (e) {
    logger.info("inserting link handler failed!");
    logger.error(e);
  }
}
/**
 * Create a bridge connection between the page and the extension.
 *
 * Useful for API calls and replies. Not yet supported.
 */
function createBridgeWithExtension() {
  const port = chrome.runtime.connect();
  window.addEventListener(
    "message",
    (event) => {
      logger.debug("message received", event);
      if (event.source !== window) {
        return;
      }
      if (event.origin !== window.origin) {
        return;
      }
      if (event.data.type && event.data.type === "FROM_PAGE") {
        logger.debug("Content script received: " + event.data.text);
        port.postMessage(event.data.text);
      }
    },
    false,
  );
}
export interface ExtensionOperations {
  isInjectionEnabled: {
    request: void;
    response: boolean;
  };
}
export type MessageFromExtension = {
  channel: "extension";
  operation: Op;
  payload: ExtensionOperations[Op]["request"];
};
export type MessageResponse = CoreApiResponse;
async function callBackground(
  operation: Op,
  payload: ExtensionOperations[Op]["request"],
): Promise {
  const message: MessageFromExtension = {
    channel: "extension",
    operation,
    payload,
  };
  const response = await sendMessageToBackground(message);
  if (response.type === "error") {
    throw new Error(`Background operation "${operation}" failed`);
  }
  return response.result as any;
}
let nextMessageIndex = 0;
async function sendMessageToBackground(
  message: MessageFromExtension,
): Promise {
  const messageWithId = { ...message, id: `id_${nextMessageIndex++ % 1000}` };
  return new Promise((resolve, reject) => {
    // logger.trace("send operation to the wallet background", message);
    let timedout = false;
    const timerId = setTimeout(() => {
      timedout = true;
      throw new Error("timeout");
      // throw TalerError.fromDetail(TalerErrorCode.GENERIC_TIMEOUT, {});
    }, 5 * 1000); //five seconds
    chrome.runtime.sendMessage(messageWithId, (backgroundResponse) => {
      if (timedout) {
        return false; //already rejected
      }
      clearTimeout(timerId);
      if (chrome.runtime.lastError) {
        reject(chrome.runtime.lastError.message);
      } else {
        resolve(backgroundResponse);
      }
      // return true to keep the channel open
      return true;
    });
  });
}
start();