/* WScroll.ts

	Purpose:

	Description:
		A wave scrollbar control
	History:
		Wed, Feb 22, 2012  4:16:53 PM, Created by jumperchen

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

*/
function easing(x: number, t: number, b: number, c: number, d: number): number {
	return -c * ((t = t / d - 1) * t * t * t - 1) + b; // easeOutQuart
}
function snap(dg: zk.Draggable, pointer: zk.Offset): zk.Offset {
	var x = pointer[0],
		y = pointer[1];
	if (dg._isVer) {
		var move = y;

		if (move - dg._start < 0) {
			move = pointer[1] = dg._start;
		} else if (move > dg._end) {
			move = pointer[1] = dg._end;
		}

		if (dg._lastPos) { // optimized
			if (Math.abs(dg._lastPos - move) < 3)
				return pointer;
		}

		dg._lastPos = move;
	} else {
		var move = x;

		if (move - dg._start < 0) {
			move = pointer[0] = dg._start;
		} else if (move > dg._end) {
			move = pointer[0] = dg._end;
		}

		if (dg._lastPos) { // optimized
			if (Math.abs(dg._lastPos - move) < 3)
				return pointer;
		}

		dg._lastPos = move;
	}
	return pointer;
}

function starteffect(dg: zk.Draggable): void {
	var ctrl = dg.control as unknown as zul.WScroll,
		opts = ctrl.opts;

	dg._steps = opts.startStep;
	dg._endStep = opts.endStep - opts.viewport;
	dg._scale = ctrl._scale;
	dg._epos = ctrl.epos;
	dg._lastPos = dg._start;
	if (ctrl._isVer) {
		dg._isVer = true;
		dg._start = opts.startPosition;
		if (zk(ctrl.eend).isVisible()) {
			dg._end = ctrl.eend.offsetTop + Math.ceil(dg.handle!.offsetHeight / 2);
			if (dg._end > opts.viewportSize + dg._start)
				dg._end = opts.viewportSize + dg._start;
		} else {
			dg._end = opts.viewportSize + dg._start;
		}
		dg._end -= dg.node!.offsetHeight - ctrl._gap;
	} else {
		dg._isVer = false;
		dg._start = opts.startPosition;
		if (zk(ctrl.eend).isVisible()) {
			dg._end = ctrl.eend.offsetLeft + Math.ceil(dg.handle!.offsetWidth / 2);
			if (dg._end > opts.viewportSize + dg._start)
				dg._end = opts.viewportSize + dg._start;
		} else {
			dg._end = opts.viewportSize + dg._start;
		}
		dg._end -= dg.node!.offsetWidth - ctrl._gap;
	}
	jq(dg._epos).show().delay(200).fadeIn(500);

	if (dg._timer) {
		clearTimeout(dg._timer);
	}
	var lastP,
		lastS: number[] = [],
		timeout = 30,
		duration = timeout * 20,
		t = 10,
		running = function (orient: 'top' | 'left'): void {
			var norient = zk.parseFloat(dg.node!.style[orient]),
				diff = norient - zk.parseFloat(dg._epos!.style[orient]);
			if (lastP == norient) {
				lastS.push(dg._lastSteps);
				if (lastS.length > 4 && lastS.shift() == dg._lastSteps) {
					lastS[0] = dg._lastSteps;
					clearTimeout(dg._timer);
					dg._timer = setTimeout(function () {running(orient);}, 100);
					return;
				}
			} else t = 10; // reset

			lastP = norient;

			var down = diff > 0,
				total = down ? Math.max(0, diff / dg._scale) : Math.min(0, diff / dg._scale),
				step = Math.round(zul.WScroll.easing(t / duration, t, 0, total, duration));

			if (down) {
				if (total > 1)
					step = Math.max(1, step);
			} else {
				if (-total > 1)
					step = Math.min(-1, step);
			}

			if (diff == 0 && step == 0) {
				if (norient == dg._start)
					step = -dg._steps;
				else if (norient == dg._end)
					step = dg._endStep - dg._steps;
			}

			dg._steps += step;
			if (down) {
				if (dg._steps > dg._endStep)
					dg._steps = dg._endStep;
			} else {
				if (dg._steps < 0)
					dg._steps = 0;
			}
			dg._epos!.style[orient] = dg._start + (dg._scale * dg._steps) + 'px';
			t += timeout;
			if (dg._lastSteps != dg._steps) {
				dg._lastSteps = dg._steps;
				var func = orient == 'top' ? ctrl.opts.onScrollY : ctrl.opts.onScrollX;
				if (typeof func == 'function') {
					func.call((dg.control as unknown as zul.WScroll).widget, dg._steps + ctrl.opts.offset);
				}
			}
			clearTimeout(dg._timer);
			dg._timer = setTimeout(function () {running(orient);}, timeout);
		};
	dg._timer = setTimeout(function () {running((dg._isVer ? 'top' : 'left'));}, 50);
}
function endeffect(dg: zk.Draggable): void {
	var ctrl = dg.control as unknown as zul.WScroll;
	if (dg._timer) {
		clearTimeout(dg._timer);
	}
	var move: number, end: number;
	if (dg._isVer) {
		move = zk.parseInt(dg._epos!.style.top);
		end = dg._end;
		if (move > end)
			move = end;
		jq(dg.node).animate({top: move + 'px'}, 400, 'swing');

	} else {
		move = zk.parseInt(dg._epos!.style.left);
		end = dg._end;
		if (move > end)
			move = end;
		jq(dg.node).animate({left: move + 'px'}, 400, 'swing');

	}
	ctrl.opts.startStep = dg._steps;
	ctrl._syncButtonStatus();
	var $jq = jq(dg._epos),
		old = $jq.css('opacity'); // fix old IE version
	jq(dg._epos).delay(300).fadeOut(500).css('opacity', old);
}
function ignoredrag(dg: zk.Draggable, p: zk.Offset, evt: zk.Event): boolean {
	return (dg.control as unknown as WScroll).edragBody != evt.domTarget;
}

