import { MenuItemLocation } from '../plugins/api/types';
import CommandService from '../CommandService';
import KeymapService from '../KeymapService';
import { PluginStates, utils as pluginUtils } from '../plugins/reducer';
import propsHaveChanged from './propsHaveChanged';
const { createSelectorCreator, defaultMemoize } = require('reselect');
const { createCachedSelector } = require('re-reselect');

interface MenuItem {
	id: string;
	label: string;
	click: Function;
	role?: any;
	accelerator?: string;
	enabled: boolean;
}

interface MenuItems {
	[key: string]: MenuItem;
}

interface MenuItemProps {
	[key: string]: any;
}

interface MenuItemPropsCache {
	[key: string]: any;
}

interface MenuItemCache {
	[key: string]: MenuItems;
}

const createShallowObjectEqualSelector = createSelectorCreator(
	defaultMemoize,
	(prev: any, next: any) => {
		if (Object.keys(prev).length !== Object.keys(next).length) return false;
		for (const n in prev) {
			if (prev[n] !== next[n]) return false;
		}
		return true;
	}
);

// This selector ensures that for the given command names, the same toolbar
// button array is returned if the underlying toolbar buttons have not changed.
const selectObjectByCommands = createCachedSelector(
	(state: any) => state.array,
	(array: any[]) => array
)({
	keySelector: (_state: any, commandNames: string[]) => {
		return commandNames.join('_');
	},
	selectorCreator: createShallowObjectEqualSelector,
});

export default class MenuUtils {

	private service_: CommandService;
	private menuItemCache_: MenuItemCache = {};
	private menuItemPropsCache_: MenuItemPropsCache = {};

	constructor(service: CommandService) {
		this.service_ = service;
	}

	private get service(): CommandService {
		return this.service_;
	}

	private get keymapService(): KeymapService {
		return KeymapService.instance();
	}

	public commandToMenuItem(commandName: string, onClick: Function): MenuItem {
		const command = this.service.commandByName(commandName);

		const item: MenuItem = {
			id: command.declaration.name,
			label: this.service.label(commandName),
			click: () => onClick(command.declaration.name),
			enabled: true,
		};

		if (command.declaration.role) item.role = command.declaration.role;

		if (this.keymapService && this.keymapService.acceleratorExists(commandName)) {
			item.accelerator = this.keymapService.getAccelerator(commandName);
		}

		return item;
	}

	public commandToStatefulMenuItem(commandName: string, ...args: any[]): MenuItem {
		return this.commandToMenuItem(commandName, () => {
			return this.service.execute(commandName, ...args);
		});
	}

	public commandsToMenuItems(commandNames: string[], onClick: Function): MenuItems {
		const key: string = `${this.keymapService.lastSaveTime}_${commandNames.join('_')}`;
		if (this.menuItemCache_[key]) return this.menuItemCache_[key];

		const output: MenuItems = {};

		for (const commandName of commandNames) {
			output[commandName] = this.commandToMenuItem(commandName, onClick);
		}

		this.menuItemCache_[key] = output;

		return output;
	}

	public commandsToMenuItemProps(commandNames: string[], whenClauseContext: any): MenuItemProps {
		const output: MenuItemProps = {};

		for (const commandName of commandNames) {
			const newProps = {
				enabled: this.service.isEnabled(commandName, whenClauseContext),
			};

			if (newProps === null || propsHaveChanged(this.menuItemPropsCache_[commandName], newProps)) {
				output[commandName] = newProps;
				this.menuItemPropsCache_[commandName] = newProps;
			} else {
				output[commandName] = this.menuItemPropsCache_[commandName];
			}
		}

		return selectObjectByCommands({ array: output }, commandNames);
	}

	public pluginContextMenuItems(plugins: PluginStates, location: MenuItemLocation): MenuItem[] {
		const output: MenuItem[] = [];
		const pluginViewInfos = pluginUtils.viewInfosByType(plugins, 'menuItem');
		const whenClauseContext = this.service.currentWhenClauseContext();

		for (const info of pluginViewInfos) {
			if (info.view.location !== location) continue;
			const menuItem = this.commandToStatefulMenuItem(info.view.commandName);
			menuItem.enabled = this.service.isEnabled(info.view.commandName, whenClauseContext);
			output.push(menuItem);
		}

		if (output.length) output.splice(0, 0, { type: 'separator' } as any);

		return output;
	}

}