const module = 'koboldworks-ready-up',
	volumeKey = 'volume',
	lingerKey = 'linger',
	notifyKey = 'notify',
	stickyKey = 'sticky';

/** Default sound config */
const soundCfg = {
	turn: { key: 'sfx-turn-file', default: 'turn.unfa.550909.braam.webm' },
	round: { key: 'sfx-round-file', default: 'round.halgrimm.169867.swoosh.webm' },
	next: { key: 'sfx-next-file', default: 'next.garyq.135434.industrial-warehouse-lights.webm' }
};

const getPath = (file) => `modules/${module}/sounds/${file}`;
const clamp = (val, min, max) => Math.max(min, Math.min(val, max));

const active = 'active', hidden = 'hidden', ghosted = 'ghosted', next = 'next', now = 'now';

class Overlay {
	_overlay = document.createElement('div');
	dismissable = false;
	timerId;

	constructor() {
		this._overlay.id = `${module}-overlay`;
		this._overlay.classList.add(ghosted);
		this._overlay.style.display = 'none';
		const _labelNext = document.createElement('label'),
			_labelNow = document.createElement('label'),
			_container = document.createElement('div');
		_labelNow.id = `${module}-now`;
		_labelNow.textContent = game.i18n.localize('Koboldworks.ReadyUp.YourTurn')
		_labelNext.id = `${module}-next`;
		_labelNext.textContent = game.i18n.localize('Koboldworks.ReadyUp.NextUp');
		_container.classList.add('container');
		_container.append(_labelNow, _labelNext);
		this._overlay.append(_container);
		document.body.append(this._overlay);
	}

	resetTimer() {
		clearTimeout(this.timerId);
		this.timerId = undefined;
	}

	resize() {
		const sbc = window.getComputedStyle(document.getElementById('sidebar'));
		this._overlay.style.width = `calc(100% - ${sbc.width} - ${sbc.right})`; // render over all but the side bar
	}

	displayTime = Date.now();
	boundDismiss = this.dismiss.bind(this); // for event listener nonsense

	removeClickListener() {
		document.getElementById('board').removeEventListener('click', this.boundDismiss);
	}

	dismiss(ev) {
		if ((Date.now() - this.displayTime) < 250) return; // Ignore clicks too soon after displaying.

		this.dismissable ? this.hide() : this.ghost();

		const o = this._overlay;
		if (!o.classList.contains(ghosted)) {
			// Prevent event bubbling
			ev?.preventDefault();
			ev?.stopPropagation();
		}
	}

	hide() {
		this.resetTimer();
		this.removeClickListener();
		const o = this._overlay;
		if (!o.classList.contains(ghosted)) o.classList.add(ghosted);
		$(o).stop(true, true).fadeOut(150);
	}

	show({ now = false, dismissable = false }) {
		this.displayTime = Date.now();
		now ? this.now() : this.next();

		const sticky = game.settings.get(module, stickyKey);
		this.dismissable = (sticky && dismissable) || !sticky;
		const o = this._overlay;
		if (o.classList.contains(ghosted)) o.classList.remove(ghosted);
		this.resize();

		document.getElementById('board').addEventListener('click', this.boundDismiss);

		$(o).stop(true, true).fadeIn(150);
		o.style.opacity = null; // hacky fix to help deal with jquery failing

		const linger = game.settings.get(module, lingerKey);
		if (linger > 0.1) {
			this.resetTimer();
			this.timerId = setTimeout(this.boundDismiss, linger * 1_000);
		}
	}

	next() {
		const o = this._overlay;
		if (!o.classList.contains(next)) o.classList.add(next);
		if (o.classList.contains(now)) o.classList.remove(now);
	}

	now() {
		const o = this._overlay;
		if (!o.classList.contains(now)) o.classList.add(now);
		if (o.classList.contains(next)) o.classList.remove(next);
	}

	ghost() {
		this.resetTimer();
		this.removeClickListener();
		const o = this._overlay;
		if (!o.classList.contains(ghosted)) o.classList.add(ghosted);
	}

