/*
 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: unknown[]) => {},
    info: (...msg: unknown[]) =>
      console.log(`${new Date().toISOString()} TALER`, ...msg),
    error: (...msg: unknown[]) =>
      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 ||
    !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-simple/${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();
      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() {
      if (document.body) overrideAllAnchor(document.body);
      new MutationObserver(checkForNewAnchors).observe(document, {
        childList: true,
        subtree: true,
        attributes: false,
      });
    }

    return {
      convertURIToWebExtensionPath,
      anchorOnClick,
      registerProtocolHandler,
    };
  }

  function start() {
    if (shouldNotRun) return;
    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";
    const apiEnabled = searchParams.get("api") === "true";
    const hijackEnabled = searchParams.get("hijack") === "true";
    const callbackEnabled = searchParams.get("callback") === "true";

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

    if (debugEnabled) {
      logger.debug = logger.info;
    }

    const taler: TalerSupport = {
      info,
      __internal: buildApi(info),
    };

    if (apiEnabled) {
      // @ts-expect-error we now that `taler` doesn't exist.
      // we are creating the property
      window.taler = taler;
    }

    if (hijackEnabled) {
      taler.__internal.registerProtocolHandler();
    }

    if (callbackEnabled) {
      if ("talerCallback" in window) {
        (window as any).talerCallback({ present: true });
      }
    }
  }

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

  start();
})();
