/* Tabs.ts

{{IS_NOTE
	Purpose:

	Description:

	History:
		Fri Jan 23 10:32:43 TST 2009, Created by Flyworld
}}IS_NOTE

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

{{IS_RIGHT
}}IS_RIGHT
*/
/**
 * A collection of tabs ({@link Tab}).
 *
 * @defaultValue {@link getZclass}: z-tabs.
 */
@zk.WrapClass('zul.tab.Tabs')
export class Tabs extends zul.Widget {
	override parent!: zul.tab.Tabbox | undefined;

	/** @internal */
	_tabsScrollLeft = 0;
	/** @internal */
	_tabsScrollTop = 0;
	/** @internal */
	_shallCheck?: boolean;
	/** @internal */
	_doingScroll?: Record<string, number>;

	/**
	 * @returns the tabbox owns this component.
	 */
	getTabbox(): zul.tab.Tabbox | undefined {
		return this.parent;
	}

	override getWidth(): string | undefined {
		var wd = this._width;
		if (!wd) {
			if (this.getTabbox()?.isVertical())
				return '50px';
		}
		return wd;
	}

	override onSize(): void {
		this._fixWidth(true); //ZK-2810: set height to tabbox when onSize (maybe setHeight or setWidth)

		// ZK-5429, ZK-5433: reset the cache when onSize
		let tabs = this.$n_();
		this._tabsScrollLeft = tabs.scrollLeft;
		this._tabsScrollTop = tabs.scrollTop;

		// Bug Z35-tabbox-004.zul, we need to check again.
		this._scrollcheck('init');
	}

	beforeSize(): void {
		var tabbox = this.getTabbox()!,
			width = tabbox.getWidth(),
			style = this.$n_().style;
		if ((!width || width.endsWith('%') || width == 'auto') && !tabbox.inAccordionMold() && !tabbox.isVertical()) {
			this.$n_('cave').style.width = '';
			if (style.width) {
				style.width = '';
				if (!tabbox.isTabscroll())
					tabbox.$n_().style.width = '';
			}
		}
	}

	/** @internal */
	override insertChildHTML_(child: zk.Widget, before?: zk.Widget, desktop?: zk.Desktop): void {
		var last = child.previousSibling,
			outHTML;
		if (before)
			jq(before).before(outHTML = child.redrawHTML_());
		else if (last)
			jq(last).after(outHTML = child.redrawHTML_());
		else {
			var edge = this.$n('edge');
			if (edge)
				jq(edge).before(outHTML = child.redrawHTML_());
			else
				jq(this.getCaveNode()).append(outHTML = child.redrawHTML_());
		}
		// ZK-5009, if out is empty, ignore for bind()
		if (outHTML) {
			child.bind(desktop);
		}
	}

	//bug #3014664
	override setVflex(vflex: boolean | string | undefined): this { //vflex ignored for Tabs
		if (vflex != 'min') vflex = false;
		return super.setVflex(vflex);
	}

	//bug #3014664
	override setHflex(hflex: boolean | string | undefined): this { //hflex ignored for Tabs
		if (hflex != 'min') hflex = false;
		return super.setHflex(hflex);
	}

	/** @internal */
	override bind_(desktop?: zk.Desktop, skipper?: zk.Skipper, after?: CallableFunction[]): void {
		super.bind_(desktop, skipper, after);
		zWatch.listen({onSize: this, onResponse: this, beforeSize: this});
	}

	/** @internal */
	override unbind_(skipper?: zk.Skipper, after?: CallableFunction[], keepRod?: boolean): void {
		zWatch.unlisten({onSize: this, onResponse: this, beforeSize: this});
		super.unbind_(skipper, after, keepRod);
	}

