/**
 * eGroupWare egw_action framework - JS Menu abstraction
 *
 * @link http://www.egroupware.org
 * @author Andreas Stöckel <as@stylite.de>
 * @copyright 2011 by Andreas Stöckel
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package egw_action
 *
 */
import {EgwMenuShoelace} from "./EgwMenuShoelace";

//Global variable which is used to store the currently active menu so that it
//may be closed when another menu opens
export var _egw_active_menu: egwMenu = null;

/**
 * Internal function which parses the given menu tree in _elements and adds the
 * elements to the given parent.
 */
function _egwGenMenuStructure(_elements: any[], _parent)
{
	const items: egwMenuItem[] = [];

	//Go through each object in the elements array
	for (const obj of _elements)
	{
		//Go through each key of the current object
		const item = new egwMenuItem(_parent, null);
		for (const key in obj)
		{
			if (key == "children" && obj[key].constructor === Array)
			{
				//Recursively load the children.
				item.children = _egwGenMenuStructure(obj[key], item);
			} else
			{
				//Directly set the other keys
				//TODO Sanity necessary checks here?
				//TODO Implement menu item getters?
				if (key == "id" || key == "caption" || key == "iconUrl" ||
					key == "checkbox" || key == "checked" || key == "groupIndex" ||
					key == "enabled" || key == "default" || key == "onClick" ||
					key == "hint" || key == "shortcutCaption")
				{
					item['set_' + key](obj[key]);
				}
			}
		}

		items.push(item);
	}

	return items;
}

/**
 * Internal function which searches for the given ID inside an element tree.
 */
function _egwSearchMenuItem(_elements: any[], _id: any): egwMenuItem
{
	for (const item1 of _elements)
	{
		if (item1.id === _id)
			return item1;

		const item = _egwSearchMenuItem(item1.children, _id);
		if (item)
			return item;
	}

	return null;
}

/**
 * Internal function which allows to set the onClick handler of multiple menu items
 */
function _egwSetMenuOnClick(_elements, _onClick)
{
	for (const item of _elements)
	{
		if (item.onClick === null)
		{
			item.onClick = _onClick;
		}
		_egwSetMenuOnClick(item.children, _onClick);
	}
}

/**
 * replacement function for jquery trigger
 * @param selector
 * @param eventType
 */
function trigger(selector, eventType)
{
	if (typeof eventType === 'string' && typeof selector[eventType] === 'function')
	{
		selector[eventType]();
	} else
	{
		const event =
			typeof eventType === 'string'
				? new Event(eventType, {bubbles: true})
				: eventType;
		selector.dispatchEvent(event);
	}
}


/**
 * Constructor for the egwMenu object. The egwMenu object is an abstract representation
 * of a context/popup menu. The actual generation of the menu can be done by
 * so-called menu implementations. Those are activated by simply including the JS file
 * of such an implementation.
 *
 * The current use implementation is "EgwShoelaceMenu.js" which is based on Shoelace.
 */
export class egwMenu
{
	//The "items" variable contains all menu items of the menu
	children: egwMenuItem[] = [];

	//The "instance" variable contains the currently opened instance. There may
	//only be one instance opened at a time.
	instance: EgwMenuShoelace = null; // This is equivalent to iface in other classes and holds an egwMenuImpl
	constructor()
	{
	}

	remove()
	{
		this.instance?.remove();
	}

	/**
	 * The private _checkImpl function checks whether a menu implementation is available.
	 *
	 * @returns bool whether a menu implementation is available.
	 */
	private _checkImpl()
	{
		return typeof egwMenuImpl == 'function';
	}

	/**
	 * Hides the menu if it is currently opened. Otherwise, nothing happens.
	 */
	public hide()
	{
		//Reset the currently active menu variable
		if (_egw_active_menu == this)
			_egw_active_menu = null;

		//Check whether a currently opened instance exists. If it does, close it.
		if (this.instance != null)
		{
			this.instance.hide();
		}
	}

