import { parse, format, templateParser, parseDigit, templateFormatter } from 'input-format';
import { AsYouType } from 'libphonenumber-js';

const ANDROID_USER_AGENT_REG_EXP = /Android/i;

/**
 * @function isAndroid
 * @description Checks if the user agent is Android.
 *              Copied from the original function `isAndroid` from `input-format` package.
 *              The original function is not exported, so it was copied here.
 *
 * @since 5.5.0
 *
 * @return {boolean} Whether the user agent is Android.
 */
export const isAndroid = () => {
	// `navigator` is not defined when running mocha tests.
	if ( typeof navigator !== 'undefined' ) {
		return ANDROID_USER_AGENT_REG_EXP.test( navigator.userAgent );
	}
};

/**
 * @function getCaretPosition
 * @description Gets the caret position.
 *              Copied from the original function `getCaretPosition` from `input-format` package.
 *              The original function is not exported, so it was copied here.
 *
 * @since 5.5.0
 *
 * @param {HTMLElement} element The element to get the caret position from.
 *
 * @return {number} The caret position.
 */
export const getCaretPosition = ( element ) => {
	return element.selectionStart;
};

/**
 * @function setCaretPosition
 * @description Sets the caret position.
 *              Copied from the original function `setCaretPosition` from `input-format` package.
 *              The original function is not exported, so it was copied here.
 *
 * @since 5.5.0
 *
 * @param {HTMLElement} element       The element to set the caret position on.
 * @param {number}      caretPosition The caret position to set.
 */
export const setCaretPosition = ( element, caretPosition ) => {
	// Sanity check
	if ( caretPosition === undefined ) {
		return;
	}

	// Set caret position.
	// There has been an issue with caret positioning on Android devices.
	// https://github.com/catamphetamine/input-format/issues/2
	// I was revisiting this issue and looked for similar issues in other libraries.
	// For example, there's [`text-mask`](https://github.com/text-mask/text-mask) library.
	// They've had exactly the same issue when the caret seemingly refused to be repositioned programmatically.
	// The symptoms were the same: whenever the caret passed through a non-digit character of a mask (a whitespace, a bracket, a dash, etc), it looked as if it placed itself one character before its correct position.
	// https://github.com/text-mask/text-mask/issues/300
	// They seem to have found a basic fix for it: calling `input.setSelectionRange()` in a timeout rather than instantly for Android devices.
	// https://github.com/text-mask/text-mask/pull/400/files
	// I've implemented the same workaround here.
	if ( isAndroid() ) {
		setTimeout( () => element.setSelectionRange( caretPosition, caretPosition ), 0 );
	} else {
		element.setSelectionRange( caretPosition, caretPosition );
	}
};

/**
 * @function edit
 * @description Edits text based on the operation.
 *              Copied from the original function `edit` from `input-format` package.
 *              The original function is not exported, so it was copied here.
 *
 * @param {string} value     The value to edit.
 * @param {number} caret     The caret position.
 * @param {string} operation The operation to perform, one of `Backspace` or `Delete`.
 *
 * @return {object} The edited value and the caret position.
 */
export const edit = ( value, caret, operation ) => {
	// Edits text `value` (if `operation` is passed) and repositions the `caret` if needed.
	//
	// Example:
	//
	// value - '88005553535'
	// caret - 2 // starting from 0; is positioned before the first zero
	// operation - 'Backspace'
	//
	// Returns
	// {
	// 	value: '8005553535'
	// 	caret: 1
	// }
	//
	// Currently supports just 'Delete' and 'Backspace' operations
	//
	switch ( operation ) {
		case 'Backspace':
			// If there exists the previous character,
			// then erase it and reposition the caret.
			if ( caret > 0 ) {
				// Remove the previous character
				value = value.slice( 0, caret - 1 ) + value.slice( caret );
				// Position the caret where the previous (erased) character was
				caret--;
			}
			break;

		case 'Delete':
			// Remove current digit (if any)
			value = value.slice( 0, caret ) + value.slice( caret + 1 );
			break;
	}

	return { value, caret };
};

/**
 * @function parseDigitWithPlus
 * @description Parses a digit with a plus sign.
 *
 * @since 5.5.0
 *
 * @param {boolean} international Whether the phone number is in international format.
 *
 * @return {Function} The function to parse a digit with a plus sign.
 */
export const parseDigitWithPlus = ( international ) => ( character, value ) => {
	// Leading plus is allowed
	if ( international && character === '+' && ! value ) {
		return character;
	}
	// Digits are allowed
	return parseDigit( character );
};

/**
 * @function getFormattedInputText
 * @description Gets the formatted input text.
 *              Modified from the original function `formatInputText` from `input-format` package.
 *
 * @since 5.5.0
 *
 * @param {string}           inputValue    The input value.
 * @param {number}           caretPosition The caret position.
 * @param {string}           country       The country code.
 * @param {boolean}          international Whether the phone number is in international format.
 * @param {string|undefined} operation     The operation to perform, one of `Backspace` or `Delete`.
 *
 * @return {object} The formatted input text, caret position, and the AsYouType instance.
 */
export const getFormattedInputText = (
	inputValue,
	caretPosition,
	country,
	international,
	operation,
) => {
	const asYouType = new AsYouType( country );
	asYouType.input( inputValue );

	const parser = templateParser( asYouType.getTemplate(), parseDigitWithPlus( international ) );

	// Parse input value.
	// Get the `value` and `caret` position.
	let { value, caret } = parse( inputValue, caretPosition, parser );

	// If a user performed an operation ("Backspace", "Delete")
	// then apply that operation and get the new `value` and `caret` position.
	if ( operation ) {
		const newValueAndCaret = edit( value, caret, operation );

		value = newValueAndCaret.value;
		caret = newValueAndCaret.caret;
	}

	asYouType.reset();
	asYouType.input( value );
	const formatter = templateFormatter( asYouType.getTemplate(), 'x', true );

	// Format the `value` and reposition the caret accordingly.
	return {
		asYouType,
		formatted: format( value, caret, formatter ),
	};
};

/**
 * @function getTextAfterEraseSelection
 * @description Gets the text after erasing the selection.
 *              Modified from the original function `eraseSelection` from `input-format` package.
 *              The original function is not exported, so it was copied here.
 *
 * @since 5.5.0
 *
 * @param {string} text      The text to erase the selection from.
 * @param {object} selection The selection to erase. The selection is an object with `start` and `end` properties.
 *
 * @return {string} The text after erasing the selection.
 */
export const getTextAfterEraseSelection = ( text, selection ) => {
	const start = Math.min( selection.start, selection.end );
	const end = Math.max( selection.start, selection.end );
	return text.slice( 0, start ) + text.slice( end );
};

/**
 * @function getSelection
 * @description Gets the selection.
 *              Copied from the original function `getSelection` from `input-format` package.
 *              The original function is not exported, so it was copied here.
 *
 * @since 5.5.0
 *
 * @param {HTMLElement} input The input element.
 *
 * @return {object|undefined} The selection object with `start` and `end` properties.
 */
export const getSelection = ( input ) => {
	// If no selection, return nothing
	if ( input.selectionStart === input.selectionEnd ) {
		return;
	}

	const start = Math.min( input.selectionStart, input.selectionEnd );
	const end = Math.max( input.selectionStart, input.selectionEnd );
	return { start, end };
};