	/** @internal */
	_scrollcheck(way: string, tb?: zul.tab.Tab): void {
		this._shallCheck = false;
		var tabbox = this.getTabbox()!;
		if (!this.desktop
			|| (tabbox && (!tabbox.isRealVisible() || !tabbox.isTabscroll())))
			return;

		var tabs = this.$n(),
			tbx = tabbox.$n();

		if (!tabs || !tbx)
			return;	// tabbox is delete , no need to check scroll

		if (tabbox.isVertical()) { //vertical
			var tabsOffsetHeight = tabs.offsetHeight,
				tabsScrollTop = tabs.scrollTop,
				childHeight = 0;

			jq(this.$n_('cave')).children().each(function () {
				childHeight += this.offsetHeight;
			});

			if (tabbox._scrolling) { //already in scrolling status
				var btnsize = this._getArrowSize();
				if (tabs.offsetHeight <= btnsize) return;

				var sel = tabbox.getSelectedTab(),
					node = tb ? tb.$n() : (sel ? sel.$n() : undefined),
					nodeOffsetTop = node ? node.offsetTop : 0,
					nodeOffsetHeight = node ? node.offsetHeight : 0;

				if (childHeight <= tabsOffsetHeight + btnsize) {
					tabbox._scrolling = false;
					this._showbutton(false);
					tabs.style.height = jq.px0(tbx.offsetHeight - 2);
					tabs.scrollTop = 0;
				}
				switch (way) {
				case 'end':
					var d = childHeight - tabsOffsetHeight - tabsScrollTop;
					this._doScroll(d >= 0 ? 'down' : 'up', d >= 0 ? d : Math.abs(d));
					break;
				case 'init':
				case 'vsel':
					if (nodeOffsetTop < tabsScrollTop) {
						this._doScroll('up', tabsScrollTop - nodeOffsetTop);
					} else if (nodeOffsetTop + nodeOffsetHeight > tabsScrollTop + tabsOffsetHeight) {
						this._doScroll('down', nodeOffsetTop + nodeOffsetHeight - tabsScrollTop - tabsOffsetHeight);
					}
					break;
				}
			} else { // not enough tab to scroll
				if (childHeight - tabsOffsetHeight > 0) {
					tabbox._scrolling = true;
					this._showbutton(true);
					var btnsize = this._getArrowSize(),
						temp = tbx.offsetHeight - btnsize;
					tabs.style.height = temp > 0 ? temp + 'px' : '';
					if (way == 'end') {
						var d = childHeight - tabsOffsetHeight - tabsScrollTop + 2;
						if (d >= 0)
							this._doScroll(this.uuid, d);
					}
				} else {
					this._showbutton(false);
				}
			}
		} else if (!tabbox.inAccordionMold()) {
			var cave = this.$n_('cave'),
				sel = tabbox.getSelectedTab(),
				node = tb ? tb.$n() : (sel ? sel.$n() : undefined),
				nodeOffsetLeft = node ? node.offsetLeft : 0,
				nodeOffsetWidth = node ? node.offsetWidth : 0,
				tabsOffsetWidth = tabs.offsetWidth,
				tabsScrollLeft = this._tabsScrollLeft,
				childWidth = 0,
				toolbar = tabbox.toolbar,
				toolbarWidth = 0;

			jq(cave).children().each(function () {
				childWidth += this.offsetWidth;
			});

			if (toolbar && toolbar.desktop)
				toolbarWidth = toolbar.$n_().offsetWidth;

			if (tabbox._scrolling) { //already in scrolling status
				var btnsize = this._getArrowSize();
				tabbox.$n_('right').style.right = toolbarWidth + 'px';

				if (childWidth <= tabsOffsetWidth + btnsize) {
					tabbox._scrolling = false;
					this._showbutton(false);
					tabs.style.width = jq.px0(tbx.offsetWidth - toolbarWidth);
					this._fixTabsScrollLeft(0);
				}
				// scroll to specific position
				switch (way) {
				case 'end':
					var d = childWidth - tabsOffsetWidth - tabsScrollLeft;
					this._doScroll(d >= 0 ? 'right' : 'left', d >= 0 ? d : Math.abs(d));
					break;
				case 'init':
				case 'sel':
					if (nodeOffsetLeft == tabsScrollLeft) // nothing to do
						break;

					if (nodeOffsetLeft < tabsScrollLeft) {
						this._doScroll('left', tabsScrollLeft - nodeOffsetLeft);
					} else if (nodeOffsetLeft + nodeOffsetWidth > tabsScrollLeft + tabsOffsetWidth) {
						this._doScroll('right', nodeOffsetLeft + nodeOffsetWidth - tabsScrollLeft - tabsOffsetWidth);
					}
					break;
				}
			} else { // not enough tab to scroll
				if (childWidth - tabsOffsetWidth > 0) {
					tabbox._scrolling = true;
					this._showbutton(true);
					var btnsize = this._getArrowSize(),
						temp = tbx.offsetWidth - toolbarWidth - btnsize;//coz show button then getsize again
					tabs.style.width = temp > 0 ? temp + 'px' : '';
					tabbox.$n_('right').style.right = toolbarWidth + 'px';

					if (way == 'sel') {
						if (nodeOffsetLeft < tabsScrollLeft) {
							this._doScroll('left', tabsScrollLeft - nodeOffsetLeft);
						} else if (nodeOffsetLeft + nodeOffsetWidth > tabsScrollLeft + tabsOffsetWidth) {
							this._doScroll('right', nodeOffsetLeft + nodeOffsetWidth - tabsScrollLeft - tabsOffsetWidth);
						}
					}
				} else {
					this._showbutton(false);
				}
			}
		}
	}

