/* eslint-disable regexp/no-useless-escape */
import type { VNode } from 'vue';
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 FormItemWrapper from '../form/form-item-wrapper';
import './css/password-input.css';

export interface PasswordRequirements {
	minLength?: number;
	requireUppercase?: boolean;
	requireLowercase?: boolean;
	requireDigit?: boolean;
	requireSpecialChar?: boolean;
}

export class PasswordInputResources {
	static prefix = 'At least ';
	static minLength = '{0} characters';
	static uppercase = '1 uppercase letter';
	static lowercase = '1 lowercase letter';
	static digit = '1 number';
	static specialChar = '1 special character';
	static weak = 'Weak';
	static medium = 'Medium';
	static strong = 'Strong';
}

export enum PasswordStrength {
	None = 'none',
	Weak = 'weak',
	Medium = 'medium',
	Strong = 'strong',
}

interface PasswordInputArgs extends Omit<FormItemWrapperArgs, 'errorId'> {
	value: string;
	changed: (newValue: string) => void;
	placeholder?: string;
	disabled?: boolean;
	autoCompleteText?: string;
	requirements?: PasswordRequirements;
	showRequirements?: boolean;
	/** When true, reveal the requirements checklist immediately — even before the user types. */
	alwaysShowRequirements?: boolean;
	nameAttr?: string;
	updateMode?: 'input' | 'change';
}

@Component
class PasswordInputComponent extends TsxComponent<PasswordInputArgs> implements PasswordInputArgs {
	@Prop() label!: string | VNode;
	@Prop() value!: string;
	@Prop() placeholder!: string;
	@Prop() cssClass!: string;
	@Prop() mandatory!: boolean;
	@Prop() disabled!: boolean;
	@Prop() wrap!: boolean;
	@Prop() hint: string;
	@Prop() appendIcon: string;
	@Prop() prependIcon: string;
	@Prop() maxWidth?: number;
	@Prop() marginType?: MarginType;
	@Prop() nameAttr?: string;
	@Prop() autoCompleteText?: string;
	@Prop() changed: (newValue: string) => void;
	@Prop() updateMode?: 'input' | 'change';
	@Prop() requirements?: PasswordRequirements;
	@Prop() showRequirements?: boolean;
	@Prop() alwaysShowRequirements?: boolean;

	isPasswordVisible: boolean = false;

	get currentValue(): string {
		return this.value || '';
	}

	get meetsMinLength(): boolean {
		if (!this.requirements?.minLength) {
			return true;
		}

		return this.currentValue.length >= this.requirements.minLength;
	}

	get meetsUppercase(): boolean {
		if (!this.requirements?.requireUppercase) {
			return true;
		}

		return /[A-Z]/.test(this.currentValue);
	}

	get meetsLowercase(): boolean {
		if (!this.requirements?.requireLowercase) {
			return true;
		}

		return /[a-z]/.test(this.currentValue);
	}

	get meetsDigit(): boolean {
		if (!this.requirements?.requireDigit) {
			return true;
		}

		return /\d/.test(this.currentValue);
	}

