import utils from "./utils.js"

const isChrome = browser.runtime.getBrowserInfo === undefined
window.browser = window.browser || window.chrome

let config, options

function init() {
	return new Promise(async resolve => {
		options = await utils.getOptions()
		config = await utils.getConfig()
		resolve()
	})
}

init()
browser.storage.onChanged.addListener(init)

function all(service, frontend, options, config) {
	let instances = []
	if (!frontend) {
		for (const frontend in config.services[service].frontends) {
			if (options[frontend]) {
				instances.push(...options[frontend])
			}
		}
	} else if (options[frontend]) {
		instances = options[frontend]
	}
	return instances
}

function regexArray(service, url, config, frontend) {
	let targetList = config.services[service].targets
	if (frontend && config.services[service].frontends[frontend].excludeTargets) {
		targetList = targetList.filter(val =>
			!config.services[service].frontends[frontend].excludeTargets.includes(targetList.indexOf(val))
		)
	}
	for (const targetString in targetList) {
		const target = new RegExp(targetList[targetString])
		if (target.test(url.href)) return true
	}
	return false
}

async function redirectAsync(url, type, initiator, forceRedirection) {
	await init()
	return redirect(url, type, initiator, forceRedirection)
}

function redirect(url, type, initiator, forceRedirection) {
	if (type != "main_frame" && type != "sub_frame" && type != "image") return
	let randomInstance
	let frontend
	for (const service in config.services) {
		if (!forceRedirection && !options[service].enabled) continue

		frontend = options[service].frontend ?? Object.keys(config.services[service].frontends)[0]

		if (!regexArray(service, url, config, frontend)) {
			frontend = null
			continue
		}

		if (
			(config.services[service].embeddable && type != options[service].redirectType && options[service].redirectType != "both")
			||
			(!config.services[service].embeddable && type != "main_frame")
		) {
			if (options[service].unsupportedUrls == 'block') return 'CANCEL'
			return
		}

		let instanceList = options[frontend]
		if (instanceList === undefined) break
		if (instanceList.length === 0) return

		if (
			initiator
			&&
			instanceList.includes(initiator.origin)
		) return "BYPASSTAB"

		randomInstance = utils.getRandomInstance(instanceList)

		break
	}

	// Here is a (temperory) space for defining constants required in 2 or more switch cases.
	const mapCentreRegex = /@(-?\d[0-9.]*),(-?\d[0-9.]*),(\d{1,2})[.z]/
	const dataLatLngRegex = /!3d(-?[0-9]{1,}.[0-9]{1,})!4d(-?[0-9]{1,}.[0-9]{1,})/
	const placeRegex = /\/place\/(.*)\//
	function convertMapCentre() {
		let [lat, lon, zoom] = [null, null, null]
		if (url.pathname.match(mapCentreRegex)) {
			// Set map centre if present
			;[lat, lon, zoom] = url.pathname.match(mapCentreRegex)
		} else if (url.searchParams.has("center")) {
			;[lat, lon] = url.searchParams.get("center").split(",")
			zoom = url.searchParams.get("zoom") ?? "17"
		}
		return [zoom, lon, lat]
	}

	if (!frontend) return

	switch (frontend) {
		// This is where all instance-specific code must be ran to convert the service url to one that can be understood by the frontend.
		case "beatbump": {
			return `${randomInstance}${url.pathname}${url.search}`
				.replace("/watch?v=", "/listen?id=")
				.replace("/channel/", "/artist/")
				.replace("/playlist?list=", "/playlist/VL")
				.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/") + "?filter=all")
		}
		case "hyperpipe": {
			return `${randomInstance}${url.pathname}${url.search}`.replace(/\/search\?q=.*/, searchQuery => searchQuery.replace("?q=", "/"))
		}
		case "lbryDesktop": {
			return url.href.replace(/^https?:\/{2}odysee\.com\//, "lbry://").replace(/:(?=[a-zA-Z0-9])/g, "#")
		}
		case "searx":
		case "searxng":
			return `${randomInstance}/${url.search}`
		case "whoogle": {
			return `${randomInstance}/search${url.search}`
		}
		case "librex": {
			return `${randomInstance}/search.php${url.search}`
		}
		case "send": {
			return randomInstance
		}
		case "nitter": {
			let search = new URLSearchParams(url.search)

			search.delete("ref_src")
			search.delete("ref_url")
			search.delete("s") // type of device that shared the link
			search.delete("t") // some sort of tracking ID

			search = search.toString()
			if (search !== "") search = `?${search}`

			if (url.host.split(".")[0] === "pbs" || url.host.split(".")[0] === "video") {
				try {
					const [, id, format, extra] = search.match(/(.*)\?format=(.*)&(.*)/)
					const query = encodeURIComponent(`${id}.${format}?${extra}`)
					return `${randomInstance}/pic${url.pathname}${query}`
				} catch {
					return `${randomInstance}/pic${url.pathname}${search}`
				}
			}

			if (url.pathname.split("/").includes("tweets")) return `${randomInstance}${url.pathname.replace("/tweets", "")}${search}`
			if (url.host == "t.co") return `${randomInstance}/t.co${url.pathname}`
			return `${randomInstance}${url.pathname}${search}#m`
		}
		case "yattee": {
			return url.href.replace(/^https?:\/{2}/, "yattee://")
		}
		case "freetube": {
			return `freetube://https://youtu.be${url.pathname}${url.search}`.replace(/watch\?v=/, "")
		}
		case "invidious":
		case "piped":
		case "pipedMaterial":
		case "cloudtube": {
			if (url.pathname.startsWith("/live_chat")) {
				return null;
			}
			return `${randomInstance}${url.pathname}${url.search}`;
		}
		case "poketube": {
			if (url.pathname.startsWith("/live_chat")) {
				return null;
			}
			if (url.pathname.startsWith('/channel')) {
				const reg = /\/channel\/(.*)\/?$/.exec(url.pathname)
				if (reg) {
					const id = reg[1]
					return `${randomInstance}/channel?id=${id}${url.search}`
				}
			}
			if (/\/@[a-z]+\//.exec(url.pathname)) {
				return randomInstance
			}
			return `${randomInstance}${url.pathname}${url.search}`
		}
		case "scribe": {
			const regex = url.hostname.match(/^(link|cdn-images-\d+|.*)\.medium\.com/)
			if (regex && regex.length > 1) {
				const subdomain = regex[1]
				if (subdomain != "link" || !subdomain.startsWith("cdn-images")) {
					return `${randomInstance}/@${subdomain}${url.pathname}${url.search}`
				}
			}
			return `${randomInstance}${url.pathname}${url.search}`
		}
		case "simplyTranslate": {
			return `${randomInstance}/${url.search}`
		}
		case "libreTranslate": {
			return `${randomInstance}/${url.search}`
				.replace(/(?<=\/?)sl/, "source")
				.replace(/(?<=&)tl/, "target")
				.replace(/(?<=&)text/, "q")
		}
		case "osm": {
			if (initiator && initiator.host === "earth.google.com") return
			const travelModes = {
				driving: "fossgis_osrm_car",
				walking: "fossgis_osrm_foot",
				bicycling: "fossgis_osrm_bike",
				transit: "fossgis_osrm_car", // not implemented on OSM, default to car.
			}

			function addressToLatLng(address) {
				const xmlhttp = new XMLHttpRequest()
				xmlhttp.timeout = 5000
				http.ontimeout = () => {
					return
				}
				http.onerror = () => {
					return
				}
				xmlhttp.send()
				http.onreadystatechange = () => {
					if (xmlhttp.status === 200) {
						const json = JSON.parse(xmlhttp.responseText)[0]
						if (json) {
							return [`${json.lat},${json.lon}`, `${json.boundingbox[2]},${json.boundingbox[1]},${json.boundingbox[3]},${json.boundingbox[0]}`]
						}
					}
					console.info("Error: Status is " + xmlhttp.status)
				}
				xmlhttp.open("GET", `https://nominatim.openstreetmap.org/search/${address}?format=json&limit=1`, false)
			}

			let mapCentre = "#"
			let prefs = {}

			const mapCentreData = convertMapCentre()
			if (mapCentreData[0] && mapCentreData[1] && mapCentreData[2]) mapCentre = `#map=${mapCentreData[0]}/${mapCentreData[1]}/${mapCentreData[2]}`
			if (url.searchParams.get("layer")) prefs.layers = osmLayers[url.searchParams.get("layer")]

			if (url.pathname.includes("/embed")) {
				// Handle Google Maps Embed API
				// https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France
				//console.log("embed life")

				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 [coords, boundingbox] = addressToLatLng(query)
				prefs.bbox = boundingbox
				prefs.marker = coords
				prefs.layer = "mapnik"
				let prefsEncoded = new URLSearchParams(prefs).toString()
				return `${randomInstance}/export/embed.html?${prefsEncoded}`
			} else if (url.pathname.includes("/dir")) {
				// Handle Google Maps Directions
				// https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling

				let travMod = url.searchParams.get("travelmode")
				if (url.searchParams.has("travelmode")) prefs.engine = travelModes[travMod]

				let orgVal = url.searchParams.get("origin")
				let destVal = url.searchParams.get("destination")

				let org = addressToLatLng(orgVal)
				let dest = addressToLatLng(destVal)
				prefs.route = `${org};${dest}`

				let prefsEncoded = new URLSearchParams(prefs).toString()
				return `${randomInstance}/directions?${prefsEncoded}${mapCentre}`
			} else if (url.pathname.includes("data=") && url.pathname.match(dataLatLngRegex)) {
				// Get marker from data attribute
				// https://www.google.com/maps/place/41%C2%B001'58.2%22N+40%C2%B029'18.2%22E/@41.032833,40.4862063,17z/data=!3m1!4b1!4m6!3m5!1s0x0:0xf64286eaf72fc49d!7e2!8m2!3d41.0328329!4d40.4883948
				//console.log("data life")

				let [, mlat, mlon] = url.pathname.match(dataLatLngRegex)

				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
			} else if (url.searchParams.has("ll")) {
				// Get marker from ll param
				// https://maps.google.com/?ll=38.882147,-76.99017
				//console.log("ll life")

				const [mlat, mlon] = url.searchParams.get("ll").split(",")

				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
			} else if (url.searchParams.has("viewpoint")) {
				// Get marker from viewpoint param.
				// https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80
				//console.log("viewpoint life")

				const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")

				return `${randomInstance}/search?query=${mlat}%2C${mlon}`
			} else {
				// Use query as search if present.
				//console.log("normal life")

				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]

				let prefsEncoded = new URLSearchParams(prefs).toString()
				if (query) return `${randomInstance}/search?query="${query}${mapCentre}&${prefsEncoded}`
			}

			let prefsEncoded = new URLSearchParams(prefs).toString()
			// console.log("mapCentre", mapCentre)
			// console.log("prefs", prefs)
			// console.log("prefsEncoded", prefsEncoded)
			return `${randomInstance}/${mapCentre}&${prefsEncoded}`
		}
		case "facil": {
			if (initiator && initiator.host === "earth.google.com") return
			const travelModes = {
				driving: "car",
				walking: "pedestrian",
				bicycling: "bicycle",
				transit: "car", // not implemented on Facil, default to car.
			}
			const mapCentreData = convertMapCentre()
			let mapCentre = "#"
			if (mapCentreData[0] && mapCentreData[1] && mapCentreData[2]) mapCentre = `#${mapCentreData[0]}/${mapCentreData[1]}/${mapCentreData[2]}`

			if (url.pathname.includes("/embed")) {
				// Handle Google Maps Embed API
				// https://www.google.com/maps/embed/v1/place?key=AIzaSyD4iE2xVSpkLLOXoyqT-RuPwURN3ddScAI&q=Eiffel+Tower,Paris+France
				//console.log("embed life")

				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.

				return `${randomInstance}/#q=${query}`
			} else if (url.pathname.includes("/dir")) {
				// Handle Google Maps Directions
				// https://www.google.com/maps/dir/?api=1&origin=Space+Needle+Seattle+WA&destination=Pike+Place+Market+Seattle+WA&travelmode=bicycling

				let travMod = url.searchParams.get("travelmode")

				let orgVal = url.searchParams.get("origin")
				let destVal = url.searchParams.get("destination")

				return `${randomInstance}/#q=${orgVal}%20to%20${destVal}%20by%20${travelModes[travMod]}`
			} else if (url.pathname.includes("data=") && url.pathname.match(dataLatLngRegex)) {
				// Get marker from data attribute
				// https://www.google.com/maps/place/41%C2%B001'58.2%22N+40%C2%B029'18.2%22E/@41.032833,40.4862063,17z/data=!3m1!4b1!4m6!3m5!1s0x0:0xf64286eaf72fc49d!7e2!8m2!3d41.0328329!4d40.4883948
				//console.log("data life")

				let [, mlat, mlon] = url.pathname.match(dataLatLngRegex)

				return `${randomInstance}/#q=${mlat}%2C${mlon}`
			} else if (url.searchParams.has("ll")) {
				// Get marker from ll param
				// https://maps.google.com/?ll=38.882147,-76.99017
				//console.log("ll life")

				const [mlat, mlon] = url.searchParams.get("ll").split(",")

				return `${randomInstance}/#q=${mlat}%2C${mlon}`
			} else if (url.searchParams.has("viewpoint")) {
				// Get marker from viewpoint param.
				// https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=48.857832,2.295226&heading=-45&pitch=38&fov=80
				//console.log("viewpoint life")

				const [mlat, mlon] = url.searchParams.get("viewpoint").split(",")

				return `${randomInstance}/#q=${mlat}%2C${mlon}`
			} else {
				// Use query as search if present.
				//console.log("normal life")

				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]

				if (query) return `${randomInstance}/${mapCentre}/Mpnk/${query}`
			}
		}
		case "lingva": {
			let params_arr = url.search.split("&")
			params_arr[0] = params_arr[0].substring(1)
			let params = {}
			for (let i = 0; i < params_arr.length; i++) {
				let pair = params_arr[i].split("=")
				params[pair[0]] = pair[1]
			}
			if (params.sl && params.tl && params.text) {
				return `${randomInstance}/${params.sl}/${params.tl}/${params.text}`
			}
			return randomInstance
		}
		case "breezeWiki": {
			let wiki, urlpath = ""
			if (url.hostname.match(/^[a-zA-Z0-9-]+\.(?:fandom|wikia)\.com/)) {
				wiki = url.hostname.match(/^[a-zA-Z0-9-]+(?=\.(?:fandom|wikia)\.com)/)
				if (wiki == "www" || !wiki) wiki = ""
				else wiki = `/${wiki}`
				urlpath = url.pathname
			} else {
				wiki = url.pathname.match(/(?<=wiki\/w:c:)[a-zA-Z0-9-]+(?=:)/)
				if (!wiki) wiki = ""
				else {
					wiki = "/" + wiki + "/wiki/"
					urlpath = url.pathname.match(/(?<=wiki\/w:c:[a-zA-Z0-9-]+:).+/)
				}
			}
			if (url.href.search(/Special:Search\?query/) > -1) {
				return `${randomInstance}${wiki}${urlpath}${url.search}`.replace(/Special:Search\?query/, "search?q").replace(/\/wiki/, "")
			}
			return `${randomInstance}${wiki}${urlpath}${url.search}`
		}
		case "rimgo": {
			if (url.href.search(/^https?:\/{2}(?:[im]\.)?stack\./) > -1) {
				return `${randomInstance}/stack${url.pathname}${url.search}`
			}
			return `${randomInstance}${url.pathname}${url.search}`
		}
		case "libreddit": {
			const subdomain = url.hostname.match(/^(?:(?:external-)?preview|i)(?=\.redd\.it)/)
			if (!subdomain) return `${randomInstance}${url.pathname}${url.search}`
			switch (subdomain[0]) {
				case "preview":
					return `${randomInstance}/preview/pre${url.pathname}${url.search}`
				case "external-preview":
					return `${randomInstance}/preview/external-pre${url.pathname}${url.search}`
				case "i":
					return `${randomInstance}/img${url.pathname}`
			}
			return randomInstance
		}
		case "teddit": {
			if (/^(?:(?:external-)?preview|i)\.redd\.it/.test(url.hostname)) {
				if (url.search == "") return `${randomInstance}${url.pathname}?teddit_proxy=${url.hostname}`
				else return `${randomInstance}${url.pathname}${url.search}&teddit_proxy=${url.hostname}`
			}
			return `${randomInstance}${url.pathname}${url.search}`
		}
		case "neuters": {
			const p = url.pathname
			if (p.startsWith('/article/') || p.startsWith('/pf/') || p.startsWith('/arc/') || p.startsWith('/resizer/')) {
				return null
			}
			return `${randomInstance}${p}`
		}
		case "dumb": {
			if (url.pathname.endsWith('-lyrics')) {
				return `${randomInstance}${url.pathname}`
			}
		}
		case "ruralDictionary": {
			if (!url.pathname.includes('/define.php') && !url.pathname.includes('/random.php') && url.pathname != '/') return
			return `${randomInstance}${url.pathname}${url.search}`
		}
		case "anonymousOverflow": {
			if (!url.pathname.startsWith('/questions') && url.pathname != '/') return
			if (url.hostname == "stackoverflow.com") {
				const threadID = /\/(\d+)\/?$/.exec(url.pathname)
				if (threadID) return `${randomInstance}/questions/${threadID[1]}${url.search}`
				return `${randomInstance}${url.pathname}${url.search}`
			}
			const regex = url.href.match(/https?:\/{2}(?:([a-zA-Z0-9-]+)\.)?stackexchange\.com\//)
			if (regex && regex.length > 1) {
				const subdomain = regex[1]
				return `${randomInstance}/exchange/${subdomain}${url.pathname}${url.search}`
			}
		}
		case "biblioReads": {
			if (!url.pathname.startsWith('/book/show/') && url.pathname != '/') return
			return `${randomInstance}${url.pathname}${url.search}`
		}
		case "wikiless": {
			let GETArguments = []
			if (url.search.length > 0) {
				let search = url.search.substring(1) //get rid of '?'
				let argstrings = search.split("&")
				for (let i = 0; i < argstrings.length; i++) {
					let args = argstrings[i].split("=")
					GETArguments.push([args[0], args[1]])
				}
			}

			let link = `${randomInstance}${url.pathname}`
			let urlSplit = url.host.split(".")
			if (urlSplit[0] != "wikipedia" && urlSplit[0] != "www") {
				if (urlSplit[0] == "m") GETArguments.push(["mobileaction", "toggle_view_mobile"])
				else GETArguments.push(["lang", urlSplit[0]])
				if (urlSplit[1] == "m") GETArguments.push(["mobileaction", "toggle_view_mobile"])
				// wikiless doesn't have mobile view support yet
			}
			for (let i = 0; i < GETArguments.length; i++) link += (i == 0 ? "?" : "&") + GETArguments[i][0] + "=" + GETArguments[i][1]
			return link + url.hash
		}
		case "proxiTok": {
			if (url.pathname.startsWith('/email')) return
			return `${randomInstance}${url.pathname}${url.search}`
		}
		case "waybackClassic": {
			const regex = /^\/\web\/[0-9]+\*\/(.*)/.exec(url.pathname)
			if (regex) {
				const link = regex[1]
				return `${randomInstance}/cgi-bin/history.cgi?utf8=✓&q=${encodeURIComponent(link)}`
			}
			return `${randomInstance}`
		}
		case "gothub": {
			const regex = /^\/(.*)\/(.*)\/(?:blob|tree)\/(.*)\/(.*)/.exec(url.pathname)
			if (regex) {
				const user = regex[1]
				const repo = regex[2]
				const branch = regex[3]
				const path = regex[4]
				return `${randomInstance}/file/${user}/${repo}/${branch}/${path}`
			}
			return `${randomInstance}${url.pathname}${url.search}`
		}
		case "mikuIndividious": {
			if (url.hostname == "bilibili.com" || url.hostname == "www.bilibili.com" || url.hostname == 'b23.tv') {
				return `${randomInstance}${url.pathname}${url.search}`
			}
			if (url.hostname == "space.bilibili.com") {
				return `${randomInstance}/space${url.pathname}${url.search}`
			}
		}
		case "tent": {
			if (url.hostname == 'bandcamp.com' && url.pathname == '/search') {
				const query = url.searchParams.get('q')
				return `${randomInstance}/search.php?query=${encodeURIComponent(query)}`
			}
			if (url.hostname.endsWith('bandcamp.com')) {
				const regex = /^(.*)\.bandcamp\.com/.exec(url.hostname)
				const artist = regex[1]
				if (url.pathname == '/') {
					return `${randomInstance}/artist.php?name=${artist}`
				} else {
					const regex = /^\/(.*)\/(.*)/.exec(url.pathname)
					if (regex) {
						const type = regex[1]
						const name = regex[2]
						return `${randomInstance}/release.php?artist=${artist}&type=${type}&name=${name}`
					}
				}
			}
			if (url.hostname == 'f4.bcbits.com') {
				const regex = /\/img\/(.*)/.exec(url.pathname)
				const image = regex[1]
				return `${randomInstance}/image.php?file=${image}`
			}
			if (url.hostname == 't4.bcbits.com') {
				const regex = /\/stream\/(.*)\/(.*)\/(.*)/.exec(url.pathname)
				if (regex) {
					const directory = regex[1]
					const format = regex[2]
					const file = regex[3]
					const token = url.searchParams.get('token')
					return `${randomInstance}/audio.php/?directory=${directory}&format=${format}&file=${file}&token=${encodeURIComponent(token)}`
				}
			}
		}
		default: {
			return `${randomInstance}${url.pathname}${url.search}`
		}
	}
}