	/** @internal */
	_doScroll(to: string, move: number): void {
		if (!this._doingScroll)
			this._doingScroll = {};
		if (move <= 0 || this._doingScroll[to])
			return;
		var step: number,
			self = this,
			tabs = this.$n();

		this._doingScroll[to] = move;
		//the tab bigger , the scroll speed faster
		step = move <= 60 ? 5 : (5 * (zk.parseInt(move / 60) + 1));
		//Use to scroll
		var goscroll = function (tabs, to: string, step: number): void {
			switch (to) {
			case 'right':
				self._fixTabsScrollLeft(self._tabsScrollLeft + step);
				break;
			case 'left':
				self._fixTabsScrollLeft(self._tabsScrollLeft - step);
				break;
			case 'up':
				self._fixTabsScrollTop(self._tabsScrollTop - step);
				break;
			default:
				self._fixTabsScrollTop(self._tabsScrollTop + step);
			}
			var tabsScrollLeft = self._tabsScrollLeft,
				tabsScrollTop = self._tabsScrollTop;
			self._fixTabsScrollLeft(tabsScrollLeft <= 0 ? 0 : tabsScrollLeft);
			self._fixTabsScrollTop(tabsScrollTop <= 0 ? 0 : tabsScrollTop);
		},
		run = setInterval(function () {
			if (!move || !self.desktop) {
				delete self._doingScroll![to];
				clearInterval(run);
				return;
			} else {
				//high speed scroll, need break
				move < step ? goscroll(tabs, to, move) : goscroll(tabs, to, step);
				move -= step;
				move = move < 0 ? 0 : move;
			}
		}, 10);
	}

	/** @internal */
	_getArrowSize(): number {
		var tabbox = this.getTabbox()!,
			isVer = tabbox.isVertical(),
			btnA = isVer ? tabbox.$n('up') : tabbox.$n('left'),
			btnB = isVer ? tabbox.$n('down') : tabbox.$n('right'),
			size = 0;
		if (btnA && btnB) {
			size = isVer ? btnA.offsetHeight + btnB.offsetHeight : btnA.offsetWidth + btnB.offsetWidth;
		}
		return size;
	}

	/** @internal */
	_showbutton(show: boolean): void {
		var tabbox = this.getTabbox()!;
		if (tabbox.isTabscroll()) {
			var cls = tabbox.$s('scroll');
			jq(tabbox).removeClass(cls);
			if (show) {
				// ZK-1959: the height of arrow should not change when the tabbox add tab
				if (!tabbox.isVertical() && !tabbox.inAccordionMold()) {
					var tb = tabbox.toolbar;
					tabbox.$n_('left').style.height = tabbox.$n_('right').style.height = '';
					if (tb)
						tb.$n_().style.height = '';
				}

				jq(tabbox).addClass(cls);
			}
		}
	}

	/** @internal */
	_fixWidth(toSel: boolean): void {
		var tabs = this.$n_(),
			tabbox = this.getTabbox()!,
			tbx = tabbox.$n_(),
			btnsize = tabbox._scrolling ? this._getArrowSize() : 0;
		this._fixHgh(toSel); //ZK-2810: don't set height to tabbox when deselect
		if (tabbox.isVertical()) {
			//LI in IE doesn't have width...
			if (tabs.style.width) {
				tabs._width = tabs.style.width;
			} else {
				//vertical tabs have default width 50px
				tabs.style.width = tabs._width ?? '50px';
			}
		} else if (!tabbox.inAccordionMold()) {
			if (tbx.offsetWidth < btnsize)
				return;
			if (tabbox.isTabscroll()) {
				var toolbar: zul.wgt.Toolbar | HTMLElement | undefined = tabbox.toolbar;
				if (toolbar)
					toolbar = toolbar.$n();
				if (!tbx.style.width) {
					tbx.style.width = '100%';
					if (tabbox._scrolling)
						tabs.style.width = jq.px0(zk(tbx).contentWidth() - (toolbar ? toolbar.offsetWidth : 0) - btnsize);
					else
						tabs.style.width = jq.px0(zk(tbx).contentWidth() - (toolbar ? toolbar.offsetWidth : 0));
				} else {
					if (tabbox._scrolling)
						tabs.style.width = jq.px0(zk(tbx).contentWidth() - (toolbar ? toolbar.offsetWidth : 0) - btnsize);
					else
						tabs.style.width = jq.px0(zk(tbx).contentWidth() - (toolbar ? toolbar.offsetWidth : 0));
				}
				if (toolbar && tabbox._scrolling)
					tabbox.$n_('right').style.right = toolbar.offsetWidth + 'px';

			} else {
				if (!tbx.style.width) {
					if (tbx.offsetWidth) {
						tbx.style.width = jq.px0(tbx.offsetWidth);
						tabs.style.width = jq.px0(zk(tbx).contentWidth() - zk(tabs).marginWidth());
					}
				} else {
					tabs.style.width = jq.px0(zk(tbx).contentWidth() - zk(tabs).marginWidth());
				}
			}
		}
	}

