/**
 * JS for Apple Pay.
 *
 * @package flo-checkout
 */

/* global jQuery, floApplePay */
let NMC_Apple_Pay_Instance;
jQuery( document ).ready(
	function () {
		NMC_Apple_Pay_Instance = new NMC_Apple_Pay();
	}
);

class NMC_Apple_Pay {
	constructor() {
		console.debug( 'Construct Apple Pay instance.' );

		this.checkout_updated              = this.checkout_updated.bind( this );
		this.get_payment_request           = this.get_payment_request.bind( this );
		this.store_payment_request         = this.store_payment_request.bind( this );
		this.payment_method_selected       = this.payment_method_selected.bind( this );
		this.apple_pay_checkout            = this.apple_pay_checkout.bind( this );
		this.checkout_error                = this.checkout_error.bind( this );
		this.apple_pay_checkout_success    = this.apple_pay_checkout_success.bind( this );
		this.validate_merchant             = this.validate_merchant.bind( this );
		this.payment_authorized            = this.payment_authorized.bind( this );
		this.merchant_validated            = this.merchant_validated.bind( this );
		this.merchant_validation_error     = this.merchant_validation_error.bind( this );
		this.show_wc_error                 = this.show_wc_error.bind( this );
		this.disable_apple_pay             = this.disable_apple_pay.bind( this );
		this.handle_pending_session_update = this.handle_pending_session_update.bind( this );
		this.coupon_code_changed           = this.coupon_code_changed.bind( this );
		this.shipping_contact_selected     = this.shipping_contact_selected.bind( this );

		this.apply_pay_available      = false;
		this.method_id                = floApplePay.payment_method_id;
		this.payment_data_input_name  = floApplePay.payment_data_name;
		this.session                  = null;
		this.apple_pay_version        = 14;
		this.is_apple_pay_open        = false;
		this.payment_request_data     = false;
		this.pending_complete_payment = false;

		this.shipping_different_from_billing_before_selection = false;

		this.$body = jQuery( 'body' );
		this.$form = jQuery( 'form.woocommerce-checkout' );

		this.has_address_errors      = false;
		this.pending_session_updates = {
			completeShippingContactSelection: false,
			completeShippingMethodSelection: false,
			completePaymentMethodSelection: false,
			completeCouponCodeChange: false,
		};

		this.$body.on( 'updated_checkout', this.checkout_updated );
		this.$body.on( 'payment_method_selected', this.payment_method_selected );
		this.$form.on( 'checkout_place_order_' + this.method_id, this.apple_pay_checkout );
		this.$form.on( 'checkout_place_order_success', this.apple_pay_checkout_success );
		this.$body.on( 'checkout_error', this.checkout_error );

		this.initial_apple_pay_check();
	}

	initial_apple_pay_check() {
		console.debug( 'Initial check.' );
		try {
			if ( ! window.ApplePaySession ) {
				this.disable_apple_pay( 'Apple Pay JS API is not available.' );
				return;
			}

			// TODO: Provide fallback to earlier API versions.
			if ( ! ApplePaySession.supportsVersion( this.apple_pay_version ) ) {
				this.disable_apple_pay( 'Current Apple Pay JS version not supported.' );
				return;
			}

			if ( ! ApplePaySession.canMakePayments() ) {
				this.disable_apple_pay( 'Apple Pay cannot make payments.' );
				return;
			}

		} catch ( error ) {
			this.disable_apple_pay( error );
			return;
		}

		this.apply_pay_available = true;
	}