	/** Reset and remove current overlay */
	static delete() {
		const s = Overlay.instance;
		if (s) {
			s.resetTimer();
			s._overlay?.remove();
			s._overlay = null;
		}
		Overlay.instance = null;
	}

	/** @type {undefined|Overlay} */
	static instance;
}

/**
 * Simplified layer over HTML Audio and Foundry's AudioHelper
 */
class SoundProxy {
	key;
	file;
	audio;

	constructor(key, file, volume = 0.5) {
		this.key = key;
		this.file = file;

		//this.audio = game.audio.create({ src: file, preload: true }); // doesn't exist?
		this.audio = new Audio(file);
		this.audio.preload = 'auto';
		this.audio.defaultMuted = true;
		this.audio.autoplay = false;
		this.audio.loop = false;
		this.audio.volume = clamp(volume, 0.0, 1.0);

		this.audio.addEventListener('error', this.errorHandler.bind(this));
		this.audio.addEventListener('canplay', this.onLoadBound);
	}

	onLoadBound = this.onLoad.bind(this);
	/** Temporary onload handler  */
	onLoad() {
		this.play = this.playReal;
		this.audio.removeEventListener('canplay', this.onLoadBound);
		if (this.queued) this.play();
		delete this.onLoadBound;
		delete this.queued;
		delete this.onLoad;
	}

	errorHandler() {
		const err = this.audio.error;
		if (err.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED ||
			(err.code === MediaError.MEDIA_ERR_NETWORK && this.audio.networkState === 3)) {
			console.warn(`READY UP | ${this.key} | Failed to load "${this.file}"`);
		}
	}

	set volume(value) {
		this.audio.volume = value;
	}

	queued = false;
	/** Temporary until onLoad happens */
	play = function () { this.queued = true; }; // placeholder until loaded

	async playReal(volume = null) {
		if (volume) this.volume = volume;
		//this.audio.play({ offset: 0, volume: volume ?? this.volume }); // AudioHelper

		if (!this.audio.paused)
			this.reset();

		return this.audio.play();
	}

	reset() {
		this.audio.currentTime = 0;
	}
}

const audio = {
	next: undefined,
	round: undefined,
	turn: undefined,
};

function updateAudioVolume(value) {
	const newVolume = clamp(value / 100, 0.0, 1.0);
	console.log("READY UP | Volume | Set:", value, "%");
	for (let s of Object.values(audio))
		s.volume = newVolume;
}

let lastCombat = null, lastTurn = null, lastTurnAnticipation = null;

async function updateCombatEvent(combat, _update, _options, _userId) {
	if (!combat.data.active) return;
	if (!combat.started) return;

	Overlay.instance?.hide(); // Hide previous banner

	// New Round
	if (combat.turn === 0) {
		if (!(lastCombat?.id === combat.id && lastCombat?.round === combat.round))
			audio.round.play();
		lastCombat = { id: combat.id, round: combat.round };
	}

	if (game.user.isGM) return; // GM doesn't need the rest

	const currentPlayer = combat.combatant.players.find(u => u.id === game.user.id);

	// Current player's turn
	if (currentPlayer) {
		if (!(lastTurn?.id === combat.id && lastTurn?.round === combat.round)) {
			audio.turn.play();
			Overlay.instance?.show({ now: true, dismissable: true });
		}
		lastTurn = { id: combat.id, round: combat.round };
		return;
	}

	const nextTurn = (combat.turn + 1) % combat.turns.length;
	const nextCombatant = combat.turns[nextTurn];

	const nextPlayer = nextCombatant.players.find(u => u.id === game.user.id);
	//if (currentPlayer && nextPlayer) return; // same player for current and next turn

	// Current player is next
	if (nextPlayer) {
		if (!(lastTurnAnticipation?.id === combat.id && lastTurnAnticipation?.round === combat.round)) {
			audio.next.play();
			Overlay.instance?.show({ now: false, dismissable: false });
		}
		lastTurnAnticipation = { id: combat.id, round: combat.round };
		return;
	}
}

async function assessActiveCombat({ sound=false} = {}) {
	// Unknown how well this works with multiple combats
	if (game.combats?.active)
		await updateCombatEvent(game.combats.active);
}