export interface WScrollOptions {
	orient: 'vertical' | 'horizontal';
	startPosition: number;
	startStep: number;
	offset: number;

	anchor?: HTMLElement | null; // eslint-disable-line zk/noNull
	viewport: number;
	endStep: number;
	viewportSize: number;
	syncSize: boolean;
	onScrollX?(this: zk.Widget, delta: number): void;
	onScrollY?(this: zk.Widget, delta: number): void;
}

/**
 * A wave Scrollbar used to scroll the specific content and provides four controls
 * to navigate the content, such as Home/Previous/Next/End, and also supports the
 * mousewheel control.
 */
@zk.WrapClass('zul.WScroll')
export class WScroll extends zk.Object {
	/** The control object for this scrolling that user can scroll the whole content
	 * @type DOMElement
	 */
	//control: null,
	/** The widget object that owns the control object.
	 * @type zk.Widget
	 */
	//widget: null,
	/**
	 * The opts of this scrollbar controls.
	 * <h4>startPosition</h4>
	 * int startPosition
	 * <p>Specifies the start position according to the scrolling area, like offset top for
	 * the vertical scrolling and offset left for the horizental scrolling.
	 *
	 * <h4>startStep</h4>
	 * int startStep
	 * <p>Specifies the start step for the scrolling.
	 * <p>Note: it cannot be negative.
	 *
	 * <h4>endStep</h4>
	 * int endStep
	 * <p>Specifies how many steps for the scrolling.
	 * <p>Note: it cannot be negative.
	 *
	 * <h4>viewport</h4>
	 * int viewport
	 * <p>Specifies how many steps will show in the viewport.
	 * <p>Note: it cannot be negative.
	 *
	 * <h4>viewportSize</h4>
	 * int viewportSize
	 * <p>Specifies how many pixels for the viewport size, like offsetHeight for
	 * vertical scrolling and offsetWidth for horizental scrolling.
	 * <p>Note: it cannot be negative.
	 *
	 * <h4>orient</h4>
	 * String orient
	 * <p>Specifies either 'vertical' or 'horizontal' to indicate that it can be
	 * scrolled only in the vertical or horizontal direction.
	 * @defaultValue 'horizontal'
	 *
	 * <h4>anchor</h4>
	 * DOMElement anchor
	 * <p>Specifies the anchor that indicates the scrollbar will be its child node.
	 * @defaultValue the parent node of the control.
	 *
	 * <h4>syncSize</h4>
	 * boolean syncSize
	 * <p>Specifies whether to sync the scrolling area size at initial phase.
	 * @defaultValue `true`.
	 *
	 * <h4>onScrollY</h4>
	 * ```ts
	 * void onScrollY(int step);
	 * ```
	 * <p>Specifies the callback function for the vertical scrolling, when user
	 * changes the vertical scrolling step.
	 *
	 * <h4>onScrollX</h4>
	 * ```ts
	 * void onScrollX(int step);
	 * ```
	 * <p>Specifies the callback function for the horizental scrolling, when user
	 * changes the horizental scrolling step.
	 *
	 * <h4>offset</h4>
	 * int offset
	 * <p>Specifies the offset for the scrolling step to shift when the callback
	 * functions (onScrollX and onScrollY) are invoked.
	 * For example, if the offset is 2, then the steps in the onScrollX/Y event
	 * will start at 2.
	 * @defaultValue `0`
	 */
	//opts: null,
	opts: WScrollOptions;
	/** @internal */
	_gap = 0;
	control: HTMLElement;
	widget: zk.Widget & {_cols?: number};
	/** @internal */
	_scale!: number; // Initialized before use.
	/** @internal */
	_isVer: boolean;
	uid: string;
	zcls: string;
	anchor: HTMLElement;