function computeService(url, returnFrontend) {
	return new Promise(async resolve => {
		const config = await utils.getConfig()
		const options = await utils.getOptions()
		for (const service in config.services) {
			if (regexArray(service, url, config)) {
				resolve(service)
				return
			} else {
				for (const frontend in config.services[service].frontends) {
					if (all(service, frontend, options, config).includes(utils.protocolHost(url))) {
						if (returnFrontend)
							resolve([service, frontend, utils.protocolHost(url)])
						else
							resolve(service)
						return
					}
				}
			}
		}
		resolve()
	})
}

function switchInstance(url) {
	return new Promise(async resolve => {
		let options = await utils.getOptions()
		let config = await utils.getConfig()

		const protocolHost = utils.protocolHost(url)
		for (const service in config.services) {
			let frontend = options[service].frontend ?? Object.keys(config.services[service].frontends)[0]
			let instancesList = options[frontend]
			if (instancesList === undefined) continue
			if (!instancesList.includes(protocolHost)) continue

			instancesList.splice(instancesList.indexOf(protocolHost), 1)
			if (instancesList.length === 0) {
				resolve()
				return
			}

			const randomInstance = utils.getRandomInstance(instancesList)
			const newUrl = `${randomInstance}${url.pathname}${url.search}`
			resolve(newUrl)
			return
		}
		resolve()
	})
}

