/**
 * Vanquish Multiple Addresses — frontend.
 *
 * Drives two UIs that share one address editor:
 *   - My Account: the address book (cards + inline editor).
 *   - Checkout:   the saved-address selectors on the billing and shipping forms,
 *                 plus one modal editor rendered outside the checkout form, so
 *                 its fields are never posted with the order.
 *
 * An address has no billing/shipping type: both selectors offer the same list,
 * and an address simply carries two "use me by default" flags.
 */
( function ( $ ) {
	'use strict';

	if ( typeof window.vanmuad === 'undefined' ) {
		return;
	}

	var settings = window.vanmuad;

	// ------------------------------------------------- Country / state fields

	/*
	 * WooCommerce's own country-select.js cannot drive our fields: it looks the
	 * state field up by a hardcoded id ('#billing_state, #shipping_state,
	 * #calc_shipping_state'), and ours are namespaced so they do not collide with
	 * the real checkout fields sitting on the same page. So we rebuild the state
	 * control ourselves, from the same country/state data WooCommerce localises.
	 *
	 * The country and state fields are deliberately left as PLAIN selects.
	 *
	 * They were enhanced with selectWoo (WooCommerce's select2 fork — same
	 * internals, same behaviour) and it cost four separate bugs inside this
	 * editor: the dropdown painted behind the overlay; re-parenting it to fix
	 * that misplaced it, because select2 positions with document coordinates that
	 * do not hold inside a `position: fixed` box; instances came out of a
	 * selection stuck with aria-expanded="true"; and destroying an instance —
	 * which we do on every country change — orphaned its dropdown container in
	 * <body>, where it silently swallowed the page's scroll events.
	 *
	 * The common thread is that select2 does not cope with being torn down and
	 * rebuilt inside a fixed overlay. A native select has none of those problems,
	 * still supports type-ahead, and on mobile opens the OS picker, which is
	 * better than select2 anyway. The only loss is the search box on desktop.
	 */

	/**
	 * The country => states map, handed over with the rest of our config.
	 *
	 * Carried ourselves rather than read off WooCommerce's `wc-country-select`:
	 * that script exists only on the frontend, and depending on it made WordPress
	 * drop this one entirely in the admin (see the note on
	 * AddressForm::country_states).
	 *
	 * @return {Object} Country code => states object.
	 */
	function countryStates() {
		return settings.countries || {};
	}

	/**
	 * Apply WooCommerce's searchable select to one of our selects.
	 *
	 * @param {Object} $select The select.
	 */
	/**
	 * Keep WooCommerce's own scripts off one of our selects.
	 *
	 * `woocommerce_form_field()` hardcodes `country_to_state` and `country_select`
	 * on a country field, and those classes are what WooCommerce's country-select
	 * script binds to. It cannot do anything useful with our fields (it looks the
	 * state up by a hardcoded id) but it can still enhance them with selectWoo
	 * behind our back, which is exactly what we are getting away from.
	 *
	 * @param {Object} $select The select.
	 */
	function detachWooBehaviour( $select ) {
		$select.removeClass( 'country_to_state country_select state_select' );
	}

	/**
	 * Rebuild the state control for the selected country: a dropdown for countries
	 * that have states, a free-text input for those that do not.
	 *
	 * @param {Object} $form The editor container.
	 * @param {string} value State value to preselect.
	 */
	function refreshStateField( $form, value ) {
		var $state = $form.find( '[data-vanmuad-field="state"]' );

		if ( ! $state.length ) {
			return;
		}

		var country = $form.find( '[data-vanmuad-field="country"]' ).val() || '';
		var states = countryStates()[ country ];
		var name = $state.attr( 'name' );
		var id = $state.attr( 'id' );
		var $new;

		if ( ! states || $.isEmptyObject( states ) ) {
			$new = $( '<input type="text" />' )
				.attr( { name: name, id: id, 'data-vanmuad-field': 'state' } )
				.addClass( 'input-text' )
				.val( value || '' );

			$state.replaceWith( $new );

			return;
		}

		$new = $( '<select></select>' )
			.attr( { name: name, id: id, 'data-vanmuad-field': 'state' } );

		$new.append( $( '<option></option>' ).val( '' ).text( settings.i18n.select_state ) );

		$.each( states, function ( code, label ) {
			$new.append( $( '<option></option>' ).val( code ).text( label ) );
		} );

		$state.replaceWith( $new );
		$new.val( value || '' );
	}

	/**
	 * Prepare an editor's selects when it becomes visible.
	 *
	 * @param {Object} $form The editor container.
	 * @param {string} state State value to preselect ('' when adding).
	 */
	function enhanceEditor( $form, state ) {
		detachWooBehaviour( $form.find( '[data-vanmuad-field="country"]' ) );
		refreshStateField( $form, state );
	}

	/*
	 * Country changed inside one of our editors. Delegated, because the state
	 * control is replaced wholesale each time.
	 *
	 * Deferred by a tick, and that is not a nicety. This handler runs inside
	 * Select2's own selection cycle, while it is still closing the country
	 * dropdown. Tearing down one Select2 instance and creating another in the
	 * middle of that confuses its global registry of open instances: the close
	 * never completes, the control is left with aria-expanded="true", and every
	 * later click just toggles a dropdown that is not really there — the field
	 * looks dead. Letting Select2 finish first costs nothing and avoids all of it.
	 */
	// Country changed inside one of our editors — delegated, because the state
	// control is replaced wholesale each time.
	$( document.body ).on( 'change', '[data-vanmuad-form] [data-vanmuad-field="country"]', function () {
		refreshStateField( $( this ).closest( '[data-vanmuad-form]' ), '' );
	} );

	// -------------------------------------------------------------- Helpers

	/**
	 * Post an action to admin-ajax.
	 *
	 * @param {string} action Action name.
	 * @param {Object} data   Payload.
	 * @return {Object} The jQuery request.
	 */
	function request( action, data ) {
		return $.post(
			settings.ajax_url,
			$.extend( {}, data, { action: action, nonce: settings.nonce } )
		);
	}

	/**
	 * Pull the error payload out of a failed response.
	 *
	 * @param {Object} xhr The jQuery xhr.
	 * @return {Object} The error data.
	 */
	function errorData( xhr ) {
		return ( xhr.responseJSON && xhr.responseJSON.data ) || {};
	}

	/**
	 * Show a message in the notice box nearest to a scope.
	 *
	 * @param {Object}  $scope  Element to search from.
	 * @param {string}  message Text.
	 * @param {boolean} isError Whether it is an error.
	 */
	function notify( $scope, message, isError ) {
		var $notice = $scope.find( '[data-vanmuad-notice]' ).first();

		if ( ! $notice.length ) {
			$notice = $( '[data-vanmuad-notice]' ).first();
		}

		if ( ! $notice.length || ! message ) {
			return;
		}

		$notice
			.text( message )
			.toggleClass( 'vanmuad-notice--error', !! isError )
			.prop( 'hidden', false );
	}

	// --------------------------------------------------------- Editor (shared)

	/**
	 * Clear any per-field error state.
	 *
	 * @param {Object} $form The editor container.
	 */
	function clearErrors( $form ) {
		$form.find( '.vanmuad-field-error' ).remove();
		$form.find( '.vanmuad-has-error' ).removeClass( 'vanmuad-has-error' );
	}

	/**
	 * Render validation errors next to their fields.
	 *
	 * @param {Object} $form  The editor container.
	 * @param {Object} errors Field name => message.
	 */
	function showErrors( $form, errors ) {
		$.each( errors, function ( name, message ) {
			var $row = $form.find( '[data-vanmuad-field="' + name + '"]' ).closest( '.form-row' );

			if ( ! $row.length ) {
				return;
			}

			$row.addClass( 'vanmuad-has-error' );
			$( '<span class="vanmuad-field-error"></span>' ).text( message ).appendTo( $row );
		} );
	}

	/**
	 * Reset an editor to the "add new" state.
	 *
	 * @param {Object} $form The editor container.
	 */
	function resetEditor( $form ) {
		clearErrors( $form );

		$form.find( '[data-vanmuad-field-id]' ).val( '' );
		$form.find( '[data-vanmuad-field-name]' ).val( '' );
		$form.find( '[data-vanmuad-field-default-billing]' ).prop( 'checked', false );
		$form.find( '[data-vanmuad-field-default-shipping]' ).prop( 'checked', false );
		$form.find( '[data-vanmuad-field]' ).each( function () {
			var $field = $( this );

			if ( $field.is( 'select' ) ) {
				$field.val( $field.find( 'option' ).first().val() );
			} else {
				$field.val( '' );
			}
		} );
	}

	/**
	 * Fill an editor from a stored address record.
	 *
	 * @param {Object} $form   The editor container.
	 * @param {Object} address The record.
	 */
	function fillEditor( $form, address ) {
		resetEditor( $form );

		var fields = address.fields || {};

		$form.find( '[data-vanmuad-field-id]' ).val( address.id );
		$form.find( '[data-vanmuad-field-name]' ).val( address.internal_name || '' );
		$form.find( '[data-vanmuad-field-default-billing]' ).prop( 'checked', !! address.is_default_billing );
		$form.find( '[data-vanmuad-field-default-shipping]' ).prop( 'checked', !! address.is_default_shipping );

		$.each( fields, function ( name, value ) {
			$form.find( '[data-vanmuad-field="' + name + '"]' ).val( value );
		} );

		// The state control depends on the country, so it is rebuilt (and the
		// stored state re-applied) once the country is in place.
		enhanceEditor( $form, fields.state || '' );
	}

	/**
	 * Read the values out of an editor.
	 *
	 * @param {Object} $form The editor container.
	 * @return {Object} The payload.
	 */
	function collectEditor( $form ) {
		var payload = {
			address_id: $form.find( '[data-vanmuad-field-id]' ).val(),
			internal_name: $form.find( '[data-vanmuad-field-name]' ).val(),
			is_default_billing: $form.find( '[data-vanmuad-field-default-billing]' ).is( ':checked' ) ? 1 : 0,
			is_default_shipping: $form.find( '[data-vanmuad-field-default-shipping]' ).is( ':checked' ) ? 1 : 0,
			fields: {}
		};

		$form.find( '[data-vanmuad-field]' ).each( function () {
			payload.fields[ $( this ).data( 'vanmuad-field' ) ] = $( this ).val();
		} );

		return payload;
	}

	/**
	 * Save an editor over AJAX.
	 *
	 * @param {Object}   $form   The editor container.
	 * @param {Function} onSaved Called with the response data on success.
	 */
	function saveEditor( $form, onSaved ) {
		var $save = $form.find( '[data-vanmuad-save]' );

		clearErrors( $form );
		$save.prop( 'disabled', true );

		request( 'vanmuad_save_address', collectEditor( $form ) )
			.done( function ( response ) {
				onSaved( response.data );
			} )
			.fail( function ( xhr ) {
				var data = errorData( xhr );

				notify( $form, data.message || settings.i18n.generic_error, true );

				if ( data.errors ) {
					showErrors( $form, data.errors );
				}
			} )
			.always( function () {
				$save.prop( 'disabled', false );
			} );
	}

	/**
	 * Set an editor's heading.
	 *
	 * @param {Object} $form The editor container.
	 * @param {string} title Heading text.
	 */
	function setTitle( $form, title ) {
		$form.find( '[data-vanmuad-form-title]' ).text( title );
	}

	/**
	 * Show or hide an overlay editor, and stop the page behind it scrolling.
	 *
	 * @param {Object}  $modal The overlay.
	 * @param {boolean} open   Whether to show it.
	 */
	function toggleModal( $modal, open ) {
		$modal.prop( 'hidden', ! open );

		/*
		 * The body lock is derived from what is actually on screen, not from the
		 * caller's intent. Toggling it straight from `open` means any close path
		 * that is ever missed leaves the page permanently unscrollable — a state
		 * only a reload clears, and one the customer cannot make sense of.
		 * Recomputing costs nothing and cannot get stuck.
		 */
		$( 'body' ).toggleClass(
			'vanmuad-modal-open',
			$( '.vanmuad-modal' ).filter( ':visible' ).length > 0
		);
	}

	// Escape closes whichever editor is open — expected of any dialog, and the
	// quickest way out when the overlay covers the page.
	$( document ).on( 'keydown', function ( event ) {
		if ( 27 !== event.keyCode ) {
			return;
		}

		var $open = $( '.vanmuad-modal' ).filter( ':visible' );

		if ( $open.length ) {
			toggleModal( $open, false );
		}
	} );

	// ------------------------------------------------------ My Account book

	function initBook() {
		var $book = $( '[data-vanmuad-book]' );

		if ( ! $book.length ) {
			return;
		}

		/*
		 * Usually the editor sits inside the book. It does not have to: the admin
		 * profile screen prints it in the footer instead, to keep its named inputs
		 * out of <form id="your-profile"> — see Premium\UserProfile\Profile.
		 *
		 * Relocating it from here instead would not work. Core's user-profile.js
		 * serializes that form on load and compares on unload, so moving the inputs
		 * out afterwards is itself a change, and raises the unsaved-changes warning
		 * it was meant to prevent. Where the editor lives is the server's call.
		 */
		var $modal = $book.find( '[data-vanmuad-modal]' ).first();

		if ( ! $modal.length ) {
			$modal = $( '[data-vanmuad-modal]' ).first();
		}

		var $form = $modal.find( '[data-vanmuad-form]' ).first();

		/*
		 * Save and cancel live in the editor, so they are delegated from it — the
		 * book is no longer their ancestor.
		 */
		$modal.on( 'click', '[data-vanmuad-cancel]', function () {
			toggleModal( $modal, false );
		} );

		$modal.on( 'click', '[data-vanmuad-save]', function () {
			saveEditor( $form, function () {
				window.location.reload();
			} );
		} );

		$book.on( 'click', '[data-vanmuad-add]', function () {
			resetEditor( $form );
			setTitle( $form, settings.i18n.add_title );

			toggleModal( $modal, true );
			enhanceEditor( $form, '' );
		} );

		$book.on( 'click', '[data-vanmuad-edit]', function () {
			var $card = $( this ).closest( '[data-vanmuad-card]' );

			setTitle( $form, settings.i18n.edit_title );

			toggleModal( $modal, true );
			fillEditor( $form, $card.data( 'address' ) );
		} );

		$book.on( 'click', '[data-vanmuad-delete]', function () {
			var $card = $( this ).closest( '[data-vanmuad-card]' );

			if ( ! window.confirm( settings.i18n.confirm_delete ) ) {
				return;
			}

			request( 'vanmuad_delete_address', { address_id: $card.data( 'address-id' ) } )
				.done( function ( response ) {
					notify( $book, response.data.message, false );
					$card.remove();
				} )
				.fail( function ( xhr ) {
					notify( $book, errorData( xhr ).message || settings.i18n.generic_error, true );
				} );
		} );

		// Quick "use this one for billing / for shipping" from a card. The page is
		// reloaded because the change moves a badge off another card too.
		$book.on( 'click', '[data-vanmuad-default]', function () {
			var $card = $( this ).closest( '[data-vanmuad-card]' );

			request(
				'vanmuad_set_default_address',
				{
					address_id: $card.data( 'address-id' ),
					purpose: $( this ).data( 'vanmuad-default' ),
					enable: 1
				}
			)
				.done( function () {
					window.location.reload();
				} )
				.fail( function ( xhr ) {
					notify( $book, errorData( xhr ).message || settings.i18n.generic_error, true );
				} );
		} );
	}

	// --------------------------------------------------------- Checkout

	function initCheckout() {
		var $selectors = $( '[data-vanmuad-selector]' );

		if ( ! $selectors.length ) {
			return;
		}

		var $modal = $( '[data-vanmuad-editor]' );
		var $form = $modal.find( '[data-vanmuad-form]' );

		// One editor serves both selectors, so it has to remember who opened it.
		var $requester = null;

		/**
		 * Copy an address into WooCommerce's own checkout fields and let the
		 * checkout recalculate (shipping depends on the destination).
		 *
		 * @param {string} purpose 'billing' or 'shipping'.
		 * @param {Object} address The record.
		 */
		function applyToCheckout( purpose, address ) {
			var fields = address.fields || {};

			$.each( fields, function ( name, value ) {
				var $field = $( '#' + purpose + '_' + name );

				if ( $field.length ) {
					$field.val( value );
				}
			} );

			// The country drives the state field, so change it first and re-apply
			// the state afterwards.
			$( '#' + purpose + '_country' ).trigger( 'change' );
			$( '#' + purpose + '_state' ).val( fields.state || '' ).trigger( 'change' );

			/**
			 * An address was applied to the checkout.
			 *
			 * The extension point premium uses to act on what the customer picked
			 * — copying delivery notes into the order notes, for one — without the
			 * free script needing to know such a thing exists.
			 *
			 * @param {string} purpose 'billing' or 'shipping'.
			 * @param {Object} address The whole address record.
			 */
			$( document.body ).trigger( 'vanmuad_address_applied', [ purpose, address ] );

			$( document.body ).trigger( 'update_checkout' );
		}

		/**
		 * Show or hide the edit/delete actions depending on the selection.
		 *
		 * @param {Object} $selector The selector container.
		 */
		function refreshActions( $selector ) {
			var hasSelection = '' !== $selector.find( '[data-vanmuad-select]' ).val();

			$selector.find( '[data-vanmuad-edit], [data-vanmuad-delete]' ).prop( 'hidden', ! hasSelection );
		}

		/**
		 * The address record behind the current selection.
		 *
		 * @param {Object} $selector The selector container.
		 * @return {Object|null} The record.
		 */
		function selectedAddress( $selector ) {
			var $option = $selector.find( '[data-vanmuad-select] option:selected' );

			return $option.length ? $option.data( 'address' ) || null : null;
		}

		function openEditor( $selector, address ) {
			$requester = $selector;

			$modal.find( '[data-vanmuad-notice]' ).prop( 'hidden', true );

			toggleModal( $modal, true );

			if ( address ) {
				setTitle( $form, settings.i18n.edit_title );
				fillEditor( $form, address );
			} else {
				resetEditor( $form );
				setTitle( $form, settings.i18n.add_title );
				enhanceEditor( $form, '' );
			}
		}

		function closeEditor() {
			toggleModal( $modal, false );
			$requester = null;
		}

		/**
		 * Add or refresh an address in every selector — both offer the same list.
		 *
		 * @param {Object} address The saved record.
		 * @param {string} label   Its dropdown label.
		 */
		function syncOption( address, label ) {
			$selectors.each( function () {
				var $select = $( this ).find( '[data-vanmuad-select]' );
				var $option = $select.find( 'option[value="' + address.id + '"]' );

				if ( ! $option.length ) {
					$option = $( '<option></option>' ).attr( 'value', address.id ).appendTo( $select );
				}

				$option.text( label ).data( 'address', address );
			} );
		}

		// Apply the selection (and preselect on load, when one is set).
		$selectors.each( function () {
			var $selector = $( this );

			refreshActions( $selector );

			var preselected = selectedAddress( $selector );
			if ( preselected ) {
				applyToCheckout( $selector.data( 'vanmuad-selector' ), preselected );
			}
		} );

		$selectors.on( 'change', '[data-vanmuad-select]', function () {
			var $selector = $( this ).closest( '[data-vanmuad-selector]' );
			var address = selectedAddress( $selector );

			refreshActions( $selector );

			if ( address ) {
				applyToCheckout( $selector.data( 'vanmuad-selector' ), address );
			}
		} );

		$selectors.on( 'click', '[data-vanmuad-add]', function () {
			openEditor( $( this ).closest( '[data-vanmuad-selector]' ), null );
		} );

		$selectors.on( 'click', '[data-vanmuad-edit]', function () {
			var $selector = $( this ).closest( '[data-vanmuad-selector]' );

			openEditor( $selector, selectedAddress( $selector ) );
		} );

		$selectors.on( 'click', '[data-vanmuad-delete]', function () {
			var $selector = $( this ).closest( '[data-vanmuad-selector]' );
			var id = $selector.find( '[data-vanmuad-select]' ).val();

			if ( ! id || ! window.confirm( settings.i18n.confirm_delete ) ) {
				return;
			}

			request( 'vanmuad_delete_address', { address_id: id } )
				.done( function () {
					// The address is gone from the book, so from every selector.
					$selectors.each( function () {
						var $select = $( this ).find( '[data-vanmuad-select]' );

						$select.find( 'option[value="' + id + '"]' ).remove();

						if ( ! $select.val() ) {
							$select.val( '' );
						}

						refreshActions( $( this ) );
					} );
				} )
				.fail( function ( xhr ) {
					notify( $selector, errorData( xhr ).message || settings.i18n.generic_error, true );
				} );
		} );

		// Editor actions (the modal sits outside the selectors, so bind on body).
		$( document.body ).on( 'click', '[data-vanmuad-editor] [data-vanmuad-cancel]', function () {
			closeEditor();
		} );

		$( document.body ).on( 'click', '[data-vanmuad-editor] [data-vanmuad-save]', function () {
			saveEditor( $form, function ( data ) {
				syncOption( data.address, data.label );

				// Select it in whichever selector asked for the editor.
				if ( $requester ) {
					$requester.find( '[data-vanmuad-select]' ).val( data.address.id ).trigger( 'change' );
				}

				closeEditor();
			} );
		} );
	}

	$( function () {
		initBook();
		initCheckout();
	} );
} )( jQuery );
