/* HeadWidget.ts

	Purpose:

	Description:

	History:
		Mon Dec 29 17:15:38     2008, Created by jumperchen

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.
*/
//Bug 1926480: opera failed to add listheader dynamically (since hdfakerflex introduced)
var _fixOnChildChanged = zk.opera ? function (head: HeadWidget): boolean {
	return !!(head.parent && head.parent.rerender()); //later
} : zk.$void;

function _syncFrozen(wgt: HeadWidget): void {
	var mesh = wgt.getMeshWidget(),
		frozen: zul.mesh.Frozen | undefined;
	if (mesh && (frozen = mesh.frozen)) {
		var hdfaker: HTMLTableColElement | undefined;
		if (mesh._nativebar) {
			frozen._syncFrozen();
		} else if ((hdfaker = mesh.ehdfaker)) {
			//_scrollScale is used in Scrollbar.js
			frozen._scrollScale = hdfaker.childNodes.length - frozen._columns! - 1;

			// Bug ZK-2264
			frozen._shallSyncScale = false;
		}
	}
}

@zk.WrapClass('zul.mesh.HeadWidget')
export class HeadWidget extends zul.Widget<HTMLTableRowElement> {
	// NOTE: Parent could be null because it is checked in `afterChildrenFlex_`.
	override parent?: zul.mesh.MeshWidget;
	override firstChild?: zul.mesh.HeaderWidget;
	override lastChild?: zul.mesh.HeaderWidget;
	override previousSibling?: zul.mesh.HeadWidget;
	override nextSibling?: zul.mesh.HeadWidget;
	/** @internal */
	_sizable?: boolean;
	// FIXME: The following three properties are never assigned.
	hdfaker!: HTMLTableColElement;
	bdfaker!: HTMLTableColElement;
	ftfaker!: HTMLTableColElement;
	/** @internal */
	_menupopup?: string;
	/** @internal */
	_mpop?: zul.menu.Menupopup;

	constructor() {
		super(); // FIXME: reconsider constructor params
		this.listen({ onColSize: this }, -1000);
	}

	/**
	 * @returns whether the width of the child column is sizable.
	 */
	isSizable(): boolean {
		return !!this._sizable;
	}

	/**
	 * Sets whether the width of the child column is sizable.
	 * If true, an user can drag the border between two columns (e.g.,
	 * {@link HeaderWidget})
	 * to change the widths of adjacent columns.
	 * @defaultValue `false`.
	 */
	setSizable(sizable: boolean, opts?: Record<string, boolean>): this {
		const o = this._sizable;
		this._sizable = sizable;

		if (o !== sizable || opts?.force) {
			this.rerender();
		}

		return this;
	}

	override isVisible(): boolean {
		return this._visible;
	}

	override setVisible(visible: boolean, opts?: Record<string, boolean>): this {
		const o = this._visible;
		this._visible = visible;

		if (o !== visible || opts?.force) {
			this.rerender();
			var mesh = this.getMeshWidget();
			setTimeout(function () {
				// ZK-2217: fix height if mesh.desktop exists
				if (mesh && mesh.desktop) {
					// ZK-2130: should fix ebody height
					// ZK-2217: should contain foot and paging
					var foot = mesh.$n('foot'),
						pgib = mesh.$n('pgib'),
						hgh = zk(mesh).contentHeight() - mesh.$n_('head').offsetHeight
							- (foot ? foot.offsetHeight : 0) - (pgib ? pgib.offsetHeight : 0)
							- (mesh._nativebar && mesh.frozen ? mesh.frozen.$n_().offsetHeight : 0);
					mesh.ebody!.style.height = jq.px0(hgh);
				}
			}, 0);
		}

		return this;
	}

	/** @internal */
	override removeChildHTML_(child: zul.mesh.HeaderWidget, ignoreDom?: boolean): void {
		super.removeChildHTML_(child, ignoreDom);
		if (!(this instanceof zul.mesh.Auxhead))
			for (var fs = zul.mesh.HeaderWidget._faker, i = fs.length; i--;)
				jq(child.uuid + '-' + fs[i], zk).remove();
	}

	//bug #3014664
	override setVflex(vflex: string): this { //vflex ignored for Listhead/Columns/Treecols
		return super.setVflex(false);
	}