	/**
	 * The showAtElement function shows the menu at the given screen position in a
	 * (hopefully) optimal orientation. There can only be one instance of the menu opened at
	 * one time and the menu implementation should care that there is only one menu
	 * opened globally at all.
	 *
	 * @param {number} _x is the x position at which the menu will be opened
	 * @param {number} _y is the y position at which the menu will be opened
	 * @param {boolean} _force if true, the menu will be reopened at the given position,
	 * 	even if it already had been opened. Defaults to false.
	 * @returns {boolean} whether the menu had been opened
	 */
	public showAt(_x: number, _y: number, _force: boolean = false)
	{
		//Hide any other currently active menu
		if (_egw_active_menu != null)
		{
			if (_egw_active_menu == this && !_force)
			{
				this.hide();
				return false;
			} else
			{
				_egw_active_menu.hide();
			}
		}

		if (this.instance == null && this._checkImpl)
		{
			//Obtain a new egwMenuImpl object and pass this instance to it
			this.instance = new EgwMenuShoelace(this.children);

		}

		_egw_active_menu = this;

		this.instance.showAt(_x, _y, () =>
		{
			_egw_active_menu = null;
		});
		return true;
	}


	/**
	 * Enable / disable menu items for the given selection & target
	 *
	 * @param _context
	 * @param _links
	 * @param _selected
	 * @param _target
	 * @private
	 */
	public applyContext(_links, _selected, _target)
	{
		if(!this.instance)
		{
			this.instance = new EgwMenuShoelace(this.children);
		}
		this.instance.updateComplete.then(() => {this.instance.applyContext(_links, _selected, _target);});
		let setOnClick = (menuItem) =>
		{
			menuItem.set_onClick((elem) =>
			{
				// Copy the "checked" state
				if(typeof elem.data.checked != "undefined")
				{
					elem.data.checked = elem.checked;
				}
				elem.data.execute(_selected, _target);
			});
			menuItem.children.forEach(c => setOnClick(c));
		};
		this.children.forEach(menuItem =>
		{
			setOnClick(menuItem);
		});
	}

	/**
	 * Adds a new menu item to the list and returns a reference to that object.
	 *
	 * @param {string} _id is a unique identifier of the menu item. You can use
	 * 	the getItem function to search a specific menu item inside the menu tree. The
	 * 	id may also be false, null or "", which makes sense for items like separators,
	 * 	which you don't want to access anymore after adding them to the menu tree.
	 * @param {string} _caption is the caption of the newly generated menu item. Set the caption
	 * 	to "-" in order to create a separator.
	 * @param {string} _iconUrl is the URL of the icon which should be prepended to the
	 * 	menu item. It may be false, null or "" if you don't want an icon to be displayed.
	 * @param {function} _onClick is the JS function which is being executed when the
	 * 	menu item is clicked.
	 * @param {string|null} _color color
	 * @param _indentation The level of indentation applied to the element
	 * @returns {egwMenuItem} the newly generated menu item, which had been appended to the
	 * 	menu item list.
	 */
	public addItem(_id, _caption, _iconUrl, _onClick, _color,_indentation?): egwMenuItem
	{
		//Append the item to the list
		const item: egwMenuItem = new egwMenuItem(this, _id, _caption, _iconUrl, _onClick, _color, _indentation);
		this.children.push(item);

		return item;
	}

	/**
	 * Removes all elements from the menu structure.
	 */
	public clear()
	{
		this.children = [];
	}

	/**
	 * Loads the menu structure from the given object tree. The object tree is an array
	 * of objects which may contain a subset of the menu item properties. The "children"
	 * property of such an object is interpreted as a new sub-menu tree and appended
	 * to that child.
	 *
	 * @param {array} _elements is an array of elements which should be added to the menu
	 */
	public loadStructure(_elements)
	{
		this.children = _egwGenMenuStructure(_elements, this);
	}

	/**
	 * Searches for the given item id within the element tree.
	 */
	public getItem(_id) {
		return _egwSearchMenuItem(this.children, _id);
	}

	/**
	 * Applies the given onClick handler to all menu items which don't have a clicked
	 * handler assigned yet.
	 */
	setGlobalOnClick(_onClick)
	{
		_egwSetMenuOnClick(this.children, _onClick);
	}
}


