// ==UserScript==
// @name            netzradar-linker
// @source    		  https://gitlab.ard.de/ida/netzradar-linker
// @description     extrahiert Metadaten (Headline, Hostnamen, URL) des gegenwärtigen Dokuments, generiert Kurz-URL, und kopiert Metadaten und Kurz-URL in die Zwischenablage.
// @version  		    5
// @grant           GM_setClipboard
// @grant           GM.xmlHttpRequest
// @match           *://*/*
// ==/UserScript==

(function () {
  "use strict";

  /*

  =====================
    Einstellungen
  =====================

  */

  /* Status-Meldungen */

  /** Zeige diesen Text im Button an */
  const STATUS_START_TEXT = "Link kürzen";
  const STATUS_START_COLOR = "#e52600";
  /** Zeige diesen Text im Button an, wenn kopiert wurde */
  const STATUS_COPIED_TEXT = "Link in 📋  🥳";
  const STATUS_COPIED_COLOR = "black";
  /** Zeige diesen Text im Button an, wenn Yourls-Abruf nicht klappt */
  const STATUS_YOURLS_ERROR_TEXT = "❌ Yourls-Fehler ❌";
  /** Setze STATUS_COPIED zurück auf STATUS_START zurück (nach Sekunden) */
  const STATUS_RESET_AFTER = 2;

  /* Ausgabe */

  /** Kopiere diese Werte in dieser Form in die Zwischenablage */
  const OUTPUT_TEMPLATE = "{{domain}}: {{headline}}\n{{url}}";

  /* Headline */
  /** Nutze diese Tags (in dieser Reihenfolge), um eine Headline zu finden */
  const HEADLINE_TAGS = ["h1", "title"];
  /** Nutze diesen Text, wenn keine Headline gefunden wird */
  const HEADLINE_FALLBACK = "<Titel bitte manuell eintragen>";

  /* Yourls-API */
  /** URL unter der die Yourls-API erreichbar */
  const YOURLS_API_URL = new URL('http://x.swr.de/a/yourls-api.php');
  /**
   * @typedef {Object} YourlsApiParams
   * @property {string} username Benutzername
   * @property {string} password Passwort
   * @property {'shorturl'} action durchzuführende Aktion
   * @property {'json'} format Rückgabe-Format
   */
  /**
   * Parameter für Yourls-API. Details können unter http://x.swr.de/a/ eingesehen werden.
   * @type {YourlsApiParams}
   */
  const YOURLS_API_PARAMS = {
    username: "User",
    password: "Password",
    action: "shorturl",
    format: "json",
  };

  
  /*

  =====================
    Funktionen
  =====================

  */

  /* Extraktoren */

  /**
   * Erhalte den Domainname (ohne WWW) von einer URL.
   * @param {URL} targetURL URL deren Domainname ausgegeben werden soll
   * @returns {string} Domainname (ohne WWW)
   */
  const extractDomainName = (targetURL) =>
    targetURL.hostname.toLowerCase().startsWith("www.")
      ? targetURL.hostname.slice(4)
      : targetURL.hostname;

  /**
   * Extrahiere die Headline aus einem Dokument.
   * @param {Document} document Dokument aus dem die Headline ausgelesen werden soll
   * @param {Array.<string>} headlineTags Suche in diesen Tags in dieser Reihenfolge nach der Headline
   * @returns {string | undefined} Headline (wenn gefunden), sonst undefined
   */
  const extractHeadline = (document, headlineTags) =>
    headlineTags
      .map((tag) => document.querySelector(tag)?.innerText)
      .filter((mayBeTitle) => typeof mayBeTitle === "string")[0]
      ?.trim();

  /* API-Calls */

  /**
   * Erhalte eine Kurz-URL von `targetURL` von der Yourls-API.
   * @param {URL} apiURL URL der Yourls-API
   * @param {YourlsApiParams} apiParams Paramater der der Yourls-API
   * @param {URL} targetURL URL die gekürzt werden soll
   * @returns {Promise<string>} Die gekürzte URL
   * @throws {Error} Es konnte keine Kurz-URL abgeholt werden
   */
  const fetchShortURL = async (apiURL, apiParams, targetURL) => {
    // GET-URL bauen für Yourls-API
    const params = { ...apiParams, url: targetURL.toString() };
    Object.entries(params).forEach(([name, value]) =>
      apiURL.searchParams.append(name, value)
    );

    // Request durchführen
    const request = await GM.xmlHttpRequest({
      method: "GET",
      url: apiURL.href
    });
    const data = JSON.parse(request.response)

    return data.shorturl;
  };


  /* Button */

  /**
   * Füge Interaktions-Button ins Dokument ein.
   * @param {Document} document Dokument dem der Button hinzufgefügt werden soll
   * @param {(event: MouseEvent, button: HTMLButtonElement) => Promise<void>} handleClick Löse diese Funktion bei Klick aus
   * @returns {HTMLButtonElement} Das erstellte HTML-Button-Element
   */
  const addButton = (document, handleClick) => {
    const button = document.createElement("button");
    button.setAttribute("id", "myshortenLink");
    button.addEventListener("click", (e) => handleClick(e, button), false);
    button.innerHTML = STATUS_START_TEXT;

    // Button stylen
    button.style.bottom = "20px";
    button.style.right = "50px";
    button.style.height = "50px";
    button.style.width = "200px";
    button.style.position = "fixed";
    button.style.color = "white";
    button.style.fontWeight = "700";
    button.style.fontFamily = "sans-serif";
    button.style.fontSize = "14px";
    button.style.padding = "3px";
    button.style.borderStyle = "none";
    button.style.borderRadius = "25px";
    button.style.zIndex = 99999999;
    button.style.cursor = "pointer";
    button.style.backgroundColor = STATUS_START_COLOR;
    button.style.transition = "background-color 0.7s ease";

    // Button dem Dokument hinzufügen
    document.body.appendChild(button);

    return button;
  };

  /** wurde der Button gedrückt? */
  let buttonIsPressed = false;
  /**
   * Manage den Anzeige-Text und Anzeige-Stil des Buttons
   * (vgl. für Status: `buttonIsPressed`).
   * @param {HTMLButtonElement} button Der Knopf
   */
  const toggleButton = (button) => {
    buttonIsPressed = !buttonIsPressed;

    // Löse Reflow aus, um sicherzustellen, dass die Transition startet
    void button.offsetWidth;

    if (buttonIsPressed) {
      button.style.backgroundColor = STATUS_COPIED_COLOR;
      button.innerHTML = STATUS_COPIED_TEXT;
      return;
    }

    button.innerHTML = STATUS_START_TEXT;
    button.style.backgroundColor = STATUS_START_COLOR;
  };

  /*

  =====================
    Programmablauf
  =====================

  */

  /**
   * Extrahiere Metadaten (Headline, Hostnamen, URL) des aktuellen Dokuments,
   * generiere Kurz-URL,
   * und kopiere Metadaten und Kurz-URL in die Zwischenablage.
   * @param {MouseEvent} event Das auslösende Klick-Event
   * @param {HTMLButtonElement} button Der gedrückte Knopf
   */
  const handleClick = async (event, button) => {
    event.preventDefault();

    /* Extrahiere Metadaten */

    /** URL der Website, die verlinkt werden soll  */
    const targetURL = new URL(window.location.href);
    const domainName = extractDomainName(targetURL);
    // Wenn die extrahierte falsy ist (hier relevant: leerer String oder undefined),
    // soll HEADLINE_FALLBACK genutzt werden
    const headline =
      extractHeadline(document, HEADLINE_TAGS) || HEADLINE_FALLBACK;

    /* Hole Short-URL und breche ab, falls nicht möglich */

    let shortURL = "";
    try {
      shortURL = await fetchShortURL(
        YOURLS_API_URL,
        YOURLS_API_PARAMS,
        targetURL
      );
    } catch (error) {
      console.error(error);
      button.innerHTML = STATUS_YOURLS_ERROR_TEXT;
      throw new Error('Fataler Fehler, konnte short-URL nicht generieren');
    }

    /* Formatiere Ausgabe */

    const output = OUTPUT_TEMPLATE.replace("{{domain}}", domainName)
      .replace("{{headline}}", headline)
      .replace("{{url}}", shortURL);

    // Kopiere Ausgabe in den Zwischenspeicher
    // vgl. https://www.tampermonkey.net/documentation.php?locale=en#api:GM_setClipboard
    GM_setClipboard(output);

    /* Informiere Userin, dass kopiert */

    toggleButton(button);
    // Setze Text zurück (falls Userin noch einmal kopieren möchte)
    let buttonPressedTimeoutID = undefined;
    // Lösche ggf. existierendes Timeout
    clearTimeout(buttonPressedTimeoutID);
    buttonPressedTimeoutID = setTimeout(
      () => toggleButton(button),
      STATUS_RESET_AFTER * 1000
    );
  };

  // Button hinzufügen
  addButton(document, handleClick);
})();