/**
 * Code to manage username prefixes that force battles to be public or disable modchat.
 * @author Annika
 */

import {FS} from '../../lib';

const PREFIXES_FILE = 'config/chat-plugins/username-prefixes.json';
const PREFIX_DURATION = 10 * 24 * 60 * 60 * 1000;

export class PrefixManager {
	/** prefix:timeout */
	timeouts = new Map<ID, NodeJS.Timeout>();
	constructor() {
		// after a restart/newly using the plugin, load prefixes from config.js
		if (!Chat.oldPlugins['username-prefixes']) this.refreshConfig(true);
	}

	save() {
		FS(PREFIXES_FILE).writeUpdate(() => JSON.stringify(Config.forcedprefixes || []));
	}

	refreshConfig(configJustLoaded = false) {
		if (!Config.forcedprefixes) Config.forcedprefixes = [];
		// ensure everything is in the right format
		if (!Array.isArray(Config.forcedprefixes)) {
			const convertedPrefixes = [];
			for (const type in Config.forcedprefixes) {
				for (const prefix of Config.forcedprefixes[type].map(toID)) {
					convertedPrefixes.push({type, prefix, expireAt: Date.now() + PREFIX_DURATION});
					this.timeouts.set(prefix, setTimeout(() => {
						this.removePrefix(prefix, type as 'privacy' | 'modchat');
					}, PREFIX_DURATION));
				}
			}
			Config.forcedprefixes = convertedPrefixes;
		}
		if (configJustLoaded) {
			for (const entry of Config.forcedprefixes) {
				entry.prefix = toID(entry.prefix);
				if (!this.timeouts.get(entry.prefix)) {
					const expireTime = entry.expireAt - Date.now();
					if (expireTime < 0) {
						this.removePrefix(entry.prefix, entry.type as 'privacy' | 'modchat');
						continue;
					}
					this.timeouts.set(entry.prefix, setTimeout(() => {
						this.removePrefix(entry.prefix, entry.type as 'privacy' | 'modchat');
					}, expireTime));
				}
			}
		}

		let data: AnyObject[];
		try {
			data = JSON.parse(FS(PREFIXES_FILE).readSync());
		} catch (e: any) {
			if (e.code !== 'ENOENT') throw e;
			return;
		}
		if (data.length) {
			for (const entry of data) {
				if (Config.forcedprefixes.includes(entry)) continue;
				Config.forcedprefixes.push(entry);
				if (!this.timeouts.get(entry.prefix)) {
					const expireTime = entry.expireAt - Date.now();
					if (expireTime < 0) {
						this.removePrefix(entry.prefix, entry.type as 'privacy' | 'modchat');
						continue;
					}
					this.timeouts.set(entry.prefix, setTimeout(() => {
						this.removePrefix(entry.prefix, entry.type as 'privacy' | 'modchat');
					}, expireTime));
				}
			}
		}
	}

	addPrefix(prefix: ID, type: 'privacy' | 'modchat') {
		if (!Config.forcedprefixes) Config.forcedprefixes = [];
		const entry = Config.forcedprefixes.find((x: AnyObject) => x.prefix === prefix && x.type === type);
		if (entry) {
			throw new Chat.ErrorMessage(`Username prefix '${prefix}' is already configured to force ${type}.`);
		}

		Config.forcedprefixes.push({type, prefix, expireAt: Date.now() + PREFIX_DURATION});
		this.timeouts.set(prefix, setTimeout(() => {
			this.removePrefix(prefix, type);
		}, PREFIX_DURATION));
		this.save();
	}

	removePrefix(prefix: ID, type: 'privacy' | 'modchat') {
		const entry = Config.forcedprefixes.findIndex((x: AnyObject) => x.prefix === prefix && x.type === type);
		if (entry < 0) {
			throw new Chat.ErrorMessage(`Username prefix '${prefix}' is not configured to force ${type}!`);
		}

		Config.forcedprefixes.splice(entry, 1);
		const timeoutEntry = this.timeouts.get(prefix);
		if (timeoutEntry) {
			clearTimeout(timeoutEntry);
			this.timeouts.delete(prefix);
		}
		this.save();
	}

	validateType(type: string) {
		if (type !== 'privacy' && type !== 'modchat') {
			throw new Chat.ErrorMessage(`'${type}' is not a valid type of forced prefix. Valid types are 'privacy' and 'modchat'.`);
		}
		return type;
	}
}

export const prefixManager = new PrefixManager();

export const commands: Chat.ChatCommands = {
	forceprefix: 'usernameprefix',
	forcedprefix: 'usernameprefix',
	forcedprefixes: 'usernameprefix',
	usernameprefixes: 'usernameprefix',
	usernameprefix: {
		help: '',
		''() {
			this.parse(`/help forcedprefix`);
		},

		delete: 'add',
		remove: 'add',
		add(target, room, user, connection, cmd) {
			this.checkCan('rangeban');

			const isAdding = cmd.includes('add');

			const [prefix, type] = target.split(',').map(toID);
			if (!prefix || !type) return this.parse(`/help usernameprefix`);
			if (prefix.length > 18) {
				throw new Chat.ErrorMessage(`Specified prefix '${prefix}' is longer than the maximum user ID length.`);
			}

			if (isAdding) {
				prefixManager.addPrefix(prefix, prefixManager.validateType(type));
			} else {
				prefixManager.removePrefix(prefix, prefixManager.validateType(type));
			}

			this.globalModlog(`FORCEDPREFIX ${isAdding ? 'ADD' : 'REMOVE'}`, null, `'${prefix}' ${isAdding ? 'to' : 'from'} ${type}`);
			this.addGlobalModAction(`${user.name} set the username prefix ${prefix} to${isAdding ? '' : ' no longer'} disable ${type}.`);
		},

		view(target) {
			this.checkCan('rangeban');

			const types = target ? [prefixManager.validateType(toID(target))] : ['privacy', 'modchat'];

			const entries = Config.forcedprefixes.filter((x: any) => types.includes(x.type));

			return this.sendReplyBox(types.map(type => {
				const prefixes = entries.filter((x: any) => x.type === type).map((x: any) => x.prefix);
				const info = prefixes.length ?
					`<code>${prefixes.join('</code>, <code>')}</code>` : `none`;
				return `Username prefixes that disable <strong>${type}</strong>: ${info}.`;
			}).join(`<br />`));
		},
	},
	usernameprefixhelp() {
		return this.sendReplyBox(
			`<code>/usernameprefix add [prefix], [type]</code>: Sets the username prefix [prefix] to disable privacy or modchat on battles where at least one player has the prefix.<br />` +
			`<code>/usernameprefix remove [prefix], [type]</code>: Removes a prefix configuration.<br />` +
			`<code>/usernameprefix view [optional type]</code>: Displays the currently configured username prefixes.<br />` +
			`Valid types are <code>privacy</code> (which forces battles to take place in public rooms) and <code>modchat</code> (which prevents players from setting moderated chat).<br />` +
			`Requires: &`
		);
	},
};
