import { StringLike } from './StringLike';
import { Format } from './format';
import { Emitter, Unsubscribe, createNanoEvents } from 'nanoevents';
import Config from '../../config.json';

/// All string keys.
export enum I18nStringKey {
	// Generic things
	kGeneric_CollabVM = 'kGeneric_CollabVM',
	kGeneric_Yes = 'kGeneric_Yes',
	kGeneric_No = 'kGeneric_No',
	kGeneric_Ok = 'kGeneric_Ok',
	kGeneric_Cancel = 'kGeneric_Cancel',
	kGeneric_Send = 'kGeneric_Send',
	kGeneric_Understood = 'kGeneric_Understood',
	kGeneric_Username = 'kGeneric_Username',
	kGeneric_Password = 'kGeneric_Password',
	kGeneric_Login = 'kGeneric_Login',
	kGeneric_Register = 'kGeneric_Register',
	kGeneric_EMail = 'kGeneric_EMail',
	kGeneric_DateOfBirth = 'kGeneric_DateOfBirth',
	kGeneric_VerificationCode = 'kGeneric_VerificationCode',
	kGeneric_Verify = 'kGeneric_Verify',
	kGeneric_Update = 'kGeneric_Update',
	kGeneric_Logout = 'kGeneric_Logout',

	kWelcomeModal_Header = 'kWelcomeModal_Header',
	kWelcomeModal_Body = 'kWelcomeModal_Body',

	kSiteButtons_Home = 'kSiteButtons_Home',
	kSiteButtons_FAQ = 'kSiteButtons_FAQ',
	kSiteButtons_Rules = 'kSiteButtons_Rules',
	kSiteButtons_DarkMode = 'kSiteButtons_DarkMode',
	kSiteButtons_LightMode = 'kSiteButtons_LightMode',
	kSiteButtons_Languages = 'kSiteButtons_Languages',

	kVM_UsersOnlineText = 'kVM_UsersOnlineText',

	kVM_TurnTimeTimer = 'kVM_TurnTimeTimer',
	kVM_WaitingTurnTimer = 'kVM_WaitingTurnTimer',
	kVM_VoteCooldownTimer = 'kVM_VoteCooldownTimer',

	kVM_VoteForResetTitle = 'kVM_VoteForResetTitle',
	kVM_VoteForResetTimer = 'kVM_VoteForResetTimer',

	kVMButtons_TakeTurn = 'kVMButtons_TakeTurn',
	kVMButtons_EndTurn = 'kVMButtons_EndTurn',
	kVMButtons_ChangeUsername = 'kVMButtons_ChangeUsername',
	kVMButtons_Keyboard = 'kVMButtons_Keyboard',
	KVMButtons_CtrlAltDel = 'KVMButtons_CtrlAltDel',

	kVMButtons_VoteForReset = 'kVMButtons_VoteForReset',
	kVMButtons_Screenshot = 'kVMButtons_Screenshot',

	// Admin VM buttons
	kQEMUMonitor = 'kQEMUMonitor',
	kAdminVMButtons_PassVote = 'kAdminVMButtons_PassVote',
	kAdminVMButtons_CancelVote = 'kAdminVMButtons_CancelVote',

	kAdminVMButtons_Restore = 'kAdminVMButtons_Restore',
	kAdminVMButtons_Reboot = 'kAdminVMButtons_Reboot',
	kAdminVMButtons_ClearTurnQueue = 'kAdminVMButtons_ClearTurnQueue',
	kAdminVMButtons_BypassTurn = 'kAdminVMButtons_BypassTurn',
	kAdminVMButtons_IndefiniteTurn = 'kAdminVMButtons_IndefiniteTurn',
	kAdminVMButtons_GhostTurnOn = 'kAdminVMButtons_GhostTurnOn',
	kAdminVMButtons_GhostTurnOff = 'kAdminVMButtons_GhostTurnOff',

	kAdminVMButtons_Ban = 'kAdminVMButtons_Ban',
	kAdminVMButtons_Kick = 'kAdminVMButtons_Kick',
	kAdminVMButtons_TempMute = 'kAdminVMButtons_TempMute',
	kAdminVMButtons_IndefMute = 'kAdminVMButtons_IndefMute',
	kAdminVMButtons_Unmute = 'kAdminVMButtons_Unmute',
	kAdminVMButtons_GetIP = 'kAdminVMButtons_GetIP',

