"use strict";

import commonHelper from "../../assets/javascripts/helpers/common.js";
import twitterHelper from "../../assets/javascripts/helpers/twitter.js";
import youtubeHelper from "../../assets/javascripts/helpers/youtube.js";
import instagramHelper from "../../assets/javascripts/helpers/instagram.js";
import mapsHelper from "../../assets/javascripts/helpers/google-maps.js";
import redditHelper from "../../assets/javascripts/helpers/reddit.js";
import searchHelper from "../../assets/javascripts/helpers/google-search.js";

const nitterInstances = twitterHelper.redirects;
const twitterDomains = twitterHelper.targets;
const youtubeDomains = youtubeHelper.targets;
const invidiousInstances = youtubeHelper.redirects;
const instagramDomains = instagramHelper.targets;
const bibliogramInstances = instagramHelper.redirects;
const instagramReservedPaths = instagramHelper.reservedPaths;
const bibliogramBypassPaths = instagramHelper.bypassPaths;
const osmDefault = mapsHelper.redirects[0];
const googleMapsRegex = mapsHelper.targets;
const mapCentreRegex = mapsHelper.mapCentreRegex;
const dataLatLngRegex = mapsHelper.dataLatLngRegex;
const placeRegex = mapsHelper.placeRegex;
const travelModes = mapsHelper.travelModes;
const layers = mapsHelper.layers;
const redditInstances = redditHelper.redirects;
const redditDomains = redditHelper.targets;
const redditBypassPaths = redditHelper.bypassPaths;
const redditDefault = redditHelper.redirects[0];
const googleSearchRegex = searchHelper.targets;
const searchEngineInstances = searchHelper.redirects;

let disableNitter;
let disableInvidious;
let disableBibliogram;
let disableOsm;
let disableReddit;
let disableSearchEngine;
let nitterInstance;
let invidiousInstance;
let bibliogramInstance;
let osmInstance;
let redditInstance;
let searchEngineInstance;
let alwaysProxy;
let onlyEmbeddedVideo;
let videoQuality;
let invidiousDarkMode;
let invidiousVolume;
let invidiousPlayerStyle;
let invidiousSubtitles;
let invidiousAutoplay;
let useFreeTube;
let nitterRandomPool;
let invidiousRandomPool;
let bibliogramRandomPool;
let exceptions;

window.browser = window.browser || window.chrome;

browser.storage.sync.get(
  [
    "nitterInstance",
    "invidiousInstance",
    "bibliogramInstance",
    "osmInstance",
    "redditInstance",
    "searchEngineInstance",
    "disableNitter",
    "disableInvidious",
    "disableBibliogram",
    "disableOsm",
    "disableReddit",
    "disableSearchEngine",
    "alwaysProxy",
    "onlyEmbeddedVideo",
    "videoQuality",
    "invidiousDarkMode",
    "invidiousVolume",
    "invidiousPlayerStyle",
    "invidiousSubtitles",
    "invidiousAutoplay",
    "useFreeTube",
    "nitterRandomPool",
    "invidiousRandomPool",
    "bibliogramRandomPool",
    "exceptions",
  ],
  (result) => {
    nitterInstance = result.nitterInstance;
    invidiousInstance = result.invidiousInstance;
    bibliogramInstance = result.bibliogramInstance;
    osmInstance = result.osmInstance || osmDefault;
    redditInstance = result.redditInstance || redditDefault;
    searchEngineInstance = result.searchEngineInstance;
    disableNitter = result.disableNitter;
    disableInvidious = result.disableInvidious;
    disableBibliogram = result.disableBibliogram;
    disableOsm = result.disableOsm;
    disableReddit = result.disableReddit;
    disableSearchEngine = result.disableSearchEngine;
    alwaysProxy = result.alwaysProxy;
    onlyEmbeddedVideo = result.onlyEmbeddedVideo;
    videoQuality = result.videoQuality;
    invidiousDarkMode = result.invidiousDarkMode;
    exceptions = result.exceptions
      ? result.exceptions.map((e) => {
          return new RegExp(e);
        })
      : [];
    invidiousVolume = result.invidiousVolume;
    invidiousPlayerStyle = result.invidiousPlayerStyle;
    invidiousSubtitles = result.invidiousSubtitles || "";
    invidiousAutoplay = result.invidiousAutoplay;
    useFreeTube = result.useFreeTube;
    nitterRandomPool = result.nitterRandomPool
      ? result.nitterRandomPool.split(",")
      : commonHelper.filterInstances(nitterInstances);
    invidiousRandomPool = result.invidiousRandomPool
      ? result.invidiousRandomPool.split(",")
      : commonHelper.filterInstances(invidiousInstances);
    bibliogramRandomPool = result.bibliogramRandomPool
      ? result.bibliogramRandomPool.split(",")
      : commonHelper.filterInstances(bibliogramInstances);
  }
);