	// The following properties are initialized in _initDragdrop which is called
	// by the constructor.
	node!: HTMLElement;
	edrag!: HTMLElement;
	edragBody!: ChildNode;
	epos!: HTMLElement;
	eend!: HTMLElement;
	drag!: zk.Draggable;

	constructor(control: HTMLElement, opts: WScrollOptions) {
		super();
		this.control = control;
		this.opts = zk.$default(opts, {
			orient: 'horizontal',
			startPosition: 0,
			startStep: 0,
			offset: 0
		});
		this.anchor = this.opts.anchor || control.parentNode as HTMLElement;
		this.widget = zk.Widget.$(control)!;
		this.uid = this.widget.uuid;
		this.zcls = /*safe*/ this.widget.getZclass();
		this._isVer = opts.orient == 'vertical';
		// ZK-2178: viewportSize is 0 if biglistbox has not model
		if (!opts.viewportSize && opts.viewportSize != 0)
			throw 'Handle required for a viewport size: {viewportSize: size}';
		this.redraw(this.anchor);
		this._initDragdrop();
		this._listenMouseEvent();
		if (this.opts.syncSize)
			this.syncSize();
		this._syncButtonStatus();
	}

	/**
	 * Syncs the scrolling area and control bar size.
	 * @param opts - the opts can override the initail opts data for resizing.
	 */
	syncSize(opts?: Record<string, unknown>): void {
		if (opts) {
			this.opts = zk.copy(this.opts, opts);
		}
		this.edrag.style.display = '';
		if (this._isVer) {
			const opts = this.opts,
				top = opts.startPosition,
				start = opts.startStep,
				view = opts.viewport,
				end = opts.endStep,
				rest = end - view,
				edragHeight = this.edrag.offsetHeight - this._gap;
			let vsize = opts.viewportSize;
			if (rest <= 0) {
				this.eend.style.display = this.edrag.style.display = 'none';
				if (typeof this.opts.onScrollY == 'function')
					this.opts.onScrollY.call(this.widget, opts.offset); //reset scrolling
				return;
			}
			vsize -= edragHeight;
			if (vsize > rest) {
				this.epos.style.height = edragHeight + 'px';
				this._scale = 1;
				var es = this.eend.style;
				es.display = '';
				es.top = top + edragHeight + rest + 'px';
			} else {
				var rate = vsize / rest,
					height = Math.max(edragHeight * rate, 5);
				this.epos.style.height = height + 'px';
				this._scale = rate;
				this.eend.style.display = 'none'; // no end point
				if (vsize < 10)
					this.edrag.style.display = 'none';
			}
			var top1 = top + (this._scale * start),
				top2 = top + vsize;
			if (top1 > top2)
				top1 = top2;
			this.epos.style.top = this.edrag.style.top = top1 + 'px';
		} else {
			const opts = this.opts,
				left = opts.startPosition,
				start = opts.startStep,
				view = opts.viewport,
				end = opts.endStep,
				rest = end - view,
				edragWidth = this.edrag.offsetWidth - this._gap;
			let vsize = opts.viewportSize;
			if (rest <= 0) {
				this.eend.style.display = this.edrag.style.display = 'none';
				if (typeof this.opts.onScrollX == 'function')
					this.opts.onScrollX.call(this.widget, opts.offset); //reset scrolling
				return;
			}
			vsize -= edragWidth;
			if (vsize > rest) {
				this.epos.style.width = edragWidth + 'px';
				this._scale = 1;
				var es = this.eend.style;
				es.display = '';
				es.left = left + edragWidth + rest + 'px';
			} else {
				var rate = vsize / rest,
					width = Math.max(edragWidth * rate, 5);
				this.epos.style.width = width + 'px';
				this._scale = rate;
				this.eend.style.display = 'none'; // no end point
				if (vsize < 10)
					this.edrag.style.display = 'none';
			}

			var left1 = left + (this._scale * start),
				left2 = left + vsize;
			if (left1 > left2)
				left1 = left2;
			this.epos.style.left = this.edrag.style.left = left1 + 'px';
		}
	}

