import type { VNode } from 'vue';
import type { ValidationState } from '../../common/static-wrappers/interfaces/validation-interface';
import type { DropdownButtonItemArgs } from '../dropdown-button/dropdown-button-item';
import { Tooltip } from 'bootstrap';
import { Prop, toNative } from 'vue-facing-decorator';
import TsxComponent, { Component } from '../../app/vuetsx';
import { isNullOrEmpty } from '../../common/utils/is-null-or-empty';
import { PortalUtils } from '../../common/utils/utils';
import DropdownButtonItem from '../dropdown-button/dropdown-button-item';
import './css/form-item-wrapper.css';

export enum HintType {
	Default = 0,
	Label = 1,
}

export enum MarginType {
	Standard = 0,
	None = 1,
	Small = 2,
}

export interface FormItemWrapperArgs {
	label?: string | VNode;
	mandatory?: boolean;
	hint?: string;
	hintType?: HintType;
	marginType?: MarginType;
	subtitle?: string;
	labelButtons?: DropdownButtonItemArgs[];
	flex?: boolean;
	cssClass?: string;
	maxWidth?: number;
	validationState?: ValidationState;
	showClearValueButton?: boolean;
	appendIcon?: string;
	prependIcon?: string;
	appendClicked?: () => void;
	prependClicked?: () => void;
	prependIconClicked?: () => void;
	appendIconClicked?: () => void;
	wrap?: boolean;
	labelInputRow?: boolean;
	errorId?: string;
}

export class FormItemWrapperConfig {
	static labelColumnClass: string = 'col-md-4';
	static contentColumnClass: string = 'col-md-8';
	static customRender: (this: typeof FormItemWrapper.prototype, h, instance?: typeof FormItemWrapper.prototype) => void;
}

@Component
class FormItemWrapperComponent extends TsxComponent<FormItemWrapperArgs> implements FormItemWrapperArgs {
	@Prop() label: string | VNode;
	@Prop() labelButtons: DropdownButtonItemArgs[];
	@Prop() mandatory: boolean;
	@Prop() flex: boolean;
	@Prop() hint: string;
	@Prop() subtitle: string;
	@Prop() hintType: HintType;
	@Prop() marginType?: MarginType;
	@Prop() maxWidth?: number;
	@Prop() appendIcon: string;
	@Prop() prependIcon: string;
	@Prop() cssClass: string;
	@Prop() showClearValueButton?: boolean;
	@Prop() appendClicked: () => void;
	@Prop() prependClicked: () => void;
	@Prop() prependIconClicked: () => void;
	@Prop() appendIconClicked: () => void;
	@Prop() wrap: boolean;
	@Prop() labelInputRow?: boolean;
	@Prop() errorId?: string;
	tooltipBound: boolean = false;
	uuid: string = PortalUtils.randomString(8);
	private labelAssociationDone: boolean = false;

	get labelId(): string {
		return `fiw-label-${this.uuid}`;
	}

	mounted() {
		if (this.showClearValueButton) {
			this.bindClearValueButton();
		}

		this.ensureControlLabelled();
	}

	updated() {
		if (this.showClearValueButton) {
			this.handleCurrentInputState();
		}

		this.ensureControlLabelled();
	}

	// ── Accessible-name association ───────────────────────────────────────────
	// The visible field label lives in this wrapper, but the actual control is
	// passed in via the default slot, so we cannot wire `for`/`id` at render
	// time. Instead we associate them on the client (mounted/updated never run
	// during SSR) by pointing the control at the label via `aria-labelledby`.
	// Conservative: only ever ADDS a name to a control that has none, and skips
	// controls that are already named natively or via an explicit/implicit
	// <label>, so it can only improve the accessibility tree, never break it.
	ensureControlLabelled(): void {
		if (this.labelAssociationDone) {
			return;
		}

		const root = this.$el as HTMLElement | null;
		if (root == null) {
			return;
		}

		const labelEl = root.querySelector<HTMLElement>('label.col-form-label');
		if (labelEl == null || isNullOrEmpty(labelEl.textContent?.trim())) {
			return;
		}

		const control = root.querySelector<HTMLElement>('input, select, textarea, [role="combobox"], [role="switch"], [role="radiogroup"], [role="listbox"], [role="spinbutton"]');
		if (control == null) {
			return;
		}

		// Already has a programmatic name — leave it alone.
		if (control.hasAttribute('aria-label') || control.hasAttribute('aria-labelledby')) {
			this.labelAssociationDone = true;

			return;
		}

		// Wrapped by an (implicit) <label> already — e.g. NowUi checkbox/radio skins.
		if (control.closest('label') != null) {
			this.labelAssociationDone = true;

			return;
		}

		// Has an explicit <label for=…> elsewhere in the field — e.g. Material skin.
		const controlId = control.getAttribute('id');
		if (controlId != null && root.querySelector(`label[for="${controlId}"]`) != null) {
			this.labelAssociationDone = true;

			return;
		}

		labelEl.id = this.labelId;
		control.setAttribute('aria-labelledby', this.labelId);
		this.labelAssociationDone = true;
	}

	handleAppendClicked() {
		if (this.appendClicked != null) {
			this.appendClicked();
		}
	}

	handlePrependIconClicked(): void {
		if (this.prependIconClicked != null) {
			this.prependIconClicked();
		}
	}

	handleAppendIconClicked(): void {
		if (this.appendIconClicked != null) {
			this.appendIconClicked();
		}
	}

