export default modules => {
	let self

	const video = modules.globals.video // Shorthand
	video.classList.add("hasCustomCaptions")

	// Elements
	const customStyle = document.createElement("style")
	customStyle.setAttribute("type", "text/css")
	document.head.appendChild(customStyle)

	const captionBox = document.createElement("div")
	captionBox.className = "caption-box"
	video.parentNode.appendChild(captionBox)

	const captionInner = document.createElement("div")
	captionInner.className = "caption-inner"
	captionBox.appendChild(captionInner)

	// Parsing
	const regexVVTStyle = /::cue\(([^\. \)]+)?([^\)]*)\)/g
	const regexVVTCue = /([^\n]*)\n((?:\d\d:)?\d\d:\d\d\.\d{3}) --> ((?:\d\d:)?\d\d:\d\d\.\d{3}) ?([^\n]+)?\n((?:.+\n)+)/g
	const regexTimedText = /<((?:\d\d:)?\d\d:\d\d\.\d{3})> *<([^>]+)>/g
	const regexCueText = /<([^\. />]+)([^>]+?)>/g

	const defaultStyle = `
	.caption-box .cue-container [data-details=".white"] { color: rgba(255, 255, 255, 1); }
	.caption-box .cue-container [data-details=".lime"] { color: rgba(0, 255, 0, 1); }
	.caption-box .cue-container [data-details=".cyan"] { color: rgba(0, 255, 255, 1); }
	.caption-box .cue-container [data-details=".red"] { color: rgba(255, 0, 0, 1); }
	.caption-box .cue-container [data-details=".yellow"] { color: rgba(255, 255, 0, 1); }
	.caption-box .cue-container [data-details=".magenta"] { color: rgba(255, 0, 255, 1); }
	.caption-box .cue-container [data-details=".blue"] { color: rgba(0, 0, 255, 1); }
	.caption-box .cue-container [data-details=".black"] { color: rgba(0, 0, 0, 1); }
	.caption-box .cue-container [data-details=".bg_white"] { background-color: rgba(255, 255, 255, 1); }
	.caption-box .cue-container [data-details=".bg_lime"] { background-color: rgba(0, 255, 0, 1); }
	.caption-box .cue-container [data-details=".bg_cyan"] { background-color: rgba(0, 255, 255, 1); }
	.caption-box .cue-container [data-details=".bg_red"] { background-color: rgba(255, 0, 0, 1); }
	.caption-box .cue-container [data-details=".bg_yellow"] { background-color: rgba(255, 255, 0, 1); }
	.caption-box .cue-container [data-details=".bg_magenta"] { background-color: rgba(255, 0, 255, 1); }
	.caption-box .cue-container [data-details=".bg_blue"] { background-color: rgba(0, 0, 255, 1); }
	.caption-box .cue-container [data-details=".bg_black"] { background-color: rgba(0, 0, 0, 1); }
	.caption-box .cue-container timedtext.future { color: rgba(0, 0, 0, 0); }
	`.replaceAll("\t", "")

	function timecodeToSeconds(tc) {
		let outNum = 0

		const digits2 = tc.split(".")
		const digits1 = digits2[0].split(":")
		for (let i = digits1.length - 1; i >= 0; i--) {
			const step = digits1.length - i
			// Seconds
			if (step == 1)
				outNum += Number(digits1[i])
			// Minutes
			else if (step == 2)
				outNum += Number(digits1[i]) * 60
			// Hours
			else if (step == 3)
				outNum += Number(digits1[i]) * 3600
		}

		outNum += Number(`0.${digits2[1]}`)

		return outNum
	}

	function parseVVTStyle(s) {
		for (const match of s.matchAll(regexVVTStyle)) {
			const wholeSelector = match[0]
			let className = match[1]
			const details = match[2] || null

			if (className == ":past" || className == ":future")
				className = className.replace(":", "timedtext.")

			s = s.replace(wholeSelector, `.caption-box .cue-container ${className || ""}${details ? `[data-details="${details}"]` : ""}`)
		}
		return s
	}

	function parseVVTSource(s) {
		// Newlines
		s = s.replaceAll("\r\n", "\n").replaceAll("\r", "\n")
		if (!s.endsWith("\n"))
			s = s + "\n"

		// Style
		let style = ""
		if (s.indexOf("\nStyle:\n") > -1) {
			const origStyleBlock = s.split("\nStyle:\n")[1].split("\n##\n")[0]
			let newStyleBlock = parseVVTStyle(origStyleBlock)

			// Add default styles
			newStyleBlock = defaultStyle + newStyleBlock

			
			style = newStyleBlock
			s.replace(origStyleBlock, newStyleBlock)
		}
		style = defaultStyle + style

		// Cues
		let cues = []
		for (const match of s.matchAll(regexVVTCue)) {
			const wholeCue = match[0]
			const id = match[1]
			const startTime = match[2]
			const endTime = match[3]
			let tags = match[4]
			let text = match[5]

			// Cue replacing would go here?
			/*
			let newCue = wholeCue

			if (newCue != wholeCue)
				s = s.replace(wholeCue, newCue)
			*/

			// Generate VTTCue
			const cueObj = new VTTCue(timecodeToSeconds(startTime), timecodeToSeconds(endTime), text)
			if (id)
				cueObj.id = id
			if (tags) {
				for (const tag of tags.split(" ")) {
					let results = null
					if ((results = /^align:(start|middle|center|end|left|right)$/.exec(tag)))
						cueObj.align = results[1]
					else if ((results = /^vertical:(lr|rl)$/.exec(tag)))
						cueObj.vertical = results[1]
					else if ((results = /^size:([\d.]+)%$/.exec(tag)))
						cueObj.size = Number(results[1])
					else if ((results = /^position:([\d.]+)%(?:,(line-left|line-right|middle|center|start|end|auto))?$/.exec(tag))) {
						cueObj.position = Number(results[1])
						if (results[2])
							cueObj.positionAlign = results[2]
					// REGION??
					//} else if ()
					} else {
						if ((results = /^line:([\d.]+)%(?:,(start|end|center))?$/.exec(tag))) {
							cueObj.lineInterpretation = "percentage"
							// interpret as percentage
							cueObj.line = Number(results[1]);
							if (results[2])
								cueObj.lineAlign = results[2]
						} else if (results = /^line:(-?\d+)(?:,(start|end|center))?$/.exec(tag)) {
							cueObj.lineInterpretation = "number"
							// interpret as line number
							cueObj.line = Number(results[1]);
							if (results[2])
								cueObj.lineAlign = results[2]
						}
					}
				}
			}

			cues.push(cueObj)
		}

		return { s, style, cues }
	}

	function parseCueText(t) {
		if (!t)
			return t
		// Parse timestamp tags
		let previousTimestamp = null
		for (const match of t.matchAll(regexTimedText)) {
			const wholeElement = match[0]
			const timestamp = match[1]

			t = t.replace(wholeElement, `${previousTimestamp ? `</timedtext>` : ""}<timedtext class="future" time="${timecodeToSeconds(timestamp)}">`)
			previousTimestamp = timestamp
		}
		if (previousTimestamp)
			t = t + "</timedtext>"

		// Parse normal cues
		for (const match of t.matchAll(regexCueText)) {
			const wholeElement = match[0]
			const className = match[1]
			const details = match[2]

			if (wholeElement == "<br>")
				continue
			if (className == "timedtext")
				continue

			t = t.replace(wholeElement, `<${className} data-details="${details}">`)
		}

		return t
	}

	/////

	let lastTrack = null
	let timedCues = []

	function cueChange(track) {
		if (track.eirWasHere) {
			if (lastTrack != track && track.style)
				customStyle.innerHTML = track.style
			handleTrack(track)
		}
		lastTrack = track
	}

	// Detect caption switching
	// for (const track of video.textTracks)
	// 	track.addEventListener("cuechange", () => cueChange(track))

	// Timed cues
	video.addEventListener("timeupdate", () => {
		let currentCue
		for (const cue of timedCues) {
			const time = Number(cue.getAttribute("time"))

			if (video.currentTime >= time) {
				cue.className = "past"
				currentCue = cue
			} else
				cue.className = "future"
		}
		if (currentCue)
			currentCue.className = "current"
	})

	// Functionality
	function handleTrack(track) {
		self.clearCaptions()

		for (const c of track.activeCues) {
			const cueContainer = document.createElement("div")
			cueContainer.className = "cue-container"
			captionInner.appendChild(cueContainer)

			const cuePadding = document.createElement("div")
			cuePadding.className = "cue-padding"
			cueContainer.appendChild(cuePadding)

			const cueContainerV = document.createElement("div")
			cueContainerV.className = "cue-container-v"
			cueContainer.appendChild(cueContainerV)

			const cuePaddingV = document.createElement("div")
			cuePaddingV.className = "cue-padding"
			cueContainerV.appendChild(cuePaddingV)

			const cueInner = document.createElement("div")
			cueInner.className = "cue-inner"
			cueContainerV.appendChild(cueInner)

			cueInner.innerHTML = parseCueText(c.text.replaceAll("\n", "<br>"))
			for (const child of cueInner.childNodes) {
				if (child.nodeName == "#text") {
					const span = document.createElement("span")
					span.innerText = child.textContent
					cueInner.insertBefore(span, child)
					cueInner.removeChild(child)
				} else if (child.tagName == "TIMEDTEXT")
					timedCues.push(child)
			}

			// Set styles???????
			cueInner.style.whiteSpace = "pre-wrap"

			let direction = "ltr"

			/*
			style.display = 'flex';
			style.flexDirection = 'column';
			style.alignItems = 'center';
			if (cue.textAlign == Cue.textAlign.LEFT ||
				cue.textAlign == Cue.textAlign.START) {
				style.width = '100%';
				style.alignItems = 'start';
			} else if (cue.textAlign == Cue.textAlign.RIGHT ||
				cue.textAlign == Cue.textAlign.END) {
				style.width = '100%';
				style.alignItems = 'end';
			}

			if (cue.displayAlign == Cue.displayAlign.BEFORE) {
				style.justifyContent = 'flex-start';
			} else if (cue.displayAlign == Cue.displayAlign.CENTER) {
				style.justifyContent = 'center';
			} else {
				style.justifyContent = 'flex-end';
			}
			*/
			cueInner.style.margin = "0"

			cueInner.style.textAlign = c.align
			if (c.vertical && c.vertical != "")
				cueInner.style.writingMode = `vertical-${c.vertical}`
			else
				cueInner.style.writingMode = "horizontal-tb"

			// Line
			let line = c.line
			let lineAlign = c.lineAlign

			if (c.line == "auto") {
				line = 100
				lineAlign = "end"
			}

			// Converts line number to percentage
			if (c.lineInterpretation == "number") {
				let maxLines = 16
				if ((Number(video.getAttribute("width")) / Number(video.getAttribute("height"))) < 1)
					maxLines = 32
				if (line < 0)
					line = 100 + ((line / maxLines) * 100)
				else
					line = (line / maxLines) * 100
			}

			// Do percent
			cueInner.style.position = "relative" // "absolute"
			if (!c.vertical || c.vertical == "") { // Horizontal top to bottom
				cueInner.style.width = "100%"
				if (lineAlign == "start")
					cueInner.style.top = `${line}%`
				else if (lineAlign == "end")
					cueInner.style.top = `${line}%` // cueInner.style.bottom = `${100 - line}%`
			} else if (c.vertical == "lr") {
				cueInner.style.height = "100%"
				if (lineAlign == "start")
					cueInner.style.left = `${line}%`
				else if (lineAlign == "end")
					cueInner.style.left = `${100 - line}%` // cueInner.style.right = `${line}%`
			} else {
				cueInner.style.height = "100%"
				if (lineAlign == "start")
					cueInner.style.left = `${100 - line}%` // cueInner.style.right = `${line}%`
				else if (lineAlign == "end")
					cueInner.style.left = `${line}%`
			}

			// Position align
			let positionAlign = c.positionAlign
			if (positionAlign == "auto") {
				if (c.align == "left" || (c.align == "start" && direction == "ltr") || (c.align == "end" && direction == "rtl"))
					positionAlign = "left"
				else if (c.align == "right" || (c.align == "start" && direction == "rtl") || (c.align == "end" && direction == "ltr"))
					positionAlign = "right"
				else
					positionAlign = "center"
			}

			if (positionAlign == "left") {
				// cueInner.style.cssFloat = "left"
				if (c.position != "auto") {
					// cueInner.style.position = "relative" // "absolute"
					if (!c.vertical || c.vertical == "") { // Horizontal top to bottom
						// TODO: what if its not a number?
						cueInner.style.left = `${100 - Number(c.position)}%`
						cueInner.style.width = "auto"
					} else
						cueInner.style.top = `${100 - Number(c.position)}%`
				}
			} else if (positionAlign == "right") {
				// cueInner.style.cssFloat = "right"
				if (c.position != "auto") {
					// cueInner.style.position = "relative" // "absolute"
					if (!c.vertical || c.vertical == "") { // Horizontal top to bottom
						// TODO: what if its not a number?
						cueInner.style.left = `${100 - Number(c.position)}%` // cueInner.style.right = `${Number(c.position)}%`
						cueInner.style.width = "auto"
					} else
						cueInner.style.top = `${100 - Number(c.position)}%` // cueInner.style.bottom = `${Number(c.position)}%`
				}
			} else {
				cueInner.style.left = "50%"
				if (c.position != "auto") {
					// cueInner.style.position = "relative" // "absolute"
					if (!c.vertical || c.vertical == "") { // Horizontal top to bottom
						cueInner.style.left = `${100 - Number(c.position)}%`
						cueInner.style.width = "auto"
					} else
						cueInner.style.top = `${100 - Number(c.position)}%`
				}
			}

			if (c.size != "100") {
				if (!c.vertical || c.vertical == "") // Horizontal top to bottom
					cueInner.style.width = `${Number(c.size)}%`
				else
					cueInner.style.height = `${Number(c.size)}%`
			}

			/////
			// ?????
			let textOriginH = 0
			if (c.vertical == "") { // Horizontal TB
				if (positionAlign == "left")
					textOriginH = -50
				else if (positionAlign == "right")
					textOriginH = 50
			}
			// let textOriginH = cueInner.getBoundingClientRect().width
			// // let textOriginV = -cueInner.getBoundingClientRect().height / 2
			// if (c.vertical == "") { // Horizontal TB
			// 	if (c.align == "center")
			// 		textOriginH = 0
			// } 
			// textOriginH /= cueContainer.getBoundingClientRect().width

			if (cueInner.style.left) {
				const diff = 50 - (Number(cueInner.style.left.split("%")[0]) + textOriginH)
				if (diff < 0)
					cueContainer.style.flexDirection = "row-reverse"
				cuePadding.style.width = `${Math.abs(diff)}%`
				cueInner.style.left = null
			}
			if (cueInner.style.top) {
				cuePaddingV.style.height = cueInner.style.top
				cueInner.style.top = null
			}
			/*
			if (positionAlign == "left") {
				cueContainer.style.flexDirection = "row-reverse"
				if (c.position != "auto") {
					if (!c.vertical || c.vertical == "") { // Horizontal top to bottom
						cuePadding.style.width = cueInner.style.left
						cueInner.style.left = null
					} else {
						cueContainer.style.flexDirection = "column"
						cuePadding.style.height = cueInner.style.top
						cueInner.style.top = null
					}
				}
			} else if (positionAlign == "right") {
				if (c.position != "auto") {
					if (!c.vertical || c.vertical == "") { // Horizontal top to bottom
						cuePadding.style.width = cueInner.style.right
						cueInner.style.right = null
					} else {
						cueContainer.style.flexDirection = "column-reverse"
						cuePadding.style.height = cueInner.style.bottom
						cueInner.style.bottom = null
					}
				}
			} else {
				if (c.position != "auto") {
					if (!c.vertical || c.vertical == "") { // Horizontal top to bottom
						cuePadding.style.width = cueInner.style.left
						cueInner.style.left = null
					} else {
						cueContainer.style.flexDirection = "column"
						cuePadding.style.height = cueInner.style.top
						cueInner.style.top = null
					}
				}
			}
			cuePadding.style.left = cueInner.style.left
			cueInner.style.left = null
			*/
		}
	}


	self = {
		clearCaptions: () => {
			captionInner.innerHTML = ""
			timedCues = []
		},

		registerCueChange: (track, enable) => {
			if (!enable) {
				if (track.mode == "showing")
					self.clearCaptions()
				track.oncuechange = undefined
			} else
				track.oncuechange = () => cueChange(track)

			track.mode = enable ? "showing" : "hidden"
		},

		replaceTrack: async index => {
			const track = video.textTracks[index]
			const sourceElem = video.querySelector(`track[label="${track.label}"]`)

			let r = await fetch(sourceElem.src)
			if (r.status == 429) { // Ratelimited
				newToastWhenReady("red", "x", `Too many requests. You may download captions \"${sourceElem.getAttribute("label")}\" in ${r.headers.get("retry-after")} seconds...`)
				modules.controls.clickCaptionBtn(document.getElementsByClassName("setting caption")[0])
				throw this
			}
			r = await r.text()

			// New elem
			const newTrackElem = document.createElement("track")
			newTrackElem.setAttribute("label", sourceElem.getAttribute("label"))
			newTrackElem.setAttribute("kind", sourceElem.getAttribute("kind"))
			newTrackElem.setAttribute("srclang", sourceElem.getAttribute("srclang"))
			const newTrack = newTrackElem.track
			newTrack.eirWasHere = true

			// Parse original track source, add new cues
			const parseOut = parseVVTSource(r)
			newTrack.source = parseOut.s
			newTrack.style = parseOut.style
			for (const cue of parseOut.cues)
				newTrack.addCue(cue)

			video.insertBefore(newTrackElem, sourceElem)
			sourceElem.remove()

			return newTrack
		},
	}

	return self
}