	/** @internal */
	_listenMouseEvent(): void {
		if (this._isVer) {
			// @ts-expect-error: zk.Event is not completely compatible with JQueryMousewheelEventObject
			jq(this.control).mousewheel(this._mousewheelY.bind(this));
		} else if (!zk.opera) { // ie and opera unsupported
			// @ts-expect-error: zk.Event is not completely compatible with JQueryMousewheelEventObject
			jq(this.control).mousewheel(this._mousewheelX.bind(this));
		}

		var $drag = jq(this.edrag);
		$drag.children('div')
			.on('mouseover', this._mouseOver.bind(this))
			.on('mouseout', this._mouseOut.bind(this))
			.on('mouseup', this._mouseUp.bind(this))
			.on('mousedown', this._mouseDown.bind(this));
		$drag.on('click', zk.$void);
	}

	/** @internal */
	_unlistenMouseEvent(): void {
		if (this._isVer)
			// @ts-expect-error: unmousewheel expects 0 arguments, but got 1
			jq(this.control).unmousewheel(this._mousewheelY.bind(this));
		else if (!zk.opera) // ie and opera unsupported
			// @ts-expect-error: unmousewheel expects 0 arguments, but got 1
			jq(this.control).unmousewheel(this._mousewheelX.bind(this));

		var $drag = jq(this.edrag);
		$drag.children('div')
			.off('mouseover', this._mouseOver.bind(this))
			.off('mouseout', this._mouseOut.bind(this))
			.off('mouseup', this._mouseUp.bind(this))
			.off('mousedown', this._mouseDown.bind(this));
		$drag.off('click', zk.$void);
	}

	/** @internal */
	_mouseOver(evt: zk.Event): void {
		var cls = evt.target.className,
			index = cls.lastIndexOf('-'),
			key = cls.substring(index + 1),
			$drag = jq(this.edrag);
		if ($drag.hasClass(cls + '-clk')) {
			$drag.removeClass(cls + '-clk');
		}
		switch (key) {
			case 'home':
			case 'up':
				if (this.opts.startStep > 0)
					$drag.addClass(cls + '-over');
				break;
			case 'down':
			case 'end':
				var opts = this.opts;
				if (opts.startStep < opts.endStep - opts.viewport) {
					$drag.addClass(cls + '-over');
				}
				break;
		}
	}