function updateCombatantEvent(_combatant, update, _options, _userId) {
	if (update.initiative) {
		Overlay.instance?.hide();
		Hooks.once('renderCombatTracker', assessActiveCombat); // assumption
	}
}

Hooks.on('updateCombat', updateCombatEvent);
Hooks.on('updateCombatant', updateCombatantEvent);

async function loadSound(key, file) {
	audio[key] = new SoundProxy(key, file, game.settings.get(module, volumeKey) / 100);
	return audio[key];
}

var nextSound = -1;
async function audioTest(ev, jq) {
	ev.preventDefault();
	ev.stopPropagation();

	const volume = parseFloat(jq.find(`input[name="${module}.volume"]`).val(), 10) / 100;

	const cfg = Object.values(soundCfg);
	nextSound = (nextSound + 1) % cfg.length;

	const key = Object.keys(soundCfg)[nextSound];
	const file = jq.find(`input[name="${module}.${cfg[nextSound].key}"]`).val() ?? audio[key].file;

	const tmp = new SoundProxy('test', file, volume);

	ui.notifications.info(game.i18n.localize('Koboldworks.ReadyUp.TestNotify').format(key, tmp.file, Math.floor(volume * 1_000) / 10));
	await tmp.play();
}

// Initialize settings
Hooks.once('init', () => {
	game.settings.register(
		module,
		volumeKey,
		{
			name: 'Koboldworks.ReadyUp.VolumeLabel',
			hint: 'Koboldworks.ReadyUp.VolumeHint',
			scope: 'client',
			config: true,
			type: Number,
			range: { min: 0.0, max: 100.0, step: 0.1, },
			default: 50.0,
			onChange: updateAudioVolume,
		}
	);

	for (let [key, settings] of Object.entries(soundCfg)) {
		game.settings.register(
			module,
			settings.key,
			{
				name: `Koboldworks.ReadyUp.${key.capitalize()}Sfx`,
				hint: `Koboldworks.ReadyUp.${key.capitalize()}SfxHint`,
				scope: 'world',
				config: true,
				type: String,
				default: getPath(settings.default),
				filePicker: true, // >=0.8.x file picker
				onChange: (v) => loadSound(key, v),
			}
		);

		// const sfx =
		loadSound(key, game.settings.get(module, settings.key));
		//console.log("READY UP | Registered sound:", key, sfx.file);
	}

	game.settings.register(
		module,
		notifyKey,
		{
			name: 'Koboldworks.ReadyUp.NotifyLabel',
			hint: 'Koboldworks.ReadyUp.NotifyHint',
			scope: 'client',
			config: true,
			type: Boolean,
			default: true,
			onChange: (value) => value ? (Overlay.instance = new Overlay()) : Overlay.delete()
		}
	);

	game.settings.register(
		module,
		lingerKey,
		{
			name: 'Koboldworks.ReadyUp.LingerLabel',
			hint: 'Koboldworks.ReadyUp.LingerHint',
			scope: 'client',
			config: true,
			type: Number,
			range: { min: 0.0, max: 25.0, step: 0.2, },
			default: 0.0,
		}
	);

	game.settings.register(
		module,
		stickyKey,
		{
			name: 'Koboldworks.ReadyUp.StickyLabel',
			hint: 'Koboldworks.ReadyUp.StickyHint',
			scope: 'client',
			config: true,
			type: Boolean,
			default: true,
		}
	);
});

Hooks.once('ready', async () => {
	if (game.settings.get(module, notifyKey))
		Overlay.instance = new Overlay();

	// Prepare for on-load situation to make sure nothing is missed
	assessActiveCombat();
});

// Add sound test button to module config
Hooks.on('renderSettingsConfig', (_sheet, jq) => {
	jq.find(`input[name="${module}.volume"]`).parent()
		.append($('<button>').css({ 'margin-left': '3px', 'width': '4em' }).text(game.i18n.localize('Koboldworks.ReadyUp.Test'))
			.on('click', (ev) => audioTest(ev, jq)));
});