	checkout_updated() {
		console.debug( 'Checkout updated.' );
		if ( ! this.is_apple_pay_selected() ) {
			console.debug( 'Apple Pay is not selected, abort.' );
			return;
		}

		if ( ! this.is_shipping_different_from_billing() ) {
			console.debug( 'Shipping different from not correct, delay.' );
			this.shipping_different_from_billing_before_selection = false;
			this.set_shipping_different_from_billing( true );
			return;
		}

		this.$payment_option_li    = jQuery( '.wc_payment_method.payment_method_' + this.method_id );
		this.$payment_option_radio = jQuery( '#payment_method_' + this.method_id );

		if ( ! this.is_apple_pay_open ) {
			this.clear_session();
		}

		if ( this.apply_pay_available ) {
			this.$payment_option_radio.prop( 'disabled', false );
			this.$payment_option_li.show();

			if ( ! this.has_address_errors ) {
				this.get_payment_request();
			} else {
				console.debug( 'Waiting for address change.' );
			}
		} else {
			this.$payment_option_radio.prop( 'checked', false ).prop( 'disabled', true );
			this.$payment_option_li.hide();
		}
	}

	get_payment_request() {
		console.debug( 'Get payment request.' );
		NMS_AJAX.post(
			floApplePay.payment_request_ajax,
			{},
			this.store_payment_request,
			this.disable_apple_pay,
		);
	}

	store_payment_request( response ) {
		console.debug( 'Store payment request.', response );
		this.payment_request_data = response.data;

		if ( this.session && this.is_apple_pay_open && this.has_pending_session_updates() ) {
			this.handle_pending_session_update();
		} else {
			this.create_session( response.data );
		}
	}

	create_session() {
		console.debug( 'Create Apple Pay Session.' );
		const _this = this;

		this.clear_session();
		this.session = new ApplePaySession( this.apple_pay_version, this.payment_request_data );

		this.session.onvalidatemerchant        = this.validate_merchant;
		this.session.onpaymentauthorized       = this.payment_authorized;
		this.session.oncancel                  = this.dismissed;
		this.session.onshippingcontactselected = this.shipping_contact_selected;
		this.session.oncouponcodechanged       = this.coupon_code_changed;

		// Placeholder for future use.
		this.session.onpaymentmethodselected = function ( event ) {
			console.debug( 'Apple Pay - Payment method selected.', event );
			_this.pending_session_updates.completePaymentMethodSelection = true;
			_this.handle_pending_session_update();
		}

		// Placeholder for future use.
		this.session.onshippingmethodselected = function ( event ) {
			console.debug( 'Apple Pay - Shipping method selected.', event );
			_this.pending_session_updates.completeShippingMethodSelection = true;
			_this.handle_pending_session_update();
		}

		if ( this.is_apple_pay_selected() ) {
			this.start_apple_pay_session();
		}
	}

	disable_apple_pay( error ) {
		console.debug( 'Disable Apple Pay.' );
		this.apply_pay_available = false;

		if ( error ) {
			console.error( 'Error:', error );
		}
	}

	payment_method_selected() {
		console.debug( 'Payment method selected.' );

		if ( this.is_apple_pay_selected() ) {
			if ( ! this.apply_pay_available ) {
				this.disable_apple_pay();
				this.show_wc_error( floApplePay.i18n.apple_pay_not_supported );
				return false;
			}

			if ( this.is_shipping_different_from_billing_available() && ! this.is_shipping_different_from_billing() ) {
				this.shipping_different_from_billing_before_selection = this.is_shipping_different_from_billing();
				this.set_shipping_different_from_billing( true );

			} else if ( this.session ) {
				this.start_apple_pay_session();
			}

		} else {
			this.set_shipping_different_from_billing( this.shipping_different_from_billing_before_selection );
			console.debug( 'Reset shipping_different_from_billing.', this.shipping_different_from_billing_before_selection );
		}
	}

	apple_pay_checkout() {
		console.debug( 'Apple Pay checkout.' );
		if ( jQuery( '.payment_box.payment_method_' + this.method_id + ' input[name^="' + this.payment_data_input_name + '"]' ).length ) {
			return true;
		}
		if ( ! this.session ) {
			this.get_payment_request();
			return false;
		}
		return this.start_apple_pay_session();
	}

	start_apple_pay_session() {
		console.debug( 'Start Apple Pay session.' );
		this.is_apple_pay_open = true;
		this.session.begin();

		return false;
	}