	get meetsSpecialChar(): boolean {
		if (!this.requirements?.requireSpecialChar) {
			return true;
		}

		return /[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(this.currentValue);
	}

	get allRequirementsMet(): boolean {
		return this.meetsMinLength
			&& this.meetsUppercase
			&& this.meetsLowercase
			&& this.meetsDigit
			&& this.meetsSpecialChar;
	}

	get strength(): PasswordStrength {
		const val = this.currentValue;

		if (!val || val.length === 0) {
			return PasswordStrength.None;
		}

		let score = 0;

		if (val.length >= 8) {
			score++;
		}

		if (val.length >= 12) {
			score++;
		}

		if (/[A-Z]/.test(val)) {
			score++;
		}

		if (/[a-z]/.test(val)) {
			score++;
		}

		if (/\d/.test(val)) {
			score++;
		}

		if (/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(val)) {
			score++;
		}

		if (score <= 2) {
			return PasswordStrength.Weak;
		}

		if (score <= 4) {
			return PasswordStrength.Medium;
		}

		return PasswordStrength.Strong;
	}

	get strengthLabel(): string {
		switch (this.strength) {
			case PasswordStrength.Weak:
				return PasswordInputResources.weak;
			case PasswordStrength.Medium:
				return PasswordInputResources.medium;
			case PasswordStrength.Strong:
				return PasswordInputResources.strong;
			default:
				return '';
		}
	}

	get hasRequirements(): boolean {
		if (!this.requirements) {
			return false;
		}

		return !!(
			this.requirements.minLength
			|| this.requirements.requireUppercase
			|| this.requirements.requireLowercase
			|| this.requirements.requireDigit
			|| this.requirements.requireSpecialChar
		);
	}

	get isFeatureEnabled(): boolean {
		return this.showRequirements !== false && this.hasRequirements;
	}

	get shouldShowOverlay(): boolean {
		return this.isFeatureEnabled && (this.alwaysShowRequirements === true || this.currentValue.length > 0);
	}

	private togglePasswordVisibility(): void {
		this.isPasswordVisible = !this.isPasswordVisible;
	}

	private getTypeAttribute(): string {
		if (this.isPasswordVisible) {
			return 'text';
		}

		return 'password';
	}

	private getAutoCompleteText(): string {
		if (this.autoCompleteText) {
			return this.autoCompleteText;
		}

		return 'new-password';
	}

	private raiseChangeEvent(e: Event): void {
		this.populateValidationDeclaration();

		if (this.changed != null) {
			const newValue = (e.target as HTMLInputElement).value;
			this.changed(newValue);
		}
	}

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

	private renderRequirementItem(met: boolean, label: string) {
		return (
			<div class={`pwd-req-item ${met ? 'pwd-req-met' : 'pwd-req-unmet'}`}>
				<i class={met ? 'fa fa-check' : 'fa fa-xmark'}></i>
				<span>{label}</span>
			</div>
		);
	}

	private renderOverlay() {
		if (!this.shouldShowOverlay) {
			return null;
		}

		const items: any[] = [];

		if (this.requirements.minLength) {
			const label = PasswordInputResources.minLength.replace('{0}', String(this.requirements.minLength));
			items.push(this.renderRequirementItem(this.meetsMinLength, label));
		}

		if (this.requirements.requireUppercase) {
			items.push(this.renderRequirementItem(this.meetsUppercase, PasswordInputResources.uppercase));
		}

		if (this.requirements.requireLowercase) {
			items.push(this.renderRequirementItem(this.meetsLowercase, PasswordInputResources.lowercase));
		}

		if (this.requirements.requireDigit) {
			items.push(this.renderRequirementItem(this.meetsDigit, PasswordInputResources.digit));
		}

		if (this.requirements.requireSpecialChar) {
			items.push(this.renderRequirementItem(this.meetsSpecialChar, PasswordInputResources.specialChar));
		}

		return (
			<div class="pwd-overlay">
				{items}
			</div>
		);
	}

	private renderStrengthBar() {
		if (!this.isFeatureEnabled || this.currentValue.length === 0) {
			return null;
		}

		return (
			<div class={`pwd-strength-bar pwd-strength-${this.strength}`}>
				<div class="pwd-strength-fill"></div>
			</div>
		);
	}

	render(h) {
		const hasError = this.hasValidationError;

		return (
			<FormItemWrapper
				label={this.label}
				cssClass={`pwd-input-fiw ${this.cssClass || ''}`}
				mandatory={this.mandatory}
				wrap={this.wrap}
				appendIcon={this.isPasswordVisible ? 'fa fa-eye-slash' : 'fa fa-eye'}
				appendIconClicked={() => this.togglePasswordVisibility()}
				prependIcon={this.prependIcon}
				hint={this.hint}
				marginType={this.marginType}
				maxWidth={this.maxWidth}
				validationState={this.validationState}
				errorId={this.errorId}
			>
				<input
					type={this.getTypeAttribute()}
					disabled={this.disabled as any != true ? null : 'disabled'}
					value={this.value}
					name={this.nameAttr}
					onChange={e => this.raiseChangeEvent(e)}
					onInput={(e) => {
						if (this.updateMode == 'input') {
							this.raiseChangeEvent(e);
						}
					}}
					class={PowerduckState.getFormControlCssClass()}
					placeholder={this.placeholder}
					autocomplete={this.getAutoCompleteText()}
					aria-invalid={hasError ? 'true' : undefined}
					aria-describedby={hasError ? this.errorId : undefined}
					aria-required={this.mandatory ? 'true' : undefined}
				/>
				{this.renderOverlay()}
				{this.renderStrengthBar()}
			</FormItemWrapper>
		);
	}
}

const PasswordInput = toNative(PasswordInputComponent);
export default PasswordInput;