	getFormGroupBaseClass(): string {
		let retVal: string = null;
		if (this.appendIcon == null && this.prependIcon == null && this.hint == null) {
			retVal = 'form-group';
		} else {
			retVal = 'input-group';
		}

		if (this.marginType != null) {
			retVal += ` fiw-margintype-${this.marginType}`;
		}

		if (this.showClearValueButton) {
			retVal += ' fiw-has-clear-value';
		}

		return retVal;
	}

	getPropCssClass() {
		return (!isNullOrEmpty(this.cssClass) ? `${this.cssClass} ` : '');
	}

	getFlexCssClass() {
		return (this.flex != true ? '' : 'frm-item-flex' + ' ');
	}

	ensureTooltip() {
		if (!isNullOrEmpty(this.hint) && !this.tooltipBound) {
			this.$nextTick(() => {
				const root = this.$el as HTMLElement;
				const selector = this.hintType == HintType.Label
					? '.col-form-label i'
					: '.input-group-append';
				const targets = Array.from(root.querySelectorAll<HTMLElement>(selector));

				// Match jQuery .hasClass() semantics: it returns true if ANY element in the
				// set has the class. Bail out if even one is already bound to avoid duplicate
				// Tooltip instances on re-renders.
				if (targets.length === 0 || targets.some(el => el.classList.contains('tooltip-bound'))) {
					return;
				}

				targets.forEach((el) => {
					el.classList.add('tooltip-bound');
					try {
						// eslint-disable-next-line no-new
						new Tooltip(el);
					} catch {}
				});
			});
		}
	}

	bindClearValueButton() {
		let elem: HTMLInputElement = this.$el.querySelector('.form-control');
		if (elem == null) {
			elem = this.$el.querySelector('input');
		}

		if (elem == null) {
			throw new Error('Unable to auto-bind to given control, has to be bound manually');
		}

		const button: HTMLElement = this.$el.querySelector('.fiw-clear-value-button');
		const valueCheck = () => {
			const val = elem.value;
			if (val) {
				button.classList.add('fiw-clear-value-button-visible');
			} else {
				button.classList.remove('fiw-clear-value-button-visible');
			}
		};

		const clearInput = () => {
			elem.value = '';
			elem.dispatchEvent(new Event('change'));
		};

		this.handleCurrentInputState = valueCheck;
		this.clearInputState = clearInput;
		elem.addEventListener('change', valueCheck);
	}

	// overriden in bindClearValueButton
	handleCurrentInputState() { }

	// overriden in bindClearValueButton
	clearInputState() { }

	render(h) {
		if (FormItemWrapperConfig.customRender != null) {
			return FormItemWrapperConfig.customRender.call(
				this,
				h,
				this,
			);
		}

		if (this.wrap != false) {
			const isHintLabel = this.hint && this.hintType == HintType.Label;
			this.ensureTooltip();

			return (
				<div class={`${this.labelInputRow === false ? 'column' : 'row'} form-item-wrapper`}>
					<label class={`${FormItemWrapperConfig.labelColumnClass} col-form-label`}>
						{this.label}

						{!isNullOrEmpty(this.labelButtons) && this.labelButtons.map(btn => (
							<span class="col-form-labelbuttons">
								<DropdownButtonItem
									text={btn.text}
									clicked={btn.clicked}
									disabled={btn.disabled}
									href={btn.href}
									hrefBlank={btn.hrefBlank}
									icon={btn.icon}
									img={btn.img}
									isSelected={btn.isSelected}
									emptySpace={false}
								/>
							</span>
						))}

						{this.mandatory
						&& <span class="is-required"></span>}

						{isHintLabel
						&& <i class="icon icon-info input-group-append ms-1" style="cursor: help;" title={this.hint}></i>}
					</label>
					<div class={`${FormItemWrapperConfig.contentColumnClass}`}>
						{this.maxWidth == null
							? this.renderInnerContent(h)
							: (<div style={`max-width:${this.maxWidth}px;`}>{this.renderInnerContent(h)}</div>)}

					</div>
				</div>
			);
		} else {
			return (<div class="formwrap-nomargin">{this.renderInnerContent(h)}</div>);
		}
	}

	renderInnerContent(h) {
		return (
			<div class={`${this.getFormGroupBaseClass()} maxwidth-input ${this.getPropCssClass()}${this.getFlexCssClass()}${this.validationCssClass}`}>
				{this.prependIcon
				&& <div class="input-group-text prepend-icon" style="cursor: pointer;" onClick={() => { this.handlePrependIconClicked(); }}><i class={this.prependIcon}></i></div>}

				{this.$slots.default?.()}
				{this.hint && this.hintType != HintType.Label
				&& <div onClick={() => { this.handleAppendClicked(); }} title={this.hint} class="input-group-append" style="cursor: help;"><div class="input-group-text" style=""><i class="now-ui-icons travel_info"></i></div></div>}

				{this.showClearValueButton
				&& <div onClick={() => { this.clearInputState(); }} class={`form-control fiw-clear-value-button${this.appendIcon ? ' fiw-clear-append-icon' : ''}`}>×</div>}

				{this.appendIcon
				&& <div class="input-group-text append-icon" style="cursor: pointer;" onClick={() => { this.handleAppendIconClicked(); }}><i class={this.appendIcon}></i></div>}

				{this.hasValidationError
				&& <div class="invalid-feedback" id={this.errorId || undefined} role="alert">{this.validationErrorMessage}</div>}

				{!isNullOrEmpty(this.subtitle)
				&& <small class="form-text text-muted col-form-subtitle">{this.subtitle}</small>}
			</div>
		);
	}
}

const FormItemWrapper = toNative(FormItemWrapperComponent);
export default FormItemWrapper;