function reverse(url) {
	return new Promise(async resolve => {
		let options = await utils.getOptions()
		let config = await utils.getConfig()

		let protocolHost = utils.protocolHost(url)
		for (const service in config.services) {
			let frontend = options[service].frontend ?? Object.keys(config.services[service].frontends)[0]
			if (options[frontend] == undefined) continue
			if (!options[frontend].includes(protocolHost)) continue

			switch (service) {
				case "youtube":
				case "imdb":
				case "imgur":
				case "tiktok":
				case "twitter":
				case "reddit":
				case "imdb":
				case "quora":
				case "medium":
					resolve(config.services[service].url + url.pathname + url.search)
					return
				case "fandom":
					let regex = url.pathname.match(/^\/([a-zA-Z0-9-]+)\/wiki\/([a-zA-Z0-9-]+)/)
					if (regex) {
						resolve(`https://${regex[1]}.fandom.com/wiki/${regex[2]}`)
						return
					}
					resolve()
					return
				default:
					resolve()
					return
			}
		}
		resolve()
		return
	})
}

const defaultInstances = {
	'invidious': ['https://inv.vern.cc'],
	'piped': ['https://pipedapi-libre.kavin.rocks'],
	'pipedMaterial': ['https://piped-material.xn--17b.net'],
	'cloudtube': ['https://tube.cadence.moe'],
	'poketube': ['https://poketube.fun'],
	'proxiTok': ['https://proxitok.pabloferreiro.es'],
	'send': ['https://send.vis.ee'],
	'nitter': ['https://nitter.net'],
	'libreddit': ['https://libreddit.spike.codes'],
	'teddit': ['https://teddit.net'],
	'scribe': ['https://scribe.rip'],
	'libMedium': ['https://md.vern.cc'],
	'quetre': ['https://quetre.iket.me'],
	'libremdb': ['https://libremdb.iket.me'],
	'simplyTranslate': ['https://simplytranslate.org'],
	'lingva': ['https://lingva.ml'],
	'searxng': ['https://search.bus-hit.me'],
	'rimgo': ['https://rimgo.vern.cc'],
	'librarian': ['https://lbry.vern.cc'],
	'beatbump': ['https://beatbump.ml'],
	'hyperpipe': ['https://hyperpipe.surge.sh'],
	'facil': [' https://facilmap.org '],
	'osm': ['https://www.openstreetmap.org'],
	'breezeWiki': ['https://breezewiki.com'],
	'neuters': ['https://neuters.de'],
	'dumb': ['https://dm.vern.cc'],
	'ruralDictionary': ['https://rd.vern.cc'],
	'anonymousOverflow': ['https://code.whatever.social'],
	'biblioReads': ['https://biblioreads.ml'],
	'wikiless': ['https://wikiless.org'],
	'suds': ['https://sd.vern.cc'],
	'waybackClassic': ['https://wayback-classic.net'],
	'gothub': ['https://gh.odyssey346.dev'],
	'mikuInvidious': ['https://mikuinv.resrv.org'],
	"tent": ['https://tent.sny.sh'],
	"wolfreeAlpha": ['https://gqq.gitlab.io', 'https://uqq.gitlab.io']
}