	/** @internal */
	_mouseOut(evt: zk.Event): void {
		var cls = evt.target.className,
			$drag = jq(this.edrag);
		$drag.removeClass(cls + '-over');
		if ($drag.hasClass(cls + '-clk')) {
			$drag.removeClass(cls + '-clk');
		}
	}

	/** @internal */
	_mouseUp(evt: zk.Event): void {
		jq(this.edrag).removeClass(evt.target.className + '-clk');
	}

	/** @internal */
	_mouseDown(evt: zk.Event): void {
		var cls = evt.target.className,
			index = cls.lastIndexOf('-'),
			key = cls.substring(index + 1),
			$drag = jq(this.edrag);
		if (!$drag.hasClass(cls + '-over') && !zk.mobile) //no mouse over for mobile
			return;// disable

		$drag.addClass(cls + '-clk');

		var opts = this.opts;
		switch (key) {
			case 'home':
				if (opts.startStep > 0) {
					opts.startStep = 0;
					if (this._isVer) {
						const moving = opts.startPosition + 'px';
						this.epos.style.top = moving;
					$drag.animate({top: moving}, 500);
						if (typeof this.opts.onScrollY == 'function')
							this.opts.onScrollY.call(this.widget, opts.startStep + opts.offset);
					} else {
						const moving = opts.startPosition + 'px';
						this.epos.style.left = moving;
					$drag.animate({left: moving}, 500);
						if (typeof this.opts.onScrollX == 'function')
							this.opts.onScrollX.call(this.widget, opts.startStep + opts.offset);
					}
					$drag.removeClass(cls + '-over');
				}
				break;
			case 'up':
				if (opts.startStep > 0) {
					opts.startStep -= 1;
					var move = opts.startPosition + (opts.startStep * this._scale);
					if (this._isVer) {
						var end;
						if (zk(this.eend).isVisible()) {
							end = this.eend.offsetTop;
						} else {
							end = opts.viewportSize + opts.startPosition;
						}
						end -= this.edrag.offsetHeight - this._gap;

						this.epos.style.top = move + 'px';
						if (end < move) {
							this.edrag.style.top = end + 'px';
						} else {
							this.edrag.style.top = move + 'px';
						}
						if (typeof this.opts.onScrollY == 'function')
							this.opts.onScrollY.call(this.widget, opts.startStep + opts.offset);
					} else {
						var end;
						if (zk(this.eend).isVisible()) {
							end = this.eend.offsetLeft;
						} else {
							end = opts.viewportSize + opts.startPosition;
						}
						end -= this.edrag.offsetWidth - this._gap;

						this.epos.style.left = move + 'px';
						if (end < move) {
							this.edrag.style.left = end + 'px';
						} else {
							this.edrag.style.left = move + 'px';
						}

						if (typeof this.opts.onScrollX == 'function')
							this.opts.onScrollX.call(this.widget, opts.startStep + opts.offset);
					}

					if (opts.startStep == 0)
						$drag.removeClass(cls + '-over');
				}
				break;
			case 'down':
				if (opts.startStep < opts.endStep - opts.viewport) {
					opts.startStep += 1;
					var move = opts.startPosition + (opts.startStep * this._scale);
					if (this._isVer) {
						var end;
						if (zk(this.eend).isVisible()) {
							end = this.eend.offsetTop;
						} else {
							end = opts.viewportSize + opts.startPosition;
						}
						end -= this.edrag.offsetHeight - this._gap;

						this.epos.style.top = move + 'px';
						if (end < move) {
							this.edrag.style.top = end + 'px';
						} else {
							this.edrag.style.top = move + 'px';
						}
						if (typeof this.opts.onScrollY == 'function')
							this.opts.onScrollY.call(this.widget, opts.startStep + opts.offset);
					} else {
						var end;
						if (zk(this.eend).isVisible()) {
							end = this.eend.offsetLeft;
						} else {
							end = opts.viewportSize + opts.startPosition;
						}
						end -= this.edrag.offsetWidth - this._gap;

						this.epos.style.left = move + 'px';
						if (end < move) {
							this.edrag.style.left = end + 'px';
						} else {
							this.edrag.style.left = move + 'px';
						}

						if (typeof this.opts.onScrollX == 'function')
							this.opts.onScrollX.call(this.widget, opts.startStep + opts.offset);
					}
					if (opts.startStep == opts.endStep - opts.viewport)
						$drag.removeClass(cls + '-over');
				}
				break;
			case 'end':
				if (opts.startStep < opts.endStep - opts.viewport) {
					opts.startStep = opts.endStep - opts.viewport;
					if (this._isVer) {
						let moving: number;
						if (zk(this.eend).isVisible()) {
							moving = this.eend.offsetTop - (this.edrag.offsetHeight - this._gap);
						} else {
							moving = opts.startPosition + opts.viewportSize - (this.edrag.offsetHeight - this._gap);
						}
						this.epos.style.top = moving as unknown as string;
					$drag.animate({top: moving}, 500);
						if (typeof this.opts.onScrollY == 'function')
							this.opts.onScrollY.call(this.widget, opts.startStep + opts.offset);
					} else {
						let moving: number;
						if (zk(this.eend).isVisible()) {
							moving = this.eend.offsetLeft - (this.edrag.offsetWidth - this._gap);
						} else {
							moving = opts.startPosition + opts.viewportSize - (this.edrag.offsetWidth - this._gap);
						}
						this.epos.style.left = moving as unknown as string;
					$drag.animate({left: moving}, 500);
						if (typeof this.opts.onScrollX == 'function')
							this.opts.onScrollX.call(this.widget, opts.startStep + opts.offset);
					}
					$drag.removeClass(cls + '-over');
				}
				break;
		}
		this._syncButtonStatus();
	}