	// prompts
	kVMPrompts_AdminChangeUsernamePrompt = 'kVMPrompts_AdminChangeUsernamePrompt',
	kVMPrompts_AdminRestoreVMPrompt = 'kVMPrompts_AdminRestoreVMPrompt',
	kVMPrompts_EnterNewUsernamePrompt = 'kVMPrompts_EnterNewUsernamePrompt',

	// error messages
	kError_UnexpectedDisconnection = 'kError_UnexpectedDisconnection',

	kError_UsernameTaken = 'kError_UsernameTaken',
	kError_UsernameInvalid = 'kError_UsernameInvalid',
	kError_UsernameBlacklisted = 'kError_UsernameBlacklisted',
	kError_IncorrectPassword = 'kError_IncorrectPassword',

	// Auth
	kAccountModal_Verify = 'kAccountModal_Verify',
	kAccountModal_AccountSettings = 'kAccountModal_AccountSettings',
	kAccountModal_ResetPassword = 'kAccountModal_ResetPassword',

	kAccountModal_NewPassword = 'kAccountModal_NewPassword',
	kAccountModal_ConfirmNewPassword = 'kAccountModal_ConfirmNewPassword',
	kAccountModal_CurrentPassword = 'kAccountModal_CurrentPassword',
	kAccountModal_ConfirmPassword = 'kAccountModal_ConfirmPassword',
	kAccountModal_HideFlag = 'kAccountModal_HideFlag',

	kAccountModal_VerifyText = 'kAccountModal_VerifyText',
	kAccountModal_VerifyPasswordResetText = 'kAccountModal_VerifyPasswordResetText',
	kAccountModal_PasswordResetSuccess = 'kAccountModal_PasswordResetSuccess',
	kMissingCaptcha = 'kMissingCaptcha',
	kPasswordsMustMatch = 'kPasswordsMustMatch',

	kNotLoggedIn = 'kNotLoggedIn',
}

export interface I18nEvents {
	// Called when the language is changed
	languageChanged: (lang: string) => void;
}

// This models the JSON structure.
export type Language = {
	languageName: string;
	translatedLanguageName: string;
	flag: string; // country flag, can be blank if not applicable. will be displayed in language dropdown
	author: string;

	stringKeys: {
		// This is fancy typescript speak for
		// "any string index returns a string",
		// which is our expectation.
		// See https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures if this is confusing.
		[key: string]: string;
	};
};

export type LanguageMetadata = {
	languageName: string;
	flag: string; // country flag, can be blank if not applicable. will be displayed in language dropdown
};

// `languages.json`
export type LanguagesJson = {
	// Array of language IDs to allow loading
	languages: {[key: string]: LanguageMetadata};

	// The default language (set if a invalid language not in the languages array is set, or no language is set)
	defaultLanguage: string;
};

// ID for fallback language
const fallbackId = '!!fallback';

// This language is provided in the webapp itself just in case language stuff fails
import fallbackLanguage from './fallbackLanguage.js';

interface StringKeyMap {
	[k: string]: I18nStringKey;
}

/// our fancy internationalization helper.
export class I18n {
	// The language data itself
	private langs : Map<string, LanguageMetadata> = new Map<string, Language>();
	private lang: Language = fallbackLanguage;
	private languageDropdown: HTMLSpanElement = document.getElementById('languageDropdown') as HTMLSpanElement;
	private emitter: Emitter<I18nEvents> = createNanoEvents();

	CurrentLanguage = () => this.langId;

	// the ID of the language
	private langId: string = fallbackId;

	private regionNameRenderer = new Intl.DisplayNames(['en-US'], {type: 'region'});
	
	async Init() {
		// Load language list
		var res = await fetch("lang/languages.json");
		if (!res.ok) {
			alert("Failed to load languages.json: " + res.statusText);
			await this.SetLanguage(fallbackId);
			this.ReplaceStaticStrings();
			return;
		}
		var langData = await res.json() as LanguagesJson;
		for (const langId in langData.languages) {
			this.langs.set(langId, langData.languages[langId]);
		}
		this.langs.forEach((_lang, langId) => {
			// Add to language dropdown
			var a = document.createElement('a');
			a.classList.add('dropdown-item');
			a.href = '#';
			a.innerText = `${_lang.flag} ${_lang.languageName}`;
			a.addEventListener('click', async e => {
				e.preventDefault();
				await this.SetLanguage(langId);
				this.ReplaceStaticStrings();
			});
			this.languageDropdown.appendChild(a);
		});
		let lang = null;
		let lsLang = window.localStorage.getItem('i18n-lang');
		var browserLang = navigator.language.toLowerCase();
		// If the language is set in localstorage, use that
		if (lsLang !== null && this.langs.has(lsLang)) lang = lsLang;
		// If the browser language is in the list, use that
		else if (this.langs.has(browserLang)) lang = browserLang;
		else {
			// If the exact browser language isn't in the list, try to find a language with the same prefix
			for (let langId in langData.languages) {
				if (langId.split('-')[0] === browserLang.split('-')[0]) {
					lang = langId;
					break;
				}
			}
		}
		// If all else fails, use the default language
		if (lang === null) lang = langData.defaultLanguage;
		await this.SetLanguage(lang);
		this.ReplaceStaticStrings();
	}

