/**
 * A date field that reads and writes dd-mm-yyyy.
 *
 * `<input type="date">` draws its own separators from the browser's locale —
 * dd/mm/yyyy here — and no attribute or stylesheet can change that, so the
 * visible control is a text input we format ourselves. The native calendar is
 * not lost: an off-screen type="date" input sits alongside it and the calendar
 * button opens that one through showPicker().
 *
 * `value` and `onChange` speak ISO yyyy-mm-dd, which is what the log filters
 * and the REST API expect. Only the display is hyphenated and day-first.
 */
import { useEffect, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

import { Icon, Input } from '../ui/atoms';

/** Length of a complete dd-mm-yyyy string. */
const DISPLAY_LENGTH = 10;

/**
 * Whether this browser can open a date picker on demand.
 *
 * Chrome 99, Edge 99, Firefox 101 and Safari 16 and up. Where it is missing
 * the calendar button is left out rather than rendered dead — the text field
 * still accepts a typed date.
 */
const CAN_OPEN_PICKER =
	'undefined' !== typeof window &&
	'showPicker' in window.HTMLInputElement.prototype;

/**
 * ISO yyyy-mm-dd to the displayed dd-mm-yyyy.
 *
 * @param {string} iso Stored value.
 * @return {string} Display value, empty when there is nothing stored.
 */
export function toDisplay( iso ) {
	const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec( iso || '' );

	return match ? `${ match[ 3 ] }-${ match[ 2 ] }-${ match[ 1 ] }` : '';
}

/**
 * Regroup whatever was typed as dd-mm-yyyy, dropping the hyphens in and out
 * as digits land and are deleted.
 *
 * @param {string} raw Raw field contents.
 * @return {string} Masked display value.
 */
export function mask( raw ) {
	const digits = String( raw ).replace( /\D/g, '' ).slice( 0, 8 );

	return [ digits.slice( 0, 2 ), digits.slice( 2, 4 ), digits.slice( 4, 8 ) ]
		.filter( ( part ) => '' !== part )
		.join( '-' );
}

/**
 * Displayed dd-mm-yyyy to ISO yyyy-mm-dd.
 *
 * @param {string} display Display value.
 * @return {string} ISO date, or empty when the date is not a real one.
 */
export function toIso( display ) {
	const match = /^(\d{2})-(\d{2})-(\d{4})$/.exec( display || '' );

	if ( ! match ) {
		return '';
	}

	const [ , day, month, year ] = match;
	const date = new Date( `${ year }-${ month }-${ day }T00:00:00Z` );

	if ( Number.isNaN( date.getTime() ) ) {
		return '';
	}

	// 31-02 has to be rejected by hand: the Date constructor rolls an
	// impossible day over into the next month instead of failing.
	if (
		date.getUTCDate() !== Number( day ) ||
		date.getUTCMonth() + 1 !== Number( month )
	) {
		return '';
	}

	return `${ year }-${ month }-${ day }`;
}

export default function DateField( {
	label,
	value,
	onChange,
	size = 'small',
	rounded = 'xs',
	labelFontSize = 'xs',
	ariaLabel,
} ) {
	const [ text, setText ] = useState( () => toDisplay( value ) );
	const picker = useRef( null );

	// Follow the filter state when it is reset from elsewhere, e.g. the
	// "Clear filters" button.
	useEffect( () => {
		setText( toDisplay( value ) );
	}, [ value ] );

	const complete = DISPLAY_LENGTH === text.length;
	const invalid = complete && ! toIso( text );

	// A complete dd-mm-yyyy that still will not parse is a day that does not
	// exist, like 31-02. Saying "use dd-mm-yyyy" at that point is no help.
	const errorText = /^\d{2}-\d{2}-\d{4}$/.test( text )
		? __( 'That date does not exist.', 'rewind-form-bridge-for-brevo' )
		: __( 'Use dd-mm-yyyy.', 'rewind-form-bridge-for-brevo' );

	const handleChange = ( next ) => {
		const masked = mask( next );

		setText( masked );

		if ( '' === masked ) {
			onChange( '' );
			return;
		}

		const iso = toIso( masked );

		if ( iso ) {
			onChange( iso );
		}
	};

	// A half-typed date filters nothing, so on the way out either commit it or
	// put the field back to whatever is actually filtering.
	const handleBlur = () => {
		if ( '' !== text && ! toIso( text ) ) {
			setText( toDisplay( value ) );
		}
	};

	const openPicker = () => {
		try {
			picker.current?.showPicker();
		} catch ( error ) {
			// Safari refuses outside a user gesture it recognises; the typed
			// field is the fallback and needs no help.
		}
	};

	const suffix = (
		<>
			{ CAN_OPEN_PICKER && (
				<button
					type="button"
					className="rbfb-date-field__button"
					aria-label={ __(
						'Open the calendar',
						'rewind-form-bridge-for-brevo'
					) }
					onClick={ openPicker }
				>
					<Icon
						name="calendar_month"
						fontSize="lg"
						color="gray-500"
					/>
				</button>
			) }

			<input
				ref={ picker }
				type="date"
				className="rbfb-date-field__native"
				tabIndex={ -1 }
				aria-hidden="true"
				value={ value || '' }
				onChange={ ( event ) => onChange( event.target.value ) }
			/>
		</>
	);

	return (
		<Input
			type="text"
			inputMode="numeric"
			className="rbfb-date-field"
			label={ label }
			labelFontSize={ labelFontSize }
			size={ size }
			rounded={ rounded }
			placeholder={ __( 'dd-mm-yyyy', 'rewind-form-bridge-for-brevo' ) }
			maxLength={ DISPLAY_LENGTH }
			ariaLabel={ ariaLabel }
			value={ text }
			error={ invalid }
			errorText={ errorText }
			suffix={ suffix }
			onChange={ handleChange }
			onBlur={ handleBlur }
		/>
	);
}