browser.storage.onChanged.addListener((changes) => {
  if ("nitterInstance" in changes) {
    nitterInstance = changes.nitterInstance.newValue;
  }
  if ("invidiousInstance" in changes) {
    invidiousInstance = changes.invidiousInstance.newValue;
  }
  if ("bibliogramInstance" in changes) {
    bibliogramInstance = changes.bibliogramInstance.newValue;
  }
  if ("osmInstance" in changes) {
    osmInstance = changes.osmInstance.newValue || osmDefault;
  }
  if ("redditInstance" in changes) {
    redditInstance = changes.redditInstance.newValue || redditDefault;
  }
  if ("searchEngineInstance" in changes) {
    searchEngineInstance = changes.searchEngineInstance.newValue;
  }
  if ("disableNitter" in changes) {
    disableNitter = changes.disableNitter.newValue;
  }
  if ("disableInvidious" in changes) {
    disableInvidious = changes.disableInvidious.newValue;
  }
  if ("disableBibliogram" in changes) {
    disableBibliogram = changes.disableBibliogram.newValue;
  }
  if ("disableOsm" in changes) {
    disableOsm = changes.disableOsm.newValue;
  }
  if ("disableReddit" in changes) {
    disableReddit = changes.disableReddit.newValue;
  }
  if ("disableSearchEngine" in changes) {
    disableSearchEngine = changes.disableSearchEngine.newValue;
  }
  if ("alwaysProxy" in changes) {
    alwaysProxy = changes.alwaysProxy.newValue;
  }
  if ("onlyEmbeddedVideo" in changes) {
    onlyEmbeddedVideo = changes.onlyEmbeddedVideo.newValue;
  }
  if ("videoQuality" in changes) {
    videoQuality = changes.videoQuality.newValue;
  }
  if ("invidiousDarkMode" in changes) {
    invidiousDarkMode = changes.invidiousDarkMode.newValue;
  }
  if ("invidiousVolume" in changes) {
    invidiousVolume = changes.invidiousVolume.newValue;
  }
  if ("invidiousPlayerStyle" in changes) {
    invidiousPlayerStyle = changes.invidiousPlayerStyle.newValue;
  }
  if ("invidiousSubtitles" in changes) {
    invidiousSubtitles = changes.invidiousSubtitles.newValue;
  }
  if ("invidiousAutoplay" in changes) {
    invidiousAutoplay = changes.invidiousAutoplay.newValue;
  }
  if ("useFreeTube" in changes) {
    useFreeTube = changes.useFreeTube.newValue;
  }
  if ("nitterRandomPool" in changes) {
    nitterRandomPool = changes.nitterRandomPool.newValue.split(",");
  }
  if ("invidiousRandomPool" in changes) {
    invidiousRandomPool = changes.invidiousRandomPool.newValue.split(",");
  }
  if ("bibliogramRandomPool" in changes) {
    bibliogramRandomPool = changes.bibliogramRandomPool.newValue.split(",");
  }
  if ("exceptions" in changes) {
    exceptions = changes.exceptions.newValue.map((e) => {
      return new RegExp(e);
    });
  }
});

function isException(url, initiator) {
  return (
    exceptions.some((regex) => regex.test(url.href)) ||
    (initiator && exceptions.some((regex) => regex.test(initiator.href)))
  );
}

function isFirefox() {
  return typeof InstallTrigger !== "undefined";
}

function redirectYouTube(url, initiator, type) {
  if (disableInvidious || isException(url, initiator)) {
    return null;
  }
  if (
    initiator &&
    (initiator.origin === invidiousInstance ||
      invidiousInstances.includes(initiator.origin) ||
      youtubeDomains.includes(initiator.host))
  ) {
    return null;
  }
  if (url.pathname.match(/iframe_api/) || url.pathname.match(/www-widgetapi/)) {
    // Don't redirect YouTube Player API.
    return null;
  }
  if (url.host.split(".")[0] === "studio") {
    // Avoid redirecting `studio.youtube.com`
    return null;
  }
  if (onlyEmbeddedVideo && type !== "sub_frame") {
    return null;
  }
  if (useFreeTube && type === "main_frame") {
    return `freetube://${url}`;
  }
  // Apply settings
  if (alwaysProxy) {
    url.searchParams.append("local", true);
  }
  if (videoQuality) {
    url.searchParams.append("quality", videoQuality);
  }
  if (invidiousDarkMode) {
    url.searchParams.append("dark_mode", invidiousDarkMode);
  }
  if (invidiousVolume) {
    url.searchParams.append("volume", invidiousVolume);
  }
  if (invidiousPlayerStyle) {
    url.searchParams.append("player_style", invidiousPlayerStyle);
  }
  if (invidiousSubtitles) {
    url.searchParams.append("subtitles", invidiousSubtitles);
  }
  url.searchParams.append("autoplay", invidiousAutoplay ? 1 : 0);

  return `${
    invidiousInstance || commonHelper.getRandomInstance(invidiousRandomPool)
  }${url.pathname.replace("/shorts", "")}${url.search}`;
}

