( function ( $, undefined ) {
	/**
	 *  acf.newSelect2
	 *
	 *  description
	 *
	 *  @date	13/1/18
	 *  @since	ACF 5.6.5
	 *
	 *  @param	type $var Description. Default.
	 *  @return	type Description.
	 */

	acf.newSelect2 = function ( $select, props ) {
		// defaults
		props = acf.parseArgs( props, {
			allowNull: false,
			placeholder: '',
			multiple: false,
			field: false,
			ajax: false,
			tags: false,
			ajaxAction: '',
			ajaxData: function ( data ) {
				return data;
			},
			ajaxResults: function ( json ) {
				return json;
			},
			escapeMarkup: false,
			templateSelection: false,
			templateResult: false,
			dropdownCssClass: '',
			suppressFilters: false,
		} );

		// initialize
		if ( getVersion() == 4 ) {
			var select2 = new Select2_4( $select, props );
		} else {
			var select2 = new Select2_3( $select, props );
		}

		// actions
		acf.doAction( 'new_select2', select2 );

		// return
		return select2;
	};

	/**
	 *  getVersion
	 *
	 *  description
	 *
	 *  @date	13/1/18
	 *  @since	ACF 5.6.5
	 *
	 *  @param	type $var Description. Default.
	 *  @return	type Description.
	 */

	function getVersion() {
		// v4
		if ( acf.isset( window, 'jQuery', 'fn', 'select2', 'amd' ) ) {
			return 4;
		}

		// v3
		if ( acf.isset( window, 'Select2' ) ) {
			return 3;
		}

		// return
		return false;
	}

	/**
	 *  Select2
	 *
	 *  description
	 *
	 *  @date	13/1/18
	 *  @since	ACF 5.6.5
	 *
	 *  @param	type $var Description. Default.
	 *  @return	type Description.
	 */

	var Select2 = acf.Model.extend( {
		setup: function ( $select, props ) {
			$.extend( this.data, props );
			this.$el = $select;
		},

		initialize: function () {},

		selectOption: function ( value ) {
			var $option = this.getOption( value );
			if ( ! $option.prop( 'selected' ) ) {
				$option.prop( 'selected', true ).trigger( 'change' );
			}
		},

		unselectOption: function ( value ) {
			var $option = this.getOption( value );
			if ( $option.prop( 'selected' ) ) {
				$option.prop( 'selected', false ).trigger( 'change' );
			}
		},

		getOption: function ( value ) {
			return this.$( 'option[value="' + value + '"]' );
		},

		addOption: function ( option ) {
			// defaults
			option = acf.parseArgs( option, {
				id: '',
				text: '',
				selected: false,
			} );

			// vars
			var $option = this.getOption( option.id );

			// append
			if ( ! $option.length ) {
				$option = $( '<option></option>' );
				$option.html( option.text );
				$option.attr( 'value', option.id );
				$option.prop( 'selected', option.selected );
				this.$el.append( $option );
			}

			// chain
			return $option;
		},

		getValue: function () {
			// vars
			var val = [];
			var $options = this.$el.find( 'option:selected' );

			// bail early if no selected
			if ( ! $options.exists() ) {
				return val;
			}

			// sort by attribute
			$options = $options.sort( function ( a, b ) {
				return (
					+a.getAttribute( 'data-i' ) - +b.getAttribute( 'data-i' )
				);
			} );

			// loop
			$options.each( function () {
				var $el = $( this );
				val.push( {
					$el: $el,
					id: $el.attr( 'value' ),
					text: $el.text(),
				} );
			} );

			// return
			return val;
		},

		mergeOptions: function () {},

		getChoices: function () {
			// callback
			var crawl = function ( $parent ) {
				// vars
				var choices = [];

				// loop
				$parent.children().each( function () {
					// vars
					var $child = $( this );

					// optgroup
					if ( $child.is( 'optgroup' ) ) {
						choices.push( {
							text: $child.attr( 'label' ),
							children: crawl( $child ),
						} );

						// option
					} else {
						choices.push( {
							id: $child.attr( 'value' ),
							text: $child.text(),
						} );
					}
				} );

				// return
				return choices;
			};

			// crawl
			return crawl( this.$el );
		},

		getAjaxData: function ( params ) {
			// vars
			var ajaxData = {
				action: this.get( 'ajaxAction' ),
				s: params.term || '',
				paged: params.page || 1,
			};

			// field helper
			var field = this.get( 'field' );
			if ( field ) {
				ajaxData.field_key = field.get( 'key' );

				if ( field.get( 'nonce' ) ) {
					ajaxData.nonce = field.get( 'nonce' );
				}
			}

			// callback
			var callback = this.get( 'ajaxData' );
			if ( callback ) {
				ajaxData = callback.apply( this, [ ajaxData, params ] );
			}

			// filter
			ajaxData = acf.applyFilters(
				'select2_ajax_data',
				ajaxData,
				this.data,
				this.$el,
				field || false,
				this
			);

			// return
			return acf.prepareForAjax( ajaxData );
		},

		getAjaxResults: function ( json, params ) {
			// defaults
			json = acf.parseArgs( json, {
				results: false,
				more: false,
			} );

			// callback
			var callback = this.get( 'ajaxResults' );
			if ( callback ) {
				json = callback.apply( this, [ json, params ] );
			}

			// filter
			json = acf.applyFilters(
				'select2_ajax_results',
				json,
				params,
				this
			);

			// return
			return json;
		},

		processAjaxResults: function ( json, params ) {
			// vars
			var json = this.getAjaxResults( json, params );

			// change more to pagination
			if ( json.more ) {
				json.pagination = { more: true };
			}

			// merge together groups
			setTimeout( $.proxy( this.mergeOptions, this ), 1 );

			// return
			return json;
		},

		destroy: function () {
			// destroy via api
			if ( this.$el.data( 'select2' ) ) {
				this.$el.select2( 'destroy' );
			}

			// destroy via HTML (duplicating HTML does not contain data)
			this.$el.siblings( '.select2-container' ).remove();
		},
	} );

	/**
	 *  Select2_4
	 *
	 *  description
	 *
	 *  @date	13/1/18
	 *  @since	ACF 5.6.5
	 *
	 *  @param	type $var Description. Default.
	 *  @return	type Description.
	 */

	var Select2_4 = Select2.extend( {
		initialize: function () {
			// vars
			var $select = this.$el;
			var options = {
				width: '100%',
				allowClear: this.get( 'allowNull' ),
				placeholder: this.get( 'placeholder' ),
				multiple: this.get( 'multiple' ),
				escapeMarkup: this.get( 'escapeMarkup' ),
				templateSelection: this.get( 'templateSelection' ),
				templateResult: this.get( 'templateResult' ),
				dropdownCssClass: this.get( 'dropdownCssClass' ),
				suppressFilters: this.get( 'suppressFilters' ),
				data: [],
			};

			if ( this.get( 'tags' ) ) {
				options.tags = true;
				options.tokenSeparators = [ ',' ];
			}

			// Clear empty templateSelections, templateResults, or dropdownCssClass.
			if ( ! options.templateSelection ) {
				delete options.templateSelection;
			}
			if ( ! options.templateResult ) {
				delete options.templateResult;
			}
			if ( ! options.dropdownCssClass ) {
				delete options.dropdownCssClass;
			}

			// Only use the template if SelectWoo is not loaded to work around https://github.com/woocommerce/woocommerce/pull/30473
			if ( ! acf.isset( window, 'jQuery', 'fn', 'selectWoo' ) ) {
				if ( ! options.templateSelection ) {
					options.templateSelection = function ( selection ) {
						var $selection = $(
							'<span class="acf-selection"></span>'
						);
						$selection.html(
							options.escapeMarkup( selection.text )
						);
						$selection.data( 'element', selection.element );
						return $selection;
					};
				}
			} else {
				delete options.templateSelection;
				delete options.templateResult;
			}

			// Use a default, filterable escapeMarkup if not provided.
			if ( ! options.escapeMarkup ) {
				options.escapeMarkup = function ( markup ) {
					if ( typeof markup !== 'string' ) {
						return markup;
					}

					if ( this.suppressFilters ) {
						return acf.strEscape( markup );
					}

					return acf.applyFilters(
						'select2_escape_markup',
						acf.strEscape( markup ),
						markup,
						$select,
						this.data,
						field || false,
						this
					);
				};
			}

			// multiple
			if ( options.multiple ) {
				// reorder options
				this.getValue().map( function ( item ) {
					item.$el.detach().appendTo( $select );
				} );
			}

			// Temporarily remove conflicting attribute.
			var attrAjax = $select.attr( 'data-ajax' );
			if ( attrAjax !== undefined ) {
				$select.removeData( 'ajax' );
				$select.removeAttr( 'data-ajax' );
			}

			// ajax
			if ( this.get( 'ajax' ) ) {
				options.ajax = {
					url: acf.get( 'ajaxurl' ),
					delay: 250,
					dataType: 'json',
					type: 'post',
					cache: false,
					data: $.proxy( this.getAjaxData, this ),
					processResults: $.proxy( this.processAjaxResults, this ),
				};
			}

			// filter for 3rd party customization
			if ( ! options.suppressFilters ) {
				var field = this.get( 'field' );
				options = acf.applyFilters(
					'select2_args',
					options,
					$select,
					this.data,
					field || false,
					this
				);
			}
			// add select2
			$select.select2( options );

			// get container (Select2 v4 does not return this from constructor)
			var $container = $select.next( '.select2-container' );

			// multiple
			if ( options.multiple ) {
				// vars
				var $ul = $container.find( 'ul' );

				// sortable
				$ul.sortable( {
					stop: function ( e ) {
						// loop
						$ul.find( '.select2-selection__choice' ).each(
							function () {
								// Attempt to use .data if it exists (select2 version < 4.0.6) or use our template data instead.
								if ( $( this ).data( 'data' ) ) {
									var $option = $(
										$( this ).data( 'data' ).element
									);
								} else {
									var $option = $(
										$( this )
											.find( 'span.acf-selection' )
											.data( 'element' )
									);
								}

								// detach and re-append to end
								$option.detach().appendTo( $select );
							}
						);

						// trigger change on input (JS error if trigger on select)
						$select.trigger( 'change' );
					},
				} );

				// on select, move to end
				$select.on(
					'select2:select',
					this.proxy( function ( e ) {
						this.getOption( e.params.data.id )
							.detach()
							.appendTo( this.$el );
					} )
				);
			}

			// add handler to auto-focus searchbox (for jQuery 3.6)
			$select.on( 'select2:open', () => {
				$( '.select2-container--open .select2-search__field' )
					.get( -1 )
					.focus();
			} );

			// add class
			$container.addClass( '-acf' );

			// Add back temporarily removed attr.
			if ( attrAjax !== undefined ) {
				$select.attr( 'data-ajax', attrAjax );
			}

			// action for 3rd party customization
			if ( ! options.suppressFilters ) {
				acf.doAction(
					'select2_init',
					$select,
					options,
					this.data,
					field || false,
					this
				);
			}
		},

		mergeOptions: function () {
			// vars
			var $prevOptions = false;
			var $prevGroup = false;

			// loop
			$( '.select2-results__option[role="group"]' ).each( function () {
				// vars
				var $options = $( this ).children( 'ul' );
				var $group = $( this ).children( 'strong' );

				// compare to previous
				if ( $prevGroup && $prevGroup.text() === $group.text() ) {
					$prevOptions.append( $options.children() );
					$( this ).remove();
					return;
				}

				// update vars
				$prevOptions = $options;
				$prevGroup = $group;
			} );
		},
	} );

	/**
	 *  Select2_3
	 *
	 *  description
	 *
	 *  @date	13/1/18
	 *  @since	ACF 5.6.5
	 *
	 *  @param	type $var Description. Default.
	 *  @return	type Description.
	 */

	var Select2_3 = Select2.extend( {
		initialize: function () {
			// vars
			var $select = this.$el;
			var value = this.getValue();
			var multiple = this.get( 'multiple' );
			var options = {
				width: '100%',
				allowClear: this.get( 'allowNull' ),
				placeholder: this.get( 'placeholder' ),
				separator: '||',
				multiple: this.get( 'multiple' ),
				data: this.getChoices(),
				escapeMarkup: function ( string ) {
					return acf.escHtml( string );
				},
				dropdownCss: {
					'z-index': '999999999',
				},
				initSelection: function ( element, callback ) {
					if ( multiple ) {
						callback( value );
					} else {
						callback( value.shift() );
					}
				},
			};
			// get hidden input
			var $input = $select.siblings( 'input' );
			if ( ! $input.length ) {
				$input = $( '<input type="hidden" />' );
				$select.before( $input );
			}

			// set input value
			inputValue = value
				.map( function ( item ) {
					return item.id;
				} )
				.join( '||' );
			$input.val( inputValue );

			// multiple
			if ( options.multiple ) {
				// reorder options
				value.map( function ( item ) {
					item.$el.detach().appendTo( $select );
				} );
			}

			// remove blank option as we have a clear all button
			if ( options.allowClear ) {
				options.data = options.data.filter( function ( item ) {
					return item.id !== '';
				} );
			}

			// remove conflicting atts
			$select.removeData( 'ajax' );
			$select.removeAttr( 'data-ajax' );

			// ajax
			if ( this.get( 'ajax' ) ) {
				options.ajax = {
					url: acf.get( 'ajaxurl' ),
					quietMillis: 250,
					dataType: 'json',
					type: 'post',
					cache: false,
					data: $.proxy( this.getAjaxData, this ),
					results: $.proxy( this.processAjaxResults, this ),
				};
			}

			// filter for 3rd party customization
			var field = this.get( 'field' );
			options = acf.applyFilters(
				'select2_args',
				options,
				$select,
				this.data,
				field || false,
				this
			);

			// add select2
			$input.select2( options );

			// get container
			var $container = $input.select2( 'container' );

			// helper to find this select's option
			var getOption = $.proxy( this.getOption, this );

			// multiple
			if ( options.multiple ) {
				// vars
				var $ul = $container.find( 'ul' );

				// sortable
				$ul.sortable( {
					stop: function () {
						// loop
						$ul.find( '.select2-search-choice' ).each( function () {
							// vars
							var data = $( this ).data( 'select2Data' );
							var $option = getOption( data.id );

							// detach and re-append to end
							$option.detach().appendTo( $select );
						} );

						// trigger change on input (JS error if trigger on select)
						$select.trigger( 'change' );
					},
				} );
			}

			// on select, create option and move to end
			$input.on( 'select2-selecting', function ( e ) {
				// vars
				var item = e.choice;
				var $option = getOption( item.id );

				// create if doesn't exist
				if ( ! $option.length ) {
					$option = $(
						'<option value="' +
							item.id +
							'">' +
							item.text +
							'</option>'
					);
				}

				// detach and re-append to end
				$option.detach().appendTo( $select );
			} );

			// add class
			$container.addClass( '-acf' );

			// action for 3rd party customization
			acf.doAction(
				'select2_init',
				$select,
				options,
				this.data,
				field || false,
				this
			);

			// change
			$input.on( 'change', function () {
				var val = $input.val();
				if ( val.indexOf( '||' ) ) {
					val = val.split( '||' );
				}
				$select.val( val ).trigger( 'change' );
			} );

			// hide select
			$select.hide();
		},

		mergeOptions: function () {
			// vars
			var $prevOptions = false;
			var $prevGroup = false;

			// loop
			$( '#select2-drop .select2-result-with-children' ).each(
				function () {
					// vars
					var $options = $( this ).children( 'ul' );
					var $group = $( this ).children( '.select2-result-label' );

					// compare to previous
					if ( $prevGroup && $prevGroup.text() === $group.text() ) {
						$prevGroup.append( $options.children() );
						$( this ).remove();
						return;
					}

					// update vars
					$prevOptions = $options;
					$prevGroup = $group;
				}
			);
		},

		getAjaxData: function ( term, page ) {
			// create Select2 v4 params
			var params = {
				term: term,
				page: page,
			};

			// filter
			var field = this.get( 'field' );
			params = acf.applyFilters(
				'select2_ajax_data',
				params,
				this.data,
				this.$el,
				field || false,
				this
			);
			// return
			return Select2.prototype.getAjaxData.apply( this, [ params ] );
		},
	} );

	// manager
	var select2Manager = new acf.Model( {
		priority: 5,
		wait: 'prepare',
		actions: {
			duplicate: 'onDuplicate',
		},
		initialize: function () {
			// vars
			var locale = acf.get( 'locale' );
			var rtl = acf.get( 'rtl' );
			var l10n = acf.get( 'select2L10n' );
			var version = getVersion();

			// bail early if no l10n
			if ( ! l10n ) {
				return false;
			}

			// bail early if 'en'
			if ( locale.indexOf( 'en' ) === 0 ) {
				return false;
			}

			// initialize
			if ( version == 4 ) {
				this.addTranslations4();
			} else if ( version == 3 ) {
				this.addTranslations3();
			}
		},

		addTranslations4: function () {
			// vars
			var l10n = acf.get( 'select2L10n' );
			var locale = acf.get( 'locale' );

			// modify local to match html[lang] attribute (used by Select2)
			locale = locale.replace( '_', '-' );

			// select2L10n
			var select2L10n = {
				errorLoading: function () {
					return l10n.load_fail;
				},
				inputTooLong: function ( args ) {
					var overChars = args.input.length - args.maximum;
					if ( overChars > 1 ) {
						return l10n.input_too_long_n.replace( '%d', overChars );
					}
					return l10n.input_too_long_1;
				},
				inputTooShort: function ( args ) {
					var remainingChars = args.minimum - args.input.length;
					if ( remainingChars > 1 ) {
						return l10n.input_too_short_n.replace(
							'%d',
							remainingChars
						);
					}
					return l10n.input_too_short_1;
				},
				loadingMore: function () {
					return l10n.load_more;
				},
				maximumSelected: function ( args ) {
					var maximum = args.maximum;
					if ( maximum > 1 ) {
						return l10n.selection_too_long_n.replace(
							'%d',
							maximum
						);
					}
					return l10n.selection_too_long_1;
				},
				noResults: function () {
					return l10n.matches_0;
				},
				searching: function () {
					return l10n.searching;
				},
			};

			// append
			jQuery.fn.select2.amd.define(
				'select2/i18n/' + locale,
				[],
				function () {
					return select2L10n;
				}
			);
		},

		addTranslations3: function () {
			// vars
			var l10n = acf.get( 'select2L10n' );
			var locale = acf.get( 'locale' );

			// modify local to match html[lang] attribute (used by Select2)
			locale = locale.replace( '_', '-' );

			// select2L10n
			var select2L10n = {
				formatMatches: function ( matches ) {
					if ( matches > 1 ) {
						return l10n.matches_n.replace( '%d', matches );
					}
					return l10n.matches_1;
				},
				formatNoMatches: function () {
					return l10n.matches_0;
				},
				formatAjaxError: function () {
					return l10n.load_fail;
				},
				formatInputTooShort: function ( input, min ) {
					var remainingChars = min - input.length;
					if ( remainingChars > 1 ) {
						return l10n.input_too_short_n.replace(
							'%d',
							remainingChars
						);
					}
					return l10n.input_too_short_1;
				},
				formatInputTooLong: function ( input, max ) {
					var overChars = input.length - max;
					if ( overChars > 1 ) {
						return l10n.input_too_long_n.replace( '%d', overChars );
					}
					return l10n.input_too_long_1;
				},
				formatSelectionTooBig: function ( maximum ) {
					if ( maximum > 1 ) {
						return l10n.selection_too_long_n.replace(
							'%d',
							maximum
						);
					}
					return l10n.selection_too_long_1;
				},
				formatLoadMore: function () {
					return l10n.load_more;
				},
				formatSearching: function () {
					return l10n.searching;
				},
			};

			// ensure locales exists
			$.fn.select2.locales = $.fn.select2.locales || {};

			// append
			$.fn.select2.locales[ locale ] = select2L10n;
			$.extend( $.fn.select2.defaults, select2L10n );
		},

		onDuplicate: function ( $el, $el2 ) {
			$el2.find( '.select2-container' ).remove();
		},
	} );
} )( jQuery );
