/**
 * Vvveb
 *
 * Copyright (C) 2021  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * https://github.com/givanz/Vvveb
 */
 
let Input = {
	
	init: function(name) {
	},


	onChange: function(event, node, input) {
		if (event && event.target) {
			const e = new CustomEvent('propertyChange', { detail: {value : input.value ?? this.value, input: this, origEvent:event} });
			event.currentTarget.dispatchEvent(e);
			//event.data.element.trigger('propertyChange', [this.value, this, event]);
		}
	},

	renderTemplate: function(name, data) {
		return tmpl("vvveb-input-" + name, data);
	},

	setValue: function(value) {
		if (this.element[0] && value) {
			let input = this.element[0].querySelector('input');
		
			if (input) { 
				input.value = value;
			}
		}
	},
	
	render: function(name, data) {
		let html = this.renderTemplate(name, data);
		this.element = generateElements(html);
		
		//bind events
		if (this.events)
		for (let i in this.events) {
			ev = this.events[i][0];
			fun = this[ this.events[i][1] ];
			el = this.events[i][2];
		
			//this.element[0].querySelector(el).addEventListener(ev, function (ev, el, fun, target, event) {
			this.element[0].addEventListener(ev, function (ev, el, fun, target, event) {
			  if (event.target.closest(el)) {
				  //target, event, element, input
				return fun.call(event.target, event, target, this);
			  }
			}.bind(this, ev, el, fun, this.element[0]));		
		}

		return this.element[0];
	}
};

let TextInput = { ...Input, ...{

    events: [
	//event, listener, child element
        ["focusout", "onChange", "input"],
	 ],
	
	init: function(data) {
		return this.render("textinput", data);
	},
  }
}

let TextareaInput = { ...Input, ...{

    events: [
        ["keyup", "onChange", "textarea"],
	 ],
	
	setValue: function(value) {
		if (this.element[0] && value) {
			let input = this.element[0].querySelector('textarea');
		
			if (input) { 
				input.value = value;
			}
		}
	},
	
	init: function(data) {
		return this.render("textareainput", data);
	},
  }	
}

let CheckboxInput = { ...Input, ...{

    events: [
        ["change", "onCheck", "input"],
	 ],
	
	onCheck: function(event, node, input) {
		input.value = this.checked;
		return input.onChange.call(this, event, node, input);
	},

	setValue: function(value) {
		if (this.element[0]) {
			let input = this.element[0].querySelector('input');

			if (input) { 
				if (value) {
					input.checked = true;
				} else {
					input.checked = false;
				}
			}
		}
	},
	
	init: function(data) {
		return this.render("checkboxinput", data);
	},
  }
}

let SelectInput = { ...Input, ...{
	
    events: [
        ["change", "onChange", "select"],
	 ],
	

	setValue: function(value) {
		if (this.element[0] && value) {
			let input = this.element[0].querySelector('select');
		
			if (input) { 
				input.value = value;
			}
		}
	},
	
	init: function(data) {
		return this.render("select", data);
	},
  }
}

let IconSelectInput = { ...Input, ...{
	
    events: [
        ["change", "onChange", "select"],
	 ],
	

	setValue: function(value) {
		if (this.element[0] && value) {
			let input = this.element[0].querySelector('select');
		
			if (input) { 
				input.value = value;
			}
		}
	},
	
	init: function(data) {
		return this.render("icon-select", data);
	},
  }
}

let HtmlListSelectInput = { ...Input, ...{
	
	data:{},
	cache:{},
	
    events: [
        //["click", "onChange", "li"],
        ["change", "onListChange", "select"],
        ["keyup", "searchElement", "input.search"],
        ["click", "clearSearch", "button.clear-backspace"],
	 ],
	
	clearSearch : function(event, element, input) {
		let search = element.querySelector("input.search");
		if (search) {
			search.value = "";
			input.searchElement(event, element, input);
		}
		
		search.dispatchEvent(new KeyboardEvent("keyup", {
			bubbles: true,
			cancelable: true,
		}));
	},


	searchElement : function(event, element, input) {
		searchText = this.value;
		
		delay(() => {
			element.querySelectorAll("li").forEach((el, i) => {
				
				if (!searchText || el.title.indexOf(searchText) > -1) { 
					el.style.display = '';
				} else {
					el.style.display = 'none';
				}
			});
			
		}, 500);
	},	

	onElementClick: function(event, element, input) {
		let data = input.data;
		let svg = this.closest(data.insertElement);
		let value = svg.outerHTML ?? "<svg></svg>";
		input.value = value;
		let ret = input.onChange.call(this, event, element, input);
		
		return element;
	},
	
	onListChange: function(event, element, input) {
		let url = input.data.url.replace('{value}', this.value);
		let elements = element.querySelector(".elements");
		
		elements.innerHTML = `<div class="p-4"><div class="spinner-border spinner-border-sm" role="status">
		  <span class="visually-hidden">Loading...</span>
		</div> Loading...</div>`;
		
		//cache ajax requests
		if (input.cache[url] != undefined) {
			elements.innerHTML = input.cache[url];
		} else {
			fetch(url)
			.then((response) => {
				if (!response.ok) { throw new Error(response) }
				return response.text()
			})
			.then((text) => {
					input.cache[url] = text;
					elements.innerHTML = text;
			})
			.catch(error => {
				console.log(error.statusText);
				displayToast("danger", "Error", "Error loading list");
			});
		}
	},
	
	setValue: function(value) {
		let select = this.element[0].querySelector("select");
		if (value && select) {
			select.value = value;
		}
	},
	
	init: function(data) {
		this.data = data;
		this.events.push(["click", "onElementClick", data.clickElement]);
		let template = this.render("html-list-select", data);
		let select = template.querySelector("select");
		this.onListChange.call(select, new Event('change'), template, this);
		return template;
	},
  }
}