	//bug #3014664
	override setHflex(hflex: string): this { //hflex ignored for Listhead/Columns/Treecols
		return super.setHflex(false);
	}

	/**
	 * @returns the mesh widget that this belongs to.
	 * @since 5.0.5
	 */
	getMeshWidget(): zul.mesh.MeshWidget | undefined {
		return this.parent;
	}

	onColSize(evt: zk.Event & { widths?: string[] }): void {
		var owner = this.parent!,
			widths = evt.widths,
			headWidth: string | 0 = 0;
		if (widths) {
			for (var i = 0; i < widths.length; i++)
				headWidth += parseInt(widths[i]);
			if (headWidth != 0) headWidth = jq.px0(headWidth);
		}
		owner._innerWidth = headWidth || owner.eheadtbl!.width || owner.eheadtbl!.style.width;
		owner.fire('onInnerWidth', owner._innerWidth);
		owner.fireOnRender(zk.gecko ? 200 : 60);
		owner.disableAutoSizing_(); // clear span and sizedByContent without rerender
	}

	/** @internal */
	override bind_(desktop: zk.Desktop | undefined, skipper: zk.Skipper | undefined, after: CallableFunction[]): void {
		super.bind_(desktop, skipper, after);
		after.push(() => {
			_syncFrozen(this);
		});
	}

	/** @internal */
	override unbind_(skipper?: zk.Skipper, after?: CallableFunction[], keepRod?: boolean): void {
		jq(this.hdfaker).remove();
		jq(this.bdfaker).remove();
		jq(this.ftfaker).remove();
		super.unbind_(skipper, after, keepRod);
	}

	/** @internal */
	override onChildAdded_(child: zul.mesh.HeaderWidget): void {
		super.onChildAdded_(child);
		if (this.desktop) {
			var _getWidth = zul.mesh.MeshWidget._getWidth,
				mesh = this.getMeshWidget()!;
			if (!_fixOnChildChanged(this) && mesh && mesh._fixHeaders()) {
				// refix-ZK-2466 : grid dynamic add childern should 'syncSize' at the end (instead of 'add one and trigger one's onSize')
				mesh._syncSize(true);
			}
			_syncFrozen(this);
			mesh._minWd = undefined;
			var frozen = mesh.frozen;
			if (frozen)
				frozen.onSize();

			// B70-ZK-2128: Auxhead doesn't have to add faker.
			if (this instanceof zul.mesh.Auxhead)
				return;

			// ZK-2098: recovery the header faker if not exists
			var head = this,
				fakers = ['hdfaker', 'bdfaker', 'ftfaker'];
			// B30-1926480: ie8 does not support array.forEach
			// eslint-disable-next-line @typescript-eslint/prefer-for-of
			for (var i = 0; i < fakers.length; i++) {
				var faker = fakers[i],
					$faker = jq(mesh['e' + faker]);
				// ZK-3643: Handle fakerbar/bar even if the faker exists
				if ($faker[0] != null) {
					//B70-ZK-2130: virtual bar doesn't have to add fakerbar
					//fkaker bar need recover if native bar has vscrollbar
					var $bar = jq(mesh).find('.' + head.$s('bar')), // head.$n('bar') still exists after remove
						bar = $bar[0],
						$hdfakerbar = jq(head.$n_('hdfaker')).find('[id*=hdfaker-bar]'),
						hdfakerbar = $hdfakerbar[0],
						barstyle = '', hdfakerbarstyle = '';

					// ZK-2096, ZK-2124: should refresh this.$n('bar') if children change with databinding
					// B30-1926480: the bar should be removed
					if ((faker == 'hdfaker') && bar) {
						var s: CSSStyleDeclaration;
						if (s = bar.style) {
							// ZK-2114: should not store display
							// barstyle = s.display ? 'display:' + s.display + ';' : '';
							barstyle += s.width ? 'width:' + s.width + ';' : '';
						}
						$bar.remove();
						delete this._subnodes.bar;

						if (hdfakerbar && (s = hdfakerbar.style)) {
							hdfakerbarstyle = s.display ? 'display:' + s.display + ';' : '';
							hdfakerbarstyle += s.width ? 'width:' + s.width + ';' : '';
						}
						$hdfakerbar.remove();
						delete this._subnodes['hdfaker-bar'];
					}

					if (!$faker.find(child.$n_(faker)).length) {
						var wd = _getWidth(child, child._hflexWidth ? child._hflexWidth + 'px' : child.getWidth());
						wd = wd ? 'width:' + wd + ';' : '';
						var html = '<col id="' + /*safe*/ child.uuid + '-' + /*safe*/ faker + '" style="' +/*safe*/ wd + '"></col>',
							index = child.getChildIndex();
						// B30-1926480: child can be added after any brother node
						if (index > 0)
							jq($faker.find('col')[index - 1]).after(/*safe*/ html);
						else
							$faker.append(/*safe*/ html);
					}

					// resync var
					$bar = jq(mesh).find('.' + head.$s('bar'));
					bar = $bar[0];
					$hdfakerbar = jq(head.$n_('hdfaker')).find('[id*=hdfaker-bar]');
					hdfakerbar = $hdfakerbar[0];

					if ((faker == 'hdfaker') && !bar) {
						if (!hdfakerbar)
							jq(head.$n_('hdfaker')).append(/*safe*/ '<col id="' + /*safe*/ head.uuid +
								'-hdfaker-bar" style="' + hdfakerbarstyle + '" ></col>');
						jq(head).append(/*safe*/ '<th id="' + /*safe*/ head.uuid + '-bar" class="' + head.$s('bar') + '" style="' + /*safe*/ barstyle + '" ></th>');
					}
				}
			}
		}
	}

