import type { VNode } from 'vue';
import type { DropdownButtonItemArgs } from '../dropdown-button/dropdown-button-item';
import type { FormItemWrapperArgs, MarginType } from '../form/form-item-wrapper';
import { Prop, toNative } from 'vue-facing-decorator';
import PowerduckState from '../../app/powerduck-state';
import TsxComponent, { Component } from '../../app/vuetsx';
import { isNullOrEmpty } from '../../common/utils/is-null-or-empty';
import FormItemWrapper from '../form/form-item-wrapper';

export type BirthdateFormat = 'DD.MM.YYYY' | 'MM.DD.YYYY' | 'YYYY.MM.DD' | 'DD/MM/YYYY' | 'MM/DD/YYYY' | 'YYYY/MM/DD';

interface FormatConfig {
	separator: string;
	order: ('day' | 'month' | 'year')[];
	placeholder: string;
	maxLength: number;
}

const FORMAT_CONFIGS: Record<BirthdateFormat, FormatConfig> = {
	'DD.MM.YYYY': { separator: '.', order: [
		'day',
		'month',
		'year',
	], placeholder: 'DD.MM.YYYY', maxLength: 10 },
	'MM.DD.YYYY': { separator: '.', order: [
		'month',
		'day',
		'year',
	], placeholder: 'MM.DD.YYYY', maxLength: 10 },
	'YYYY.MM.DD': { separator: '.', order: [
		'year',
		'month',
		'day',
	], placeholder: 'YYYY.MM.DD', maxLength: 10 },
	'DD/MM/YYYY': { separator: '/', order: [
		'day',
		'month',
		'year',
	], placeholder: 'DD/MM/YYYY', maxLength: 10 },
	'MM/DD/YYYY': { separator: '/', order: [
		'month',
		'day',
		'year',
	], placeholder: 'MM/DD/YYYY', maxLength: 10 },
	'YYYY/MM/DD': { separator: '/', order: [
		'year',
		'month',
		'day',
	], placeholder: 'YYYY/MM/DD', maxLength: 10 },
};

interface BirthdateInputArgs extends Omit<FormItemWrapperArgs, 'errorId'> {
	value: Temporal.PlainDate;
	disabled?: boolean;
	placeholder?: string;
	format?: BirthdateFormat;
	changed: (newValue: Temporal.PlainDate) => void;
}

@Component
class BirthdateInputComponent extends TsxComponent<BirthdateInputArgs> implements BirthdateInputArgs {
	@Prop() label!: string | VNode;
	@Prop() labelButtons!: DropdownButtonItemArgs[];
	@Prop() subtitle!: string;
	@Prop() value!: Temporal.PlainDate;
	@Prop() disabled!: boolean;
	@Prop() placeholder!: string;
	@Prop() mandatory!: boolean;
	@Prop() wrap!: boolean;
	@Prop() hint: string;
	@Prop() appendIcon: string;
	@Prop() prependIcon: string;
	@Prop() appendClicked: () => void;
	@Prop() prependClicked: () => void;
	@Prop() maxWidth?: number;
	@Prop() marginType?: MarginType;
	@Prop() changed: (newValue: Temporal.PlainDate) => void;
	@Prop() showClearValueButton!: boolean;
	@Prop() format?: BirthdateFormat;

	inputValue: string = '';

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

	get formatConfig(): FormatConfig {
		return FORMAT_CONFIGS[this.format || 'DD.MM.YYYY'];
	}

