/**
 * External dependencies
 */
import { useEffect, useState, useRef, useCallback } from '@wordpress/element';
import { useStripe } from '@stripe/react-stripe-js';
import { getSetting } from '@woocommerce/settings';
import { __ } from '@wordpress/i18n';
import isShallowEqual from '@wordpress/is-shallow-equal';

/**
 * Internal dependencies
 */
import {
	getPaymentRequest,
	updatePaymentRequest,
	canDoPaymentRequest,
	normalizeShippingAddressForCheckout,
	normalizeShippingOptionSelectionsForCheckout,
	getStripeServerData,
	pluckAddress,
	normalizeShippingOptions,
} from '../stripe-utils';
import { useEventHandlers } from './use-event-handlers';

/**
 * @typedef {import('../stripe-utils/type-defs').StripePaymentRequest} StripePaymentRequest
 */

export const useInitialization = ( {
	billing,
	shippingData,
	setExpressPaymentError,
	onClick,
	onClose,
	onSubmit,
} ) => {
	const stripe = useStripe();
	/**
	 * @type {[ StripePaymentRequest|null, function( StripePaymentRequest ):void]}
	 */
	// @ts-ignore
	const [ paymentRequest, setPaymentRequest ] = useState( null );
	const [ isFinished, setIsFinished ] = useState( false );
	const [ isProcessing, setIsProcessing ] = useState( false );
	const [ canMakePayment, setCanMakePayment ] = useState( false );
	const [ paymentRequestType, setPaymentRequestType ] = useState( '' );
	const currentShipping = useRef( shippingData );
	const {
		paymentRequestEventHandlers,
		clearPaymentRequestEventHandler,
		setPaymentRequestEventHandler,
	} = useEventHandlers();

	// Update refs when any change.
	useEffect( () => {
		currentShipping.current = shippingData;
	}, [ shippingData ] );

	// Create the initial paymentRequest object. Note, we can't do anything if stripe isn't available yet or we have zero total.
	useEffect( () => {
		if (
			! stripe ||
			! billing.cartTotal.value ||
			isFinished ||
			isProcessing ||
			paymentRequest
		) {
			return;
		}
		const pr = getPaymentRequest( {
			total: billing.cartTotal,
			currencyCode: billing.currency.code.toLowerCase(),
			countryCode: getSetting( 'baseLocation', {} )?.country,
			shippingRequired: shippingData.needsShipping,
			cartTotalItems: billing.cartTotalItems,
			stripe,
		} );
		canDoPaymentRequest( pr ).then( ( result ) => {
			setPaymentRequest( pr );
			setPaymentRequestType( result.requestType || '' );
			setCanMakePayment( result.canPay );
		} );
	}, [
		billing.cartTotal,
		billing.currency.code,
		shippingData.needsShipping,
		billing.cartTotalItems,
		stripe,
		isProcessing,
		isFinished,
		paymentRequest,
	] );

	// When the payment button is clicked, update the request and show it.
	const onButtonClick = useCallback( () => {
		setIsProcessing( true );
		setIsFinished( false );
		setExpressPaymentError( '' );
		updatePaymentRequest( {
			// @ts-ignore
			paymentRequest,
			total: billing.cartTotal,
			currencyCode: billing.currency.code.toLowerCase(),
			cartTotalItems: billing.cartTotalItems,
		} );
		onClick();
	}, [
		onClick,
		paymentRequest,
		setExpressPaymentError,
		billing.cartTotal,
		billing.currency.code,
		billing.cartTotalItems,
	] );

	const abortPayment = useCallback( ( paymentMethod ) => {
		paymentMethod.complete( 'fail' );
		setIsProcessing( false );
		setIsFinished( true );
	}, [] );

	const completePayment = useCallback( ( paymentMethod ) => {
		paymentMethod.complete( 'success' );
		setIsFinished( true );
		setIsProcessing( false );
	}, [] );

	// whenever paymentRequest changes, hook in event listeners.
	useEffect( () => {
		const noop = { removeAllListeners: () => void null };
		let shippingAddressChangeEvent = noop,
			shippingOptionChangeEvent = noop,
			sourceChangeEvent = noop,
			cancelChangeEvent = noop;

		if ( paymentRequest ) {
			const cancelHandler = () => {
				setIsFinished( false );
				setIsProcessing( false );
				setPaymentRequest( null );
				onClose();
			};

			const shippingAddressChangeHandler = ( event ) => {
				const newShippingAddress = normalizeShippingAddressForCheckout(
					event.shippingAddress
				);
				if (
					isShallowEqual(
						pluckAddress( newShippingAddress ),
						pluckAddress( currentShipping.current.shippingAddress )
					)
				) {
					// the address is the same so no change needed.
					event.updateWith( {
						status: 'success',
						shippingOptions: normalizeShippingOptions(
							currentShipping.current.shippingRates
						),
					} );
				} else {
					// the address is different so let's set the new address and
					// register the handler to be picked up by the shipping rate
					// change event.
					currentShipping.current.setShippingAddress(
						normalizeShippingAddressForCheckout(
							event.shippingAddress
						)
					);
					setPaymentRequestEventHandler(
						'shippingAddressChange',
						event
					);
				}
			};

			const shippingOptionChangeHandler = ( event ) => {
				currentShipping.current.setSelectedRates(
					normalizeShippingOptionSelectionsForCheckout(
						event.shippingOption
					)
				);
				setPaymentRequestEventHandler( 'shippingOptionChange', event );
			};

			const sourceHandler = ( paymentMethod ) => {
				if (
					// eslint-disable-next-line no-undef
					! getStripeServerData().allowPrepaidCard &&
					paymentMethod.source.card.funding
				) {
					setExpressPaymentError(
						__(
							"Sorry, we're not accepting prepaid cards at this time.",
							'woocommerce-gateway-stripe'
						)
					);
					return;
				}
				setPaymentRequestEventHandler( 'sourceEvent', paymentMethod );
				// kick off checkout processing step.
				onSubmit();
			};

			// @ts-ignore
			shippingAddressChangeEvent = paymentRequest.on(
				'shippingaddresschange',
				shippingAddressChangeHandler
			);
			// @ts-ignore
			shippingOptionChangeEvent = paymentRequest.on(
				'shippingoptionchange',
				shippingOptionChangeHandler
			);
			// @ts-ignore
			sourceChangeEvent = paymentRequest.on( 'source', sourceHandler );
			// @ts-ignore
			cancelChangeEvent = paymentRequest.on( 'cancel', cancelHandler );
		}

		return () => {
			if ( paymentRequest ) {
				shippingAddressChangeEvent.removeAllListeners();
				shippingOptionChangeEvent.removeAllListeners();
				sourceChangeEvent.removeAllListeners();
				cancelChangeEvent.removeAllListeners();
			}
		};
	}, [
		paymentRequest,
		canMakePayment,
		isProcessing,
		setPaymentRequestEventHandler,
		setExpressPaymentError,
		onSubmit,
		onClose,
	] );

	return {
		paymentRequest,
		paymentRequestEventHandlers,
		clearPaymentRequestEventHandler,
		isProcessing,
		canMakePayment,
		onButtonClick,
		abortPayment,
		completePayment,
		paymentRequestType,
	};
};
