import type { VNode } from 'vue';
import type { DropdownButtonItemArgs } from '../dropdown-button/dropdown-button-item';
import type { FormItemWrapperArgs, HintType, MarginType } from '../form/form-item-wrapper';
import { render } from 'vue';
import { Prop, toNative } from 'vue-facing-decorator';
import TsxComponent, { Component } from '../../app/vuetsx';
import { capitalize } from '../../common/extensions/string-extensions';
import { PortalUtils } from '../../common/utils/utils';
import FormItemWrapper from '../form/form-item-wrapper';
import HtmlLiteral from '../html-literal/html-literal';
import './css/radio-button-group.css';

type RowToString = (row) => string;
interface RadioButtonGroupArgs extends Omit<FormItemWrapperArgs, 'errorId'> {
	changed: (newValue: string | any) => void;
	customRenderOption?: (h, state: RadioDisplayArgs) => any;
	customRenderSelectionResult?: (h, state: RadioDisplayArgs) => any;
	options: Array<string> | Array<any>;
	displayMember?: string | RowToString;
	valueMember?: string | RowToString;
	selected: string | any;
	radioButtonSize?: RadioButtonSize;
}

export const enum RadioButtonSize {
	Small = 'sm',
	Medium = 'md',
}

interface RadioDisplayArgs {
	id: string;
	text: string;
	dataRow: any;
}

@Component
class RadioButtonGroupComponent extends TsxComponent<RadioButtonGroupArgs> implements RadioButtonGroupArgs {
	@Prop() label!: string | VNode;
	@Prop() labelButtons!: DropdownButtonItemArgs[];
	@Prop() subtitle!: string;
	@Prop() cssClass!: string;
	@Prop() mandatory!: boolean;
	@Prop() wrap!: boolean;
	@Prop() hint: string;
	@Prop() hintType!: HintType;
	@Prop() appendIcon: string;
	@Prop() prependIcon: string;
	@Prop() appendClicked: () => void;
	@Prop() prependClicked: () => void;
	@Prop() radioButtonSize?: RadioButtonSize;
	@Prop() marginType?: MarginType;
	@Prop() maxWidth?: number;
	@Prop() options!: Array<string> | Array<any>;
	@Prop() displayMember!: (row) => string | string;
	@Prop() valueMember!: (row) => string | string;
	@Prop() selected!: string | any;
	@Prop() changed: (newValue: string | any) => void;
	@Prop() customRenderOption?: (h, state: RadioDisplayArgs) => any;
	@Prop() customRenderSelectionResult?: (h, state: RadioDisplayArgs) => any;

	private get errorId(): string {
		return `radiogroup-error-${this.$.uid}`;
	}

	getCustomFormatOptions(h, item: RadioDisplayArgs): string {
		if (this.customRenderOption != null) {
			return this.customRenderOption(h, item);
		}
	}

	raiseChangedEvent(newValue: RadioDisplayArgs) {
		if (newValue == this.selected) {
			return;
		}

		this.populateValidationDeclaration();

		if (this.changed != null) {
			this.changed(newValue);
		}
	}

	getValueFromArr(paramArr: string[], row): string {
		for (let i = 0, len = paramArr.length; i < len; i++) {
			let val = row[paramArr[i]];
			if (val == null) {
				val = row[paramArr[i][capitalize]()];
			}

			if (val != null) {
				if (PortalUtils.isString(val) && val.length > 0) {
					return val;
				} else if (PortalUtils.isNumber(val)) {
					return val.toString();
				} else if (PortalUtils.isFunction(val)) {
					return val.call(row, row);
				}
			}
		}

		return null;
	}

	getReflectedRowValue(row: any, isValueMember: boolean): string {
		const member = (isValueMember ? this.valueMember : this.displayMember);
		if (member == null) {
			if (isValueMember) {
				return this.getValueFromArr(['id'], row);
			} else {
				return this.getValueFromArr([
					'name',
					'text',
					'identifier',
				], row);
			}
		} else if (PortalUtils.isString(member)) {
			return row[member as any];
		} else if (PortalUtils.isNumber(member)) {
			return row[member as any].toString();
		} else if (PortalUtils.isFunction(member)) {
			return member.call(row, row);
		}
	}