	raiseChangeEvent(newValue: Temporal.PlainDate) {
		this.populateValidationDeclaration();

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

	getDisabled(): boolean {
		return this.disabled ?? false;
	}

	formatDateToDisplay(date: Temporal.PlainDate): string {
		if (date == null) {
			return '';
		}

		const config = this.formatConfig;
		const day = date.day.toString().padStart(2, '0');
		const month = date.month.toString().padStart(2, '0');
		const year = date.year.toString();

		// eslint-disable-next-line array-callback-return
		const parts = config.order.map((part) => {
			switch (part) {
				case 'day': return day;
				case 'month': return month;
				case 'year': return year;
			}
		});

		return parts.join(config.separator);
	}

	parseDateFromInput(input: string): Temporal.PlainDate | null {
		if (isNullOrEmpty(input)) {
			return null;
		}

		const config = this.formatConfig;
		const separatorRegex = new RegExp(`[^\\d${config.separator === '.' ? '\\.' : config.separator}]`, 'g');
		const cleaned = input.replace(separatorRegex, '');
		const parts = cleaned.split(config.separator);

		if (parts.length !== 3) {
			return null;
		}

		let day: number, month: number, year: number;

		config.order.forEach((part, index) => {
			const value = parseInt(parts[index], 10);
			switch (part) {
				case 'day':
					day = value;
					break;
				case 'month':
					month = value;
					break;
				case 'year':
					year = value;
					break;
			}
		});

		// Validate basic ranges
		if (isNaN(day) || isNaN(month) || isNaN(year)) {
			return null;
		}

		if (day < 1 || day > 31 || month < 1 || month > 12 || year < 1900 || year > 2100) {
			return null;
		}

		try {
			return Temporal.PlainDate.from({ year, month, day });
		} catch {
			return null;
		}
	}

	applyInputMask(value: string, previousValue: string): string {
		const config = this.formatConfig;
		const sep = config.separator;
		const sepRegex = sep === '.' ? '\\.' : sep;

		// Only allow digits and the configured separator
		let cleaned = value.replace(new RegExp(`[^\\d${sepRegex}]`, 'g'), '');

		// Remove leading separators
		while (cleaned.startsWith(sep)) {
			cleaned = cleaned.substring(1);
		}

		// Don't allow consecutive separators
		cleaned = cleaned.replace(new RegExp(`${sepRegex}{2,}`, 'g'), sep);

		// Split by separator
		const parts = cleaned.split(sep);

		// Limit to 3 parts
		if (parts.length > 3) {
			parts.length = 3;
		}

		// Process each part based on format order
		const result: string[] = [];

		for (let i = 0; i < parts.length; i++) {
			let part = parts[i];
			const partType = config.order[i];

			if (partType === 'day') {
				if (part.length > 2) {
					part = part.substring(0, 2);
				}

				if (part.length === 2) {
					const num = parseInt(part, 10);
					if (num > 31) {
						part = '31';
					} else if (num === 0) {
						part = '01';
					}
				}
			} else if (partType === 'month') {
				if (part.length > 2) {
					part = part.substring(0, 2);
				}

				if (part.length === 2) {
					const num = parseInt(part, 10);
					if (num > 12) {
						part = '12';
					} else if (num === 0) {
						part = '01';
					}
				}
			} else if (partType === 'year') {
				if (part.length > 4) {
					part = part.substring(0, 4);
				}
			}

			result.push(part);
		}

		// Auto-add separator when part is complete
		let finalValue = result.join(sep);
		const isTyping = value.length > previousValue.length;
		const endsWithSep = value.endsWith(sep);

		// Determine expected length of first two parts based on format
		const firstPartLength = config.order[0] === 'year' ? 4 : 2;
		const secondPartLength = config.order[1] === 'year' ? 4 : 2;

		if (result.length === 1 && result[0].length === firstPartLength && isTyping && !endsWithSep) {
			finalValue += sep;
		} else if (result.length === 2 && result[1].length === secondPartLength && isTyping && !endsWithSep) {
			finalValue += sep;
		}

		return finalValue;
	}

	handleInputChange(e: Event) {
		const input = e.target as HTMLInputElement;
		const newValue = input.value;
		const previousValue = this.inputValue;

		// Apply input mask
		const maskedValue = this.applyInputMask(newValue, previousValue);
		this.inputValue = maskedValue;

		// Update the input element value if it differs
		if (input.value !== maskedValue) {
			input.value = maskedValue;
		}

		// Try to parse and raise change event
		const parsedDate = this.parseDateFromInput(maskedValue);

		// Only raise change if we have a complete valid date or the input is empty
		if (parsedDate != null || isNullOrEmpty(maskedValue)) {
			this.raiseChangeEvent(parsedDate);
		}
	}

	handleBlur(e: Event) {
		const input = e.target as HTMLInputElement;
		const parsedDate = this.parseDateFromInput(input.value);

		if (parsedDate != null) {
			// Reformat to ensure consistent display
			this.inputValue = this.formatDateToDisplay(parsedDate);
			input.value = this.inputValue;
		} else if (!isNullOrEmpty(input.value)) {
			// Invalid date entered, try to preserve what we can or clear
			const parts = input.value.split('.');
			if (parts.length === 3 && parts[2].length === 4) {
				// Has year but invalid date - keep as is for user to fix
			} else {
				// Incomplete - leave for user to complete
			}
		}

		this.raiseChangeEvent(parsedDate);
	}

	mounted() {
		if (this.value != null) {
			this.inputValue = this.formatDateToDisplay(this.value);
		}
	}

	updated() {
		// Sync input value with prop if changed externally
		const propFormatted = this.formatDateToDisplay(this.value);
		const currentParsed = this.parseDateFromInput(this.inputValue);
		const currentFormatted = this.formatDateToDisplay(currentParsed);

		// Only update if the external value is different from what we have
		if (propFormatted !== currentFormatted) {
			this.inputValue = propFormatted;
		}
	}

	render(h) {
		return (
			<FormItemWrapper
				label={this.label}
				mandatory={this.mandatory}
				wrap={this.wrap}
				appendIcon={this.appendIcon}
				prependIcon={this.prependIcon}
				hint={this.hint}
				appendClicked={this.appendClicked}
				prependClicked={this.prependClicked}
				marginType={this.marginType}
				maxWidth={this.maxWidth}
				validationState={this.validationState}
				labelButtons={this.labelButtons}
				subtitle={this.subtitle}
				showClearValueButton={this.showClearValueButton}
				errorId={this.errorId}
			>
				<input
					type="text"
					inputmode="numeric"
					data-hasval={this.value != null}
					value={this.inputValue}
					disabled={this.getDisabled()}
					onInput={e => this.handleInputChange(e)}
					onBlur={e => this.handleBlur(e)}
					class={PowerduckState.getFormControlCssClass()}
					placeholder={this.formatConfig.placeholder}
					maxlength={this.formatConfig.maxLength}
					autocomplete="bday"
					aria-invalid={this.hasValidationError ? 'true' : undefined}
					aria-describedby={this.hasValidationError ? this.errorId : undefined}
					aria-required={this.mandatory ? 'true' : undefined}
				/>
			</FormItemWrapper>
		);
	}
}

const BirthdateInput = toNative(BirthdateInputComponent);
export default BirthdateInput;