	/** @internal */
	_mousewheelY(evt: zk.Event, delta: number, deltaX: number, deltaY: number): void {
		if (deltaY) {
			evt.stop();
			var opts = this.opts,
				steps = opts.startStep,
				endStep = opts.endStep - opts.viewport,
				scale = this._scale,
				wgt = this.widget;
			if (deltaY > 0) { // up
				opts.startStep -= Math.max(Math.round(wgt._cols! / 5), 1);
				if (opts.startStep < 0)
					opts.startStep = 0;
			} else { // down
				opts.startStep += Math.max(Math.round(wgt._cols! / 5), 1);
				if (opts.startStep > endStep)
					opts.startStep = endStep;
			}
			if (steps == opts.startStep)
				return;// nothing changed

			var moving = opts.startPosition + (opts.startStep * scale),
				end = zk(this.eend).isVisible() ? this.eend.offsetTop - (this.edrag.offsetHeight - this._gap)
					: opts.startPosition + opts.viewportSize - (this.edrag.offsetHeight - this._gap);
			this.epos.style.top = moving + 'px';

			if (moving > end)
				moving = end;

			this.edrag.style.top = moving + 'px';
			if (typeof this.opts.onScrollY == 'function')
				this.opts.onScrollY.call(this.widget, opts.startStep + opts.offset);
			this._syncButtonStatus();
		}
	}

