/* Notification.ts

	Purpose:

	Description:

	History:
		Thu Mar 15 17:12:46     2012, Created by simon

Copyright (C) 2012 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.
*/
var _iconMap = {
		'warning': 'z-icon-exclamation-circle',
		'info': 'z-icon-info-circle',
		'error': 'z-icon-times-circle'
	} as const,
	_dirMap = {
		'u': 'up',
		'd': 'down',
		'l': 'left',
		'r': 'right'
	} as const;
export type NotificationType = keyof typeof _iconMap;

export interface NotificationOptions {
	ref?: zk.Widget;
	pos?: string;
	off?: zk.Offset;
	dur?: number;
	type?: zul.wgt.NotificationType;
	closable?: boolean;
}
/**
 * A notification widget.
 * @since 6.0.1
 */
@zk.WrapClass('zul.wgt.Notification')
export class Notification extends zul.wgt.Popup {
	/** @internal */
	override _keepVisible = true;
	/** @internal */
	_closable?: boolean;
	/** @internal */
	_dur?: number;
	/** @internal */
	_nftPos?: string;
	/** @internal */
	_dir?: keyof typeof _dirMap | 'n';
	/** @internal */
	_ref?: zk.Widget;
	/** @internal */
	_msg: string;
	/** @internal */
	_type: NotificationOptions['type'];

	constructor(msg: string, opts: NotificationOptions) {
		// @ts-expect-error: doesn't match zk.Widget.prototype.constructor
		super(msg, opts);
		this._msg = msg;
		this._type = opts.type;
		this._ref = opts.ref;
		this._dur = opts.dur;
		this._closable = opts.closable;
	}

	override redraw(out: string[]): void {
		var uuidHTML = this.uuid,
			iconHTML = this.$s('icon');
		out.push('<div', this.domAttrs_(), '>');
		if (this._ref) //output arrow if reference exist
			out.push('<div id="', uuidHTML, '-p" class="', this.$s('pointer'), '"></div>');
		out.push('<i id="', uuidHTML, '-icon" class="', iconHTML, ' ', (/*safe*/ _iconMap[this._type!]), '"></i>');
		out.push('<div id="', uuidHTML, '-cave" class="', this.$s('content'), '">',
			DOMPurify.sanitize(this._msg), '</div>');
		if (this._closable)
			out.push('<div id="', uuidHTML, '-cls" class="', this.$s('close'),
					'"><i id="', uuidHTML, '-clsIcon" class="', iconHTML, ' z-icon-times"></i></div>');
		out.push('</div>'); // not encoded to support HTML
	}

	/** @internal */
	override domClass_(no?: zk.DomClassOptions): string {
		var type = this._type,
			/*safe*/ s = super.domClass_(no);
		if (type)
			s += ' ' + this.$s(zUtl.encodeXML(type));
		return s;
	}

	/** @internal */
	override doClick_(evt: zk.Event, popupOnly?: boolean): void {
		var p = evt.domTarget;
		if (p == this.$n('cls') || p == this.$n('clsIcon')) //may click on font-icon
			this.close();
		else
			super.doClick_(evt, popupOnly);
	}

	override onFloatUp(ctl: zk.ZWatchController, opts: zk.FireOptions): void {
		if (opts && opts.triggerByFocus) //only mouse click should close notification
			return;
		if (!this.isVisible())
			return;
		var wgt: zk.Widget | undefined = ctl.origin as zk.Widget | undefined;
		for (var floatFound = false; wgt; wgt = wgt.parent) {
			if (wgt == this) {
				if (!floatFound)
					this.setTopmost();
				return;
			}
			if (wgt == this.parent && wgt.ignoreDescendantFloatUp_(this))
				return;
			floatFound = floatFound ?? wgt.isFloating_();
		}
		if (!this._closable && this._dur! <= 0)
			this.close({sendOnOpen: true});
	}

	override open(ref: zk.Widget, offset?: zk.Offset, position?: string, opts?: zul.wgt.PopupOptions): void {
		super.open(ref, offset, position, opts);
		this._fixarrow(ref); //ZK-1583: modify arrow position based on reference component
		zk(this).redoCSS(-1, {'fixFontIcon': true});
	}

	override position(ref: zk.Widget, offset?: zk.Offset, position?: string, opts?: zk.PositionOptions): void {
		if (ref && !ref.$n())
			return;
		super.position(ref, offset, position, opts);
		this._fixarrow(ref); //ZK-1583: modify arrow position based on reference component
	}

	/** @internal */
	override _posInfo(ref?: zul.wgt.Ref, offset?: zk.Offset, position?: string, opts?: zul.wgt.PopupOptions): zul.wgt.PositionInfo | undefined {
		this._fixPadding(position);
		return super._posInfo(ref, offset, position, opts);
	}

