(function( $ ) {		
	'use strict';

	/**
 	 * Replace element classes with pattern matching.
 	 */
	$.fn.aiovgReplaceClass = function( pattern, additions ) {
		this.removeClass(function( index, classes ) {
			var matches = classes.match( pattern );
			return matches ? matches.join( ' ' ) : '';	
		}).addClass( additions );

		return this;
	};	

	/**
 	 * Init metabox UI (Tabs + Accordion). 
 	 */
	function initMetaboxUI( container ) {
		const $container = $( container );

		// Tabs
		$container.find( '.aiovg-tab' ).on( 'click', function( e ) {
			e.preventDefault();

			const $this  = $( this );
			const target = $this.data( 'target' );

			$container.find( '.aiovg-tab' ).removeClass( 'aiovg-active' );
			$this.addClass( 'aiovg-active' );

			$container.find( '.aiovg-tab-content' ).hide();
			$container.find( target ).fadeIn( 200 );
		});

		// Accordion
		$container.find( '.aiovg-accordion-header' ).on( 'click', function( e ) {
			e.preventDefault();

			const $header    = $( this );
			const $accordion = $header.closest( '.aiovg-accordion' );

			if ( $accordion.data( 'collapsible' ) ) {
				const $icon = $header.find( '.dashicons' );

				$accordion.find( '.aiovg-accordion-body' ).slideToggle( 200 );
				$header.toggleClass( 'aiovg-open' );
				$icon.toggleClass( 'dashicons-arrow-down-alt2 dashicons-arrow-up-alt2' );
			}
		});
	}
	
	/**
	 * Render media uploader.
	 */
	function renderMediaUploader( callback ) { 
		// Check if the wp.media library is available
		if ( typeof wp === 'undefined' || typeof wp.media === 'undefined' ) {
			return;
		}

		// Use the wp.media library to define the settings of the media uploader.
		var fileFrame = wp.media.frames.file_frame = wp.media({
			frame: 'post',
			state: 'insert',
			multiple: false
		});		

		// Setup an event handler for what to do when a media has been selected.
		fileFrame.on( 'insert', function() { 
			// Read the JSON data returned from the media uploader.
			var json = fileFrame.state().get( 'selection' ).first().toJSON();
		
			// First, make sure that we have the URL of the media to display.
			if ( json.url.trim().length == 0 ) {
				return false;
			}
		
			callback( json );			 
		});

		fileFrame.state( 'embed' ).on( 'select', function() {
			// Read the JSON data returned from the media uploader.
			var json = fileFrame.state().props.toJSON();

			// First, make sure that we have the URL of the media to display.
			if ( json.url.trim().length == 0 ) {
				return false;
			}

			json.id = 0;
			callback( json );			 
		});

		// Now display the actual fileFrame.
		fileFrame.on( 'open', function() { 
			jQuery( '#menu-item-gallery, #menu-item-playlist, #menu-item-video-playlist' ).hide();
		});

		fileFrame.open(); 
	}

	/**
	 * Toggle Thumbnail Generator.
	 */
	function toggleThumbnailGenerator() {
		var url = window.aiovgResolveUrl( $( '#aiovg-mp4' ).val() );

		if ( url && url.trim().length > 0 && ! /\.m3u8/.test( url.toLowerCase() ) ) {
			$( '#aiovg-field-mp4' ).removeClass( 'aiovg-is-bunny-stream' );
			$( '#aiovg-thumbnail-generator' ).show();
		} else {
			$( '#aiovg-thumbnail-generator' ).hide();
		}
	}

	/**
 	 * Init autocomplete ui to search videos. 
 	 */
	function initAutocomplete( $el ) {
		$el.autocomplete({
			source: function( request, response ) {
				$.ajax({
					url: ajaxurl,
					dataType: 'json',
					method: 'post',
					data: {
						action: 'aiovg_autocomplete_get_videos',
						security: aiovg_admin.ajax_nonce,
						term: request.term
					},
					success: function( data ) {
						response( $.map( data, function( item ) {
							return {
								label: item.post_title,
								value: item.post_title,
								data: item
							}
						}));
					}
				});
			},
			autoFocus: true,
			minLength: 0,
			select: function( event, ui ) {
				var $field = $( this ).closest( '.aiovg-widget-field' );
				var html = '';

				if ( ui.item.data.ID != 0 ) {
					html  = '<span class="dashicons dashicons-yes-alt"></span> ';
					html += '<span>' + ui.item.data.post_title + '</span> ';
					html += '<a href="javascript:void(0);" class="aiovg-remove-autocomplete-result">' + aiovg_admin.i18n.remove + '</a>';
				} else {
					html  = '<span class="dashicons dashicons-info"></span> ';
					html += '<span>' + aiovg_admin.i18n.no_video_selected + '</span>';
				}
				
				$field.find( '.aiovg-widget-input-id' ).val( ui.item.data.ID ).trigger( 'change' );
				$field.find( '.aiovg-autocomplete-result' ).html( html );
			},
			open: function() {
				$( this ).removeClass( 'ui-corner-all' ).addClass( 'ui-corner-top' );
			},
			close: function() {
				$( this ).removeClass( 'ui-corner-top' ).addClass( 'ui-corner-all' );
				$( this ).val( '' );
			}
		});		
	}

	/**
 	 * Copy to Clipboard.
 	 */
	function copyToClipboard( value ) {
		if ( navigator.clipboard && navigator.clipboard.writeText ) {
			navigator.clipboard.writeText( value ).catch( () => {} );
		} else {
			var input = document.createElement( 'input' );
			input.value = value;

			document.body.appendChild( input );
			input.focus();
			input.select();

			document.execCommand( 'copy' );

			input.remove();
		}
		
		alert( aiovg_admin.i18n.copied + "\n" + value );
	}

	/**
	 * Removes a dot (.) at the end of a string.
	 */
	function removeEndingDot( str ) {
		return str.charAt( str.length - 1 ) === '.' ? str.slice( 0, -1 ) : str;
	}

	/**
	 * Bunny Stream Uploader.
	 */
	class BunnyStreamUploader {

		constructor( config ) {
			this.$uploadButton = $( config.uploadButtonSelector );

			if ( this.$uploadButton.length === 0 ) {
				return;
			}

			this.$root          = $( config.rootSelector );
			this.$uploadWrapper = $( config.rootSelector + ' .aiovg-media-uploader' );
			this.$uploadField   = $( config.rootSelector + ' input[type="file"]' );
			this.$uploadStatus  = $( config.rootSelector + ' .aiovg-upload-status' );

			this.srcSelector               = config.srcSelector;
			this.videoIdSelector           = config.videoIdSelector;
			this.deletableVideoIdsSelector = config.deletableVideoIdsSelector;
			this.imageSelector             = config.imageSelector    || null;
			this.durationSelector          = config.durationSelector || null;
			this.titleSuffix               = config.titleSuffix      || '';
			this.onCancel                  = config.onCancel         || null;

			this.upload  = null;
			this.timeout = null;
			this.options = {};

			this.initOptions();
			this.bindEvents();
		}

		initOptions() {
			this.upload = null;

			if ( this.timeout ) clearTimeout( this.timeout );
			this.timeout = null;

			this.options = {
				status: '',
				videoId: '',
				retryCount: 0,
				maxRetries: 30,
				cache: null
			};
		}

		bindEvents() {
			this.$uploadButton.on( 'click', ( e ) => {
				e.preventDefault();
				this.$uploadField.click();
			});

			this.$uploadField.on( 'change', ( e ) => {
				this.handleUpload( e );
			});

			this.$root.on( 'click', '.aiovg-upload-cancel', ( e ) => {
				e.preventDefault();
				this.cancelUpload();

				if ( this.onCancel ) {
					this.onCancel();
				}
			});
		}

		handleUpload( e ) {
			const file = e.target.files[0];
			if ( ! file ) return;

			this.initOptions();
			this.$uploadStatus.html( '<span class="aiovg-text-success">' + aiovg_admin.i18n.preparing_upload + '</span><span class="aiovg-animate-dots"></span>' );
			this.$root.addClass( 'aiovg-is-bunny-stream' );
			this.$uploadWrapper.addClass( 'aiovg-uploading' );

			if ( this.imageSelector ) {
				$( '#aiovg-thumbnail-generator' ).hide();
			}

			let title = $( '#title' ).val();
			if ( ! title || title.trim().length === 0 ) {
				title = file.name;
			}

			if ( this.titleSuffix ) {
				title = title + this.titleSuffix;
			}

			const data = {
				action: 'aiovg_create_bunny_stream_video',
				security: aiovg_admin.ajax_nonce,
				post_id: aiovg_admin.post_id,
				title: title
			};

			$.post( ajaxurl, data, ( response ) => {
				if ( ! response.success ) {
					this.$uploadField.val( '' );
					this.$uploadStatus.html( '<span class="aiovg-text-error">' + response.data.error + '</span>' );
					this.$uploadWrapper.removeClass( 'aiovg-uploading' );
					return;
				}

				const metadata = {
					filetype: file.type,
					title: title
				};

				if ( response.data.collection_id ) {
					metadata.collection = response.data.collection_id;
				}

				this.options.videoId = response.data.video_id;

				// TUS Upload
				this.upload = new tus.Upload( file, {
					endpoint: 'https://video.bunnycdn.com/tusupload',
					retryDelays: [0, 3000, 5000, 10000, 20000],
					headers: {
						AuthorizationSignature: response.data.token,
						AuthorizationExpire: response.data.expires,
						VideoId: response.data.video_id,
						LibraryId: response.data.library_id
					},
					metadata: metadata,
					onError: ( error ) => {
						this.$uploadField.val( '' );
						this.$uploadStatus.html( '<span class="aiovg-text-error">' + error + '</span>' );
						this.$uploadWrapper.removeClass( 'aiovg-uploading' );
					},
					onProgress: ( bytesUploaded, bytesTotal ) => {
						if ( this.options.status === 'cancelled' ) return;

						const percent = ( ( bytesUploaded / bytesTotal ) * 100 ).toFixed(2);
						const status  = aiovg_admin.i18n.upload_status.replace( '%d', Math.min( 99.99, percent ) );

						if ( this.$uploadStatus.find( '.aiovg-upload-cancel' ).length > 0 ) {
							this.$uploadStatus.find( '.aiovg-text-success' ).html( status );
						} else {
							this.$uploadStatus.html( '<span class="aiovg-text-success">' + status + '</span> <a class="aiovg-upload-cancel" href="javascript: void(0);">' + aiovg_admin.i18n.cancel_upload + '</a>' );
						}
					},
					onSuccess: () => {
						if ( this.options.status === 'cancelled' ) return;

						this.upload = null;
						this.$uploadField.val( '' );

						this.options.cache = {
							src: $( this.srcSelector ).val(),
							video_id: $( this.videoIdSelector ).val(),
							deletable_video_ids: $( this.deletableVideoIdsSelector ).val()
						};

						$( this.srcSelector ).val( response.data.video_url );
						$( this.videoIdSelector ).val( response.data.video_id );

						if ( this.imageSelector ) {
							this.options.cache.image = $( this.imageSelector ).val();
							$( this.imageSelector ).val( response.data.thumbnail_url );
						}

						if ( this.options.cache.video_id ) {
							let deletableVideoIds = this.options.cache.deletable_video_ids ? this.options.cache.deletable_video_ids.split( ',' ) : [];

							if ( deletableVideoIds.indexOf( this.options.cache.video_id ) === -1 ) {
								deletableVideoIds.push( this.options.cache.video_id );
							}

							$( this.deletableVideoIdsSelector ).val( deletableVideoIds.join( ',' ) );
						}

						if ( this.$uploadStatus.find( '.aiovg-upload-cancel' ).length > 0 ) {
							this.$uploadStatus.find( '.aiovg-text-success' ).html( removeEndingDot( aiovg_admin.i18n.upload_processing ) + '<span class="aiovg-animate-dots"></span>' );
						} else {
							this.$uploadStatus.html( '<span class="aiovg-text-success">' + removeEndingDot( aiovg_admin.i18n.upload_processing ) + '<span class="aiovg-animate-dots"></span></span> <a class="aiovg-upload-cancel" href="javascript: void(0);">' + aiovg_admin.i18n.cancel_upload + '</a>' );
						}

						this.checkVideoStatus();
					}
				});

				this.upload.start();
			}, 'json');
		}

		checkVideoStatus() {
			if ( ! this.options.videoId || this.options.retryCount++ >= this.options.maxRetries ) return;

			$.ajax({
				url: ajaxurl,
				method: 'POST',
				data: {
					action: 'aiovg_get_bunny_stream_video',
					security: aiovg_admin.ajax_nonce,
					post_id: aiovg_admin.post_id,
					video_id: this.options.videoId
				},
				success: ( response ) => {
					if ( this.options.status === 'cancelled' ) return;

					if ( ! response.success ) {
						this.resetFieldValues();
						this.$uploadStatus.html( '<span class="aiovg-text-error">' + response.data.error + '</span>' );
						this.$uploadWrapper.removeClass( 'aiovg-uploading' );
						return;
					}

					if ( response.data.status == 4 ) {
						this.$uploadStatus.html( '<span class="aiovg-text-success">' + response.data.message + '</span>' );
						this.$uploadWrapper.removeClass( 'aiovg-uploading' );

						if ( this.durationSelector ) {
							$( this.durationSelector ).val( response.data.duration );
						}
					} else {
						if ( this.$uploadStatus.find( '.aiovg-upload-cancel' ).length > 0 ) {
							this.$uploadStatus.find( '.aiovg-text-success' ).html( removeEndingDot( response.data.message ) + '<span class="aiovg-animate-dots"></span>' );
						} else {
							this.$uploadStatus.html( '<span class="aiovg-text-success">' + removeEndingDot( response.data.message ) + '<span class="aiovg-animate-dots"></span></span> <a class="aiovg-upload-cancel" href="javascript: void(0);">' + aiovg_admin.i18n.cancel_upload + '</a>' );
						}

						this.timeout = setTimeout( () => this.checkVideoStatus(), 5000 );
					}
				}
			});
		}

		cancelUpload() {
			clearTimeout( this.timeout );
			this.options.status = 'cancelled';
			this.$uploadField.val( '' );
			this.$uploadStatus.html( '' );
			this.resetFieldValues();

			if ( this.upload && typeof this.upload.abort === 'function' ) {
				this.upload.abort().then( () => this.deleteVideo() ).catch( () => this.deleteVideo() );
			} else {
				this.deleteVideo();
			}

			this.$uploadWrapper.removeClass( 'aiovg-uploading' );
			this.$root.removeClass( 'aiovg-is-bunny-stream' );
		}

		deleteVideo() {
			if ( ! this.options.videoId ) return;
			this.upload = null;

			const data = {
				action: 'aiovg_delete_bunny_stream_video',
				security: aiovg_admin.ajax_nonce,
				post_id: aiovg_admin.post_id,
				video_id: this.options.videoId
			};

			setTimeout( () => {
				$.post( ajaxurl, data, null, 'json' );
			}, 500 );
		}

		resetFieldValues() {
			if ( ! this.options.cache ) return;

			$( this.srcSelector ).val( this.options.cache.src );
			$( this.videoIdSelector ).val( this.options.cache.video_id );
			$( this.deletableVideoIdsSelector ).val( this.options.cache.deletable_video_ids );

			if ( this.imageSelector ) {
				$( this.imageSelector ).val( this.options.cache.image );
			}
		}

	}

	/**
	 * Called when the page has loaded.
	 */
	$(function() {
		
		// Common: Init metabox UI (Tabs + Accordion).
		$( '.aiovg-metabox-ui' ).each(function() {
			initMetaboxUI( this );
		});

		// Common: Upload files.
		$( document ).on( 'click', '.aiovg-upload-media', function( event ) { 
            event.preventDefault();

			var $this = $( this );

            renderMediaUploader(function( json ) {
				$this.closest( '.aiovg-media-uploader' )
					.find( 'input[type=text]' )
					.val( json.url )
					.trigger( 'file.uploaded' );
			}); 
		});
		
		// Common: Init color picker.
		if ( $.fn.wpColorPicker ) {
			$( '.aiovg-color-picker' ).wpColorPicker();

			$( document ).on( 'widget-added widget-updated', function( event, widget ) {
				widget.find( '.aiovg-color-picker' ).wpColorPicker({
					change: _.throttle( function() { // For Customizer
						$( this ).trigger( 'change' );
					}, 3000)
				});
			});
		}

		// Common: Init the popup.
		if ( $.fn.magnificPopup ) {
			$( '.aiovg-modal-button' ).magnificPopup({
				type: 'inline',
				mainClass: 'mfp-fade'
			});
		}

		// Common: Repeatable fields.
		$( '.aiovg-repeatable-ui' ).each(function() {
			const $containerEl = $( this );

			// Add new field.
			$containerEl.find( '.aiovg-button-add' ).on( 'click', function( event ) { 
				event.preventDefault();
				
				const href     = $( this ).data( 'href' );
				const template = document.querySelector( href );

				if ( template !== null ) {
					const el = template.content.cloneNode( true );	
							
					$containerEl.find( 'tbody' ).append( el );
					$containerEl.find( 'table' ).show();
				}
			});

			// Add a field by default.
			if ( $containerEl.find( 'tr' ).length === 0 ) {
				$containerEl.find( '.aiovg-button-add' ).trigger( 'click' );
			}	
			
			// Delete a field.
			$containerEl.on( 'click', '.aiovg-button-delete', function( event ) { 
				event.preventDefault();	

				$( this ).closest( 'tr' ).remove(); 

				if ( $containerEl.find( 'tr' ).length === 0 ) {
					$containerEl.find( 'table' ).hide();
				}
			});
			
			// Make fields sortable.
			if ( $.fn.sortable ) {
				const $el = $containerEl.find( 'tbody' );
							
				if ( $el.hasClass( 'ui-sortable' ) ) {
					$el.sortable( 'destroy' );
				}
					
				$el.sortable({
					handle: '.aiovg-sort-handle',
					helper: function(e, ui) {
						// ui is the original element being dragged
						const $originals = ui.children();
						const $helper    = ui.clone(); // Clone the original element

						// Iterate through the children of the helper and set their widths
						$helper.children().each(function( index ) {
							$( this ).width( $originals.eq( index ).width() );
						});

						return $helper;
					}
				}).disableSelection();
			}
		});

		// Dashboard: Toggle shortcode forms.
		$( '#aiovg-shortcode-selector input[type=radio]' ).on( 'change', function() {
			var value = $( '#aiovg-shortcode-selector input[type=radio]:checked' ).val();

			$( '.aiovg-shortcode-form' ).hide();
			$( '#aiovg-shortcode-form-' + value ).show();

			$( '.aiovg-shortcode-instructions' ).hide();			
			$( '#aiovg-shortcode-instructions-' + value ).show();
		}).trigger( 'change' );

		// Dashboard: Toggle fields based on the selected video source type.
		$( '#aiovg-shortcode-form-video select[name=type]' ).on( 'change', function() {			
			var value = $( this ).val();			
			$( '#aiovg-shortcode-form-video' ).aiovgReplaceClass( /\aiovg-type-\S+/ig, 'aiovg-type-' + value );
		});

		// Dashboard: Toggle fields based on the selected videos template.
		$( '#aiovg-shortcode-form-videos select[name=template]' ).on( 'change', function() {			
			var value = $( this ).val();			
			$( '#aiovg-shortcode-form-videos' ).aiovgReplaceClass( /\aiovg-template-\S+/ig, 'aiovg-template-' + value );
		}).trigger( 'change' );

		// Dashboard: Toggle fields based on the selected categories template.
		$( '#aiovg-shortcode-form-categories select[name=template]' ).on( 'change', function() {			
			var value = $( this ).val();			
			$( '#aiovg-shortcode-form-categories' ).aiovgReplaceClass( /\aiovg-template-\S+/ig, 'aiovg-template-' + value );
		}).trigger( 'change' );

		// Dashboard: Generate shortcode.
		$( '#aiovg-generate-shortcode' ).on( 'click', function( event ) { 
			event.preventDefault();			

			// Shortcode
			var shortcode = $( '#aiovg-shortcode-selector input[type=radio]:checked' ).val();

			// Build attributes.
			var attributes = shortcode;
			var obj = {};
			
			$( '.aiovg-shortcode-field', '#aiovg-shortcode-form-' + shortcode ).each(function() {							
				var $this = $( this );
				var type  = $this.attr( 'type' );
				var name  = $this.attr( 'name' );				
				var value = $this.val();
				var def   = 0;
				
				if ( typeof $this.data( 'default' ) !== 'undefined' ) {
					def = $this.data( 'default' );
				}				
				
				// Is checkbox?
				if ( type == 'checkbox' ) {
					value = $this.is( ':checked' ) ? 1 : 0;
				} else {
					// Is category or tag?
					if ( name == 'category' || name == 'tag' ) {					
						value = $this.find( 'input[type=checkbox]:checked' ).map(function() {
							return this.value;
						}).get().join( ',' );
					}
				}
				
				// Add only if the user input differ from the global configuration.
				if ( value != def ) {
					obj[ name ] = value;
				}				
			});
			
			for ( var name in obj ) {
				if ( obj.hasOwnProperty( name ) ) {
					attributes += ( ' ' + name + '="' + obj[ name ] + '"' );
				}
			}

			// Shortcode output.	
			$( '#aiovg-shortcode').val( '[aiovg_' + attributes + ']' ); 
		});
		
		// Dashboard: Toggle checkboxes in the issues table.
		$( '#aiovg-issues-check-all' ).on( 'change', function() {
			var value = $( this ).is( ':checked' ) ? true : false;	
			$( '#aiovg-issues .aiovg-issue' ).prop( 'checked', value );
		});	

		// Dashboard: Validate the issues form.
		$( '#aiovg-issues-form' ).on( 'submit', function() {
			var hasValue = $( '#aiovg-issues .aiovg-issue:checked' ).length > 0;

			if ( ! hasValue ) {
				alert( aiovg_admin.i18n.no_issues_selected );
				return false;
			}			
		});

		// Videos: Copy URL.
		$( '.aiovg-copy-url' ).on( 'click', function() {
			var url = $( this ).data( 'url' );
			copyToClipboard( url );			
		});

		// Videos: Copy shortcode.
		$( '.aiovg-copy-shortcode' ).on( 'click', function() {
			var id = parseInt( $( this ).data( 'id' ) );
			var shortcode = '[aiovg_video id="' + id + '"]';

			copyToClipboard( shortcode );
		});
		
		// Videos: Toggle fields based on the selected video source type.
		$( '#aiovg-video-type' ).on( 'change', function( event ) {
            event.preventDefault();

			var type = $( this ).val();

			$( '.aiovg-toggle-fields' ).hide();
			$( '.aiovg-type-' + type ).slideDown();

			// Toggle Thumbnail Generator
			if ( type == 'default' ) {
				toggleThumbnailGenerator();
			} else {
				$( '#aiovg-thumbnail-generator' ).hide();
			}
		}).trigger( 'change' );

		// Videos: Toggle trailer source type fields.
		$( '#aiovg-trailer-type' ).on( 'change', function() {			
			$( '.aiovg-trailer-fields' ).hide();

			var type = 'embedcode' === $( this ).val() ? 'embedcode' : 'default';
			$( '.aiovg-trailer-type-' + type ).show();
		}).trigger( 'change' );
		
		// Videos: Add new source fields when "Add More Quality Levels" link is clicked.
		$( '#aiovg-add-new-source' ).on( 'click', function( event ) {
			event.preventDefault();				
			
			var $this = $( this );

			var limit  = parseInt( $( this ).data( 'limit' ) );
			var length = $( '#aiovg-field-mp4 .aiovg-quality-selector' ).length;	
			var index  = length - 1;
			
			if ( index == 0 ) {
				$( '#aiovg-field-mp4 .aiovg-quality-selector' ).show();
			}

			var template = document.querySelector( '#aiovg-template-source' );
			if ( template !== null ) {
				var el = template.content.cloneNode( true );
				$( el ).find( 'input[type=radio]' ).attr( 'name', 'quality_levels[' + index + ']' );
				$( el ).find( 'input[type=text]' ).attr( 'name', 'sources[' + index + ']' );

				$this.before( el );
			} 		
			
			if ( ( length + 1 ) >= limit ) {
				$this.hide();
			}
		});

		// Videos: On quality level selected.
		$( '#aiovg-field-mp4' ).on( 'change', '.aiovg-quality-selector input[type=radio]', function() {
			var $this = $( this );
			var values = [];

			$( '.aiovg-quality-selector' ).each(function() {
				var value = $( this ).find( 'input[type=radio]:checked' ).val();

				if (  value ) {
					if ( values.includes( value ) ) {
						$this.prop( 'checked', false );
						alert( aiovg_admin.i18n.quality_exists );
					} else {
						values.push( value );
					}					
				}
			});
		});
		
		// Videos: Toggle Thumbnail Generator.
		$( '#aiovg-mp4' ).on( 'blur file.uploaded', ( e ) => {
			toggleThumbnailGenerator();				
		});

		// Videos: Upload tracks.	
		$( document ).on( 'click', '.aiovg-upload-track', function( event ) { 
            event.preventDefault();

			var $this = $( this );

            renderMediaUploader(function( json ) {
				$this.closest( 'tr' )
					.find( '.aiovg-track-src input[type=text]' )
					.val( json.url );
			}); 
        });

		// Videos: Toggle fields based on the selected access control.
		$( '#aiovg-field-access_control select' ).on( 'change', function() {	
			var value = parseInt( $( this ).val() );
			if ( value == 2 ) {
				$( '#aiovg-field-restricted_roles' ).show();
			} else {
				$( '#aiovg-field-restricted_roles' ).hide();
			}
		});

		// Videos: Bunny Stream.
		if ( ( typeof tus !== 'undefined' && typeof tus.Upload === 'function' ) ) {
			new BunnyStreamUploader({
				uploadButtonSelector: '#aiovg-bunny-stream-upload-button',
				rootSelector: '#aiovg-field-mp4',
				srcSelector: '#aiovg-mp4',
				imageSelector: '#aiovg-image',
				durationSelector: '#aiovg-duration',
				videoIdSelector: '#aiovg-bunny_stream_video_id',
				deletableVideoIdsSelector: '#aiovg-deletable_bunny_stream_video_ids',
				onCancel: toggleThumbnailGenerator
			});

			if ( $( '#aiovg-field-trailer-src' ).length > 0 ) {
				new BunnyStreamUploader({
					uploadButtonSelector: '#aiovg-trailer-bunny-stream-upload-button',
					rootSelector: '#aiovg-field-trailer-src',
					srcSelector: '#aiovg-trailer-src',
					videoIdSelector: '#aiovg-trailer_bunny_stream_video_id',
					deletableVideoIdsSelector: '#aiovg-deletable_trailer_bunny_stream_video_ids',
					titleSuffix: ' (' + aiovg_admin.i18n.trailer + ')'
				});
			}
		}

		// Categories: Upload Image.
		$( '#aiovg-categories-upload-image' ).on( 'click', function( event ) { 
            event.preventDefault();

			renderMediaUploader(function( json ) {
				$( '#aiovg-categories-image-wrapper' ).html( '<img src="' + json.url + '" alt="" />' );

				$( '#aiovg-categories-image' ).val( json.url );
				$( '#aiovg-categories-image_id' ).val( json.id );				
			
				$( '#aiovg-categories-upload-image' ).hide();
				$( '#aiovg-categories-remove-image' ).show();
			}); 
        });
		
		// Categories: Remove Image.
		$( '#aiovg-categories-remove-image' ).on( 'click', function( event ) {														 
            event.preventDefault();					
			
			$( '#aiovg-categories-image-wrapper' ).html( '' );

			$( '#aiovg-categories-image' ).val( '' );
			$( '#aiovg-categories-image_id' ).val( '' );			
			
			$( '#aiovg-categories-remove-image' ).hide();
			$( '#aiovg-categories-upload-image' ).show();	
		});
		
		// Categories: Clear the custom fields.
		$( document ).ajaxComplete(function( e, xhr, settings ) {			
			if ( $( '#aiovg-categories-image' ).length && settings.data ) {	
				var queryStringArr = settings.data.split( '&' );
			   
				if ( $.inArray( 'action=add-tag', queryStringArr ) !== -1 ) {
					var response = $( xhr.responseXML ).find( 'term_id' ).text();

					if ( response ) {						
						$( '#aiovg-categories-image-wrapper' ).html( '' );	
						
						$( '#aiovg-categories-image' ).val( '' );	
						$( '#aiovg-categories-image_id' ).val( '' );				
						
						$( '#aiovg-categories-remove-image' ).hide();
						$( '#aiovg-categories-upload-image' ).show();

						$( '#aiovg-categories-exclude_search_form' ).prop( 'checked', false );
						$( '#aiovg-categories-exclude_video_form' ).prop( 'checked', false );
					}
				}		
			}			
		});

		// Settings: Bind section ID.
		$( '#aiovg-settings .form-table' ).each(function() { 
			var str = $( this ).find( 'tr:first th label' ).attr( 'for' );
			var id  = str.split( '[' );
			id = id[0].replace( /_/g, '-' );

			$( this ).attr( 'id', id );
		});
		
		// Settings: Toggle fields based on the selected categories template.
		$( '#aiovg-categories-settings tr.template select' ).on( 'change', function() {			
			var value = $( this ).val();			
			$( '#aiovg-categories-settings' ).aiovgReplaceClass( /\aiovg-template-\S+/ig, 'aiovg-template-' + value );
		}).trigger( 'change' );

		// Settings: Toggle fields based on the selected videos template.
		$( '#aiovg-videos-settings tr.template select' ).on( 'change', function() {			
			var value = $( this ).val();			
			$( '#aiovg-videos-settings' ).aiovgReplaceClass( /\aiovg-template-\S+/ig, 'aiovg-template-' + value );
		}).trigger( 'change' );

		// Settings: Toggle fields based on whether Token Authentication is enabled or disabled.
		$( '#aiovg-bunny-stream-settings tr.enable_token_authentication input[type="checkbox"]' ).on( 'change', function() {			
			var value = $( this ).is( ':checked' ) ? 'enabled' : 'disabled';			
			$( '#aiovg-bunny-stream-settings' ).aiovgReplaceClass( /\aiovg-token-authentication-\S+/ig, 'aiovg-token-authentication-' + value );
		}).trigger( 'change' );

		// Settings: Toggle fields based on the selected access control for the videos.
		$( '#aiovg-restrictions-settings tr.access_control select' ).on( 'change', function() {	
			var value = parseInt( $( this ).val() );
			if ( value == 2 ) {
				$( '#aiovg-restrictions-settings tr.restricted_roles' ).show();
			} else {
				$( '#aiovg-restrictions-settings tr.restricted_roles' ).hide();
			}
		}).trigger( 'change' );

		// Categories Widget: Toggle fields based on the selected categories template.
		$( document ).on( 'change', '.aiovg-widget-form-categories .aiovg-widget-input-template', function() {			
			var value = $( this ).val();	
			$( this ).closest( '.aiovg-widget-form-categories' ).aiovgReplaceClass( /\aiovg-template-\S+/ig, 'aiovg-template-' + value );
		});

		// Videos Widget: Toggle fields based on the selected videos template.
		$( document ).on( 'change', '.aiovg-widget-form-videos .aiovg-widget-input-template', function() {			
			var value = $( this ).val();
			$( this ).closest( '.aiovg-widget-form-videos' ).aiovgReplaceClass( /\aiovg-template-\S+/ig, 'aiovg-template-' + value );
		});

		// Video Widget: Init autocomplete.
		if ( $.fn.autocomplete ) {
			$( '.aiovg-autocomplete-input' ).each(function() {
				initAutocomplete( $( this ) );
			});

			$( document ).on( 'widget-added widget-updated', function( event, widget ) {
				var $el = widget.find( '.aiovg-autocomplete-input' );
				
				if ( $el.length > 0 ) {
					initAutocomplete( $el );
				}
			});

			$( document ).on( 'click', '.aiovg-remove-autocomplete-result', function() {
				var $field = $( this ).closest( '.aiovg-widget-field' );				

				var html = '<span class="dashicons dashicons-info"></span> ';
				html += '<span>' + aiovg_admin.i18n.no_video_selected + '</span>';

				$field.find( '.aiovg-widget-input-id' ).val( 0 ).trigger( 'change' );
				$field.find( '.aiovg-autocomplete-result' ).html( html );
			});
		}
			   
	});	

})( jQuery );