	validate_merchant( event ) {
		console.debug( 'Validate merchant.', event );
		NMS_AJAX.post(
			floApplePay.merchant_validation_ajax,
			{
				validation_url: event.validationURL,
			},
			this.merchant_validated,
			this.merchant_validation_error,
		);
	}

	merchant_validated( response ) {
		console.debug( 'Merchant validation successful.', response );
		this.session.completeMerchantValidation( response.data );
	}

	merchant_validation_error( response ) {
		console.error( 'Merchant validation error', response );
		this.show_wc_error( floApplePay.i18n.merchant_validation_error );
		this.session.abort();
	}

	shipping_contact_selected( event ) {
		console.debug( 'Shipping contact selected.', event );
		this.pending_session_updates.completeShippingContactSelection = true;
		this.has_address_errors                                       = false;

		let errors = [];

		if ( this.is_shipping_different_from_billing_available() ) {
			errors = this.update_and_validate_field( '#shipping_city', event.shippingContact, errors );
			errors = this.update_and_validate_field( '#shipping_postcode', event.shippingContact, errors );
			errors = this.update_and_validate_field( '#shipping_country', event.shippingContact, errors );
		} else {
			console.debug( 'Shipping fields not available.' );
		}

		if ( errors.length ) {
			const data = {
				'newTotal': this.payment_request_data.total,
				'newLineItems': this.payment_request_data.lineItems,
				'errors': errors,
			}

			this.has_address_errors = true;
			this.session.completeShippingContactSelection( data );
			this.pending_session_updates.completeShippingContactSelection = false;
			console.debug( 'Complete pending session update.', 'completeShippingContactSelection' );
		}
	}

	coupon_code_changed( event ) {
		console.debug( 'Apple Pay - Coupon code changed.', event );
		this.pending_session_updates.completeCouponCodeChange = true;

		const coupon        = event.couponCode;
		const $coupon_form  = jQuery( 'form.woocommerce-form-coupon' );
		const $coupon_input = $coupon_form.find( 'input[name="coupon_code"]' );
		$coupon_input.val( coupon );
		$coupon_form.submit();
	}

	handle_pending_session_update() {
		console.debug( 'Handle pending session update.', this.pending_session_updates );

		// Loop through pending updates and handle them.
		for ( const [ action, is_pending ] of Object.entries( this.pending_session_updates ) ) {
			if ( ! is_pending ) {
				continue;
			}

			switch ( action ) {
				case 'completeCouponCodeChange':
					this.handle_pending_coupon_code_update();
					break;

				case 'completeShippingContactSelection':
				case 'completeShippingMethodSelection':
				case 'completePaymentMethodSelection':
					const data = {
						'newTotal': this.payment_request_data.total,
						'newLineItems': this.payment_request_data.lineItems,
					}

					this.session[ action ]( data );
					break;
			}
			this.pending_session_updates[ action ] = false;
			console.debug( 'Complete pending session update.', action );
		}
	}

	handle_pending_coupon_code_update() {
		console.debug( 'Handle pending coupon code update.' );

		const data         = {
			'newTotal': this.payment_request_data.total,
			'newLineItems': this.payment_request_data.lineItems,
		};
		const $coupon_form = jQuery( 'form.woocommerce-form-coupon' );

		if ( $coupon_form.prev().is( 'ul.woocommerce-error' ) ) {
			const message = $coupon_form.prev().find( 'li' ).text().trim();
			data.errors   = [ new ApplePayError( 'couponCodeInvalid', null, message ) ];
			console.error( 'Coupon code error:', message );
		} else {
			console.debug( 'Coupon code applied.' );
		}

		console.debug( 'Complete coupon code change.', data );

		this.session.completeCouponCodeChange( data );
	}

	payment_authorized( event ) {
		console.debug( 'Payment authorized', event );

		const errors = this.update_contact_data_fields( event.payment, [] );

		if ( errors.length ) {
			this.pending_complete_payment = false;
			this.session.completePayment(
				{
					status: ApplePaySession.STATUS_FAILURE,
					errors: errors,
				}
			);
		} else {
			this.pending_complete_payment = true;
			this.store_token_payment_data( event.payment.token.paymentData );
			this.$form.submit();
		}
	}