	/** @internal */
	_fixPadding(position?: string): void {
		var p = this.$n('p');
		if (!p)
			return;
		var n = this.$n()!,
			pw = 2 + (zk(p).borderWidth() / 2) || 0,
			ph = 2 + (zk(p).borderHeight() / 2) || 0;

		n.style.padding = '0';
		// cache arrow direction for _fixarrow() later
		switch (position) {
		case 'before_start':
		case 'before_center':
		case 'before_end':
			this._dir = 'd';
			n.style.paddingBottom = ph + 'px';
			break;
		case 'after_start':
		case 'after_center':
		case 'after_end':
			this._dir = 'u';
			n.style.paddingTop = ph + 'px';
			break;
		case 'end_before':
		case 'end_center':
		case 'end_after':
			this._dir = 'l';
			n.style.paddingLeft = pw + 'px';
			break;
		case 'start_before':
		case 'start_center':
		case 'start_after':
			this._dir = 'r';
			n.style.paddingRight = pw + 'px';
			break;
		case 'top_left':
		case 'top_center':
		case 'top_right':
		case 'middle_left':
		case 'middle_center':
		case 'middle_right':
		case 'bottom_left':
		case 'bottom_center':
		case 'bottom_right':
		case 'overlap':
		case 'overlap_end':
		case 'overlap_before':
		case 'overlap_after':
			this._dir = 'n';
			break;
		// at_pointer, after_pointer, etc.
		default:
			this._dir = 'n';
		}
	}

	/** @internal */
	_fixarrow(ref: zk.Widget): void {
		var p = this.$n('p');
		if (!p)
			return;

		var /*safe*/ pzcls = this.$s('pointer'),
			n = this.$n()!,
			refn = ref.$n()!,
			dir = this._dir,
			zkp = zk(p),
			pw = zkp.borderWidth(),
			ph = zkp.borderHeight(),
			nOffset = zk(n).revisedOffset(),
			refOffset = zk(refn).revisedOffset(),
			arrXOffset = (refn.offsetWidth - pw) / 2,
			arrYOffset = (refn.offsetHeight - ph) / 2;
		if (dir != 'n') {
			// positioning
			if (dir == 'u' || dir == 'd') {
				var b = dir == 'u',
					l1 = (n.offsetWidth - pw) / 2 || 0,
					l2 = refOffset[0] - nOffset[0] + arrXOffset || 0;
				p.style.left = (refn.offsetWidth >= n.offsetWidth ? l1 : l2) + 'px'; //ZK-1583: assign arrow position to reference widget if it is smaller than notification
				p.style[b ? 'top' : 'bottom'] = ((2 - ph / 2) || 0) + 'px';
				p.style[b ? 'bottom' : 'top'] = '';
			} else {
				var b = dir == 'l',
					t1 = (n.offsetHeight - ph) / 2 || 0,
					t2 = refOffset[1] - nOffset[1] + arrYOffset || 0;
				p.style.top = (refn.offsetHeight >= n.offsetHeight ? t1 : t2) + 'px'; //ZK-1583: assign arrow position to reference widget if it is smaller than notification
				p.style[b ? 'left' : 'right'] = ((2 - pw / 2) || 0) + 'px';
				p.style[b ? 'right' : 'left'] = '';
			}

			/*safe*/ p.className = pzcls + (_dirMap[dir!] ? ' ' + this.$s(_dirMap[dir!]) : '');
			jq(p).show();

		} else {
			p.className = pzcls;
			jq(p).hide();
		}
	}

	/** @internal */
	override openAnima_(ref?: zul.wgt.Ref, offset?: zk.Offset, position?: string, opts?: zul.wgt.PopupOptions): void {
		var self = this;
		jq(this.$n()).fadeIn(500, function () {
			self.afterOpenAnima_(ref, offset, position, opts);
		});
	}

	/** @internal */
	override closeAnima_(opts?: zul.wgt.PopupOptions): void {
		var self = this;
		jq(this.$n()).fadeOut(500, function () {
			self.afterCloseAnima_(opts);
		});
	}

	/** @internal */
	override afterCloseAnima_(opts?: zul.wgt.PopupOptions): void {
		if (opts && opts.keepVisible) {
			this.setVisible(false);
			this.setFloating_(false);
			if (opts.sendOnOpen)
				this.fire('onOpen', {open: false});
		} else {
			this.detach();
		}
	}

	/** @internal */
	override getPositionArgs_(): zul.wgt.PositionArgs {
		return [this._fakeParent, undefined, this._nftPos, undefined];
	}

	override reposition(): void {
		super.reposition();
		if (this._ref) this._fixarrow(this._ref);
	}

	/**
	 * Shows a notification.
	 * TODO
	 */
	static show(msg: string, pid: string, opts: NotificationOptions): void {
		if (!opts)
			opts = {};
		var ref = opts.ref,
			pos = opts.pos,
			dur = opts.dur,
			ntf = new zul.wgt.Notification(msg, opts),
			off = opts.off,
			n: HTMLElement | undefined,
			isInView = true;

		if (ref) {
			n = ref.$n('real') || ref.$n();
			isInView = zk(n).isRealScrollIntoView();
		}

		// TODO: allow user to specify arrow direction?

		//ZK-2687, don't show notification if wgt is not in view
		if (!isInView)
			return;

		if (!pos && !off)
			pos = ref ? 'end_center' : 'middle_center';

		jq(document.body).append(ntf);
		ntf._nftPos = pos;
		ntf.open(ref!, off, pos);

		// auto dismiss
		if (dur! > 0)
			setTimeout(function () {
				if (ntf.desktop)
					ntf.close();
			}, dur);
	}
}