let LinkInput = { ...TextInput, ...{

    events: [
        ["change", "onChange", "input"],
	 ],
	/*
	setValue: function(value) {
		//value = value.replace(/(?<!\/)www\./, 'https://www.');
		this.element.querySelector('input').value = value;
	},
	*/
	init: function(data) {
		return this.render("textinput", data);
	},
  }
}

let DateInput = { ...TextInput, ...{

    events: [
        ["change", "onChange", "input"],
	 ],
	
	init: function(data) {
		return this.render("dateinput", data);
	},
  }
}

let RangeInput = { ...Input, ...{

    events: [
        ["change", "onRangeChange", "input"],
	 ],
	 
	onRangeChange: function(event, node, input) {
		this.parentNode.querySelector('input[type=number]').value = this.value;
		this.parentNode.querySelector('input[type=range]').value = this.value;
		return input.onChange.call(this, event, node, input);
	},	 

	setValue: function(value) {
		this.element[0].querySelector('input[type=number]').value = value;
		this.element[0].querySelector('input[type=range]').value = value;
	},
	
	init: function(data) {
		return this.render("rangeinput", data);
	},
  }
}

let NumberInput = { ...Input, ...{

    events: [
        ["change", "onChange", "input"],
	 ],
	
	init: function(data) {
		return this.render("numberinput", data);
	},
  }
}

let CssUnitInput = { ...Input, ...{

	number:0,
	unit:"px",

    events: [
        ["change", "onInputChange", "select"],
        ["change", "onInputChange", "input"],
        ["keyup", "onInputChange", "input"],
	 ],
		
	onInputChange: function(event, node, input) {
		if (node) {
			let number = node.querySelector("input").value;
			let unit = node.querySelector("select").value;

			if (this.value != "") input[this.name] = this.value;// this.name = unit or number	
			if (unit == "") unit = "px";//if unit is not set use default px
		
			let value = "";	
			if (unit == "auto")  {
				node.classList.add("auto");
				value = unit;
			}
			else  {
				node.classList.remove("auto");
				value = number + (unit ? unit : "");
			}
			
			input.value = value;

			return input.onChange.call(this, event, node, input);
		}
	},
	
	setValue: function(value) {
		if (value && this.element) {
			let element = this.element[0];
			this.number = parseFloat(value);
			this.unit = value.replace(this.number, '').trim();
			
			if (this.unit == "auto") element.classList.add("auto");

			element.querySelector("input[type=number]").value = this.number;
			element.querySelector("select").value = this.unit;
		}
	},
	
	init: function(data) {
		return this.render("cssunitinput", data);
	},
  }
}

let ColorInput = { ...Input, ...{

	 //html5 color input only supports setting values as hex colors even if the picker returns only rgb
	 rgb2hex: function(value) {
		 if (value) {
			 value = value.trim();
			 
			 if (rgb = value.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i)) {
				 
				 return (rgb && rgb.length === 4) ? "#" +
				  ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) +
				  ("0" + parseInt(rgb[2],10).toString(16)).slice(-2) +
				  ("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : rgb;
			 }
		}
		 
		 return value;
	},

    events: [
        ["change", "onChange", "input"],
	 ],

	setValue: function(value) {
		if (this.element[0] && value) {
			let input = this.element[0].querySelector('input');
		
			if (input) { 
				input.value = this.rgb2hex(value);
			}
		}
	},
	
	init: function(data) {
		//if no palette provided use default
		if (!data.palette) {
			data.palette = Vvveb.ColorPalette.getAll();
		}
		
		return this.render("colorinput", data);
	},
  }
}

