/* mount.js

	Purpose:

	Description:

	History:
		Sat Oct 18 19:24:38     2008, Created by tomyeh

Copyright (C) 2008 Potix Corporation. All Rights Reserved.

	This program is distributed under LGPL Version 2.1 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
*/
export namespace mount_global {
	//define a package and returns the package info (used in WpdExtendlet)
	export function zkpi(nm: string, wv: boolean): Record<string, unknown> | undefined {
		return zk.isLoaded(nm) ? undefined : {n: nm, p: zk.$package(nm, false, wv)};
	}

	//ZK JSP: page creation (backward compatible)
	export function zkpb(pguid: string, dtid: string, contextURI: string, updateURI: string, resourceURI: string, reqURI: string, props: Record<string, string>): void {
		if (props === undefined && typeof reqURI !== 'string') { //ZK-4827: for backward compatible (other addons, ex. zuljsp)
			props = reqURI;
			reqURI = resourceURI;
			resourceURI = updateURI;
		}
		window.zkx([0, pguid,
			Object.assign(props ?? {}, {
				dt: dtid,
				cu: contextURI,
				uu: updateURI,
				rsu: resourceURI,
				ru: reqURI
			}), {}, []]);
	}

	//ZK JSP (useless; backward compatible)
	export const zkpe = zk.$void;

	//Initializes with version and options
	export function zkver(ver: string, build: string, ctxURI: string, updURI: string, modVers: Record<string, string>, opts: Record<string, unknown>): void {
		zk.version = ver;
		zk.build = build;
		zk.contextURI = ctxURI;
		zk.updateURI = updURI;

		for (var nm in modVers)
			zk.setVersion(nm, modVers[nm]);

		if (!zk.feature)
			zk.feature = {standard: true};
		window.zkopt(opts);
	}

	//Define a mold
	export function zkmld(wgtcls: Record<string, unknown>, molds: Record<string, (() => void)>): void {
		if (!wgtcls.$oid) {
			zk.afterLoad(function () {
				window.zkmld(wgtcls, molds);
			});
			return;
		}

		var ms = wgtcls.molds = {};
		for (var nm in molds) {
			var fn = molds[nm];
			// eslint-disable-next-line @typescript-eslint/dot-notation
			ms[nm] = typeof fn == 'function' ? fn : fn[0]['molds'][fn[1]];
		}
	}

	//Run Ajax-as-a-service's main
	export function zkamn(pkg: string, fn: (() => void)): void {
		zk.load(pkg, function () {
			setTimeout(function () {
				zk.afterMount(fn);
			}, 20);
		});
	}
}

interface Pcai {
	s: number;
	e: number;
	f0: (() => void)[];
	f1: (() => void)[];
	i?: number;
}

interface MountContext {
	curdt?: zk.Desktop;
	bindOnly: boolean;
}
type WidgetType = number | string;
type Uuid = string;
type WidgetProps = Record<'dt' | 'cu' | 'uu' | 'rsu' | 'ru' | 'ow' | 'wc' | string, unknown>;
type ShadowProps = Record<string, unknown[]>;
type WidgetInfo = [WidgetType, Uuid, WidgetProps, ShadowProps, WidgetInfo[], string?/*mold*/];
type AuCmds = string[];
//extra is [stub-fn, filter] if AU,  aucmds if BL
type ExtraInfo = [CallableFunction, CallableFunction] | AuCmds;
type InfoBeforeLoad = [zk.Desktop, WidgetInfo, boolean | undefined, zk.Widget | undefined, ExtraInfo?] & {pked?};

var Widget = zk.Widget,
	_wgt_$ = Widget.$, //the original zk.Widget.$
	_crInfBL0: InfoBeforeLoad[] = [], _crInfBL1: [zk.Desktop, zk.Widget, boolean | undefined, ExtraInfo?][] = [], //create info for BL
	_crInfAU0: InfoBeforeLoad[] = [], //create info for AU
	_aftMounts: (() => void)[] = [], //afterMount
	_aftResizes: (() => void)[] = [], //afterResize
	_mntctx: Partial<MountContext> = {}, //the context
	_paci: Pcai | undefined = {s: 0, e: -1, f0: [], f1: []}, //for handling page's AU responses
	_t0 = Date.now();

