let view = {}
let extensions = {
	audio: [ "mp3", "wav", "ogg" ]
}
let entryTypes = {
	DIRECTORY: 0,
	AUDIO: 1
}
let list = {
	focused: false,
	directory: null,
	entries: []
}
let player = {
	audio: null,
	queue: [],
	history: []
}

const main = async () => {
	view.list = document.getElementById('list')

	view.trackTitle = document.getElementById('track-title')
	view.trackArtist = document.getElementById('track-artist')

	view.playbackProgress = document.getElementById('playback-progress')
	view.timestamp = document.getElementById('timestamp')
	view.trackLength = document.getElementById('track-length')

	view.prev = document.getElementById('prev')
	view.playpause = document.getElementById('playpause')
	view.next = document.getElementById('next')

	view.pauseIcon = document.getElementById('pause-icon')
	view.playIcon = document.getElementById('play-icon')

	view.prev.addEventListener('click', previousTrack)
	view.next.addEventListener('click', nextTrack)
	view.playpause.addEventListener('click', togglePlay)

	window.addEventListener('keydown', onKeydown)
	window.addEventListener('mousemove', onMouseMove)

	let files = await IDBStore.get('files') ?? {
		entries: [],
		root: true
	}

	displayPaused()
	openDirectory(files)
	grabFocus()
}

/* Interface */

const renderList = (entries, isRoot) => {
	let focused = isListFocused()
	
	while (view.list.firstChild) {
		view.list.removeChild(view.list.lastChild)
	}
	
	list.entries = []
	let audioEntries = []
	let index = 0

	const appendEntry = entry => {
		view.list.appendChild(renderEntry(entry, index))
		list.entries.push(entry)
		index++
	}
	
	for(let entry of entries) {
		if(entry.type == entryTypes.DIRECTORY) {
			appendEntry(entry)
		} else {
			audioEntries.push(entry)
		}
	}

	for(let entry of orderTracks(audioEntries)) {
		appendEntry(entry)
	}
	
	if(focused) {
		grabFocus()
	}
}

const orderTracks = (entries) => {
	return entries.sort((a, b) => a.tags.trackNumber - b.tags.trackNumber)
}

const renderEntry = (entry, index) => {
	let button = document.createElement('button')
	let item = document.createElement('li')

	if(entry.type == entryTypes.DIRECTORY) {
		populateDirectoryEntry(entry, button)
	} else {
		populateTrackEntry(entry, button)
	}

	item.appendChild(button)
	button.dataset.index = index
	button.addEventListener('click', () => onClickListButton(button))

	return item
}

const populateDirectoryEntry = (entry, button) => {
	button.classList.add('directory')
	button.innerText = entry.name + '/'
}

const populateTrackEntry = (entry, button) => {
	if(!entry.tags.title) {
		button.innerText = entry.name
		return
	}

	button.classList.add('titled-track')

	let trackNumber = document.createElement('code')
	let title = document.createElement('b')
	let artist = document.createElement('cite')

	trackNumber.innerText = formatTrackNumber(entry.tags.trackNumber)
	title.innerText = entry.tags.title
	artist.innerText = entry.tags.artist

	button.appendChild(trackNumber)
	button.appendChild(title)
	button.appendChild(artist)
}

const formatTrackNumber = trackNumber => {
	return trackNumber == Infinity ? '--' : trackNumber.toString().padStart(2, '0')
}

const openDirectory = (entry) => {
	renderList(entry.entries, entry.root)
	list.directory = entry
}


const displayPlaying = () => {
	view.playIcon.style.display = "none"
	view.pauseIcon.style.display = ""
}

const displayPaused = () => {
	view.playIcon.style.display = ""
	view.pauseIcon.style.display = "none"
}

/* Population */

const onAddFiles = async () => {
	const fileHandle = await window.showDirectoryPicker({
		mode: 'read',
		startIn: 'music'
	})

	let entry = await loadDirectory(fileHandle, null, true)
	await IDBStore.set('files', entry)
	openDirectory(entry)
}