let ImageInput = { ...Input, ...{

    events: [
        ["focusout", "onChange", "input[type=text]"],
        ["change", "onUpload", "input[type=file]"],
	 ],

	setValue: function(value) {

		//don't set blob value to avoid slowing down the page		
		if (value.indexOf("data:image") == -1) {
			element.querySelector('input[type="text"]').value = value;
		}
	},

	onUpload: function(event, node) {

		if (this.files && this.files[0]) {
            let reader = new FileReader();
            reader.onload = imageIsLoaded;
            reader.readAsDataURL(this.files[0]);
            //reader.readAsBinaryString(this.files[0]);
            file = this.files[0];
        }

		function imageIsLoaded(e) {
				
				image = e.target.result;
				
				event.data.element.trigger('propertyChange', [image, this]);
				
				//return;//remove this line to enable php upload

				let formData = new FormData();
				formData.append("file", file);
    
				$.ajax({
					type: "POST",
					url: 'upload.php',//set your server side upload script url
					data: formData,
					processData: false,
					contentType: false,
					success: function (data) {
						console.log("File uploaded at: ", data);
						
						//if image is succesfully uploaded set image url
						event.data.element.trigger('propertyChange', [data, this]);
						
						//update src input
						event.data.element.querySelector('input[type="text"]').value = data;
					},
					error: function (data) {
						alert(data.responseText);
					}
				});		
		}
	}		
 }	
}

let FileUploadInput = { ...TextInput, ...{

    events: [
        ["focusout", "onChange", "input"],
	 ],

	init: function(data) {
		return this.render("textinput", data);
	},
  }
}

let RadioInput = { ...Input, ...{

	events: [
        ["change", "onChange", "input"],
	 ],

	setValue: function(value) {
		if (this.element[0] && value) {
			let input = this.element[0].querySelector('input');
		
			if (input) { 
				if (value == input.value) {
					input.setAttribute("checked", "true");
					input.checked = true;
				} else {
					input.checked = false;
					input.removeAttribute("checked");
				}
				
			}
		}
	},
	
	init: function(data) {
		return this.render("radioinput", data);
	},
  }
}

let RadioButtonInput = { ...RadioInput, ...{

	setValue: function(value) {
		if (this.element[0] && value) {
			let inputs = this.element[0].querySelectorAll('input');
			inputs.forEach((el, i) => {
				if (value == el.value) {
					selected = el;
				} else {
					el.checked = false;
					el.removeAttribute("checked");
				}		
			});

			selected.checked = true;
			selected.setAttribute("checked", "checked");
		}
	},

	init: function(data) {
		return this.render("radiobuttoninput", data);
	},
  }
}

let ToggleInput = { ...Input, ...{
	events: [
        ["change", "onToggleChange", "input"],
	 ],

	onToggleChange: function(event, node, input) {
		input.value = this.checked ? this.getAttribute("data-value-on") : this.getAttribute("data-value-off");
		return input.onChange.call(this, event, node, input);
	},

	setValue: function(value) {
		if (this.element[0]) {
			let input = this.element[0].querySelector('input');

			if (input) { 
				if (value == input.getAttribute("data-value-on")) {
					input.checked = true;
					input.setAttribute("checked", true);
				} else {
					input.checked = false;
					input.removeAttribute("checked");
				}
			}
		}
	},
	
	init: function(data) {
		return this.render("toggle", data);
	},
  }
}

let ValueTextInput = { ...TextInput, ...{

    events: [
        ["focusout", "onChange", "input"],
	 ],
	
	init: function(data) {
		return this.render("textinput", data);
	},
  }
}

let GridLayoutInput = { ...TextInput, ...{

    events: [
        ["focusout", "onChange", "input"],
	 ],
	
	init: function(data) {
		return this.render("textinput", data);
	},
  }
}

let ProductsInput = { ...TextInput, ...{

    events: [
        ["focusout", "onChange", "input"],
	 ],
	
	init: function(data) {
		return this.render("textinput", data);
	},
  }
}

let GridInput = { ...Input, ...{
	

    events: [
        ["change", "onChange", "select" /*'select'*/],
        ["click", "onChange", "button" /*'select'*/],
	 ],
	

	setValue: function(value) {
		if (this.element[0] && value) {
			let input = this.element[0].querySelector('select');
		
			if (input) { 
				input.value = value;
				input.querySelector("option[selected]").selected = true;
			}
		}
	},
	
	init: function(data) {
		return this.render("grid", data);
	},
  }
}

let TextValueInput = { ...Input, ...{
	

    events: [
        ["focusout", "onChange", "input"],
	    ["click", "onChange", "button" /*'select'*/],
	 ],
	
	setValue: function(value) {
		return false;
	},
		
	init: function(data) {
		return this.render("textvalue", data);
	},
  }
}

let ButtonInput = { ...Input, ...{

    events: [
        ["click", "onChange", "button" /*'select'*/],
	 ],
	

	setValue: function(value) {
		if (this.element[0] && value) {
			let input = this.element[0].querySelector('button');
		
			if (input) { 
				input.value = value;
			}
		}		
	},
	
	init: function(data) {
		return this.render("button", data);
	},
  }
}