//Issue of handling page's AU responses
//1. page's AU must be processed after all zkx(), while they might be added
//  before zkx (such as test/test.zhtml), or multiple zkx (such jspTags.jsp)
//2. mount.js:_startCheck must be called after processing page's AU
//  (otherwise, zksandbox will jump to #f1 causing additional step)
//Note: it is better to block zAu but the chance to be wrong is low --
//a timer must be started early and its response depends page's AU
jq(function () {
	/** @internal */
	function _stateless(): boolean {
		var dts = zk.Desktop.all;
		for (var dtid in dts)
			if (dts[dtid].stateless) return true;
		return false;
	}

	if (!_paci) return;
	_paci.i = window.setInterval(function () {
		var stateless;
		if (_paci != undefined && ((zk.booted && !zk.mounting) || (stateless = _stateless())))
			if (stateless || _paci.s == _paci.e) { //done
				clearInterval(_paci.i);
				var fs = _paci.f0.concat(_paci.f1);
				_paci = undefined;
				for (var f: undefined | CallableFunction; (f = fs.shift());)
					f();
			} else
				_paci.e = _paci.s;
	}, 25);
});
//run after page AU cmds
/** @internal */
export function _apac(fn: () => void, _which_?: string): void {
	if (_paci) {
		(_paci[_which_ || 'f1'] as CallableFunction[]).push(fn);
		return;
	}
	zk.afterMount(fn); //it might happen if ZUML loaded later (with custom JS code)
}
zk._apac = _apac;

/**
 * Adds a function that will be executed after the mounting is done. By mounting we mean the creation of peer widgets.
 * <p>By mounting we mean the creation of the peer widgets under the
 * control of the server. To run after the mounting of the peer widgets,
 * <p>If the delay argument is not specified and no mounting is taking place,
 * the function is executed with `setTimeout(fn, 0)`.
 * @param fn - the function to execute after mounted
 * @param delay - (since 5.0.6) how many milliseconds to wait before execute if
 * there is no mounting taking place. If omitted, 0 is assumed.
 * If negative, the function is executed immediately (if no mounting is taking place).
 * @returns true if this method has been called before return (delay must
 * be negative, and no mounting); otherwise, undefined is returned.
 * @see {@link mounting}
 * @see {@link afterLoad}
 * @see {@link afterAnimate}
 */
export function afterMount(fn?: () => void, delay?: number): boolean { //part of zk
	if (fn) {
		if (!jq.isReady)
			jq(function () {
				zk.afterMount(fn);
			}); //B3278524
		else if (zk.mounting)
			_aftMounts.push(fn); //normal
		else if (zk.loading)
			zk.afterLoad(fn);
		else if (delay !== undefined && delay < 0) {
			fn();
			return true; //called
		} else
			setTimeout(fn, delay);
	}
	return false;
}
zk.afterMount = afterMount;

/**
 * Adds a function that will be executed after all of the onSize events are done.
 * <p>Here lists the execution phases:
 * <ol>
 *     <li>After the page loaded, the function added in the afterResze() will be invoked</li>
 *     <li>After the browser resized, the function added in the afterResze() will be invoked</li>
 *     <li>After zWatch.fire/fireDown('onSize'), the function added in the afterResze() will be invoked</li>
 * </ol>
 * </p>
 * @param fn - the function to execute after resized
 * @since 6.5.2
 */
export function afterResize(fn: () => void): void {
	if (fn)
		_aftResizes.push(fn);
}
zk.afterResize = afterResize;

export function doAfterResize(): void {
	for (var fn: undefined | CallableFunction; fn = _aftResizes.shift();) {
		fn();
	}
}
zk.doAfterResize = doAfterResize;

function _curdt(): zk.Desktop {
	return _mntctx.curdt ?? (_mntctx.curdt = zk.Desktop.$());
}

//Load all required packages
function mountpkg(infs: InfoBeforeLoad[]): void {
	var types: Record<string, zk.Desktop> = {};
	for (var j = infs.length; j--;) {
		var inf = infs[j];
		if (!inf.pked) { //mountpkg might be called multiple times before mount()
			inf.pked = true;
			getTypes(types, inf[0], inf[1] as never);
		}
	}

	for (var type in types) {
		var index: number = type.lastIndexOf('.');
		if (index >= 0)
			zk._load(type.substring(0, index), types[type]); //use _load for better performance
	}
}

//Loads package of a widget tree
function getTypes(types: Record<string, zk.Desktop>, dt: zk.Desktop, wi: WidgetInfo): void {
	var type = wi[0];
	if (type === 0) //page
		type = wi[2].wc as string;
	else if (type === 1) //1: zhtml.Widget
		wi[0] = type = 'zhtml.Widget';
	if (type)
		types[type] = dt;

	for (var children = wi[4], j = children.length; j--;) {
		getTypes(types, dt, children[j] as never);
	}
}