	getOptions(h): RadioDisplayArgs[] {
		const retVal: RadioDisplayArgs[] = [];
		const opts = this.options as any;

		if (opts != null && opts.length > 0) {
			const firstItem = opts[0];
			if (PortalUtils.isString(firstItem)) {
				opts.forEach(item => retVal.push({ id: item, text: item, dataRow: item }));
			} else if (PortalUtils.isNumber(firstItem)) {
				opts.forEach(item => retVal.push({ id: item.toString(), text: item.toString(), dataRow: item.toString() }));
			} else {
				opts.forEach((item) => {
					retVal.push({
						id: this.getReflectedRowValue(item, true),
						text: this.getReflectedRowValue(item, false),
						dataRow: item,
					});
				});
			}
		}

		return retVal;
	}

	private handleCustomRenderResult(renderResult: any): string {
		if (renderResult == null) {
			return '';
		}

		if (renderResult?.__v_isVNode) {
			const container = document.createElement('div');
			render(renderResult as any, container);
			return container.innerHTML;
		}

		if (renderResult instanceof HTMLElement) {
			return renderResult.outerHTML;
		}

		return renderResult;
	}

	getSelectedItem(h, opts?: RadioDisplayArgs[]): RadioDisplayArgs {
		opts = opts || this.getOptions(h);

		if (this.selected == null || opts.length == 0) {
			return opts[0];
		}

		let selVal: string;
		const selectedItem = this.selected;
		if (PortalUtils.isString(selectedItem) || PortalUtils.isNumber(selectedItem)) {
			selVal = selectedItem.toString();
		} else {
			selVal = this.getReflectedRowValue(selectedItem, true);
		}

		for (let i = 0, len = opts.length; i < len; i++) {
			const ci = opts[i];
			if (ci.id == selVal) {
				return ci;
			}
		}

		return null;
	}

	render(h) {
		return (
			<FormItemWrapper
				label={this.label}
				mandatory={this.mandatory}
				wrap={this.wrap}
				appendIcon={this.appendIcon}
				prependIcon={this.prependIcon}
				hint={this.hint}
				hintType={this.hintType}
				appendClicked={this.appendClicked}
				prependClicked={this.prependClicked}
				marginType={this.marginType}
				maxWidth={this.maxWidth}
				validationState={this.validationState}
				labelButtons={this.labelButtons}
				subtitle={this.subtitle}
				cssClass={this.cssClass}
				errorId={this.errorId}
			>
				{this.renderInput(h)}
			</FormItemWrapper>
		);
	}

	handleRadioValueChanged(h) {
		const selectedValue = this.$el.querySelector('input[type="radio"]:checked').value;
		if (selectedValue != null) {
			const opts = this.getOptions(h);
			for (let i = 0, len = opts.length; i < len; i++) {
				const optItem = opts[i];
				if (optItem.id == selectedValue) {
					this.raiseChangedEvent(optItem.dataRow);
					break;
				}
			}
		}
	}

	renderInput(h) {
		const nameId = `iei-group-${PortalUtils.randomString(6)}`;
		const selectedItem = this.getSelectedItem(h);
		const isSmall = this.radioButtonSize === RadioButtonSize.Small;
		const hasCustomRender = this.customRenderOption != null;

		return (
			<div class="radio-group-wrap" role="radiogroup" aria-required={this.mandatory ? 'true' : undefined} aria-invalid={this.hasValidationError ? 'true' : undefined} aria-describedby={this.hasValidationError ? this.errorId : undefined}>
				{this.getOptions(h).map((optionItem) => {
					const uuid = `iei-input-${PortalUtils.randomString(10)}`;
					const selected = optionItem.id == selectedItem?.id;
					const disabled = optionItem.dataRow?.disabled ?? false;

					const wrapperClass = [
						'inv-rbchb',
						'form-check',
						'inv-mdfw-radio',
						selected ? 'inv-selected' : '',
						isSmall ? 'inv-md-radio-sm' : '',
					].join(' ');

					return (
						<div class={wrapperClass}>
							<div class="inv-cbrb-inner">
								<input
									class="form-check-input"
									key={uuid}
									checked={selected}
									type="radio"
									name={nameId}
									id={uuid}
									value={optionItem.id}
									disabled={disabled}
									onChange={() => this.handleRadioValueChanged(h)}
								/>
								<label class="form-check-label" for={uuid}>
									{hasCustomRender
										? this.customRenderOption(h, optionItem.dataRow)
										: <HtmlLiteral innerHTML={this.handleCustomRenderResult(optionItem.text)} />}
								</label>
							</div>
						</div>
					);
				})}
			</div>
		);
	}
}

const RadioButtonGroup = toNative(RadioButtonGroupComponent);
export default RadioButtonGroup;