	getCountryName(code: string) : string {
		return this.regionNameRenderer.of(code) || code;
	}

	private async SetLanguage(id: string) {
		let lastId = this.langId;
		this.langId = id;

		let lang;
		if (id === fallbackId) lang = fallbackLanguage;
		else {
			let path = `./lang/${id}.json`;
			let res = await fetch(path);
			if (!res.ok) {
				console.error(`Failed to load lang/${id}.json: ${res.statusText}`);
				await this.SetLanguage(fallbackId);
				return;
			}
			lang = await res.json() as Language;
		}

		this.lang = lang;

		if (this.langId != lastId) {
			// Replace static strings
			this.ReplaceStaticStrings();
			
			// Update region name renderer target language
			this.regionNameRenderer = new Intl.DisplayNames([this.langId], {type: 'region'});
		};

		// Set the language ID localstorage entry
		if (this.langId !== fallbackId) {
			window.localStorage.setItem('i18n-lang', this.langId);
		}

		this.emitter.emit('languageChanged', this.langId);
		console.log('i18n initalized for', id, 'sucessfully!');
	}

	// Replaces static strings that we don't recompute
	private ReplaceStaticStrings() {
		const kDomIdtoStringMap: StringKeyMap = {
			siteNameText: I18nStringKey.kGeneric_CollabVM,
			homeBtnText: I18nStringKey.kSiteButtons_Home,
			faqBtnText: I18nStringKey.kSiteButtons_FAQ,
			rulesBtnText: I18nStringKey.kSiteButtons_Rules,
			accountLoginButton: I18nStringKey.kGeneric_Login,
			accountRegisterButton: I18nStringKey.kGeneric_Register,
			accountSettingsButton: I18nStringKey.kAccountModal_AccountSettings,
			accountLogoutButton: I18nStringKey.kGeneric_Logout,
			languageDropdownText: I18nStringKey.kSiteButtons_Languages,
			
			welcomeModalHeader: I18nStringKey.kWelcomeModal_Header,
			welcomeModalBody: I18nStringKey.kWelcomeModal_Body,
			welcomeModalDismiss: I18nStringKey.kGeneric_Understood,

			usersOnlineText: I18nStringKey.kVM_UsersOnlineText,

			voteResetHeaderText: I18nStringKey.kVM_VoteForResetTitle,
			voteYesBtnText: I18nStringKey.kGeneric_Yes,
			voteNoBtnText: I18nStringKey.kGeneric_No,

			changeUsernameBtnText: I18nStringKey.kVMButtons_ChangeUsername,
			oskBtnText: I18nStringKey.kVMButtons_Keyboard,
			ctrlAltDelBtnText: I18nStringKey.KVMButtons_CtrlAltDel,
			voteForResetBtnText: I18nStringKey.kVMButtons_VoteForReset,
			screenshotBtnText: I18nStringKey.kVMButtons_Screenshot,

			// admin stuff
			badPasswordAlertText: I18nStringKey.kError_IncorrectPassword,
			loginModalPasswordText: I18nStringKey.kGeneric_Password,
			loginButton: I18nStringKey.kGeneric_Login,
			passVoteBtnText: I18nStringKey.kAdminVMButtons_PassVote,
			cancelVoteBtnText: I18nStringKey.kAdminVMButtons_CancelVote,
			endTurnBtnText: I18nStringKey.kVMButtons_EndTurn,
			qemuMonitorBtnText: I18nStringKey.kQEMUMonitor,
			qemuModalHeader: I18nStringKey.kQEMUMonitor,
			qemuMonitorSendBtn: I18nStringKey.kGeneric_Send,

			restoreBtnText: I18nStringKey.kAdminVMButtons_Restore,
			rebootBtnText: I18nStringKey.kAdminVMButtons_Reboot,
			clearQueueBtnText: I18nStringKey.kAdminVMButtons_ClearTurnQueue,
			bypassTurnBtnText: I18nStringKey.kAdminVMButtons_BypassTurn,
			indefTurnBtnText: I18nStringKey.kAdminVMButtons_IndefiniteTurn,
			ghostTurnBtnText: I18nStringKey.kAdminVMButtons_GhostTurnOff,

			// Account modal
			accountLoginUsernameLabel: I18nStringKey.kGeneric_Username,
			accountLoginPasswordLabel: I18nStringKey.kGeneric_Password,
			accountModalLoginBtn: I18nStringKey.kGeneric_Login,
			accountForgotPasswordButton: I18nStringKey.kAccountModal_ResetPassword,
			accountRegisterEmailLabel: I18nStringKey.kGeneric_EMail,
			accountRegisterUsernameLabel: I18nStringKey.kGeneric_Username,
			accountRegisterPasswordLabel: I18nStringKey.kGeneric_Password,
			accountRegisterConfirmPasswordLabel: I18nStringKey.kAccountModal_ConfirmPassword,
			accountRegisterDateOfBirthLabel: I18nStringKey.kGeneric_DateOfBirth,
			accountModalRegisterBtn: I18nStringKey.kGeneric_Register,
			accountVerifyEmailCodeLabel: I18nStringKey.kGeneric_VerificationCode,
			accountVerifyEmailPasswordLabel: I18nStringKey.kGeneric_Password,
			accountModalVerifyEmailBtn: I18nStringKey.kGeneric_Verify,
			accountSettingsEmailLabel: I18nStringKey.kGeneric_EMail,
			accountSettingsUsernameLabel: I18nStringKey.kGeneric_Username,
			accountSettingsNewPasswordLabel: I18nStringKey.kAccountModal_NewPassword,
			accountSettingsConfirmNewPasswordLabel: I18nStringKey.kAccountModal_ConfirmNewPassword,
			accountSettingsCurrentPasswordLabel: I18nStringKey.kAccountModal_CurrentPassword,
			hideFlagCheckboxLabel: I18nStringKey.kAccountModal_HideFlag,
			updateAccountSettingsBtn: I18nStringKey.kGeneric_Update,
			accountResetPasswordEmailLabel: I18nStringKey.kGeneric_EMail,
			accountResetPasswordUsernameLabel: I18nStringKey.kGeneric_Username,
			accountResetPasswordBtn: I18nStringKey.kAccountModal_ResetPassword,
			accountResetPasswordCodeLabel: I18nStringKey.kGeneric_VerificationCode,
			accountResetPasswordNewPasswordLabel: I18nStringKey.kAccountModal_NewPassword,
			accountResetPasswordConfirmNewPasswordLabel: I18nStringKey.kAccountModal_ConfirmNewPassword,
			accountResetPasswordVerifyBtn: I18nStringKey.kAccountModal_ResetPassword,
		};

		const kDomAttributeToStringMap = {
			adminPassword: {
				placeholder: I18nStringKey.kGeneric_Password,
			},
			accountLoginUsername: {
				placeholder: I18nStringKey.kGeneric_Username,
			},
			accountLoginPassword: {
				placeholder: I18nStringKey.kGeneric_Password,
			},
			accountRegisterEmail: {
				placeholder: I18nStringKey.kGeneric_EMail,
			},
			accountRegisterUsername: {
				placeholder: I18nStringKey.kGeneric_Username,
			},
			accountRegisterPassword: {
				placeholder: I18nStringKey.kGeneric_Password,
			},
			accountRegisterConfirmPassword: {
				placeholder: I18nStringKey.kAccountModal_ConfirmPassword,
			},
			accountVerifyEmailCode: {
				placeholder: I18nStringKey.kGeneric_VerificationCode,
			},
			accountVerifyEmailPassword: {
				placeholder: I18nStringKey.kGeneric_Password,
			},
			accountSettingsEmail: {
				placeholder: I18nStringKey.kGeneric_EMail,
			},
			accountSettingsUsername: {
				placeholder: I18nStringKey.kGeneric_Username,
			},
			accountSettingsNewPassword: {
				placeholder: I18nStringKey.kAccountModal_NewPassword,
			},
			accountSettingsConfirmNewPassword: {
				placeholder: I18nStringKey.kAccountModal_ConfirmNewPassword,
			},
			accountSettingsCurrentPassword: {
				placeholder: I18nStringKey.kAccountModal_CurrentPassword,
			},
			accountResetPasswordEmail: {
				placeholder: I18nStringKey.kGeneric_EMail,
			},
			accountResetPasswordUsername: {
				placeholder: I18nStringKey.kGeneric_Username,
			},
			accountResetPasswordCode: {
				placeholder: I18nStringKey.kGeneric_VerificationCode,
			},
			accountResetPasswordNewPassword: {
				placeholder: I18nStringKey.kAccountModal_NewPassword,
			},
			accountResetPasswordConfirmNewPassword: {
				placeholder: I18nStringKey.kAccountModal_ConfirmNewPassword,
			},
		};

		const kDomClassToStringMap: StringKeyMap = {
			"mod-end-turn-btn": I18nStringKey.kVMButtons_EndTurn,
			"mod-ban-btn": I18nStringKey.kAdminVMButtons_Ban,
			"mod-kick-btn": I18nStringKey.kAdminVMButtons_Kick,
			"mod-change-username-btn": I18nStringKey.kVMButtons_ChangeUsername,
			"mod-temp-mute-btn": I18nStringKey.kAdminVMButtons_TempMute,
			"mod-indef-mute-btn": I18nStringKey.kAdminVMButtons_IndefMute,
			"mod-unmute-btn": I18nStringKey.kAdminVMButtons_Unmute,
			"mod-get-ip-btn": I18nStringKey.kAdminVMButtons_GetIP,
		}

		for (let domId of Object.keys(kDomIdtoStringMap)) {
			let element = document.getElementById(domId);
			if (element == null) {
				alert(`Error: Could not find element with ID ${domId} in the DOM! Please tell a site admin this happened.`);
				return;
			}

			// Do the magic.
			// N.B: For now, we assume all strings in this map are not formatted.
			// If this assumption changes, then we should just use GetString() again
			// and maybe include arguments, but for now this is okay
			element.innerHTML = this.GetStringRaw(kDomIdtoStringMap[domId]);
		}

		for (let domId of Object.keys(kDomAttributeToStringMap)) {
			let element = document.getElementById(domId);
			if (element == null) {
				alert(`Error: Could not find element with ID ${domId} in the DOM! Please tell a site admin this happened.`);
				return;
			}

			// TODO: Figure out if we can get rid of this ts-ignore
			// @ts-ignore
			let attributes = kDomAttributeToStringMap[domId];
			for (let attr of Object.keys(attributes)) {
				element.setAttribute(attr, this.GetStringRaw(attributes[attr] as I18nStringKey));
			}
		}

		for (let domClass of Object.keys(kDomClassToStringMap)) {
			let elements = document.getElementsByClassName(domClass);
			for (let element of elements) {
				element.innerHTML = this.GetStringRaw(kDomClassToStringMap[domClass]);
			}
		}
	}