//mount for browser loading
function mtBL(): void {
	for (; ;) {
		if (zk.loading) {
			zk.afterLoad(mtBL); //later
			return;
		}

		var inf = _crInfBL0.shift();
		if (!inf)
			break; //done

		_crInfBL1.push([inf[0], create(inf[3] || inf[0], inf[1] as never, true), inf[2], inf[4]]);
		//inf[0]: desktop used as default parent if no owner
		//inf[3]: owner passed from zkx
		//inf[2]: bindOnly
		//inf[4]: aucmds (if BL)
		//true: don't update DOM

		if (breathe(mtBL))
			return; //mtBL has been scheduled for later execution
	}

	mtBL0();
}

function mtBL0(): void {
	for (; ;) {
		if (_crInfBL0.length)
			return; //another page started

		if (zk.loading) {
			zk.afterLoad(mtBL0);
			return;
		}

		var inf = _crInfBL1.shift();
		if (!inf) break;

		var wgt: zk.Widget = inf[1];
		if (inf[2])
			wgt.bind(inf[0]); //bindOnly
		else {
			var $jq: JQuery | undefined;
			if (zk.processing
				&& ($jq = jq('#zk_proc')).length) {
				if ($jq.hasClass('z-loading') && $jq.parent().hasClass('z-temp')) {
					$jq[0].id = 'zna';
					if (!jq('#zk_proc').length) //B65-ZK-1431: check if progressbox exists
						zUtl.progressbox('zk_proc', window.msgzk ? msgzk.PLEASE_WAIT : 'Processing...', true);
				}
			}
			wgt.replaceHTML('#' + wgt.uuid, inf[0]);
		}

		doAuCmds(inf[3] as AuCmds); //aucmds
	}

	mtBL1();
}

function mtBL1(): void {
	if (_crInfBL0.length || _crInfBL1.length)
		return; //another page started

	zk.booted = true;
	zk.mounting = false;
	doAfterMount(mtBL1);
	_paci && ++_paci.s;
	if (!zk.clientinfo) {// if existed, the endProcessing() will be invoked after onResponse()
		zk.endProcessing();
	} else {
		mtAU(); // resume last not done yet (=zk.mounting) clientinfo ZKAU
	}

	zk.bmk.onURLChange();
	if (zk.pfmeter) {
		var dts = zk.Desktop.all;
		for (var dtid in dts)
			zAu._pfdone(dts[dtid], dtid);
	}
}

/* mount for AU */
function mtAU(): void {
	for (; ;) {
		if (zk.loading) {
			zk.afterLoad(mtAU);
			return;
		}

		var inf = _crInfAU0.shift(), filter, wgt;
		zk._crWgtUuids.shift(); //sync to _crInfAU0
		if (!inf)
			break; //done

			if (filter = inf[4]![1]) {//inf[4] is extra if AU
				_wgt_$ = Widget.$; // update the latest one, if somewhere else override it already.
				Widget.$ = function <T extends zk.Widget> (n, opts?: Partial<{exact: boolean; strict: boolean; child: boolean}>): T | undefined {
					return (filter as CallableFunction)(_wgt_$(n, opts)) as T | undefined;
				};
			}
		try {
			wgt = create(undefined, inf[1] as never);
		} finally {
			if (filter && _wgt_$) Widget.$ = _wgt_$;
		}
		(inf[4]![0] as CallableFunction)(wgt); //invoke stub

		if (breathe(mtAU))
			return; //mtAU has been scheduled for later execution
	}
	mtAU0();
}

function mtAU0(): void {
	zk.mounting = false;
	doAfterMount(mtAU0);

	zAu._doCmds(); //server-push (w/ afterLoad) and _pfdone
	doAfterMount(mtAU0);
}

function doAfterMount(fnext: CallableFunction): boolean {
	for (var fn: CallableFunction | undefined; fn = _aftMounts.shift();) {
		fn();
		if (zk.loading) {
			zk.afterLoad(fnext); //fn might load packages
			return true; //wait
		}
	}
	return false;
}

function doAuCmds(cmds?: AuCmds): void {
	if (cmds && cmds.length)
		zk._apac(function () {
			for (var j = 0; j < cmds.length; j += 2)
				zAu.process(cmds[j], cmds[j + 1]);
		}, 'f0');
}

/* create the widget tree. */

