import axios from "axios";
import {useToastService} from "@/components/ui/services/toast.js";
import "@/lib/array.js";
import reloadPage from "@/lib/reload_page.js";
import EventBus from "@/lib/events.js";

const eventBus = new EventBus()
window.X_VIEW_ACTION = {
		parent: "/index",     // 当前控制器为 admin.user
		// module: "admin",          // 当前模块为 admin
		// base: "https://flexcdn.cn",           // 基础路径为 /admin
		// actionParam: false        // 使用路径方式生成 URL
};
/**
 * @param {{popup:Boolean, popupState:Object}} [options]
 * @returns {{}}
 */
export const useTea = (options) => {
	const {newToast} = useToastService()
	const isPopup = (options !== null && typeof options === "object" && options.popup)

	if (isPopup && options == null) {
		console.error("'useTea()' in popup must contains a 'popupState' option")
	}

	const Tea = {}

	/**
	 * 序列化参数为可传递的字符串
	 *
	 * 代码来自jQuery：https://jquery.com/download/
	 *
	 * @param a 要序列化的参数集
	 * @param traditional
	 * @returns {*}
	 */
	Tea.serialize = (a, traditional) => {
		var prefix,
			s = [],
			add = function (key, valueOrFunction) {

				// If value is a function, invoke it and use its return value
				var value = (typeof (valueOrFunction) === "function") ?
					valueOrFunction() :
					valueOrFunction;

				s[s.length] = encodeURIComponent(key) + "=" +
					encodeURIComponent(value == null ? "" : value);
			};

		var
			rbracket = /\[]$/;

		var buildParams = function (prefix, obj, traditional, add) {
			var name;
			if (Array.isArray(obj)) {
				// Serialize array item.
				for (var i in obj) {
					if (!obj.hasOwnProperty(i)) {
						continue;
					}
					var v = obj[i];
					if (traditional || rbracket.test(prefix)) {

						// Treat each array item as a scalar.
						add(prefix, v);

					} else {

						// Item is non-scalar (array or object), encode its numeric index.
						buildParams(
							prefix + "[" + (typeof v === "object" && v != null ? i : "") + "]",
							v,
							traditional,
							add
						);
					}
				}

			} else if (!traditional && typeof (obj) === "object") {

				// Serialize object item.
				for (name in obj) {
					buildParams(prefix + "[" + name + "]", obj[name], traditional, add);
				}

			} else {

				// Serialize scalar item.
				add(prefix, obj);
			}
		};

		// If an array was passed in, assume that it is an array of form elements.
		if (Array.isArray(a)) {
			// Serialize the form elements
			for (let key in a) {
				if (!a.hasOwnProperty(key)) {
					continue;
				}
				add(key, a[key]);
			}

		} else {

			// If traditional, encode the "old" way (the way 1.3.2 or older
			// did it), otherwise encode params recursively.
			for (prefix in a) {
				if (!a.hasOwnProperty(prefix)) {
					continue;
				}
				buildParams(prefix, a[prefix], traditional, add);
			}
		}

		// Return the resulting serialization
		return s.join("&");
	};

	/**
	 * 取得Action对应的URL
	 *
	 * @param {string} action
	 * @param {Object} [params] 参数
	 * @param {boolean} [hashParams] 锚点参数
	 * @returns {string}
	 */
	Tea.url = (action, params, hashParams) => {
		// $
		if (action == "$") {
			if (isPopup) {
				action = options.popupState.action.path
			} else {
				action = window.location.pathname;
			}
		}

		let config = {}
		if (isPopup) {
			config = options.popupState.action
		} else {
			if (window.X_VIEW_ACTION === null || typeof window.X_VIEW_ACTION !== "object") {
				throw new Error("Tea: 'window.X_VIEW_ACTION' must be present in the page")
			}

			config = window.X_VIEW_ACTION
		}

		let controller = config.parent
		if (typeof controller !== "string") {
			controller = ""
		}

		let module = config.module
		if (typeof module !== "string") {
			module = ""
		}

		let base = config.base
		if (typeof base !== "string") {
			base = ""
		}

		let actionParam = config.actionParam
		if (typeof actionParam !== "boolean") {
			actionParam = false
		}

		let url;
		if (action.match(/\//)) {//支持URL
			url = action;

			if (typeof (params) === "object") {
				var query = Tea.serialize(params);
				if (query.length > 0) {
					url += "?" + query;
				}
			}
			if (!url.match(/^(http|https|ftp):/i)) {
				url = base + ((url.substring(0, 1) === "/") ? "" : "/") + url;
			}
		} else {
			if (action.substring(0, 2) === "..") {
				var pos = controller.lastIndexOf(".");
				if (pos === -1) {
					action = action.substring(2);
				} else {
					action = controller.substring(0, pos) + action.substring(1);
				}
				if (module !== "") {
					action = "@" + module + "." + action;
				}
			} else if (action.substring(0, 1) === ".") {
				action = controller + action;
				if (module !== "") {
					action = "@" + module + "." + action;
				}
			} else if (module !== "") {
				if (action === "@") {
					action = "@" + module;
				} else {
					action = action.replace("@.", "@" + module + ".");
				}
			}
			action = action.replace(/\.$/, "");
			if (actionParam) {
				var path = action.replace(/[.\/]+/g, "/");
				if (path.substring(0, 1) !== "/") {
					path = "/" + path;
				}
				url = base + "?__ACTION__=" + path;
			} else {
				url = base + "/" + action.replace(/[.\/]+/g, "/").replace(/^\//, "");

				// remove last "/"
				url = url.replace(/\/+$/, "")
			}
			if (typeof (params) === "object") {
				params = Tea.serialize(params);
				if (params.length > 0) {
					if (url.indexOf("?") === -1) {
						url += "?" + params;
					} else {
						url += "&" + params;
					}
				}
			}
			if (typeof (hashParams) === "string") {
				url += "#" + hashParams;
			} else if (typeof (hashParams) === "object") {
				url += "#" + Tea.serialize(hashParams);
			}
		}

		return url;
	};

	/**
	 * 跳转
	 *
	 * @param action 要跳转到的action
	 * @param params 附带的参数
	 * @param hash 附带的锚点参数
	 */
	Tea.go = (action, params, hash) => {
		var url = Tea.url(action, params)
		if (hash && hash.length > 0) {
			url += "#" + hash
		}

		window.location.href = url
	}

	/**
	 * 延时执行
	 *
	 * @param fn 要执行的函数
	 * @param ms 延时长度
	 */
	Tea.delay = (fn, ms) => {
		if (typeof (ms) === "undefined") {
			ms = 10
		}
		setTimeout(() => {
			fn.call(Tea.CurrentContext)
		}, ms)
	}

	/**
	 * 定义Action对象
	 *
	 * @param action Action
	 * @param params 参数集
	 * @constructor
	 */
	Tea.Action = class {
		_action = ""
		_params = {}
		_successFn = null
		_failFn = null
		_errorFn = null
		_doneFn = null
		_method = "POST"
		_timeout = 30
		_delay = 0 // TODO 检查是否使用此变量，如没使用，则删除
		_progressFn = null
		_refresh = false
		_alertFn = null
		_headers = {}

		constructor(action, params) {
			this._action = action
			this._params = params
		}

		params(params) {
			this._params = {...params}

			for (let k in this._params) {
				this._params[k] = this.fixParam(this._params[k])
			}

			return this;
		}

		fixParam(obj) {
			if (obj === null || obj === undefined) {
				return ""
			}

			switch (typeof obj) {
				case "string":
					return obj
				case "number":
					return obj
				case "bigint":
					return obj
				case "undefined":
					return ""
				case "boolean":
					return obj ? 1 : 0
				case "object":
					if (obj instanceof Array) {
						// [File, ...]
						if (obj.length > 0 && obj[0] instanceof File) {
							return obj
						}

						// [{...}, ...]
						if (!obj.$find((k, v) => {
							if (v === null) {
								return false
							}
							return typeof v === "object"
						})) {
							obj.forEach((v, k) => {
								obj[k] = this.fixParam(v)
							})

							return obj
						}
					}

					// FileList | File
					if (obj instanceof FileList || obj instanceof File) {
						return obj
					}

					return JSON.stringify(obj)
			}

			return ""
		}

		form(form) {
			if (form instanceof FormData) {
				this._params = form
			} else {
				this._params = new FormData(form)
			}
			return this;
		}

		header(name, value) {
			this._headers[name] = value
			return this
		}

		success(successFn) {
			this._successFn = successFn
			return this
		}

		fail(failFn) {
			this._failFn = failFn;
			return this
		}

		error(errorFn) {
			this._errorFn = errorFn
			return this
		}

		done(doneFn) {
			this._doneFn = doneFn
			return this
		}

		/**
		 *
		 * @param alertFn {function(message:string, nextFn:function, success:boolean)}
		 * @returns {this}
		 */
		alert(alertFn) {
			this._alertFn = alertFn
			return this
		}

		timeout(timeout) {
			this._timeout = timeout;
			return this
		}

		delay(delay) {
			this._delay = delay;
			return this;
		}

		progress(progressFn) {
			this._progressFn = progressFn;
			return this;
		}

		refresh() {
			this._refresh = true
			return this
		}

		post() {
			this._method = "POST"
			setTimeout(() => {
				this._post.call(this)
			})
			return this;
		}

		get() {
			this._method = "GET"
			setTimeout(() => {
				this._post.call(this)
			})
			return this
		}

		_post() {
			var params = this._params;

			// 参数配置：https://github.com/axios/axios#request-config
			var config = {
				method: this._method,
				url: Tea.url(this._action),
				timeout: this._timeout * 1000,
				withCredentials: true,
				headers: {
					"X-Requested-With": "XMLHttpRequest",
					...this._headers
				}
			};

			if (this._progressFn != null && typeof (this._progressFn) == "function") {
				config["onUploadProgress"] = (event) => {
					this._progressFn.call(Tea.CurrentContext, event.loaded, event.total, event);
				};
			}

			if (this._method === "GET") {
				config["params"] = params;
			} else {
				if (typeof (params) === "object" && params instanceof FormData) {
					Array.from(params).forEach((object, name) => {
						if (object != null && object instanceof File) {
							if (object.size === 0 && object.name.length === 0) {
								params.delete(name);
							}
						}
					})
					config["data"] = params;
				} else {
					var formData = new FormData();
					for (var key in params) {
						if (!params.hasOwnProperty(key)) {
							continue;
						}

						if (params[key] == null) {
							formData.append(key, "");
						} else {
							if (typeof (params[key]) == "object" && (params[key] instanceof Array)) {
								for (var i = 0; i < params[key].length; i++) {
									formData.append(key, params[key][i]);
								}
							} else {
								formData.append(key, params[key]);
							}
						}
					}

					config["data"] = formData;
				}
			}

			axios(config)
				.then((response) => {
					response = response.data

					setTimeout(() => {
						if (typeof (response) !== "object" || typeof (response.code) === "undefined") {
							if (typeof (this._errorFn) === "function") {
								this._errorFn.call(Tea.CurrentContext, {message: typeof (response === "string") ? "invalid response data from server (expected a json) '" + ((response.length > 500) ? response.substring(0, 500) + " ..." : response) + "'" : "invalid response data from server (expected a json)"});
							}
							return
						}

						const code = parseInt(response.code, 10);
						if (code === 200) {
							if (typeof (this._successFn) === "function") {
								var result = this._successFn.call(Tea.CurrentContext, response);
								if (typeof (result) === "boolean" && !result) {
									return;
								}
							}

							if (response.message != null && response.message.length > 0) {
								if (typeof this._alertFn === "function") {
									this._alertFn(response.message, null, true)
								} else {
									Tea.alert(response.message, null, true)
								}
							}

							if (response.next != null && typeof (response.next) === "object") {
								if (response.next.action === "*refresh") {
									Tea.reload();
								} else {
									Tea.go(response.next.action, response.next.params, response.next.hash);
								}
							}

							// 自动刷新
							if (this._refresh) {
								Tea.reload();
							}
						} else {
							if (typeof (this._failFn) === "function") {
								this._failFn.call(Tea.CurrentContext, response);
							} else {
								Tea.failResponse(response, this._alertFn)
							}
						}
					});
				})
				.catch((error) => {
					if (typeof (this._errorFn) === "function") {
						this._errorFn.call(Tea.CurrentContext, {message: error.message})
					} else {
						console.error(error)
					}
				})
				.then(() => {
					// console.log("done");
					if (typeof (this._doneFn) == "function") {
						this._doneFn.call(Tea.CurrentContext, {})
					}
				})
		}
	}

	/**
	 * 取得Action对象
	 *
	 * @param action string
	 * @returns {Tea.Action}
	 */
	Tea.action = (action) => {
		return new Tea.Action(action)
	}

	var teaEventListeners = {}; // element => { event => [ callback1, ... ] }
	function TeaElementObjects(elements) {
		var that = this;

		elements.$each(function (index, element) {
			that[index] = element;
		});

		this.bind = function (event, listener) {
			elements.$each(function (_, element) {
				if (typeof (teaEventListeners[element]) == "undefined") {
					teaEventListeners[element] = {};
				}
				if (typeof (teaEventListeners[element][event]) == "undefined") {
					teaEventListeners[element][event] = [];
				}
				teaEventListeners[element][event].push(listener);
				element.addEventListener(event, listener)
			});

			return this;
		};

		this.unbind = function (event) {
			elements.$each(function (_, element) {
				if (typeof (teaEventListeners[element]) == "undefined") {
					return;
				}
				if (typeof (teaEventListeners[element][event]) == "undefined") {
					return;
				}
				teaEventListeners[element][event].$each(function (_, listener) {
					element.removeEventListener(event, listener);
				});
				teaEventListeners[element][event] = [];
				var hasListeners = false;
				for (var k in teaEventListeners[element]) {
					if (!teaEventListeners[element].hasOwnProperty(k)) {
						continue;
					}

					if (teaEventListeners[element][k] instanceof Array && teaEventListeners[element][k].length > 0) {
						hasListeners = true;
					}
				}

				if (!hasListeners) {
					delete (teaEventListeners[element]);
				}
			});

			return this;
		};

		this.first = function () {
			var first = elements.$first();
			if (first != null) {
				return Tea.element(first);
			}
			return new TeaElementObjects([]);
		};

		this.last = function () {
			var last = elements.$last();
			if (last != null) {
				return Tea.element(last);
			}
			return new TeaElementObjects([]);
		};

		this.attrs = function () {
			var first = this.first();
			if (first.length === 0) {
				return {};
			}

			var attrs = {};
			var node = first[0];
			for (var i = 0; i < node.attributes.length; i++) {
				var attr = node.attributes[i];
				attrs[attr.name] = attr.value;
			}
			return attrs;
		};

		this.attr = function (name, value) {
			if (arguments.length === 0) {
				return "";
			}

			if (arguments.length === 1) {
				var attrs = this.attrs();
				if (typeof (attrs[name]) !== "undefined") {
					return attrs[name];
				}
				return "";
			}

			var first = this.first();
			if (first.length > 0) {
				first[0].setAttribute(name, value);
			}

			return this;
		};

		this.tagName = function () {
			var first = this.first();
			if (first.length === 0) {
				return "";
			}
			return first[0].tagName;
		};

		this.focus = function () {
			const first = this.first()
			if (first.length === 0) {
				return
			}

			const tagName = first[0].tagName.toUpperCase()
			if (!["INPUT", "TEXTAREA", "BUTTON", "A"].includes(tagName)) {
				const input = first[0].querySelector("input:not([type='hidden'])")
				if (input != null) {
					input.focus()
					return
				}

				const textarea = first[0].querySelector("textarea")
				if (textarea != null) {
					textarea.focus()
					return
				}

				const button = first[0].querySelector("button")
				if (button != null) {
					button.focus()
					return
				}

				// <TSelect> component
				if (first[0].className.indexOf("p-select ") > -1 && first[0].className.indexOf("p-focus") < 0) {
					first[0].className += " p-focus"
				}
			}

			first[0].focus()
		}

		this.blur = function () {
			this.each(function (k, v) {
				v.blur();
			});
		};

		this.each = function (iterator) {
			elements.$each(function (index, element) {
				iterator(index, element);
			});

			return this;
		};

		this.find = function (selector) {
			if (this.length == 0) {
				return new TeaElementObjects([]);
			}
			return Tea.element(selector, this.first()[0]);
		};

		this.hide = function () {
			this.each(function (_, element) {
				element.style.display = "none";
			});
			return this;
		};

		this.show = function () {
			this.each(function (_, element) {
				element.style.display = "block";
			});
			return this;
		};

		this.text = function () {
			if (arguments.length > 0) {
				var text = arguments[0];
				this.each(function (_, element) {
					if (typeof (element.textContent) != "undefined") {
						element.textContent = text;
					}
					if (typeof (element.innerText) != "undefined") {
						element.innerText = text;
					}
				});
				return this;
			}

			if (this.length == 0) {
				return "";
			}
			if (typeof (elements[0].textContent) == "string") {
				return elements[0].textContent;
			}
			return elements[0].innerText;
		};

		this.html = function () {
			if (arguments.length > 0) {
				var html = arguments[0];
				this.each(function (_, element) {
					element.innerHTML = html;
				});
				return this;
			}

			if (this.length == 0) {
				return "";
			}
			return elements[0].innerHTML;
		};

		this.val = function () {
			if (arguments.length > 0) {
				var value = arguments[0];
				this.each(function (_, element) {
					element.value = value;
				});
				return this;
			}

			if (this.length == 0) {
				return "";
			}
			return elements[0].value;
		};

		this.remove = function () {
			this.each(function (_, element) {
				var parent = element.parentNode;
				if (parent != null) {
					parent.removeChild(element);
				}
			});
			return this;
		};

		this.length = elements.length;
	}

	/**
	 * 获取元素
	 *
	 * @param selector 选择器
	 * @param parent 父节点
	 * @returns {*}
	 */
	Tea.element = (selector, parent) => {
		var elements = []
		if (typeof (selector) === "object" && /(function|object) \w+Element\b/.test(selector.constructor.toString())) {
			elements = [selector]
		} else if (typeof (selector) === "object" && /function TeaElementObjects/.test(TeaElementObjects.constructor.toString())) {
			return selector
		} else if (typeof (selector) === "string") {
			if (typeof (parent) === "object") {
				elements = Array.from(parent.querySelectorAll(selector))
			} else {
				elements = Array.from(document.querySelectorAll(selector))
			}
		}

		return new TeaElementObjects(elements)
	}

	/**
	 * 生成vue for用的key
	 * @returns {number}
	 */
	Tea.key = () => {
		return Math.random()
	}

	// 失败的响应处理
	Tea.failResponse = (response, alertFn) => {
		//消息提示
		if (response.message != null && response.message.length > 0) {
			const callback = () => {
				if (typeof (response.errors) === "object" && response.errors != null && response.errors.length > 0) {
					var fieldName = response.errors[0].param;
					var element = Tea.element("*[fieldname='" + fieldName + "']")
					if (element && element.length > 0) {
						element.focus()
					} else {
						element = Tea.element("*[name='" + fieldName + "']");
						if (element && element.length > 0) {
							element.focus();
						}
					}
				}
			}
			if (typeof alertFn === "function") {
				alertFn(response.message, callback, false)
			} else {
				Tea.alert(response.message, callback, false)
			}
			return;
		}

		// 错误提示
		if (typeof (response.errors) === "object" && response.errors != null && response.errors.length > 0) {
			/**
			 * errors: [
			 *  [field1, [ error1, error2, ....]
			 *  ...
			 * ]
			 * error: [ rule, message ]
			 */
			const fieldName = response.errors[0].param;
			const error = response.errors[0].messages[0];
			const callback = () => {
				let element = Tea.element("*[name='" + fieldName + "']")
				if (element && element.length > 0) {
					element.focus()
				} else {
					element = Tea.element("*[fieldname='" + fieldName + "']")
					if (element && element.length > 0) {
						element.focus()
					} else {
						const match = fieldName.match(/^(.+)\[(\d+)]$/)
						if (match != null) {
							var index = parseInt(match[2], 10)
							var fields = Tea.element("*[name='" + match[1].trim() + "[]']")
							if (fields.length > 0 && index < fields.length) {
								fields[index].focus()
							}
						}
					}
				}
			}

			if (typeof alertFn === "function") {
				alertFn(error, callback, false)
			} else {
				Tea.alert(error, callback, false)
			}
			return
		}
	}

	let reloadRequestId

	/**
	 *
	 * @param {String} [url]
	 * @param {{onDone:Function}} [reloadOptions]
	 */
	Tea.reloadViewData = (url, reloadOptions) => {
		const popupState = options.popupState
		if (url == null) {
			if (popupState) {
				url = popupState.url
			} else {
				url = window.location.toString()
			}
		}

		reloadRequestId = Math.random()
		let requestId = reloadRequestId

		eventBus.emit("showLoadingBar")

		Tea.action(url)
			.header("X-From-Front", "1")
			.timeout(30)
			.get()
			.success((resp) => {
				if (requestId != reloadRequestId) {
					return
				}

				if (popupState) {
					popupState.url = url
				}

				if (resp.data != null && typeof resp.data === "object") {
					let ctx = window.X_VIEW_CTX
					if (popupState) {
						ctx = popupState.ctx
					}
					for (const k in resp.data) {
						ctx[k] = resp.data[k]
					}
				}
			})
			.done(() => {
				eventBus.emit("hideLoadingBar")

				if (reloadOptions != null && typeof reloadOptions === "object" && typeof reloadOptions["onDone"] === "function") {
					reloadOptions["onDone"]()
				}
			})
	}

	Tea.currentURL = () => {
		if (options.popupState) {
			return options.popupState.url
		}
		if (window.location.search.length > 0) {
			return window.location.pathname + window.location.search
		}
		return window.location.pathname
	}

	Tea.alert = (message, callback, success) => {
		const toast = newToast(message)
		if (success) {
			toast.success()
		} else {
			toast.warning()
		}
		toast.clear()
		toast.life(2500)
		toast.show()

		if (typeof (callback) == "function") {
			callback();
		}
	}

	Tea.reload = () => {
		reloadPage()
	}

	return Tea
}