	apple_pay_checkout_success() {
		if ( ! this.is_apple_pay_selected() ) {
			return;
		}
		this.pending_complete_payment = false;
		console.debug( 'Apple Pay checkout success.' );
		this.session.completePayment(
			{
				status: ApplePaySession.STATUS_SUCCESS,
			}
		);
	}

	store_token_payment_data( payment_data ) {
		this.remove_token_payment_data()
		const $payment_box = jQuery( '.payment_box.payment_method_' + this.method_id );

		this.createHiddenInputs( $payment_box, payment_data );
	}

	remove_token_payment_data() {
		const $payment_box = jQuery( '.payment_box.payment_method_' + this.method_id );
		$payment_box.find( 'input' ).remove();
	}

	/**
	 * Iterate over the payment data object and create hidden inputs for each key-value pair.
	 *
	 * @param $payment_box     jQuery object for the payment method list item.
	 * @param data               Payment data object.
	 * @param parentKey          Parent key for nested objects.
	 */
	createHiddenInputs( $payment_box, data, parentKey = '') {
		for (const key in data) {
			if (data.hasOwnProperty( key )) {
				const value     = data[key];
				const inputName = parentKey ? `${parentKey}][${key}` : key;

				if (typeof value === 'object' && value !== null) {
					this.createHiddenInputs( $payment_box, value, inputName );
				} else {
					const $input = jQuery(
						'<input>',
						{
							type: 'hidden',
							name: `${this.payment_data_input_name}[${inputName}]`,
							value: value
						}
					);
					$payment_box.append( $input );
				}
			}
		}
	}

	update_contact_data_fields(payment_event, errors ) {
		const billing_contact = payment_event.billingContact;
		let shipping_contact  = payment_event.shippingContact;

		console.debug( 'Update contact data fields.', billing_contact, shipping_contact );

		if ( ! billing_contact ) {
			errors.push( new ApplePayError( 'unknown', '', 'No billing contact data.' ) );
			return errors;
		}

		errors = this.update_and_validate_field( '#billing_first_name', billing_contact, errors );
		errors = this.update_and_validate_field( '#billing_last_name', billing_contact, errors );
		errors = this.update_and_validate_field( '#billing_address_1', billing_contact, errors );
		errors = this.update_and_validate_field( '#billing_address_2', billing_contact, errors );
		errors = this.update_and_validate_field( '#billing_city', billing_contact, errors );
		errors = this.update_and_validate_field( '#billing_postcode', billing_contact, errors );
		errors = this.update_and_validate_field( '#billing_country', billing_contact, errors );

		errors = this.update_and_validate_field( '#billing_email', shipping_contact, errors );
		errors = this.update_and_validate_field( '#billing_phone', shipping_contact, errors );

		if ( ! this.is_shipping_different_from_billing_available() ) {
			return errors;
		}

		if (
			'available' !== this.payment_request_data.shippingContactEditingMode &&
			(
				'storePickup' === this.payment_request_data.shippingType ||
				'servicePickup' === this.payment_request_data.shippingType
			)
		) {
			// User is picking up the order, and cannot edit the shipping address.
			shipping_contact = billing_contact;
		}

		errors = this.update_and_validate_field( '#shipping_first_name', shipping_contact, errors );
		errors = this.update_and_validate_field( '#shipping_last_name', shipping_contact, errors );
		errors = this.update_and_validate_field( '#shipping_address_1', shipping_contact, errors );
		errors = this.update_and_validate_field( '#shipping_address_2', shipping_contact, errors );
		errors = this.update_and_validate_field( '#shipping_city', shipping_contact, errors );
		errors = this.update_and_validate_field( '#shipping_postcode', shipping_contact, errors );
		errors = this.update_and_validate_field( '#shipping_country', shipping_contact, errors );

		return errors;
	}