function create(parent: zk.Widget | undefined, wi: WidgetInfo, ignoreDom?: boolean): zk.Widget {
	let nm;
	var wgt: zk.Widget, stub: boolean,
		type = wi[0],
		uuid = wi[1],
		props: WidgetProps = wi[2] || {},
		seProps: ShadowProps = wi[3] || {};
	if (type === 0) { //page
		type = zk.cut(props, 'wc') as string;
		const cls = type ? zk.$import(type) as typeof zk.Page : zk.Page;
		(wgt = new cls({uuid: uuid}, zk.cut(props, 'ct') as boolean)).inServer = true;
		if (parent) parent.appendChild(wgt, ignoreDom);
	} else {
		if ((stub = type == '#stub') || type == '#stubs') {
			if (!(wgt = (_wgt_$ || Widget.$)(uuid) //use the original one since filter() might applied
				|| zAu._wgt$(uuid))) //search detached (in prev cmd of same AU)
				throw 'Unknown stub ' + uuid;
			var w = new zk.Widget();
			//Bug ZK-1596: may already unbind
			//Bug ZK-1821: should also unbind wgt if in ROD status
			if (wgt.desktop || wgt.z_rod === 9)
				wgt.unbind(); //reuse it as new widget, bug ZK-1589: should unbind first then replace
			zk._wgtutl.replace(wgt, w, stub);
			//to reuse wgt, we replace it with a dummy widget, w
			//if #stubs, we have to reuse the whole subtree (not just wgt), so don't move children
		} else {
			const cls = zk.$import(type as string) as typeof zk.Widget;
			if (!cls)
				throw 'Unknown widget: ' + type;
			(wgt = new cls(window.zkac)).inServer = true;
			//zkac used as token to optimize the performance of zk.Widget.$init
			wgt.uuid = uuid;
			const v = wi[5];
			if (v)
				wgt.setMold(v);
		}
		if (parent) parent.appendChild(wgt, ignoreDom);

		//z$is: IdSpace
		//There are two ways to specify IdSpace at client
		//1) Override $init and assign _fellows (e.g., Macro/Include/Window)
		//2) Assign this.z$is to true (used by AbstractComponent.java)
		if (zk.cut(props, 'z$is')) {
			// eslint-disable-next-line @typescript-eslint/dot-notation
			wgt['z$is'] = true;
			wgt._fellows = {};
		}

		const al = zk.cut(props, 'z$al') as Record<string, CallableFunction> | undefined;
		//z$al: afterLoad
		if (al)
			zk.afterLoad(function () {
				for (const p in al)
					wgt.set(p, al[p]() as never, true); //value must be func; fromServer
			});

		const sal = zk.cut(seProps, 'z$al') as Record<string, CallableFunction>[] | undefined;
		if (sal)
			zk.afterLoad(function () {
				for (var i = 0, j = sal.length; i < j; i++) {
					const vv = sal[i];
					for (var p in vv)
						wgt.set(p, vv[p]() as never, true); //value must be func; fromServer
				}
			});
	}

	for (nm in props)
		wgt.set(nm as string, props[nm as string] as never, true); //fromServer

	for (nm in seProps) {
		const seProp = seProps[nm as never];
		for (var i = 0, length = seProp.length; i < length; i++) {
			const vv = seProp[i];
			wgt.set(nm as string, vv as never, true); //fromServer
		}
	}

	for (var j = 0, childs = wi[4], len = childs.length;
		 j < len; ++j)
		create(wgt, childs[j] as never);

	// call afterCompose_() after zk.loaded, because some packages may not
	// be loaded yet or call afterCompose_() directly if every required
	// packages are loaded.
	if (zk.loading) {
		zk.afterLoad(() => wgt.afterCompose_());
	} else if (parent) {
		wgt.afterCompose_();
	} else {
		const callOnce = (): void => {
			wgt.afterCompose_();
			wgt.unlisten({'onBind': callOnce});
		};
		wgt.listen({'onBind': callOnce});
	}
	return wgt;
}

/* Schedules fn for later execution if it takes too long to boot up,
 * so progressbox has a chance to show
 */
function breathe(fn: CallableFunction): boolean {
	var t = Date.now(), dt = t - _t0;
	if (dt > 2500) { //huge page (the shorter the longer to load; but no loading icon)
		_t0 = t;
		dt >>= 6;
		setTimeout(fn, dt < 10 ? dt : 10); //breathe
		//IE optimize the display if delay is too short
		return true;
	}
	return false;
}

export namespace mount_global {
	//define a desktop
	export function zkdt(dtid?: string, contextURI?: string, updateURI?: string, resourceURI?: string, reqURI?: string): zk.Desktop {
		var dt = zk.Desktop.$(dtid);
		if (dt == null) {
			dt = new zk.Desktop(dtid!, contextURI, updateURI, resourceURI, reqURI);
			if (zk.pfmeter) zAu._pfrecv(dt, dtid);
		} else {
			if (updateURI != null) dt.updateURI = updateURI;
			if (resourceURI != null) dt.resourceURI = resourceURI;
			if (contextURI != null) dt.contextURI = contextURI;
			if (reqURI != null) dt.requestPath = reqURI;
		}
		_mntctx.curdt = dt;
		return dt;
	}