function redirectTwitter(url, initiator) {
  if (disableNitter || isException(url, initiator)) {
    return null;
  }
  if (url.pathname.includes("/home")) {
    return null;
  }
  if (
    isFirefox() &&
    initiator &&
    (initiator.origin === nitterInstance ||
      nitterInstances.includes(initiator.origin) ||
      twitterDomains.includes(initiator.host))
  ) {
    browser.storage.sync.set({
      redirectBypassFlag: true,
    });
    return null;
  }
  if (url.host.split(".")[0] === "pbs") {
    return `${
      nitterInstance || commonHelper.getRandomInstance(nitterRandomPool)
    }/pic/${encodeURIComponent(url.href)}`;
  } else if (url.host.split(".")[0] === "video") {
    return `${
      nitterInstance || commonHelper.getRandomInstance(nitterRandomPool)
    }/gif/${encodeURIComponent(url.href)}`;
  } else if (url.pathname.includes("tweets")) {
    return `${
      nitterInstance || commonHelper.getRandomInstance(nitterRandomPool)
    }${url.pathname.replace("/tweets", "")}${url.search}`;
  } else {
    return `${
      nitterInstance || commonHelper.getRandomInstance(nitterRandomPool)
    }${url.pathname}${url.search}`;
  }
}

function redirectInstagram(url, initiator, type) {
  if (disableBibliogram || isException(url, initiator)) {
    return null;
  }
  // Do not redirect Bibliogram view on Instagram links
  if (
    initiator &&
    (initiator.origin === bibliogramInstance ||
      bibliogramInstances.includes(initiator.origin) ||
      instagramDomains.includes(initiator.host))
  ) {
    return null;
  }
  // Do not redirect /accounts, /embeds.js, or anything other than main_frame
  if (type !== "main_frame" || url.pathname.match(bibliogramBypassPaths)) {
    return null;
  }
  if (
    url.pathname === "/" ||
    instagramReservedPaths.includes(url.pathname.split("/")[1])
  ) {
    return `${
      bibliogramInstance || commonHelper.getRandomInstance(bibliogramRandomPool)
    }${url.pathname}${url.search}`;
  } else {
    // Likely a user profile, redirect to '/u/...'
    return `${
      bibliogramInstance || commonHelper.getRandomInstance(bibliogramRandomPool)
    }/u${url.pathname}${url.search}`;
  }
}

function redirectGoogleMaps(url, initiator) {
  if (disableOsm || isException(url, initiator)) {
    return null;
  }
  let redirect;
  let mapCentre = "";
  let params = "";
  // Set map centre if present
  if (url.pathname.match(mapCentreRegex)) {
    const [, lat, lon, zoom] = url.pathname.match(mapCentreRegex);
    mapCentre = `#map=${zoom}/${lat}/${lon}`;
  } else if (url.search.includes("center=")) {
    const [lat, lon] = url.searchParams.get("center").split(",");
    mapCentre = `#map=${url.searchParams.get("zoom") || "17"}/${lat}/${lon}`;
    // Set default zoom if mapCentre not present
  } else {
    params = "&zoom=17";
  }
  // Set map layer
  params = `${params}&layers=${
    layers[url.searchParams.get("layer")] || layers["none"]
  }`;
  // Handle Google Maps Embed API
  if (url.pathname.includes("/embed")) {
    let query = "";
    if (url.searchParams.has("q")) {
      query = url.searchParams.get("q");
    } else if (url.searchParams.has("query")) {
      query = url.searchParams.has("query");
    } else if (url.searchParams.has("pb")) {
      try {
        query = url.searchParams.get("pb").split(/!2s(.*?)!/)[1];
      } catch (error) {
        console.error(error);
        // Unable to find map marker in URL.
      }
    }
    let marker, bbox;
    mapsHelper.addressToLatLng(query, (coords, boundingbox) => {
      marker = coords;
      bbox = boundingbox;
    });
    redirect = `${osmInstance}/export/embed.html?bbox=${bbox}&layer=mapnik&marker=${marker}`;
    // Handle Google Maps Directions
  } else if (url.pathname.includes("/dir")) {
    const travelMode =
      travelModes[url.searchParams.get("travelmode")] || travelModes["driving"];
    let origin;
    mapsHelper.addressToLatLng(url.searchParams.get("origin"), (coords) => {
      origin = coords;
    });
    let destination;
    mapsHelper.addressToLatLng(
      url.searchParams.get("destination"),
      (coords) => {
        destination = coords;
      }
    );
    redirect = `${osmInstance}/directions?engine=${travelMode}&route=${origin}%3B${destination}${mapCentre}${params}`;
    // Get marker from data attribute
  } else if (
    url.pathname.includes("data=") &&
    url.pathname.match(dataLatLngRegex)
  ) {
    const [mlat, mlon] = url.pathname.match(dataLatLngRegex);
    redirect = `${osmInstance}/?mlat=${mlat.replace(
      "!3d",
      ""
    )}&mlon=${mlon.replace("!4d", "")}${mapCentre}${params}`;
    // Get marker from ll param
  } else if (url.searchParams.has("ll")) {
    const [mlat, mlon] = url.searchParams.get("ll").split(",");
    redirect = `${osmInstance}/?mlat=${mlat}&mlon=${mlon}${mapCentre}${params}`;
    // Get marker from viewpoint param.
  } else if (url.searchParams.has("viewpoint")) {
    const [mlat, mlon] = url.searchParams.get("viewpoint").split(",");
    redirect = `${osmInstance}/?mlat=${mlat}&mlon=${mlon}${mapCentre}${params}`;
    // Use query as search if present.
  } else {
    let query;
    if (url.searchParams.has("q")) {
      query = url.searchParams.get("q");
    } else if (url.searchParams.has("query")) {
      query = url.searchParams.get("query");
    } else if (url.pathname.match(placeRegex)) {
      query = url.pathname.match(placeRegex)[1];
    }
    redirect = `${osmInstance}/${query ? "search?query=" + query : ""}${
      mapCentre || "#"
    }${params}`;
  }

  return redirect;
}