	update_and_validate_field( field_id, contact_data, errors ) {
		console.debug( 'Update and validate field.', field_id, contact_data, errors );
		const $field = jQuery( field_id );

		const type = field_id.includes( 'billing' ) ? 'billing' : 'shipping';

		// Remove everything up until the first underscore.
		const field_name = field_id.replace( /.*?_/, '' );

		const field_map = {
			'first_name': {
				value_prop: 'givenName',
				error_prop: 'name',
			},
			'last_name': {
				value_prop: 'familyName',
				error_prop: 'name',
			},
			'email': {
				value_prop: 'emailAddress',
				error_prop: 'emailAddress',
			},
			'phone': {
				value_prop: 'phoneNumber',
				error_prop: 'phoneNumber',
			},
			'address_1': {
				value_prop: 'addressLines',
				error_prop: 'addressLines',
			},
			'address_2': {
				value_prop: 'addressLines',
				error_prop: 'addressLines',
			},
			'city': {
				value_prop: 'locality',
				error_prop: 'locality',
			},
			'postcode': {
				value_prop: 'postalCode',
				error_prop: 'postalCode',
			},
			'country': {
				value_prop: 'countryCode',
				error_prop: 'countryCode',
			},
		};

		const data_property = field_map[ field_name ].value_prop;
		let value;

		switch ( field_name ) {
			case 'address_1':
				value = contact_data[ 'addressLines' ] ? contact_data[ 'addressLines' ][ 0 ] : '';
				break;
			case 'address_2':
				value = contact_data[ 'addressLines' ] ? contact_data[ 'addressLines' ][ 1 ] : '';
				break;
			default:
				value = contact_data[data_property] || '';
		}

		const is_invalid = $field.val( value ).
											trigger( 'change' ).
											closest( '.form-row' ).
											hasClass( 'woocommerce-invalid' );

		if ( is_invalid ) {
			const error_type     = 'billing' === type ? 'billingContactInvalid' : 'shippingContactInvalid';
			const error_property = field_map[ field_name ].error_prop;
			const error_message  = floApplePay.i18n.billing_validation[ data_property ] || 'Invalid ' + data_property + '.';
			errors.push( new ApplePayError( error_type, error_property, error_message ) );
		}
		return errors;
	}

	clear_session() {
		console.debug( 'Clear Apple Pay session.' );
		if ( this.session && this.session.abort ) {
			this.session.abort();
		}
		this.session                  = null;
		this.has_address_errors       = false;
		this.pending_complete_payment = false;
	}

	dismissed() {
		console.debug( 'Apple Pay dismissed.' );
		this.is_apple_pay_open  = false;
		this.has_address_errors = false;
	}

	is_apple_pay_selected() {
		return this.method_id === jQuery( 'input[name="payment_method"]:checked' ).val();
	}

	is_shipping_different_from_billing_available() {
		return jQuery( '#ship-to-different-address-checkbox' ).length;
	}

	is_shipping_different_from_billing() {
		return jQuery( '#ship-to-different-address-checkbox' ).is( ':checked' );
	}

	set_shipping_different_from_billing( value ) {
		jQuery( '#ship-to-different-address-checkbox' ).prop( 'checked', value ).trigger( 'change' );
	}

	checkout_error() {
		console.debug( 'Checkout error.' );
		this.remove_token_payment_data();
		if ( this.pending_complete_payment ) {
			this.session.completePayment( { status: ApplePaySession.STATUS_FAILURE } );
			this.pending_complete_payment = false
		}
	}

	show_wc_error( message ) {
		console.debug( 'Show WC error.', message );
		jQuery( '.woocommerce-NoticeGroup-checkout, .woocommerce-error, .woocommerce-message' ).remove();

		this.$form.prepend( '<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout"><ul class="woocommerce-error"><li>' + message + '</li></ul></div>' );
		this.$body.trigger( 'checkout_error', [message] );

		jQuery.scroll_to_notices( jQuery( '.woocommerce-NoticeGroup' ) );
	}

	has_pending_session_updates() {
		return Object.values( this.pending_session_updates ).some( Boolean );
	}
}