	//widget creations
	// wi's index meaning
	// wi[0] = widget type
	// wi[1] = uuid
	// wi[2] = widget properties
	// wi[3] = shadow properties - since ZK 8.0.0
	// wi[4] = children
	// wi[5] = mold
	export function zkx(wi?: WidgetInfo, extra?: ExtraInfo, aucmds?: AuCmds, js?: string): void { //extra is either delay (BL) or [stub, filter] (AU)
		zk.mounting = true;

		try {
			if (js) jq.globalEval(js);

			var mount = mtAU, infs = _crInfAU0, delay, owner: string | zk.Widget | undefined;
			if (!extra || !extra.length) { //if 2nd argument not stub, it must be BL (see zkx_)
				delay = extra;
				if (wi) {
					extra = aucmds;
					aucmds = undefined;
				}
				mount = mtBL;
				infs = _crInfBL0;
			} //else assert(!aucmds); //no aucmds if AU

			if (wi) {
				if (wi[0] === 0) { //page
					var props = wi[2],
						dtid = zk.cut(props, 'dt') as string | undefined,
						cu = zk.cut(props, 'cu') as string | undefined,
						uu = zk.cut(props, 'uu') as string | undefined,
						rsu = zk.cut(props, 'rsu') as string | undefined,
						ru = zk.cut(props, 'ru') as string | undefined;
					window.zkdt(dtid, cu, uu, rsu, ru);
					if (owner = zk.cut(props, 'ow') as string)
						owner = zk.Widget.$(owner)!;
				}

				infs.push([_curdt(), wi, _mntctx.bindOnly, owner as zk.Widget | undefined, extra]);
				if (!zk._crWgtUuids) zk._crWgtUuids = [];
				zk._crWgtUuids.push(wi[1]);
				//extra is [stub-fn, filter] if AU,  aucmds if BL
				mountpkg(infs);
			}

			if (delay)
				setTimeout(mount, 0); //Bug 2983792 (delay until non-defer script evaluated)
			else if (!breathe(mount)) //give the browser a chance to breathe
				mount();

			doAuCmds(aucmds);
		} catch (e) {
			zk.mounting = false;
			if (e instanceof Error) {
				e.message = 'Failed to mount: ' + e.message;
				zk.error(e);
			} else zk.error('Failed to mount: ' + (e as string));
			setTimeout(function () {
				throw e;
			}, 0);
		}
	}

	//widget creation called by au.js
	//args: [wi] (a single element array containing wi)
	/** @internal */
	export function zkx_(args: ArrayLike<unknown>, stub: unknown, filter?: CallableFunction): void {
		_t0 = Date.now(); //so breathe() won't do unncessary delay
		(args as unknown[])[1] = [stub, filter]; //assign stub as 2nd argument (see zkx)
		window.zkx(...args as unknown as []); //args[2] (aucmds) must be null
	}

	//Run AU commands (used only with ZHTML)
	export function zkac(): void {
		doAuCmds(arguments as never);
	}

	//mount and zkx (BL)
	export function zkmx(wi?: WidgetInfo, extra?: ExtraInfo, aucmds?: AuCmds, js?: string): void {
		window.zkmb();
		try {
			zkx.call(window, wi, extra, aucmds, js);
		} finally {
			window.zkme();
		}
	}

	//begin of mounting
	export function zkmb(bindOnly?: boolean): void {
		_mntctx.bindOnly = bindOnly;
		var t = 390 - (Date.now() - _t0);
		zk.startProcessing(t > 0 ? t : 0);
	}

	//end of mounting
	export function zkme(): void {
		_mntctx.curdt = undefined;
		_mntctx.bindOnly = false;
	}

	// window scope
	// register data-attributres handler (since 8.0.0
	export function zkdh(name: string, script: string): void {
		zk.addDataHandler('data-' + name, script);
	}
}