function redirectReddit(url, initiator, type) {
  if (disableReddit || isException(url, initiator)) {
    return null;
  }
  // Do not redirect when already on the selected view
  if (
    (initiator && initiator.origin === redditInstance) ||
    url.origin === redditInstance
  ) {
    return null;
  }
  // Do not redirect exclusions nor anything other than main_frame
  if (type !== "main_frame" || url.pathname.match(redditBypassPaths)) {
    return null;
  }
  return `${redditInstance}${url.pathname}${url.search}`;
}

function redirectSearchEngine(url, initiator) {
  if (disableSearchEngine || isException(url, initiator)) {
    return null;
  }

  const searchEngine =
    searchEngineInstance ||
    commonHelper.getRandomInstance(searchEngineInstances);
  let search = "";
  url.search
    .slice(1)
    .split("&")
    .forEach(function (input) {
      if (input.startsWith("q=")) search = input;
    });
  return `${searchEngine.link}${searchEngine.q}?${search}`;
}

browser.webRequest.onBeforeRequest.addListener(
  (details) => {
    const url = new URL(details.url);
    let initiator;
    if (details.originUrl) {
      initiator = new URL(details.originUrl);
    } else if (details.initiator) {
      initiator = new URL(details.initiator);
    }
    let redirect;
    if (youtubeDomains.includes(url.host)) {
      redirect = {
        redirectUrl: redirectYouTube(url, initiator, details.type),
      };
    } else if (twitterDomains.includes(url.host)) {
      redirect = {
        redirectUrl: redirectTwitter(url, initiator),
      };
    } else if (instagramDomains.includes(url.host)) {
      redirect = {
        redirectUrl: redirectInstagram(url, initiator, details.type),
      };
    } else if (url.href.match(googleMapsRegex)) {
      redirect = {
        redirectUrl: redirectGoogleMaps(url, initiator),
      };
    } else if (
      redditDomains.includes(url.host) ||
      redditInstances.includes(url.origin)
    ) {
      redirect = {
        redirectUrl: redirectReddit(url, initiator, details.type),
      };
    } else if (url.href.match(googleSearchRegex)) {
      redirect = {
        redirectUrl: redirectSearchEngine(url, initiator),
      };
    }
    if (redirect && redirect.redirectUrl) {
      console.info(
        "Redirecting",
        `"${url.href}"`,
        "=>",
        `"${redirect.redirectUrl}"`
      );
      console.info("Details", details);
    }
    return redirect;
  },
  {
    urls: ["<all_urls>"],
  },
  ["blocking"]
);

browser.runtime.onInstalled.addListener((details) => {
  if (details.reason === "update") {
    browser.storage.sync.get(
      ["whitelist", "exceptions", "invidiousInstance"],
      (result) => {
        if (result.whitelist) {
          let whitelist = result.whitelist.map((e) =>
            e.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")
          );
          browser.storage.sync.set({
            exceptions: result.exceptions.concat(whitelist),
            whitelist: null,
          });
        }
        if (result.invidiousInstance === "https://invidio.us") {
          browser.storage.sync.set({
            invidiousInstance: null,
          });
        }
      }
    );
  }
});