	/** @internal */
	override onChildRemoved_(child: zul.mesh.HeaderWidget): void {
		super.onChildRemoved_(child);
		if (this.desktop) {
			if (!_fixOnChildChanged(this) && !this.childReplacing_
				&& this.parent!._fixHeaders())
				this.parent!.onSize();
			this.parent!._minWd = undefined;
			// Fix IE, FF for the issue B30-1926480-1.zul and B30-1926480.zul
			var mesh = this.getMeshWidget()!;
			mesh.rerender(1);
		}
	}

	/** @internal */
	override beforeChildrenFlex_(hwgt: zul.mesh.HeaderWidget): boolean { //HeaderWidget
		if (hwgt && !hwgt._flexFixed) {
			//bug #3033010
			//clear associated hdfaker and bdfaker
			var wgt = this.parent!,
				hdfaker = wgt.ehdfaker,
				bdfaker = wgt.ebdfaker,
				hdf = hdfaker ? hdfaker.firstChild : undefined,
				bdf = bdfaker ? bdfaker.firstChild : undefined;
			for (var h = this.firstChild; h; h = h.nextSibling) {
				// B70-ZK-2036: Do not adjust widget's width if it is not visible.
				if (h.isVisible() && h._nhflex! > 0) { //not min or undefined
					if (hdf) (hdf as HTMLElement).style.width = '';
					if (bdf) (bdf as HTMLElement).style.width = '';
				}
				if (hdf) hdf = hdf.nextSibling;
				if (bdf) bdf = bdf.nextSibling;
			}
		}
		return true;
	}