const loadDirectory = async (dirHandle, parent, isRoot) => {
	let root = {
		entries: [],
		type: entryTypes.DIRECTORY,
		name: dirHandle.name,
		root: isRoot,
		parent
	}

	for await (const [key, value] of dirHandle.entries()) {
		if(value instanceof FileSystemDirectoryHandle) {
			root.entries.push(
				await loadDirectory(value, root)
			)
		} else if(value instanceof FileSystemFileHandle) {
			let entry = await loadEntry(value, dirHandle)

			if(entry) {
				root.entries.push(entry)
			}
		}
	}

	return root
}

const loadEntry = async (fileHandle, dirHandle) => {
	if(fileHandle.kind !== "file") {
		return
	}

	let extension = fileHandle.name.split('.').pop()
	if(extensions.audio.includes(extension)) {
		let entry = {
			type: entryTypes.AUDIO,
			name: fileHandle.name,
			handle: fileHandle
		}

		return await tagEntry(entry)
	}
}

const getTags = blob => new Promise((resolve, reject) => {
	jsmediatags.read(blob, {
		onSuccess(tags) {
			resolve(tags)
		},
		onError(err) {
			reject(err)
		}
	})
})

const tagEntry = async (entry) => {
	let file = await entry.handle.getFile()
	let tags = await getTags(file)
		.catch(console.error)
		.then(data => data?.tags)

	if(!tags) {
		tags = /((?<track>\d+)\s+)?((?<artist>.+)-\s*)?\s*(?<title>.+)\s*\.\w+/
			.exec(entry.name)
			.groups
	}

	entry.tags = {
		album: tags.album,
		title: tags.title,
		artist: tags.artist ?? "Unknown Artist",
		trackNumber: tags.track ? parseInt(tags.track) : Infinity
	}

	return entry
}

// const tagEntry = async (entry) => {
// 	let file = await entry.handle.getFile()
// 	let mp3tag = new MP3Tag(await toArrayBuffer(file))

// 	mp3tag.read()
// 	entry.tags = mp3tag.tags
// 	console.log(mp3tag)
// 	return entry
// }

// const toArrayBuffer = fileOrBlob => new Promise((resolve, reject) => {
// 	let fileReader = new FileReader()
// 	fileReader.addEventListener('load', event => {
// 		resolve(fileReader.result)
// 	})
// 	fileReader.addEventListener('error', event => {
// 		reject(event)
// 	})
// 	fileReader.readAsArrayBuffer(fileOrBlob)
// })

/* Playback */

const createPlayback = async entry => {
	let file = await entry.handle.getFile()
	let url = URL.createObjectURL(file)
	let audio = new Audio(url)

	return {
		file,
		audio,
		entry
	} 
}

const startPlayback = (playback) => {
	if(player.audio) {
		player.audio.pause()
	}
	let audio = playback.audio
	player.audio = playback.audio
	player.current = playback

	audio.addEventListener('playing', () => {
		view.playbackProgress.max = audio.duration
		view.trackLength.innerText = formatTime(audio.duration)
		displayPlaying()
	})
	audio.addEventListener('play', displayPlaying)
	audio.addEventListener('pause', displayPaused)
	audio.addEventListener('ended', () => {
		onPlaybackEnded(playback)
	})
	audio.addEventListener('timeupdate', () => {
		onTimeUpdate(playback)
	})
	
	view.trackTitle.innerText = playback.entry.tags.title
	view.trackArtist.innerText = playback.entry.tags.artist
	view.playbackProgress.value = 0

	audio.play()
}

const onPlaybackEnded = (playback) => {
	player.history.push(playback.entry)
	let startedNext = nextTrack()

	if(!startedNext) {
		player.audio.pause()
		player.audio.seek(0)
	}
}