/**
 * Resolves a masked, relative, or absolute URL into a usable absolute URL.
 */
(function( window ) {

	window.aiovgResolveUrl = function( url ) {
		if ( ! url || ! url.trim() ) {
			return url;
		}

		url = url.trim();

		const siteUrl    = aiovg_admin.site_url.replace( /\/+$/, '' );
		const maskPrefix = siteUrl + '/private/';

		// Step 1: Unmask private URLs
		if ( url.indexOf( maskPrefix ) === 0 ) {
			const encoded = url.substring( maskPrefix.length );

			if ( encoded ) {
				try {
					const base64 = encoded
						.replace( /-/g, '+' )
						.replace( /_/g, '/' )
						.replace( /\./g, '=' );

					const binary = atob( base64 );
					const bytes  = Uint8Array.from( binary, c => c.charCodeAt( 0 ) );

					url = new TextDecoder().decode( bytes );
				} catch ( error ) {
					// Fail-safe: keep original URL
					return url;
				}
			}
		}

		// Step 2: Already absolute URL
		if ( url.indexOf( '://' ) > 0 || url.indexOf( '//' ) === 0 ) {
			return url;
		}

		// Step 3: Resolve relative paths
		if ( url.charAt( 0 ) === '/' ) {
			return siteUrl + url;
		}

		return siteUrl + '/' + url;
	};

})( window );
