/*
 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 <http://www.gnu.org/licenses/>
 */

/**
 * WARNING
 *
 * This script will be loaded and run in every page while the
 * user us navigating. It must be short, simple and safe.
 */

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),
};

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]",
);

// this is also checked by the loader
// but a double check will prevent running and breaking user navigation
// if loaded from other location
const shouldNotRun =
  !documentDocTypeIsHTML ||
  !suffixIsNotXMLorPDF ||
  // !pageAcceptsTalerSupport || FIXME: removing this before release for testing
  !rootElementIsHTML;

interface Info {
  extensionId: string;
  protocol: string;
  hostname: string;
}
interface API {
  convertURIToWebExtensionPath: (uri: string) => string | undefined;
  anchorOnClick: (ev: MouseEvent) => void;
  registerProtocolHandler: () => void;
}
interface TalerSupport {
  info: Readonly<Info>;
  __internal: API;
}

function buildApi(config: Readonly<Info>): API {
  /**
   * Takes an anchor href that starts with taler:// and
   * returns the path to the web-extension page
   */
  function convertURIToWebExtensionPath(uri: string): string | undefined {
    if (!validateTalerUri(uri)) {
      logger.error(`taler:// URI is invalid: ${uri}`);
      return undefined;
    }
    const host = `${config.protocol}//${config.hostname}`;
    const path = `static/wallet.html#/taler-uri/${encodeURIComponent(uri)}`;
    return `${host}/${path}`;
  }

  function anchorOnClick(ev: MouseEvent) {
    if (!(ev.currentTarget instanceof Element)) {
      logger.debug(`onclick: registered in a link that is not an HTML element`);
      return;
    }
    const hrefAttr = ev.currentTarget.attributes.getNamedItem("href");
    if (!hrefAttr) {
      logger.debug(`onclick: link didn't have href with taler:// uri`);
      return;
    }
    const targetAttr = ev.currentTarget.attributes.getNamedItem("target");
    const windowTarget =
      targetAttr && targetAttr.value ? targetAttr.value : "_self";
    const page = convertURIToWebExtensionPath(hrefAttr.value);
    if (!page) {
      logger.debug(`onclick: could not convert "${hrefAttr.value}" into path`);
      return;
    }
    // we can use window.open, but maybe some browser will block it?
    window.open(page, windowTarget);
    ev.preventDefault();
    ev.stopPropagation();
    ev.stopImmediatePropagation();
    // another possibility is to change the location when the click is made
    // or when the anchor is found
    // hrefAttr.value = page
    // TODO: explore different possibilities and maybe allow the configuration
    // using the meta-tag
    return false;
  }

  function overrideAllAnchor(root: HTMLElement) {
    const allAnchors = root.querySelectorAll("a[href^=taler]");
    logger.debug(`registering taler protocol in ${allAnchors.length} links`);
    allAnchors.forEach((link) => {
      if (link instanceof HTMLElement) {
        link.addEventListener("click", anchorOnClick);
      }
    });
  }

  function checkForNewAnchors(
    mutations: MutationRecord[],
    observer: MutationObserver,
  ) {
    mutations.forEach((mut) => {
      if (mut.type === "childList") {
        mut.addedNodes.forEach((added) => {
          if (added instanceof HTMLElement) {
            logger.debug(`new element`, added);
            overrideAllAnchor(added);
          }
        });
      }
    });
  }

  /**
   * Check of every anchor and observes for new one.
   * Register the anchor handler when found
   */
  function registerProtocolHandler() {
    const observer = new MutationObserver(checkForNewAnchors);
    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: false,
    });

    overrideAllAnchor(document.body);
  }

  return {
    convertURIToWebExtensionPath,
    anchorOnClick,
    registerProtocolHandler,
  };
}

function start() {
  if (shouldNotRun) return;
  // FIXME: we can remove this if the script caller send information we need
  if (!(document.currentScript instanceof HTMLScriptElement)) return;

  const url = new URL(document.currentScript.src);
  const { protocol, searchParams, hostname } = url;
  const extensionId = searchParams.get("id") ?? "";
  const debugEnabled = searchParams.get("debug") === "true";
  if (debugEnabled) {
    logger.debug = logger.info;
  }

  const info: Info = Object.freeze({
    extensionId,
    protocol,
    hostname,
  });
  const taler: TalerSupport = {
    info,
    __internal: buildApi(info),
  };

  //@ts-ignore
  window.taler = taler;

  //default behavior: register on install
  taler.__internal.registerProtocolHandler();
}

// utils functions
function validateTalerUri(uri: string): boolean {
  return (
    !!uri && (uri.startsWith("taler://") || uri.startsWith("taler+http://"))
  );
}

start();