function initDefaults() {
	return new Promise(resolve => {
		browser.storage.local.clear(async () => {
			let config = await utils.getConfig()
			let options = {}
			for (const service in config.services) {
				options[service] = {}
				for (const defaultOption in config.services[service].options) {
					options[service][defaultOption] = config.services[service].options[defaultOption]
				}
				for (const frontend in config.services[service].frontends) {
					if (config.services[service].frontends[frontend].instanceList) {
						options[frontend] = []
					}
				}
			}
			options['exceptions'] = {
				url: [],
				regex: [],
			}
			options['theme'] = "detect"
			options['popupServices'] = ["youtube", "twitter", "tiktok", "imgur", "reddit", "quora", "translate", "maps"]
			options['fetchInstances'] = 'github'

			options = { ...options, ...defaultInstances }

			browser.storage.local.set({ options },
				() => resolve()
			)
		})
	})
}

function upgradeOptions() {
	return new Promise(async resolve => {
		let options = await utils.getOptions()
		const config = await utils.getConfig()

		options.fetchInstances = 'github'

		browser.storage.local.clear(() => {
			browser.storage.local.set({ options }, () => {
				resolve()
			})
		})
	})
}

function processUpdate() {
	return new Promise(async resolve => {
		let config = await utils.getConfig()
		let options = await utils.getOptions()
		for (const service in config.services) {
			if (!options[service]) options[service] = {}
			for (const defaultOption in config.services[service].options) {
				if (options[service][defaultOption] === undefined) {
					options[service][defaultOption] = config.services[service].options[defaultOption]
				}
			}

			for (const frontend in config.services[service].frontends) {
				if (options[frontend] === undefined && config.services[service].frontends[frontend].instanceList) {
					options[frontend] = defaultInstances[frontend]
				}
				else if (frontend in options && !(frontend in config.services[service].frontends)) {
					delete options[frontend]
				}
			}
		}
		browser.storage.local.set({ options }, () => {
			resolve()
		})
	})
}