	/** @internal */
	_mousewheelX(evt: zk.Event, delta: number, deltaX: number, deltaY: number): void {
		if (deltaX) {
			evt.stop();
			var opts = this.opts,
				steps = opts.startStep,
				endStep = opts.endStep - opts.viewport,
				scale = this._scale,
				wgt = this.widget;
			if (deltaX < 0) { // up
				opts.startStep -= Math.max(Math.round(wgt._cols! / 5), 1);
				if (opts.startStep < 0)
					opts.startStep = 0;
			} else { // down
				opts.startStep += Math.max(Math.round(wgt._cols! / 5), 1);
				if (opts.startStep > endStep)
					opts.startStep = endStep;
			}
			if (steps == opts.startStep)
				return;// nothing changed

			var moving = opts.startPosition + (opts.startStep * scale),
				end = zk(this.eend).isVisible() ? this.eend.offsetLeft - (this.edrag.offsetWidth - this._gap)
					: opts.startPosition + opts.viewportSize - (this.edrag.offsetWidth - this._gap);
			this.epos.style.left = moving + 'px';

			if (moving > end)
				moving = end;

			this.edrag.style.left = moving + 'px';
			if (typeof this.opts.onScrollX == 'function')
				this.opts.onScrollX.call(this.widget, opts.startStep + opts.offset);
			this._syncButtonStatus();
		}
	}

	/** @internal */
	_initDragdrop(): void {
		var orient = this._isVer ? 'v' : 'h',
			uuid = this.uid + '-' + orient + 'bar';
		this.node = jq(uuid, zk)[0];
		this.edrag = this.node.firstChild as HTMLElement;
		this.edragBody = this.edrag.childNodes[2];
		this.epos = this.edrag.nextSibling as HTMLElement;
		this.eend = this.node.lastChild as HTMLElement;

		// sync the gap between edrag and epos
		var s = this.epos.style,
			old = s.display;
		s.display = 'block';
		this._gap = this._isVer ? this.edrag.offsetHeight - this.epos.offsetHeight
			: this.edrag.offsetWidth - this.epos.offsetWidth;
		s.display = old;

		this.drag = new zk.Draggable(this, this.edrag, {
			constraint: this._isVer ? 'vertical' : 'horizontal',
			snap: snap,
			starteffect: starteffect,
			zIndex: '12000',
			ignoredrag: ignoredrag,
			endeffect: endeffect
		});
		jq(this.epos).hide();
	}

	destroy(): void {
		this.drag.destroy();
		this._unlistenMouseEvent();
		jq(this.node).remove();
		// @ts-expect-error: these variables should only become undefined after destroy
		this.node = this.edrag = this.epos = this.drag = undefined;
	}

	redraw(p: HTMLElement): void {
		var orient = this._isVer ? 'v' : 'h',
			ocls = this._isVer ? 'vertical' : 'horizontal',
			uuid = this.uid + '-' + orient + 'bar',
			zcls = this.widget.$s('wscroll');
		jq(p).append(/*safe*/ '<div id="' + uuid + '" class="' + zcls + '-' + ocls + '">'
			+ '<div class="' + zcls + '-drag">'
			+ '<div class="' + zcls + '-home" title="' + /*safe*/ msgzul.WS_HOME + '"></div>'
			+ '<div class="' + zcls + '-up" title="' + /*safe*/ msgzul.WS_PREV + '"></div>'
			+ '<div class="' + zcls + '-body"></div>'
			+ '<div class="' + zcls + '-down" title="' + /*safe*/ msgzul.WS_NEXT + '"></div>'
			+ '<div class="' + zcls + '-end" title="' + /*safe*/ msgzul.WS_END + '"></div>'
			+ '</div>'
			+ '<div class="' + zcls + '-pos"></div>'
			+ '<div class="' + zcls + '-endbar"></div>'
			+ '</div>');
	}

	/** @internal */
	_syncButtonStatus(): void {
		var zcls = this.zcls + '-wscroll',
			$drag = jq(this.edrag),
			opts = this.opts;
		$drag.toggleClass(zcls + '-head', opts.startStep == 0);
		$drag.toggleClass(zcls + '-tail', opts.startStep == opts.endStep - opts.viewport);
	}

	/**
	 * Sets the easing animation function for the scrolling effects.
	 * For more details, please refer to jquery's easing plugin.
	 * http://gsgd.co.uk/sandbox/jquery/easing/
	 */
	static easing = easing;
}