let SectionInput = { ...Input, ...{

    events: [
        //["click", "onChange", "button" /*'select'*/],
	 ],
	

	setValue: function(value) {
		return false;
	},
	
	init: function(data) {
		return this.render("sectioninput", data);
	},
  }
}

let ListInput = { ...Input, ...{
	
    events: [
        ["change", "onChange", "select"],
        ["click", "remove", ".delete-btn"],
        ["click", "add", ".btn-new"],
        ["click", "select", ".section-item"],
	 ],
	

	remove: function(event, node, input) {
		let sectionItem = this.closest(".section-item");
		let index = [...sectionItem.parentNode.children].indexOf(sectionItem);//sectionItem.index();
		let data = input.data;
		
		if (data.removeElement) {
			let container = input.node;
			if (data.container) {
				container.querySelector(data.container);
			}
			container.querySelector(data.selector + ":nth-child(" + (index + 1) + ")").remove();
		}
		sectionItem.remove();
		
		event.action = "remove";
		event.index = index;
		input.onChange(event, node, input, this);
		event.preventDefault();
		return false;
	},

	add: function(event, node, input) {
		let newElement = input.data.newElement ?? false;
		if (newElement) {
			let container = input.node;
			if (data.container) {
				container.querySelector(data.container);
			}			
			container.append(generateElements(newElement)[0]);
		}
		
		event.action = "add";
		input.onChange(event, node, input, this);
		return false;
	},	
	
	select: function(event, node, input) {
		let sectionItem = this.closest(".section-item");
		if (sectionItem.parentNode) {
			let index = [...sectionItem.parentNode.children].indexOf(sectionItem);//sectionItem.index();
			
			event.action = "select";
			event.index = index;
			input.onChange(event, node, input, this);
		}
		return false;
	},

	setValue: function(value) {
	},

	init: function(data, node) {
		this.component = data.component;
		this.selector = data.selector;
		this.node = node;

		let elements = this.node.querySelectorAll(data.container + " " + this.selector);
		let options = [];
		
		elements.forEach(function (e, i) {
			let element = e;
			if (data.nameElement) {
				element = element.querySelector(data.nameElement);
			}
			let name = (data.name = "text" ? element.textContent.substr(0, 15) : element.id);
			options.push({
				name: name,
				type: (data.prefix ?? "") + (i + 1) + (data.suffix ?? ""),
			});
		});

		data.options = options;
		data.elements = elements;
		this.data = data;

		return this.render("listinput", data);
	},
  }
}

let AutocompleteInput = { ...Input, ...{

    events: [
        ["autocomplete.change", "onAutocompleteChange", "input"],
	 ],

	onAutocompleteChange: function(event, node, input) {
		input.value = event.detail.value;
		return input.onChange.call(this, event, node, input);
	},

	setValue: function(value) {
		if (this.element && value) {
			let input = this.element.querySelectorAll('input').forEach(e => e.value = value);
		}
	},	

	init: function(data) {
		
		this.element = this.render("textinput", data);
		
		let autocomplete = new _AutocompleteInput(this.element.querySelector("input"), data);
		
		return this.element;
	}
  }
}

let AutocompleteList = { ...Input, ...{

    events: [
        ["autocompletelist.change", "onAutocompleteChange", "input"],
	 ],

	onAutocompleteChange: function(event, node, input) {
		input.value = event.detail;
		return input.onChange.call(this, event, node, input);
	},

	setValue: function(value) {
		if (this.element && value) {
			let input = this.element.querySelectorAll('input').forEach(e => e.value = value);
		}		
	},

	init: function(data) {
		
		this.element = this.render("textinput", data);
		
		let autocomplete = new _AutocompleteList(this.element.querySelector("input"), data);
		
		return this.element;
	}
  }
}

let TagsInput = { ...Input, ...{

    events: [
        ["tagsinput.change", "onTagsInputChange", "input"],
	 ],

	onTagsInputChange: function(event, node, input) {
		let tags = "";
		for (const tag in event.detail[0]) {
			if (tags) tags += " ";
			tags += tag;
		}
		input.value = tags;
		return input.onChange.call(this, event, node, input);
	},

	setValue: function(value) {
		if (this.autocomplete && value) {
			this.autocomplete.tagsInput.setValue(value.split(" "));
		}
	},

	init: function(data) {
		
		this.element = this.render("tagsinput", data);
		this.autocomplete = new _TagsInput(this.element.querySelector("input"), data);
		
		return this.element;
	}
  }
}


let NoticeInput = { ...Input, ...{

    events: [
	 ],
	
	init: function(data) {
		return this.render("noticeinput", data);
	},
  }
}