	/** @internal */
	override afterChildrenFlex_(hwgt?: zul.mesh.HeaderWidget): void { //hflex in HeaderWidget
		var wgt = this.parent!,
			ebody = wgt.ebody!,
			ehead = wgt.ehead,
			efoot = wgt.efoot,
			currentLeft = wgt._currentLeft;
		if (wgt) {
			if (wgt._cssflex && wgt.isChildrenFlex()) {
				var minWd = wgt._calcMinWds(),
					wds: Record<string, number> | number[] = minWd.wds,
					isFlex = false,
					emptyHead = true;

				// reset display
				ehead!.style.display = '';
				var tblWidth = 0,
					sizedByContent = wgt.isSizedByContent();
				for (let i = 0, hwgt = this.firstChild; hwgt; hwgt = hwgt.nextSibling, i++) {
					let wd = 0,
						finalWd = '',
						nhwgt = hwgt.$n_();
					if (hwgt._hflex == 'min' || (!hwgt._nhflex && sizedByContent)) {
						wd = wds[i];
					} else if (hwgt._width) {
						if (hwgt._width.indexOf('%') == -1) {
							wd = parseInt(hwgt._width);
						} else {
							finalWd = hwgt._width;
						}
					} else if (hwgt._nhflex! > 0)
						isFlex = true;

					if (!hwgt.isVisible())
						nhwgt.style.display = 'none';
					if (wd > 0 || finalWd != '') {
						if (!finalWd) {
							finalWd = jq.px(wd);
						}
						nhwgt.style.flex = '0 0 ' + finalWd;
						if (!isFlex)
							tblWidth += wd;
					}
					//check empty head
					if (hwgt.getLabel() || hwgt.getImage() || hwgt.nChildren)
						emptyHead = false;
				}

				//set table width
				var hdtbl = wgt.eheadtbl,
					bdtbl = wgt.ebodytbl,
					fttbl = wgt.efoottbl,
					tblWidthPx = jq.px0(tblWidth);
				if (hdtbl) {
					hdtbl.style.width = isFlex ? '100%' : tblWidthPx;
					if (bdtbl)
						bdtbl.style.width = isFlex ? '100%' : tblWidthPx;
					if (fttbl)
						fttbl.style.width = isFlex ? '100%' : tblWidthPx;
				}

				//set body column/footer column width
				var bdfaker = wgt.ebdfaker!,
					ftfaker = wgt.eftfaker,
					bdcol = bdfaker.firstChild,
					// eslint-disable-next-line zk/noNull
					ftcol: ChildNode | null | undefined,
					width0 = zul.mesh.MeshWidget.WIDTH0;
				if (ftfaker)
					ftcol = ftfaker.firstChild;
				wds = {};
				for (let hwgt = this.firstChild; hwgt && bdcol; hwgt = hwgt.nextSibling) {
					wds[hwgt.uuid] = hwgt.$n_().getBoundingClientRect().width;
					bdcol = bdcol.nextSibling;
				}
				bdcol = bdfaker.firstChild;
				for (let hwgt = this.firstChild; hwgt && bdcol; hwgt = hwgt.nextSibling) {
					var wd = wds[hwgt.uuid],
						wdpx = jq.px0(wd);
					if (hwgt.isVisible() && wdpx !== undefined) {
						(bdcol as HTMLElement).style.width = wdpx;
						if (ftcol)
							(ftcol as HTMLElement).style.width = wdpx;
					} else {
						(bdcol as HTMLElement).style.width = width0;
						if (ftcol)
							(ftcol as HTMLElement).style.width = width0;
					}
					bdcol = bdcol.nextSibling;
					if (ftcol)
						ftcol = ftcol.nextSibling;
				}
				ehead!.style.display = emptyHead ? 'none' : '';

				// css flex doesn't need 'bar' style, because 'colgroup' is disabled.
				const bar = this.$n('bar');
				if (bar && !bar.style.cssText)
					bar.style.display = 'none';
			} else {
				wgt._adjFlexWd();
				wgt._adjSpanWd(); //if there is span and shall span the column width for extra space

				// ZK-2772 Misaligned Grid columns
				if (wgt.frozen && !wgt._isAllWidths()) {
					wgt._calcSize(); // yes, we need to do it again.
				}
				// ZK-2551: need to restore scroll pos after flexs are fixed
				if (ebody.scrollWidth > ebody.clientWidth && currentLeft != ebody.scrollLeft) {
					ebody.scrollLeft = currentLeft;
					if (ehead)
						ehead.scrollLeft = currentLeft;
					if (efoot)
						efoot.scrollLeft = currentLeft;
				}
			}
		}
	}

	/** @internal */
	override deferRedrawHTML_(out: string[]): void {
		out.push('<tr', this.domAttrs_({ domClass: true }), ' class="z-renderdefer"></tr>');
	}

	/** @internal */
	override getFlexDirection_(): string {
		return 'row';
	}

	/** @internal */
	override afterClearFlex_(): void {
		var mesh = this.parent!;
		if (mesh._cssflex)
			mesh.ehdfaker!.style.display = '';
	}

	static redraw(this: HeadWidget, out: string[]): void {
		out.push('<tr', this.domAttrs_(), ' style="text-align: left;">');
		for (var w = this.firstChild; w; w = w.nextSibling)
			w.redraw(out);

		var mesh = this.getMeshWidget();
		if (mesh && mesh._nativebar)
			out.push('<th id="', this.uuid, '-bar" class="', this.$s('bar'), '" ></th>');
		out.push('</tr>');
	}
}