	// Returns a (raw, unformatted) string. Currently only used if we don't need formatting.
	GetStringRaw(key: I18nStringKey): string {
		if (key === I18nStringKey.kGeneric_CollabVM && Config.SiteNameOverride) return Config.SiteNameOverride;
		if (key === I18nStringKey.kWelcomeModal_Header && Config.WelcomeModalTitleOverride) return Config.WelcomeModalTitleOverride;
		if (key === I18nStringKey.kWelcomeModal_Body && Config.WelcomeModalBodyOverride) return Config.WelcomeModalBodyOverride;
		let val = this.lang.stringKeys[key];

		// Look up the fallback language by default if the language doesn't
		// have that string key yet; if the fallback doesn't have it either,
		// then just return the string key and a bit of a notice things have gone wrong
		if (val == undefined) {
			let fallback = fallbackLanguage.stringKeys[key];
			if (fallback !== undefined) val = fallback;
			else return `${key} (ERROR LOOKING UP TRANSLATION!!!)`;
		}

		return val;
	}

	// Returns a formatted localized string.
	GetString(key: I18nStringKey, ...replacements: StringLike[]): string {
		return Format(this.GetStringRaw(key), ...replacements);
	}

	on<e extends keyof I18nEvents>(event: e, cb: I18nEvents[e]): Unsubscribe {
		return this.emitter.on(event, cb);
	}
}

export let TheI18n = new I18n();