/**
 * Constructor for the egwMenuItem. Each entry in a menu (including separators)
 * is represented by a menu item.
 */
export class egwMenuItem
{
	id: string;
	color: string;
	indentation: number;

	set_id(_value)
	{
		this.id = _value;
	}

	caption = "";

	set_caption(_value)
	{
		//A value of "-" means that this element is a separator.
		this.caption = _value;
	}

	checkbox = false;

	set_checkbox(_value)
	{
		this.checkbox = _value;
	}

	checked = false;

	set_checked(_value)
	{
		if (_value && this.groupIndex > 0)
		{
			//Uncheck all other elements in this radio group
			for (const menuItem of this.parent.children)
			{
				if (menuItem.groupIndex == this.groupIndex)
					menuItem.checked = false;
			}
		}
		this.checked = _value;
	}

	groupIndex = 0;
	enabled = true;
	iconUrl = "";
	onClick = null;
	default = false;
	data = null;
	shortcutCaption = null;

	children = [];
	parent: egwMenu;
	//is set for radio Buttons
	_dhtmlx_grpid: string = "";
	//hint might get set somewhere
	hint: string = "";

	constructor(_parent: this | egwMenu, _id: string, _caption = "", _iconUrl = "", onClick = null, _color = null,
				_indentation: number = 0
	)
	{
		this.parent = _parent;
		this.id = _id;
		this.caption = _caption;
		this.iconUrl = _iconUrl;
		this.onClick = onClick;
		this.color = _color;
		this.indentation = _indentation;
	}

	/**
	 * Searches for the given item id within the element tree.
	 */
	getItem(_id)
	{
		if (this.id === _id)
			return this;

		return _egwSearchMenuItem(this.children, _id);
	}

	/**
	 * Applies the given onClick handler to all menu items which don't have a clicked
	 * handler assigned yet.
	 */
	setGlobalOnClick(_onClick)
	{
		this.onClick = _onClick;
		_egwSetMenuOnClick(this.children, _onClick);
	}

	/**
	 * Adds a new menu item to the list and returns a reference to that object.
	 *
	 * @param {string} _id is a unique identifier of the menu item. You can use
	 * 	the getItem function to search a specific menu item inside the menu tree. The
	 * 	id may also be false, null or "", which makes sense for items like separators,
	 * 	which you don't want to access anymore after adding them to the menu tree.
	 * @param {string} _caption is the caption of the newly generated menu item. Set the caption
	 * 	to "-" in order to create a separator.
	 * @param {string} _iconUrl is the URL of the icon which should be prepended to the
	 * 	menu item. It may be false, null or "" if you don't want an icon to be displayed.
	 * @param {function} _onClick is the JS function which is being executed when the
	 * 	menu item is clicked.
	 * @param _color The color the item caption will be displayed in
	 * @param _indentation The level of indentation the item should have, this is used e.g. for subcategories
	 * @returns {egwMenuItem} the newly generated menu item, which had been appended to the
	 * 	menu item list.
	 */
	addItem(_id: string, _caption: string, _iconUrl: string, _onClick: any, _color: string, _indentation?: number)
	{
		//Append the item to the list
		const item = new egwMenuItem(this, _id, _caption, _iconUrl, _onClick, _color, _indentation);
		this.children.push(item);

		return item;
	}

	set_groupIndex(_value)
	{
		//If groupIndex is greater than 0 and the element is a checkbox, it is
		//treated like a radio box
		this.groupIndex = _value;
	}

	set_enabled (_value)
	{
		this.enabled = _value;
	}

	set_onClick (_value)
	{
		this.onClick = _value;
	}

	set_iconUrl (_value)
	{
		this.iconUrl = _value;
	}

	set_default (_value)
	{
		this["default"] = _value;
	}

	set_data (_value)
	{
		this.data = _value;
	}

	set_hint (_value)
	{
		this.hint = _value;
	}

	set_shortcutCaption (_value)
	{
		this.shortcutCaption = _value;
	}
}