// For websites that have a strict policy that would not normally allow these frontends to be embedded within the website.
function modifyContentSecurityPolicy(details) {
	let isChanged = false
	if (details.type == "main_frame") {
		for (const header in details.responseHeaders) {
			if (details.responseHeaders[header].name == "content-security-policy") {
				let instancesList = []
				for (const service in config.services) {
					if (config.services[service].embeddable) {
						for (const frontend in config.services[service].frontends) {
							if (config.services[service].frontends[frontend].embeddable) {
								for (const network in config.networks) {
									instancesList.push(...options[frontend])
								}
							}
						}
					}
				}
				let securityPolicyList = details.responseHeaders[header].value.split(";")
				for (const i in securityPolicyList) securityPolicyList[i] = securityPolicyList[i].trim()
				let newSecurity = ""
				for (const item of securityPolicyList) {
					if (item.trim() == "") continue
					let regex = item.match(/([a-z-]{0,}) (.*)/)
					if (regex == null) continue
					let [, key, vals] = regex
					if (key == "frame-src") vals = vals + " " + instancesList.join(" ")
					newSecurity += key + " " + vals + "; "
				}

				details.responseHeaders[header].value = newSecurity
				isChanged = true
			}
		}
		if (isChanged) return { responseHeaders: details.responseHeaders }
	}
}

async function copyRaw(url, test) {
	const newUrl = await reverse(url)
	if (newUrl) {
		if (!test) {
			if (!isChrome) {
				navigator.clipboard.writeText(newUrl)
			} else {
				var copyFrom = document.createElement("textarea");
				copyFrom.textContent = newUrl;
				document.body.appendChild(copyFrom);
				copyFrom.select()
				document.execCommand('copy')
				copyFrom.blur();
				document.body.removeChild(copyFrom);
			}
		}
		return newUrl
	}
}

export default {
	redirect,
	redirectAsync,
	computeService,
	reverse,
	initDefaults,
	upgradeOptions,
	processUpdate,
	modifyContentSecurityPolicy,
	copyRaw,
	switchInstance
}