const onTimeUpdate = (playback) => {
	view.playbackProgress.value = playback.audio.currentTime
	view.timestamp.innerText = formatTime(playback.audio.currentTime)
}

const formatTime = currentTime => {
	let minutes = Math.floor(currentTime / 60).toString()
	let seconds = Math.floor(currentTime % 60).toString()

	return minutes.padStart(2, '0') + ':' + seconds.padStart(2, '0')
}

const playTrack = async (entry) => {
	let playback = await createPlayback(entry)
	startPlayback(playback)
}

const nextTrack = async () => {
	if(player.queue.length > 0) {
		startPlayback(await player.queue.shift().playback)
		return true
	} else {
		return false
	}
}

const queueTrack = (entry) => {
	player.queue.push({
		entry,
		playback: createPlayback(entry)
	})
}

const togglePlay = () => {
	if(!player.audio)
		return

	if(player.audio.paused) {
		player.audio.play()
	} else {
		player.audio.pause()
	}
}

const previousTrack = () => {
	
}

/* Controls */

const onMouseMove = event => {
	if(isListFocused()) {
		document.activeElement.blur()
	}
}

const onKeydown = event => {
	let prevent = true
	let entry = getSelectedEntry()

	switch(event.key) {
		case 'ArrowDown':
			navigateList(1)
			break

		case 'ArrowUp':
			navigateList(-1)
			break

		case 'ArrowRight':
		case 'Enter':
			if(entry) {
				openEntry(entry)
			}
			break

		case 'ArrowLeft':
			goBack()
			break

		case 'c':
			case ' ':
			togglePlay()
			break

		case 'q':
		case '7':
			if(entry) {
				queueTrack(entry)
			}
			break

		case 'p':
		case '8':
			if(entry) {
				playEntry(entry)
			}
			break

		default:
			prevent = false
	}

	if(prevent) {
		event.preventDefault()
	}
}

const getSelectedEntry = () => {
	if(isListFocused()) {
		return getEntry(document.activeElement)
	}
}

const isListFocused = () =>
	view.list.contains(document.activeElement)

const navigateList = (distance) => {
	let index = 0
	let children = Array.from(view.list.children)

	if(isListFocused()) {
		index = parseInt(document.activeElement.dataset.index)
	}

	index += distance
	if(index >= children.length) {
		index = 0
	} else if(index < 0) {
		index = children.length - 1
	}
	// children[Math.max(0, index - 4)]
	let element = children[index].querySelector('button')

	children[Math.max(0, index - 7)].scrollIntoView()
	element.focus({ preventScroll: true })
}

const goBack = () => {
	if(list.directory.parent) {
		openDirectory(list.directory.parent)
	}
}

const getEntry = (listButton) => {
	return list.entries[listButton.dataset.index]
}

const onClickListButton = (listButton) => {
	openEntry(getEntry(listButton))
}

const openEntry = entry => {
	switch(entry.type) {
		case entryTypes.DIRECTORY:
			openDirectory(entry)
			break
		
		case entryTypes.AUDIO:
			playTrack(entry)
			break
	}
}

const playEntry = entry => {
	switch(entry.type) {
		case entryTypes.DIRECTORY:
			playDirectory(entry)
			break
		
		case entryTypes.AUDIO:
			playTrack(entry)
			break
	}
}

const playDirectory = dir => {
	let tracks = getAllTracks(dir)

	orderTracks(tracks)
	playTrack(tracks[0])
	for(let i = 1; i < tracks.length; i++) {
		queueTrack(tracks[i])
	}
}

const getAllTracks = dir => {
	let tracks = []

	for(entry of dir.entries) {
		switch(entry.type) {
			case entryTypes.DIRECTORY:
				tracks = tracks.concat(getAllTracks(entry))
				break
			
			case entryTypes.AUDIO:
				tracks.push(entry)
				break
		}
	}

	return tracks
}

const grabFocus = () => {
	view.list.children[0]?.querySelector('button').focus()
}