/**
 * Copyright © 2023-2024 Nevis Security AG. All rights reserved.
 */

import type { PinChangeRecoverableCustomValidationError } from '../../error/pin/change/PinChangeRecoverableCustomValidationError';
import { PinEnrollmentCustomValidationError } from '../../error/pin/enrollment/PinEnrollmentCustomValidationError';

/**
 * The object defining the minimum and maximum length of the PIN.
 */
export abstract class PinPolicy {
	/**
	 * The minimum length of the PIN.
	 */
	abstract minLength: number;

	/**
	 * The maximum length of the PIN.
	 */
	abstract maxLength: number;

	/**
	 * Default constructor for {@link PinPolicy}.
	 *
	 * @param minLength the minimum length of the PIN.
	 * @param maxLength the maximum length of the PIN.
	 * @returns an {@link PinPolicy} instance.
	 */
	static create(minLength: number, maxLength: number): PinPolicy {
		return new PinPolicyImpl(minLength, maxLength);
	}

	/**
	 * Performs validation other than the minimum and maximum PIN length during PIN
	 * enrollment.
	 *
	 * This must be implemented in the case you require additional criteria for a
	 * PIN to be valid: the PIN must contain a minimum number of distinct digits,
	 * sequences of consecutive digits are disallowed, etc.
	 *
	 * If this method is not implemented, only the PIN minimum and maximum length
	 * will be validated during PIN enrollment.
	 *
	 * If validation fails, the implementation must invoke the provided `onError`
	 * with the error. The error will be the one returned by the
	 * {@link PinEnrollmentContext.lastRecoverableError} property.
	 *
	 * The implementation must guarantee that, one (and only one) of `onSuccess`
	 * and `onError` is invoked, and that the one invoked is invoked only once.
	 *
	 * Synchronous implementation example that requires PIN to have at least 4
	 * different digits:
	 * ```ts
	 *  validatePinForEnrollment(
	 *      _pin: string,
	 *      onSuccess: () => void,
	 *      _onError: (error: PinEnrollmentCustomValidationError) => void
	 *  ): void {
	 *      if(hasLessThan4DifferentDigits(pin)) {
	 *          const error = new PinEnrollmentCustomValidationError(
	 *              "The PIN has less than 4 consecutive digits."
	 *          );
	 *          onError(error);
	 *      } else {
	 *          onSuccess();
	 *      }
	 *  }
	 * ```
	 *
	 * @param pin the PIN to be validated.
	 * @param onSuccess the callback to invoke if the validation is successful.
	 * @param onError the callback to invoke with error if the validation fails.
	 */
	abstract validatePinForEnrollment(
		pin: string,
		onSuccess: () => void,
		onError: (error: PinEnrollmentCustomValidationError) => void
	): void;

	/**
	 * Performs validation other than the minimum and maximum PIN length during PIN
	 * change.
	 *
	 * This must be implemented in the case you require additional criteria for a
	 * PIN to be valid: the PIN must contain a minimum number of distinct digits,
	 * sequences of consecutive digits are disallowed, etc.
	 *
	 * If this method is not implemented, only the PIN minimum and maximum length
	 * will be validated during PIN enrollment.
	 *
	 * If validation fails, the implementation must invoke the provided `onError`
	 * with the error. The error will be the one returned by the
	 * {@link PinEnrollmentContext.lastRecoverableError} property.
	 *
	 * The implementation must guarantee that, one (and only one) of `onSuccess`
	 * and `onError` is invoked, and that the one invoked is invoked only once.
	 *
	 * Synchronous implementation example that requires the PIN to be the same as
	 * a confirmation PIN provided in another field:
	 * ```ts
	 *  validatePinForPinChange(
	 *      _pin: string,
	 *      onSuccess: () => void,
	 *      _onError: (error: PinChangeRecoverableCustomValidationError) => void
	 *  ): void {
	 *      if(isDifferentThanConfirmationPIN(pin)) {
	 *          const error = new PinChangeRecoverableCustomValidationError(
	 *              "The provided PINs do not match."
	 *          );
	 *          onError(error);
	 *      } else {
	 *          onSuccess();
	 *      }
	 *  }
	 * ```
	 *
	 * @param pin the PIN to be validated.
	 * @param onSuccess the callback to invoke if the validation is successful.
	 * @param onError the callback to invoke with error if the validation fails.
	 */
	abstract validatePinForPinChange(
		pin: string,
		onSuccess: () => void,
		onError: (error: PinChangeRecoverableCustomValidationError) => void
	): void;
}

export class PinPolicyImpl extends PinPolicy {
	minLength: number;
	maxLength: number;

	constructor(minLength: number, maxLength: number) {
		super();
		this.minLength = minLength;
		this.maxLength = maxLength;
	}

	validatePinForEnrollment(
		_pin: string,
		onSuccess: () => void,
		// eslint-disable-next-line  @typescript-eslint/no-unused-vars
		_onError: (error: PinEnrollmentCustomValidationError) => void
	): void {
		onSuccess();
	}

	validatePinForPinChange(
		_pin: string,
		onSuccess: () => void,
		// eslint-disable-next-line  @typescript-eslint/no-unused-vars
		_onError: (error: PinChangeRecoverableCustomValidationError) => void
	): void {
		onSuccess();
	}
}
