import { slugify } from '@gravityforms/utils';

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 );
	}

	return false;
};

/**
 * @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 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 };
};

/**
 * @function getId
 * @description Get the id from the prefix and key provided.
 *
 * @since 5.5.0
 *
 * @param {string} prefix The id prefix.
 * @param {string} key    The id key.
 *
 * @return {string} The id.
 */
export const getId = ( prefix, key ) => slugify( `${ prefix }-${ key }` );

/**
 * @function e164ToNumber
 * @description Convert an E.164 formatted phone number to a number.
 *
 * @since 5.5.0
 *
 * @throws {TypeError} If the input is not a string.
 *
 * @param {string} e164 The E.164 formatted phone number.
 *
 * @return {string} The phone number.
 */
export const e164ToNumber = ( e164 ) => {
	if ( typeof e164 !== 'string' ) {
		throw new TypeError( 'Input must be a string' );
	}
	return e164.replace( /^\+/, '' );
};

/**
 * @function numberToE164
 * @description Convert a phone number to E.164 format.
 *
 * @since 5.5.0
 *
 * @throws {TypeError} If the input is not a string.
 *
 * @param {string} number The phone number.
 *
 * @return {string} The E.164 formatted phone number.
 */
export const numberToE164 = ( number ) => {
	if ( typeof number !== 'string' ) {
		throw new TypeError( 'Input must be a string' );
	}
	return number.startsWith( '+' )
		? number
		: `+${ number }`;
};