//Event Handler//
jq(function () {
	var Widget = zk.Widget,
		_bfUploads: CallableFunction[] = [],
		_reszInf = {},
		_subevts = { //additonal invocation
			onClick: 'doSelect_',
			onRightClick: 'doSelect_',
			onMouseOver: 'doTooltipOver_',
			onMouseOut: 'doTooltipOut_'
		};

	zk.beforeUnload = function (fn, opts): void { //part of zk
		if (opts?.remove) _bfUploads.$remove(fn);
		else _bfUploads.push(fn);
	};

	/** @internal */
	function _doEvt(wevt: zk.Event): void {
		var wgt = wevt.target;
		if (wgt && !wgt.$weave) {
			var en = wevt.name,
				fn = _subevts[en] as string | undefined;
			if (fn) {
				// Bug 3300935, disable tooltip for IOS
				if (!zk.ios || (fn != 'doTooltipOver_' && fn != 'doTooltipOut_')) {
					(wgt[fn] as CallableFunction)(wevt);
				}
			}
			// eslint-disable-next-line @typescript-eslint/dot-notation
			if (!wevt.stopped && (!wevt['originalEvent'] || !(wevt['originalEvent'] as object)['zkstopped'])) // Bug ZK-2544
				(wgt['do' + en.substring(2) + '_'] as CallableFunction)(wevt);
			if (wevt.domStopped)
				wevt.domEvent!.stop();
		}
	}

	/** @internal */
	function _docMouseDown(evt: zk.Event, wgt: zk.Widget | undefined, noFocusChange?: boolean): void {
		zk.clickPointer[0] = evt.pageX!;
		zk.clickPointer[1] = evt.pageY!;

		if (!wgt) wgt = evt.target;

		var dEvent = evt.domEvent!,
			body = document.body;
		if (dEvent.clientX! <= body.clientWidth && dEvent.clientY! <= body.clientHeight) //not click on scrollbar
			// F70-ZK-2007: Add the button information in it.
			Widget.mimicMouseDown_(wgt, noFocusChange, evt.which); //wgt is null if mask

		_doEvt(evt);
	}

	/** @internal */
	function _docResize(): void {
		// eslint-disable-next-line @typescript-eslint/dot-notation
		if (!_reszInf['time']) return; //already handled

		var now = Date.now();
		// eslint-disable-next-line @typescript-eslint/dot-notation
		if (zk.mounting || zk.loading || now < _reszInf['time'] || zk.animating()) {
			setTimeout(_docResize, 10);
			return;
		}

		// eslint-disable-next-line @typescript-eslint/dot-notation
		_reszInf['time'] = undefined; //handled
		// eslint-disable-next-line @typescript-eslint/dot-notation
		_reszInf['lastTime'] = now + 1000;
		//ignore following for a while if processing (in slow machine)

		zAu._onClientInfo();

		// eslint-disable-next-line @typescript-eslint/dot-notation
		_reszInf['inResize'] = true;
		try {
			const dt = zk.Desktop._dt!;
			zWatch.fire('_beforeSizeForRead', dt); //notify all
			zWatch.fire('beforeSize', dt); //notify all
			zWatch.fire('onFitSize', dt, {reverse: true}); //notify all
			zWatch.fire('onSize', dt); //notify all
			zWatch.fire('afterSize', dt); //notify all
			// eslint-disable-next-line @typescript-eslint/dot-notation
			_reszInf['lastTime'] = Date.now() + 8;
		} finally {
			// eslint-disable-next-line @typescript-eslint/dot-notation
			_reszInf['inResize'] = false;
		}
	}

	//Invoke the first root wiget's afterKeyDown_
	/** @internal */
	function _afterKeyDown(wevt: zk.Event): void {
		var dts = zk.Desktop.all, Page = zk.Page;
		for (var dtid in dts)
			for (var wgt = dts[dtid].firstChild; wgt; wgt = wgt.nextSibling)
				if (wgt instanceof Page) {
					for (var w = wgt.firstChild; w; w = w.nextSibling)
						if (_afterKD(w, wevt))
							return;
				} else if (_afterKD(wgt, wevt))
					return; //handled
	}

	/** @internal */
	function _afterKD(wgt: zk.Widget | zul.Widget, wevt: zk.Event): boolean {
		if (!zk.isLoaded('zul') || !(wgt instanceof zul.Widget))
			return false; //handled
		wevt.target = wgt; //mimic as keydown directly sent to wgt
		return wgt.afterKeyDown_(wevt, true);
	}

	var lastTimestamp, lastTarget;
	jq(document)
		.keydown(function (evt) {
			const wgt = Widget.$(evt, {child: true})!,
				wevt = new zk.Event(wgt, 'onKeyDown', evt.keyData(), undefined, evt);
			if (wgt) {
				_doEvt(wevt);
				if (!wevt.stopped && zk.isLoaded('zul') && wgt instanceof zul.Widget) {
					wgt.afterKeyDown_(wevt);
					if (wevt.domStopped)
						wevt.domEvent!.stop();
				}
			// eslint-disable-next-line @typescript-eslint/dot-notation
			} else if (zk['invokeFirstRootForAfterKeyDown'])
				_afterKeyDown(wevt);

			if (evt.keyCode == 27
				&& (zk._noESC > 0 || zAu.shallIgnoreESC())) //Bug 1927788: prevent FF from closing connection
				return false; //eat
		})
		.keyup(function (evt) {
			var wgt = zk.keyCapture;
			if (wgt) zk.keyCapture = undefined;
			else wgt = Widget.$(evt, {child: true});
			_doEvt(new zk.Event(wgt, 'onKeyUp', evt.keyData(), undefined, evt));
		})
		.keypress(function (evt) {
			var wgt = zk.keyCapture;
			if (!wgt) wgt = Widget.$(evt, {child: true});
			_doEvt(new zk.Event(wgt, 'onKeyPress', evt.keyData(), undefined, evt));
		})
		.on('paste', function (evt: JQuery.TriggeredEvent & {keyData()}) {
			var wgt = zk.keyCapture;
			if (!wgt) wgt = Widget.$(evt, {child: true});
			_doEvt(new zk.Event(wgt, 'onPaste', evt.keyData(), undefined, evt));
		})
		.on('zcontextmenu', function (evt: JQuery.MouseEventBase) {
			//ios: zcontextmenu shall be listened first,
			//due to need stop other event (ex: click, mouseup)

			zk.clickPointer[0] = evt.pageX || 0;
			zk.clickPointer[1] = evt.pageY || 0;

			var wgt = Widget.$(evt, {child: true});
			if (wgt) {
				var wevt = new zk.Event(wgt, 'onRightClick', evt.mouseData(), {}, evt);
				_doEvt(wevt);
				if (wevt.domStopped)
					return false;
			}
			return true;
		})
		.on('zmousedown', function (evt: JQuery.MouseEventBase) {
			if (zk.mobile) {
				zk.currentPointer[0] = evt.pageX || 0;
				zk.currentPointer[1] = evt.pageY || 0;
			}
			var wgt = Widget.$(evt, {child: true});
			_docMouseDown(
				new zk.Event(wgt, 'onMouseDown', evt.mouseData(), undefined, evt),
				wgt);
		})
		.on('zmouseup', function (evt: JQuery.MouseUpEvent) {
			var e = zk.Draggable.ignoreMouseUp(), wgt;
			if (e === true)
				return; //ignore

			if (e != null) {
				_docMouseDown(e as zk.Event, undefined, true); //simulate mousedown
			}

			wgt = zk.mouseCapture;
			if (wgt) zk.mouseCapture = undefined;
			else wgt = Widget.$(evt, {child: true});
			_doEvt(new zk.Event(wgt as zk.Widget, 'onMouseUp', evt.mouseData(), undefined, evt));
		})
		.on('zmousemove', function (evt: JQuery.MouseMoveEvent) {
			zk.currentPointer[0] = evt.pageX || 0;
			zk.currentPointer[1] = evt.pageY || 0;

			var wgt = zk.mouseCapture;
			if (!wgt) wgt = Widget.$(evt, {child: true});
			_doEvt(new zk.Event(wgt, 'onMouseMove', evt.mouseData(), undefined, evt));
		})
		.mouseover(function (evt) {
			if (zk.mobile) return; // unsupported on touch device for better performance
			zk.currentPointer[0] = evt.pageX || 0;
			zk.currentPointer[1] = evt.pageY || 0;

			_doEvt(new zk.Event(Widget.$(evt, {child: true}), 'onMouseOver', evt.mouseData(), {ignorable: true}, evt));
		})
		.mouseout(function (evt) {
			_doEvt(new zk.Event(Widget.$(evt, {child: true}), 'onMouseOut', evt.mouseData(), {ignorable: true}, evt));
		})
		.click(function (evt) {
			if (zk.Draggable.ignoreClick()) return;

			if (zk.android
				&& (lastTimestamp && lastTimestamp + 400 > evt.timeStamp)
				&& (lastTarget && lastTarget == evt.target)) { //fix android 4.1.1 fire twice or more
				return;
			} else {
				lastTimestamp = evt.timeStamp;
				lastTarget = evt.target;

				zjq._fixClick(evt);

				if (evt.which == 1)
					_doEvt(new zk.Event(Widget.$(evt, {child: true}),
						'onClick', evt.mouseData(), {}, evt));
				//don't return anything. Otherwise, it replaces event.returnValue in IE (Bug 1541132)
			}
		})
		.on('zdblclick', function (evt: JQuery.DoubleClickEvent) {
			if (zk.Draggable.ignoreClick()) return;

			var wgt = Widget.$(evt, {child: true});
			if (wgt && evt.which == 1) {
				var wevt = new zk.Event(wgt, 'onDoubleClick', evt.mouseData(), {}, evt);
				_doEvt(wevt);
				if (wevt.domStopped)
					return false;
			}
		})
		.on((document.hidden !== undefined ? '' : zk.vendor_) + 'visibilitychange', function (evt) {
			zAu._onVisibilityChange();
		});

	if (zk.scriptErrorHandlerEnabled) {
		zk.scriptErrorHandler = function (evt: JQuery.TriggeredEvent & {originalEvent: ErrorEvent}): void {
			const errorMsg = evt.originalEvent.message,
				stack = (evt.originalEvent.error as Error).stack!,
				checkFunctionList = ['globalEval', 'script', '_doCmds', 'afterResponse', '_onResponseReady'];

			for (let i = 0, l = checkFunctionList.length, lastCheckPos = -1; i < l; i++) {
				const checkPos = stack.indexOf(checkFunctionList[i]);
				if (checkPos == -1 || checkPos < lastCheckPos)
					return; //not from Clients.evalJavascript
				lastCheckPos = checkPos;
			}
			zAu.send(new zk.Event(zk.Desktop._dt, 'onScriptError', {
				message: errorMsg,
				stack: stack
			}));
			zk.scriptErrorHandlerRegistered = false;
		};
	}

	var _sizeHandler = function (evt): void {
		if (zk.mounting)
			return;

		//Tom Yeh: 20051230:
		//1. In certain case, IE will keep sending onresize (because
		//grid/listbox may adjust size, which causes IE to send onresize again)
		//To avoid this endless loop, we ignore onresize a while if this method
		//was called
		//
		//2. IE keeps sending onresize when dragging the browser's border,
		//so we have to filter (most of) them out

		var now = Date.now();
		// eslint-disable-next-line @typescript-eslint/dot-notation
		if ((_reszInf['lastTime'] && now < _reszInf['lastTime']) || _reszInf['inResize'])
			return; //ignore resize for a while (since onSize might trigger onsize)

		var delay = zk.android ? 250 : 50;
		// eslint-disable-next-line @typescript-eslint/dot-notation
		_reszInf['time'] = now + delay - 1; //handle it later
		setTimeout(_docResize, delay);

		if (zk.mobile && zAu._cInfoReg) {
			if (!jq('#zk_proc').length && !jq('#zk_showBusy').length) {
				zUtl.progressbox('zk_proc', window.msgzk ? msgzk.PLEASE_WAIT : 'Processing...', true);
			}
		}
	};

	if (zk.mobile) {
		jq(window).on('orientationchange', _sizeHandler);

		// Bug ZK-2697
		if (zk.ios) {
			jq(window).on('pagehide', function () {
				zk.unloading = true; //to disable error message

				if (!zk.rmDesktoping) {
					rmDesktop();
				}
			});
		}
	} else {
		jq(window).resize(_sizeHandler);
	}

	jq(window).scroll(function () {
		zWatch.fire('onScroll', zk.Desktop._dt!); //notify all
	}).on('unload', function () {
		zk.unloading = true; //to disable error message

		if (!zk.rmDesktoping) {
			rmDesktop();
		}
	});

	function rmDesktop(): void {
		//20061109: Tom Yeh: Failed to disable Opera's cache, so it's better not
		//to remove the desktop.
		//Good news: Opera preserves the most udpated content, when BACK to
		//a cached page, its content. OTOH, IE/FF/Safari cannot.
		var bRmDesktop = !zk.opera && !zk.keepDesktop;
		if (bRmDesktop || zk.pfmeter) {
			zk.rmDesktoping = true;
			try {
				var dts = zk.Desktop.all;
				for (var dtid in dts)
					zAu._rmDesktop(dts[dtid], !bRmDesktop);
			} catch (e) {
				zk.debugLog((e as Error).message ?? e);
			}
		}
	}

	var _oldBfUnload = window.onbeforeunload;
	window.onbeforeunload = function (ev: BeforeUnloadEvent) {
		if (zk.confirmClose) {
			setTimeout(function () { //If user click 'cancel', the disabledRequest should be false. Ref: https://stackoverflow.com/questions/4650692/
				zAu.disabledRequest = false;
			}, 0);
			return zk.confirmClose;
		}

		for (var j = 0; j < _bfUploads.length; ++j) {
			var s = _bfUploads[j]() as unknown;
			if (s) return s;
		}

		if (_oldBfUnload) {
			var s = _oldBfUnload.call(window, ev) as unknown;
			if (s) return s;
		}

		zk.unloading = true; //FF3 aborts ajax before calling window.onunload

		// B65-ZK-2051: Remove desktop if not IE.
		rmDesktop();
		//Return nothing
	};

	zk.afterMount(function () {
		jq('script.z-runonce').remove();
	});
	//clean up the runonce script. otherwise, it might be run again if
	//the script element is moved
}); //jq()

zk.copy(window, mount_global);