/**
 * EGroupware eTemplate2 - WidgetWithSelectMixin
 *
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @package api
 * @link https://www.egroupware.org
 * @author Nathan Gray
 */

import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {StaticOptions} from "./StaticOptions";
import {dedupeMixin, html, PropertyValues, render, repeat, TemplateResult} from "@lion/core";
import {et2_readAttrWithDefault} from "../et2_core_xml";
import {find_select_options, SelectOption} from "./FindSelectOptions";

/**
 * Base class for things that do selectbox type behaviour, to avoid putting too much or copying into read-only
 * selectboxes, also for common handling of properties for more special selectboxes.
 *
 * As with most other widgets that extend Lion components, do not override render().
 * To extend this mixin, override:
 * - _optionTargetNode(): Return the HTMLElement where the "options" go.
 * - _optionTemplate(option:SelectOption): Renders the option.  To use a special widget, use its tag in render.
 * Select option:
 * ```js
 * 		return html`
 *             <option value="${option.value}" title="${option.title}" ?selected=${option.value == this.modelValue}>
 *                 ${option.label}
 *             </option>`;
 * ```
 *
 *
 * or pass it off to a different WebComponent:
 *
 * ```js
 * _optionTemplate(option:SelectOption) : TemplateResult
 * {
 *     return html`
 *           <special-option-tag .value=${option}></special-option-tag>`;
 * }
 * ```
 *
 * Optionally, you can override:
 * - _emptyLabelTemplate(): How to render the empty label
 * - slots(): Most Lion components have an input slot where the <input> tag is created.
 * You can specify something else, or return {} to do your own thing.  This is a little more complicated.  You should
 * also override _inputGroupInputTemplate() to do what you normally would in render().
 *
 *
 * Technical note:
 * LionSelect (and any other LionField) use slots to wrap a real DOM node.  ET2 doesn't expect this,
 * so we have to create the input node (via slots()) and respect that it is _external_ to the Web Component.
 * This complicates things like adding the options, since we can't just override _inputGroupInputTemplate()
 * and include them when rendering - the parent expects to find the <select> added via a slot, render() would
 * put it inside the shadowDOM.  That's fine, but then it doesn't get created until render(), and the parent
 * (LionField) can't find it when it looks for it before then.
 *
 */
export const Et2widgetWithSelectMixin = dedupeMixin((superclass) =>
{
	class Et2WidgetWithSelect extends Et2InputWidget(superclass)
	{
		static get properties()
		{
			return {
				...super.properties,
				/**
				 * Textual label for first row, eg: 'All' or 'None'.  It's value will be ''
				 */
				empty_label: String,

				/**
				 * Select box options
				 *
				 * Will be found automatically based on ID and type, or can be set explicitly in the template using
				 * <option/> children, or using widget.select_options = SelectOption[]
				 */
				select_options: Object,
			}
		}

		constructor()
		{
			super();

			this.__select_options = <StaticOptions[]>[];
		}

		/** @param {import('@lion/core').PropertyValues } changedProperties */
		updated(changedProperties : PropertyValues)
		{
			super.updated(changedProperties);

			// If the ID changed (or was just set) find the select options
			if(changedProperties.has("id"))
			{
				const options = find_select_options(this);
				if (options.length) this.select_options = options;
			}

			// Add in actual option tags to the DOM based on the new select_options
			if(changedProperties.has('select_options'))
			{
				// Add in options as children to the target node
				if(this._optionTargetNode)
				{
					render(html`${this._emptyLabelTemplate()}
                            ${repeat(<SelectOption[]>this.select_options, (option : SelectOption) => option.value, this._optionTemplate.bind(this))}`,
						this._optionTargetNode
					);
				}
			}
		}

		/**
		 * Overwritten as sometimes called before this._inputNode is available
		 *
		 * @param {*} v - modelValue: can be an Object, Number, String depending on the
		 * input type(date, number, email etc)
		 * @returns {string} formattedValue
		 */
		formatter(v)
		{
			if (!this._inputNode)
			{
				return v;
			}
			return super.formatter(v);
		}

		set_value(val)
		{
			let oldValue = this.modelValue;

			// Make sure it's a string
			val = "" + val;

			this.modelValue = val
			this.requestUpdate("value", oldValue);
		}

		/**
		 * Set the select options
		 *
		 * @param new_options
		 */
		set select_options(new_options : SelectOption[] | { [key : string] : string })
		{
			const old_options = this.__select_options;
			if(!Array.isArray(new_options))
			{
				let fixed_options = [];
				for(let key in new_options)
				{
					fixed_options.push({value: key, label: new_options[key]});
				}
				this.__select_options = fixed_options;
			}
			else
			{
				this.__select_options = new_options;
			}
			this.requestUpdate("select_options", old_options);
		}

		/**
		 * Set select options
		 *
		 * @deprecated assign to select_options
		 * @param new_options
		 */
		set_select_options(new_options : SelectOption[] | { [key : string] : string })
		{
			this.select_options = new_options;
		}

		get select_options()
		{
			return this.__select_options;
		}

		/**
		 * Get the node where we're putting the options
		 *
		 * If this were a normal selectbox, this would be just the <select> tag (this._inputNode) but in a more
		 * complicated widget, this could be anything.
		 *
		 * @overridable
		 * @returns {HTMLElement}
		 */
		get _optionTargetNode() : HTMLElement
		{
			return <HTMLElement><unknown>this;
		}

		/**
		 * Render the "empty label", used when the selectbox does not currently have a value
		 *
		 * @overridable
		 * @returns {TemplateResult}
		 */
		_emptyLabelTemplate() : TemplateResult
		{
			return html`${this.empty_label}`;
		}

		/**
		 * Render a single option
		 *
		 * Override this method to specify how to render each option.
		 * In a normal selectbox, this would be something like:
		 *```
		 * <option value="${option.value}" title="${option.title}" ?selected=${option.value == this.modelValue}>
		 *     ${option.label}
		 * </option>`;
		 * ```
		 * but you can do whatever you need.  To use a different WebComponent, just use its tag instead of "option".
		 * We should even be able to pass the whole SelectOption across
		 * ```
		 * <special-option .value=${option}></special-option>
		 * ```
		 *
		 * @overridable
		 * @param {SelectOption} option
		 * @returns {TemplateResult}
		 */
		_optionTemplate(option : SelectOption) : TemplateResult
		{
			return html`
                <span>Override _optionTemplate(). ${option.value} => ${option.label}</span>`;
		}

		/**
		 * Load extra stuff from the template node.  In particular, we're looking for any <option/> tags added.
		 *
		 * @param {Element} _node
		 */
		loadFromXML(_node : Element)
		{
			// Read the option-tags
			let options = _node.querySelectorAll("option");
			let new_options = [];
			for(let i = 0; i < options.length; i++)
			{
				new_options.push({
					value: et2_readAttrWithDefault(options[i], "value", options[i].textContent),
					// allow options to contain multiple translated sub-strings eg: {Firstname}.{Lastname}
					label: options[i].textContent.replace(/{([^}]+)}/g, function(str, p1)
					{
						return this.egw().lang(p1);
					}),
					title: et2_readAttrWithDefault(options[i], "title", "")
				});
			}

			if(this.id)
			{
				new_options = find_select_options(this, {}, new_options);
			}
			if (new_options.length)
			{
				this.select_options = new_options;
			}
		}
	}
	return Et2WidgetWithSelect;
});