	/** @internal */
	_fixHgh(toSel: boolean): void {
		if (this.getTabbox()!._scrolling) return;
		var tabbox = this.getTabbox()!;
		//fix tabpanels's height if tabbox's height is specified
		//Ignore accordion since its height is controlled by each tabpanel
		if (tabbox.isVertical()) {
			const tabs = this.$n_(),
				tbx = tabbox.$n_(),
				u = tabbox.$n('up'),
				d = tabbox.$n('down'),
				cave = this.$n_('cave'),
				allTab = jq(cave).children();

			if (!tabbox.getHeight() && (!tabbox._vflex || tabbox._vflex == 'min')) { // B50-ZK-473: vflex 1
				if (!toSel) { //ZK-2810: clear height of tabbox when deselect
					jq(tbx).css('height', '');
				} else {
					var tabsHgh = allTab.length * allTab[0].offsetHeight, // default height
						seldPanel = tabbox.getSelectedPanel(),
						panelsHgh = seldPanel && seldPanel.getPanelContentHeight_() || 0,  //B60-ZK-965
					// NOTE: seldPanel.getPanelContentHeight_() could return NaN; the return value must
					// be validated before use.
					realHgh = Math.max(tabsHgh, panelsHgh);
					tbx.style.height = jq.px0(realHgh + zk(tbx).padBorderHeight());
				}
			}
			tabs.style.height = jq.px0(zk(tbx).contentHeight() - zk(tabs).marginHeight());

			if (u && d) {
				u.style.width = d.style.width = tabs.style.width;
			}
		} else {
			const r = tabbox.$n('right'),
				l = tabbox.$n('left'),
				tb = tabbox.toolbar?.$n(),
				tabs = this.$n(),
				hgh = jq.px0(tabs ? tabs.offsetHeight : 0);
			if (r && l) {
				r.style.height = l.style.height = hgh;
			}
			if (tb) {
				tb.style.height = hgh;
			}
			if (tabs)
				tabs.style.height = '';
		}
	}

	onResponse(): void {
		if (this._shallCheck) {
			this._scrollcheck('init');
		}
	}

	/** @internal */
	override beforeChildReplaced_(oldTab: zul.tab.Tab, newTab: zul.tab.Tab): void {
		newTab.setSelected(oldTab.isSelected());
	}

	/** @internal */
	override onChildRemoved_(child: zk.Widget): void {
		var p = this.parent;
		if (p && child == p._selTab) {
			p._selTab = undefined;
		}
		if (this.desktop)
			this._shallCheck = true;
		super.onChildRemoved_(child);
	}

	/** @internal */
	override onChildAdded_(child: zk.Widget): void {
		if (this.desktop)
			this._shallCheck = true;
		super.onChildAdded_(child);
	}

	/** @internal */
	override onChildVisible_(child: zk.Widget): void {
		if (this.desktop) {
			var tabbox = this.getTabbox()!;
			if (tabbox.inAccordionMold() && tabbox.getHeight()) {
				tabbox.syncSize();
			}
		}
		super.onChildVisible_(child);
	}

	/** @internal */
	override ignoreFlexSize_(attr: zk.FlexOrient): boolean {
		var p = this.getTabbox()!;
		return (p.isVertical() && 'h' == attr)
			|| (p.isHorizontal() && 'w' == attr);
	}

	/** @internal */
	_fixTabsScrollLeft(scrollLeft: number): void {
		this.$n_().scrollLeft = this._tabsScrollLeft = scrollLeft;
	}

	/** @internal */
	_fixTabsScrollTop(scrollTop: number): void {
		this.$n_().scrollTop = this._tabsScrollTop = scrollTop;
	}
}