<?php

/**
 * Import / Export.
 *
 * @link    https://plugins360.com
 * @since   4.5.2
 *
 * @package All_In_One_Video_Gallery
 */

// Exit if accessed directly
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * AIOVG_Admin_Import_Export class.
 *
 * @since 4.5.2
 */
class AIOVG_Admin_Import_Export {

	/**
	 * Add a settings menu for the plugin.
	 *
	 * @since 4.5.2
	 */
	public function admin_menu() {	
		add_submenu_page(
			'all-in-one-video-gallery',
			__( 'All-in-One Video Gallery - Import / Export', 'all-in-one-video-gallery' ),
			__( 'Bulk Import / Export', 'all-in-one-video-gallery' ),
			'manage_aiovg_options',
			'aiovg_import_export',
			array( $this, 'display_page' )
		);	

		if ( aiovg_fs()->is_not_paying() ) {
			add_submenu_page(
				'all-in-one-video-gallery',
				'',
				'<span class="aiovg-menu-separator"></span>',
				'manage_aiovg_options',
				'all-in-one-video-gallery-separator-2',
				'__return_null'
			);
		}
	}
	
	/**
	 * Display import / export page.
	 *
	 * @since 4.5.2
	 */
	public function display_page() {
		require_once AIOVG_PLUGIN_DIR . 'admin/partials/import-export.php';		
	}	

	/**
	 * AJAX callback to import videos from a folder.
	 *
	 * @since 4.5.2
	 */
	public function ajax_callback_import_folder() {
		if ( function_exists( 'set_time_limit' ) ) {
			set_time_limit( 1200 );
		}

		if ( function_exists( 'ignore_user_abort' ) ) {
			ignore_user_abort( true );
		}

		check_ajax_referer( 'aiovg_ajax_nonce', 'security' ); // Verify the nonce for security

		if ( ! current_user_can( 'manage_aiovg_options' ) ) {
			wp_send_json_error( array( 'error' => esc_html__( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ) );
		}

		$response = array();

		// Sanitize and extract folder path
		$folder = isset( $_POST['folder'] ) ? sanitize_text_field( wp_unslash( $_POST['folder'] ) ) : '';
		
		$resolved_folder_path = $this->resolve_folder_path( $folder );
		if ( is_wp_error( $resolved_folder_path ) ) {
    		wp_send_json_error( array( 'error' => $resolved_folder_path->get_error_message() ) );
		}

		$base_folder_path = $resolved_folder_path;

		// Sanitize options
		$include_subfolders = isset( $_POST['include_subfolders'] ) ? (int) $_POST['include_subfolders'] : 0;
		$slug_strategy      = isset( $_POST['slug_strategy'] ) ? sanitize_key( $_POST['slug_strategy'] ) : 'filename';
		$set_featured_image = isset( $_POST['set_featured_image'] ) ? (int) $_POST['set_featured_image'] : 0;
		$enable_downloads   = isset( $_POST['enable_downloads'] ) ? (int) $_POST['enable_downloads'] : 0;
		$enable_comments    = isset( $_POST['enable_comments'] ) ? (int) $_POST['enable_comments'] : 0;
		$email_capture      = isset( $_POST['email_capture'] ) ? (int) $_POST['email_capture'] : -1;
		$access_control     = isset( $_POST['access_control'] ) ? (int) $_POST['access_control'] : -1;
		$restricted_roles   = isset( $_POST['restricted_roles'] ) ? array_map( 'sanitize_text_field', $_POST['restricted_roles'] ) : array();
		$offset             = isset( $_POST['offset'] ) ? (int) $_POST['offset'] : 0;
		$limit              = isset( $_POST['limit'] ) ? (int) $_POST['limit'] : 20;

		// Taxonomies
		$categories = array();
		if ( isset( $_POST['tax_input'] ) && isset( $_POST['tax_input']['aiovg_categories'] ) ) {
			$categories = array_map( 'intval', $_POST['tax_input']['aiovg_categories'] );
		}

		$tags = array();
		if ( isset( $_POST['tax_input'] ) && isset( $_POST['tax_input']['aiovg_tags'] ) ) {
			$tags = array_map( 'intval', $_POST['tax_input']['aiovg_tags'] );
		}

		// Unique ID for this import
		$import_id = md5( $folder );

		// Find video files
		$videos           = array();
		$matches          = array();
		$video_extensions = array( 'mp4', 'webm', 'ogv', 'm4v', 'mov' );
		$image_extensions = array( 'jpg', 'jpeg', 'png', 'gif', 'webp' );
		$current_index    = 0;
		$processed        = 0;

		try {
			$iterator = new RecursiveIteratorIterator(
				new RecursiveDirectoryIterator( $base_folder_path, RecursiveDirectoryIterator::SKIP_DOTS ),
				RecursiveIteratorIterator::SELF_FIRST
			);

			foreach ( $iterator as $file ) {
				if ( ! $file->isFile() ) {
					continue;
				}

				// Normalize path once per file
				$current_folder_path = wp_normalize_path( $file->getPath() );
				$file_path           = $file->getPathname();

				// Skip files from subfolders if not allowed
				if ( ! $include_subfolders && $current_folder_path !== $base_folder_path ) {
					continue;
				}				

				// Skip early if not a video
				$extension = strtolower( $file->getExtension() );
				if ( ! in_array( $extension, $video_extensions, true ) ) {
					continue;
				}				

				// Respect offset (counting videos only)
				if ( $current_index++ < $offset ) {
					continue;
				}

				// Stop when limit is reached
				if ( $processed >= $limit ) {
					break;
				}

				// Build base name and title
				$file_name  = pathinfo( $file_path, PATHINFO_FILENAME ); 
				$post_title = ucwords( preg_replace( '/[-_]+/', ' ', $file_name ) );

				// Add video entry
				$matches[ $file_name ] = array(
					'title' => $post_title,
					'video' => $this->make_absolute_url( $file_path )
				);					

				// Try to find matching image in the same folder
				foreach ( $image_extensions as $image_extension ) {
					$image_path = $current_folder_path. '/' . $file_name . '.' . $image_extension;

					if ( file_exists( $image_path ) ) {
						$matches[ $file_name ]['image']      = $this->make_absolute_url( $image_path );
						$matches[ $file_name ]['image_path'] = $image_path; // Already normalized
						break;
					}
				}

				$processed++;
			}
		} catch ( Exception $e ) {
			wp_send_json_error( array( 'error' => $e->getMessage() ) );
		}

		// Prepare videos for import
		foreach ( $matches as $name => $data ) {
			if ( empty( $data['video'] ) ) {
				continue;
			}

			$videos[] = array(
				'title'      => $data['title'],				
				'src'        => $data['video'],
				'image'      => isset( $data['image'] ) ? $data['image'] : '',
				'image_path' => isset( $data['image_path'] ) ? $data['image_path'] : ''				
			);
		}
		
		// Import videos
		$imported_data = $this->import_videos_from_folder( $videos, array(
			'slug_strategy'      => $slug_strategy,
			'categories'         => $categories,
			'tags'               => $tags,
			'comment_status'     => ( ! empty( $enable_comments ) ? 'open' : 'closed' ),
			'set_featured_image' => $set_featured_image,
			'enable_downloads'   => $enable_downloads,
			'email_capture'      => $email_capture,
			'access_control'     => $access_control,
			'restricted_roles'   => $restricted_roles,
			'import_id'          => $import_id
		) );

		// Calculate new offset
		$new_offset = $offset + $processed;

		// Batch processing
		if ( $current_index > $new_offset ) {
			$message = sprintf(
				esc_html__( '%s videos imported, %s excluded as duplicates, and %s failed to import.', 'all-in-one-video-gallery' ),
				'%%imported%%',
				'%%skipped%%',
				'%%failed%%'
			);

			$response = array(
				'step'     => 'process',
				'message'  => $message,
				'offset'   => $new_offset,		
				'imported' => $imported_data['imported'],
				'skipped'  => $imported_data['skipped'],
				'failed'   => $imported_data['failed']						
			);

			wp_send_json_success( $response );
		}

		// Build the final response
		$message = sprintf(
			esc_html__( '%s videos imported, %s excluded as duplicates, and %s failed to import.', 'all-in-one-video-gallery' ),
			'%%imported%%',
			'%%skipped%%',
			'%%failed%%'
		);

		$message .= sprintf(
			' <a href="%s" target="_blank">%s</a>',
			esc_url( admin_url( 'edit.php?post_type=aiovg_videos&aiovg_filter=imported&import_id=' . $import_id ) ),
			esc_html__( 'Click here to view the imported videos', 'all-in-one-video-gallery' )
		);

		$response = array(
			'step'     => 'done',
			'message'  => $message,
			'offset'   => $new_offset,		
			'imported' => $imported_data['imported'],
			'skipped'  => $imported_data['skipped'],
			'failed'   => $imported_data['failed']			
		);
		
		wp_send_json_success( $response ); // Send successful JSON response
	}

	/**
	 * AJAX callback to import videos from a csv file.
	 *
	 * @since 4.5.2
	 */
	public function ajax_callback_import_csv() {
		if ( function_exists( 'set_time_limit' ) ) {
			set_time_limit( 1200 );
		}

		if ( function_exists( 'ignore_user_abort' ) ) {
			ignore_user_abort( true );
		}

		check_ajax_referer( 'aiovg_ajax_nonce', 'security' ); // Verify the nonce for security

		if ( ! current_user_can( 'manage_aiovg_options' ) ) {
			wp_send_json_error( array( 'error' => esc_html__( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ) );
		}

		// Sanitize and extract parameters
		$csv_file           = isset( $_POST['csv_file'] ) ? esc_url_raw( $_POST['csv_file'] ) : '';
		$zip_file           = isset( $_POST['zip_file'] ) ? sanitize_text_field( wp_unslash( $_POST['zip_file'] ) ) : '';
		$folder             = isset( $_POST['folder'] ) ? sanitize_text_field( wp_unslash( $_POST['folder'] ) ) : '';
		$columns_separator  = isset( $_POST['columns_separator'] ) ? sanitize_text_field( $_POST['columns_separator'] ) : ',';	
		$values_separator   = isset( $_POST['values_separator'] ) ? sanitize_text_field( $_POST['values_separator'] ) : ';';	
		$add_new_term       = isset( $_POST['add_new_term'] ) ? (int) $_POST['add_new_term'] : 0;
		$add_new_user       = isset( $_POST['add_new_user'] ) ? (int) $_POST['add_new_user'] : 0;
		$set_featured_image = isset( $_POST['set_featured_image'] ) ? (int) $_POST['set_featured_image'] : 0;
		$csv_columns        = isset( $_POST['csv_columns'] ) ? array_map( 'sanitize_text_field', $_POST['csv_columns'] ) : array();
		$offset             = isset( $_POST['offset'] ) ? (int) $_POST['offset'] : 0;
		$limit              = isset( $_POST['limit'] ) ? (int) $_POST['limit'] : 20;

		$response = $this->parse_csv( $csv_file, array(
			'csv_columns'       => $csv_columns,
			'columns_separator' => $columns_separator,
			'offset'            => $offset,
			'limit'             => $limit
		) );		

		if ( is_wp_error( $response ) ) {
			wp_send_json_error( array( 'error' => $response->get_error_message() ) );
		}

		$videos     = $response['videos'];
		$has_more   = $response['has_more'];
		$new_offset = $response['new_offset'];

		if ( ! empty( $folder ) ) {
			$folder = $this->resolve_folder_path( $folder );

			if ( is_wp_error( $folder ) ) {
				wp_send_json_error( array( 'error' => $folder->get_error_message() ) );
			}
		} elseif ( ! empty( $zip_file ) ) {
			$folder = $this->resolve_import_directory( $zip_file );

			if ( is_wp_error( $folder ) ) {
				wp_send_json_error( array( 'error' => $folder->get_error_message() ) );
			}
		}

		// Unique ID for this import
		$import_id = md5( $csv_file );

		// Import videos
		$video_settings = aiovg_get_option( 'aiovg_video_settings' );

		$imported_data = $this->import_videos_from_csv( $videos, array(
			'folder'             => $folder,
			'offset'             => $offset,
			'values_separator'   => $values_separator,
			'add_new_term'       => $add_new_term,
			'add_new_user'       => $add_new_user,
			'set_featured_image' => $set_featured_image,
			'comment_status'     => ( (int) $video_settings['has_comments'] > 0 ) ? 'open' : 'closed',
			'import_id'          => $import_id	
		) );

		// Batch processing
		if ( $has_more ) {
			if ( count( $imported_data['updated'] ) > 0 ) {
				$message = sprintf(
					esc_html__( '%s videos imported, %s updated, %s excluded as duplicates, and %s failed to import.', 'all-in-one-video-gallery' ),
					'%%imported%%',
					'%%updated%%',
					'%%skipped%%',
					'%%failed%%'
				);
			} else {
				$message = sprintf(
					esc_html__( '%s videos imported, %s excluded as duplicates, and %s failed to import.', 'all-in-one-video-gallery' ),
					'%%imported%%',
					'%%skipped%%',
					'%%failed%%'
				);
			}

			$response = array(
				'step'     => 'process',
				'message'  => $message,
				'folder'   => $folder,	
				'offset'   => $new_offset,				
				'imported' => $imported_data['imported'],
				'updated'  => $imported_data['updated'],
				'skipped'  => $imported_data['skipped'],
				'failed'   => $imported_data['failed'],
				'errors'   => $imported_data['errors']						
			);

			wp_send_json_success( $response );
		}

		// Delete the attachments before returning the final response
		$maybe_attachments = array( $csv_file, $zip_file );

		foreach ( $maybe_attachments as $file_path ) {
			if ( empty( $file_path ) ) continue;

			// Only delete if it's an attachment
			$attachment_id = attachment_url_to_postid( $file_path );
			if ( $attachment_id ) {
				wp_delete_attachment( $attachment_id, true );
			}
		}

		// Build the final response
		if ( count( $imported_data['updated'] ) > 0 ) {
			$message = sprintf(
				esc_html__( '%s videos imported, %s updated, %s excluded as duplicates, and %s failed to import.', 'all-in-one-video-gallery' ),
				'%%imported%%',
				'%%updated%%',
				'%%skipped%%',
				'%%failed%%'
			);

			$message .= sprintf(
				' <a href="%s" target="_blank">%s</a>',
				esc_url( admin_url( 'edit.php?post_type=aiovg_videos&aiovg_filter=imported&import_id=' . $import_id ) ),
				esc_html__( 'Click here to view the imported videos', 'all-in-one-video-gallery' )
			);
		} else {
			$message = sprintf(
				esc_html__( '%s videos imported, %s excluded as duplicates, and %s failed to import.', 'all-in-one-video-gallery' ),
				'%%imported%%',
				'%%skipped%%',
				'%%failed%%'
			);

			$message .= sprintf(
				' <a href="%s" target="_blank">%s</a>',
				esc_url( admin_url( 'edit.php?post_type=aiovg_videos&aiovg_filter=imported&import_id=' . $import_id ) ),
				esc_html__( 'Click here to view the imported videos', 'all-in-one-video-gallery' )
			);
		}

		$response = array(
			'step'     => 'done',
			'message'  => $message,
			'folder'   => $folder,
			'offset'   => $new_offset,				
			'imported' => $imported_data['imported'],
			'updated'  => $imported_data['updated'],
			'skipped'  => $imported_data['skipped'],
			'failed'   => $imported_data['failed'],
			'errors'   => $imported_data['errors']			
		);
		
		wp_send_json_success( $response ); // Send successful JSON response
	}

	/**
	 * AJAX callback to get CSV column mapping fields.
	 *
	 * @since 4.5.2
	 */
	public function ajax_callback_get_csv_columns() {
		check_ajax_referer( 'aiovg_ajax_nonce', 'security' ); // Verify the nonce for security

		if ( ! current_user_can( 'manage_aiovg_options' ) ) {
			wp_send_json_error( array( 'error' => esc_html__( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ) );
		}

		// Sanitize and extract parameters.
		$csv_file          = isset( $_POST['csv_file'] ) ? esc_url_raw( $_POST['csv_file'] ) : '';
		$columns_separator = isset( $_POST['columns_separator'] ) ? sanitize_text_field( $_POST['columns_separator'] ) : ',';

		$response = $this->parse_csv( $csv_file, array(
			'columns_separator' => $columns_separator,
			'headers_only'      => true
		) );

		if ( is_wp_error( $response ) ) {
			wp_send_json_error( array( 'error' => $response->get_error_message() ) );
		}

		$csv_headers = $response['headers'];
		$csv_columns = $this->get_csv_column_mapping_fields();
		$html        = '';

		foreach ( $csv_headers as $index => $header ) {
			$html .= '<div class="aiovg-form-control">';

			$html .= sprintf(
				'<label for="aiovg-csv_columns-%s" class="aiovg-form-label">%s</label>',
				esc_attr( $index ),
				esc_html( $header )
			);

			$html .= sprintf(
				'<select name="csv_columns[%s]" id="aiovg-csv_columns-%s" class="widefat">',
				esc_attr( $index ),
				esc_attr( $index )
			);

			$html .= sprintf(
				'<option value="">— %s —</option>',
				esc_html__( 'Select Field', 'all-in-one-video-gallery' )
			);

			foreach ( $csv_columns as $value => $label ) {
				if ( in_array( $value, array( 'post_title', 'video' ), true ) ) {
					$label .= ' *'; // Add asterisk for required fields.
				}

				$html .= sprintf(
					'<option value="%s">%s</option>',
					esc_attr( $value ),
					esc_html( $label )
				);
			}

			$html .= '</select>';
			$html .= '</div>';
		}

		// Send successful JSON response
		wp_send_json_success( array( 'html' => $html ) );
	}

	/**
	 * AJAX callback to export videos to a CSV file.
	 *
	 * @since 4.5.2
	 */
	public function ajax_callback_export_csv() {
		if ( function_exists( 'set_time_limit' ) ) {
			set_time_limit( 1200 );
		}

		if ( function_exists( 'ignore_user_abort' ) ) {
			ignore_user_abort( true );
		}

		check_ajax_referer( 'aiovg_ajax_nonce', 'security' ); // Verify the nonce for security

		if ( ! current_user_can( 'manage_aiovg_options' ) ) {
			wp_send_json_error( array( 'error' => esc_html__( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ) );
		}

		// Sanitize and extract parameters
		$offset    = isset( $_POST['offset'] ) ? (int) $_POST['offset'] : 0;
		$limit     = isset( $_POST['limit'] ) ? (int) $_POST['limit'] : 200;
		$total     = isset( $_POST['total'] ) ? (int) $_POST['total'] : 0;
		$timestamp = isset( $_POST['timestamp'] ) ? sanitize_text_field( $_POST['timestamp'] ) : date( 'YmdHis' );
		$file_name = isset( $_POST['file_name'] ) ? sanitize_file_name( wp_unslash( $_POST['file_name'] ) ) : '';

		$csv_columns = $this->get_csv_column_mapping_fields();
		$csv_rows    = array();

		// Ensure export directory exists
		$upload_dir = wp_upload_dir();
		
		$upload_basedir = trailingslashit( $upload_dir['basedir'] );
		$upload_baseurl = trailingslashit( $upload_dir['baseurl'] );

		$export_dir = $upload_basedir . 'all-in-one-video-gallery/exports/';
		$export_url = $upload_baseurl . 'all-in-one-video-gallery/exports/';

		if ( ! file_exists( $export_dir ) ) {
			if ( ! wp_mkdir_p( $export_dir ) ) {
				wp_send_json_error( array( 'error' => __( 'Sorry, we were unable to create the export directory. Please check folder permissions and try again.', 'all-in-one-video-gallery' ) ) );
			}
		}		

		// Query videos
		$args = array(
			'post_type'      => 'aiovg_videos',
			'post_status'    => 'publish',
			'posts_per_page' => $limit,
			'offset'         => $offset,
			'orderby'        => 'ID',
			'order'          => 'ASC'
		);

		if ( $offset > 0 ) {
			$args['no_found_rows'] = true; // Only first batch needs total count
		}

		$aiovg_query = new WP_Query( $args );

		if ( ! $aiovg_query->have_posts() ) {
			wp_send_json_error( array( 'error' => __( 'Sorry, no videos were found to export.', 'all-in-one-video-gallery' ) ) );
		}

		// Get total count on first batch
		if ( $offset === 0 ) {
			$total = $aiovg_query->found_posts;
		}

		// Loop through videos and build CSV rows
		global $post;

		while ( $aiovg_query->have_posts() ) {
			$aiovg_query->the_post();

			$post_id   = get_the_ID();
			$post_meta = get_post_meta( $post_id );

			$row = array();

			foreach ( $csv_columns as $field => $column ) {
				switch ( $field ) {
					case 'post_id':
						$row[] = $post_id;
						break;
					case 'post_title':
						$row[] = $post->post_title;
						break;
					case 'post_content':
						$row[] = $post->post_content;
						break;
					case 'post_excerpt':
						$row[] = $post->post_excerpt;
						break;
					case 'aiovg_categories':
					case 'aiovg_tags':
						$terms = array();
						$terms_ids = wp_get_object_terms( get_the_ID(), $field, array( 'fields' => 'ids' ) );
						
						foreach ( $terms_ids as $term_id ) {
							$term_parents_list = get_term_parents_list( 
								$term_id, 
								$field, 
								array( 
									'separator' => '>', 
									'link'      => false 
								) 
							);

							$terms[] = rtrim( $term_parents_list, '>' );
						}

						$row[] = implode( ';', $terms );
						break;
					case 'video':
						$type = isset( $post_meta['type'] ) ? sanitize_text_field( $post_meta['type'][0] ) : 'default';
						
						if ( in_array( $type, array( 'youtube', 'vimeo', 'dailymotion', 'rumble', 'facebook' ) ) ) {
							$row[] = isset( $post_meta[ $type ] ) ? aiovg_sanitize_url( $post_meta[ $type ][0] ) : '';
						} elseif ( 'adaptive' == $type ) { 
							$src = isset( $post_meta['hls'] ) ? aiovg_sanitize_url( $post_meta['hls'][0] ) : '';
							if ( empty( $src ) ) {
								$src = isset( $post_meta['dash'] ) ? aiovg_sanitize_url( $post_meta['dash'][0] ) : '';
							}

							$row[] = $src;
						} elseif ( 'embedcode' == $type ) {
							try {
								add_filter( 'wp_kses_allowed_html', 'aiovg_allow_iframe_script_tags' );
								$row[] = isset( $post_meta['embedcode'] ) ? wp_kses_post( $post_meta['embedcode'][0] ) : '';
							} finally {
								remove_filter( 'wp_kses_allowed_html', 'aiovg_allow_iframe_script_tags' );
							}
						} else {
							$mp4     = isset( $post_meta['mp4'] ) ? aiovg_sanitize_url( $post_meta['mp4'][0] ) : '';
							$sources = isset( $post_meta['sources'] ) ? maybe_unserialize( $post_meta['sources'][0] ) : array();
							
							if ( empty( $sources ) ) {
								$row[] = wp_json_encode( array(
									'src'  => $mp4,
									'file' => $this->get_exported_filename( $mp4, $post_id )
								) );
							} else {
								$videos = array();

								$videos[] = array(
									'src'     => $mp4,
									'file'    => $this->get_exported_filename( $mp4, $post_id ),
									'quality' => ( isset( $post_meta['quality_level'] ) ? sanitize_text_field( $post_meta['quality_level'][0] ) : '' )
								);

								foreach ( $sources as $source ) {
									if ( ! empty( $source['src'] ) && ! empty( $source['quality'] ) ) {
										$src = aiovg_sanitize_url( $source['src'] );

										$videos[] = array(
											'src'     => $src,
											'file'    => $this->get_exported_filename( $src, $post_id ),
											'quality' => sanitize_text_field( $source['quality'] )
										);
									}
								}

								$row[] = wp_json_encode( $videos );
							}													
						}
						break;
					case 'image':
						$image     = isset( $post_meta['image'] ) ? aiovg_sanitize_url( $post_meta['image'][0] ) : '';
						$image_alt = isset( $post_meta['image_alt'] ) ? sanitize_text_field( $post_meta['image_alt'][0] ) : '';
							
						$row[] = wp_json_encode( array(
							'src'  => $image,
							'file' => $this->get_exported_filename( $image, $post_id ),
							'alt'  => $image_alt
						) );
						break;
					case 'track':
						$tracks = array();

						if ( ! empty( $post_meta['track'] ) ) {
							foreach ( $post_meta['track'] as $track ) {
								$track = maybe_unserialize( $track );

								if ( ! empty( $track['src'] ) ) {
									$src = aiovg_sanitize_url( $track['src'] );

									$tracks[] = array(
										'src'     => $src,
										'file'    => $this->get_exported_filename( $src, $post_id ),
										'label'   => isset( $track['label'] ) ? sanitize_text_field( $track['label'] ) : '',
										'srclang' => isset( $track['srclang'] ) ? sanitize_text_field( $track['srclang'] ) : ''
									);
								}
							}
						}

						$row[] = wp_json_encode( $tracks );
						break;
					case 'chapter':
						$chapters = array();

						if ( ! empty( $post_meta['chapter'] ) ) {
							foreach ( $post_meta['chapter'] as $chapter ) {
								$chapters[] = maybe_unserialize( $chapter );
							}
						}

						$row[] = wp_json_encode( $chapters );
						break;
					case 'duration':
						$row[] = isset( $post_meta['duration'] ) ? sanitize_text_field( $post_meta['duration'][0] ) : '';
						break;
					case 'views':
					case 'likes':
					case 'dislikes':
					case 'download':
					case 'featured':
						$row[] = isset( $post_meta[ $field ] ) ? (int) $post_meta[ $field ][0] : 0;
						break;
					case 'email_capture':
						$row[] = isset( $post_meta['email_capture'] ) ? (int) $post_meta['email_capture'][0] : -1;
						break;
					case 'access_control':
						$row[] = isset( $post_meta['access_control'] ) ? (int) $post_meta['access_control'][0] : -1;
						break;
					case 'restricted_roles':
						$restricted_roles = isset( $post_meta['restricted_roles'] ) ? maybe_unserialize( $post_meta['restricted_roles'][0] ) : array();
						$restricted_roles = is_array( $restricted_roles ) ? array_map( 'sanitize_text_field', $restricted_roles ) : array();
						$row[] = implode( ';', $restricted_roles );
						break;
					case 'post_date':
						$row[] = get_the_date( 'Y-m-d H:i:s' );
						break;
					case 'post_status':
						$row[] = $post->post_status;
						break;
					case 'comment_status':
						$row[] = $post->comment_status;
						break;
					case 'post_author':
						$row[] = get_the_author_meta( 'user_email' );
						break;
					default:
						$row[] = isset( $post_meta[ $field ] ) ? sanitize_text_field( $post_meta[ $field ][0] ) : '';
				}
			}

			$csv_rows[] = $row;
		}

		wp_reset_postdata();

		// Calculate new offset
		$new_offset = $offset + count( $aiovg_query->posts );

		// Generate file name on first batch
		if ( empty( $file_name ) ) {
			$file_name = 'aiovg-videos-' . $timestamp . '.csv';
		}

		$file_path = trailingslashit( $export_dir ) . $file_name;
		$file_url  = trailingslashit( $export_url ) . $file_name;

		// Open file in append mode (or write mode if first batch)
		$handle = fopen( $file_path, $offset === 0 ? 'w' : 'a' );
		if ( false === $handle ) {
			wp_send_json_error( array( 'error' => __( 'Sorry, we were unable to open the CSV file for writing. Please check file permissions and try again.', 'all-in-one-video-gallery' ) ) );
		}

		// Write header row only for first batch
		if ( $offset === 0 ) {
			fputcsv( $handle, array_values( $csv_columns ) );
		}

		// Write CSV rows
		foreach ( $csv_rows as $row ) {
			fputcsv( $handle, $row );
		}

		fclose( $handle );

		// Batch processing
		if ( $new_offset < $total ) {
			$message = sprintf(
				__( '%d out of %d videos processed', 'all-in-one-video-gallery' ),
				$new_offset,
				$total
			);

			$response = array(
				'step'      => 'process',
				'message'   => $message,
				'offset'    => $new_offset,
				'total'     => $total,
				'file_name' => $file_name
			);

			wp_send_json_success( $response );
		}

		// Schedule cleanup
		$this->schedule_export_directory_cleanup();

		// Build the final response
		$message = sprintf(
			__( 'Your CSV export is complete. %s video records processed successfully. <a href="%s" target="_blank">Click here to download the CSV file</a>.', 'all-in-one-video-gallery' ),
			$new_offset,
			esc_url( $file_url )
		);

		$response = array(
			'step'      => 'done',
			'message'   => $message,
			'offset'    => $new_offset,
			'total'     => $total,
			'file_name' => $file_name
		);

		wp_send_json_success( $response ); // Send successful JSON response
	}

	/**
	 * AJAX callback to export videos to ZIP files.
	 *
	 * @since 4.5.2
	 */
	public function ajax_callback_export_zip() {
		if ( function_exists( 'set_time_limit' ) ) {
			set_time_limit( 1200 );
		}

		if ( function_exists( 'ignore_user_abort' ) ) {
			ignore_user_abort( true );
		}

		check_ajax_referer( 'aiovg_ajax_nonce', 'security' ); // Verify the nonce for security

		if ( ! current_user_can( 'manage_aiovg_options' ) ) {
			wp_send_json_error( array( 'error' => esc_html__( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ) );
		}

		if ( ! class_exists( 'ZipArchive' ) ) {
			wp_send_json_error( array( 'error' => __( 'ZIP creation is not supported on your server. This feature requires the PHP "ZipArchive" class. Please ask your hosting provider to enable the PHP ZIP extension, or upgrade PHP to a version that includes it.', 'all-in-one-video-gallery' ) ) );		
		}

		// Sanitize and extract parameters
		$offset    = isset( $_POST['offset'] ) ? (int) $_POST['offset'] : 0;
		$limit     = isset( $_POST['limit'] ) ? (int) $_POST['limit'] : 50;
		$total     = isset( $_POST['total'] ) ? (int) $_POST['total'] : 0;
		$zip_index = isset( $_POST['zip_index'] ) ? (int) $_POST['zip_index'] : 1;
		$timestamp = isset( $_POST['timestamp'] ) ? sanitize_text_field( $_POST['timestamp'] ) : date( 'YmdHis' );
				
		$max_size  = apply_filters( 'aiovg_export_max_zip_size', 1024 * 1024 * 1024 ); // 1GB default
		$zip_files = array();
		$skipped   = array();
		
		// Ensure export directory exists
		$upload_dir = wp_upload_dir();
		
		$upload_basedir = trailingslashit( $upload_dir['basedir'] );
		$upload_baseurl = trailingslashit( $upload_dir['baseurl'] );

		$export_dir = $upload_basedir . 'all-in-one-video-gallery/exports/';
		$export_url = $upload_baseurl . 'all-in-one-video-gallery/exports/';

		if ( ! file_exists( $export_dir ) ) {
			if ( ! wp_mkdir_p( $export_dir ) ) {
				wp_send_json_error( array( 'error' => __( 'Sorry, we were unable to create the export directory. Please check folder permissions and try again.', 'all-in-one-video-gallery' ) ) );
			}
		}		

		// Query videos
		$args = array(
			'post_type'              => 'aiovg_videos',
			'post_status'            => 'publish',
			'posts_per_page'         => $limit,
			'offset'                 => $offset,
			'fields'                 => 'ids',
			'orderby'                => 'ID',
			'order'                  => 'ASC',
			'update_post_term_cache' => false,
			'update_post_meta_cache' => false
		);

		if ( $offset > 0 ) {
			$args['no_found_rows'] = true; // Only first batch needs total count
		}

		$aiovg_query = new WP_Query( $args );

		if ( ! $aiovg_query->have_posts() ) {
			wp_send_json_error( array( 'error' => __( 'Sorry, no videos were found to export. Please check your selection and try again.', 'all-in-one-video-gallery' ) ) );
		}

		// Get total count on first batch
		if ( $offset === 0 ) {
			$total = $aiovg_query->found_posts;
		}

		// Process posts and create ZIPs
		$added_count = 0;
		$added_size  = 0;

		$zip_name = 'aiovg-videos-' . $timestamp . '-' . $zip_index . '.zip';
		$zip_path = trailingslashit( $export_dir ) . $zip_name;
		
		$zip = new ZipArchive();

		if ( $zip->open( $zip_path, ZipArchive::CREATE ) !== TRUE ) {
			wp_send_json_error( array( 'error' => __( 'Sorry, we were unable to create the ZIP file. Please check folder permissions and try again.', 'all-in-one-video-gallery' ) ) );
		}

		foreach ( $aiovg_query->posts as $post_id ) {
			$files = array();

			// Get main video file(s)
			$type = get_post_meta( $post_id, 'type', true );
			if ( 'default' == $type ) {
				$mp4 = get_post_meta( $post_id, 'mp4', true );	
				if ( ! empty( $mp4 ) && $this->is_file_local( $mp4 ) ) {
					$files[] = $mp4;
				}

				$sources = maybe_unserialize( get_post_meta( $post_id, 'sources', true ) );
				if ( ! empty( $sources ) && is_array( $sources ) ) {
					foreach ( $sources as $source ) {
						if ( ! empty( $source['src'] ) && $this->is_file_local( $source['src'] ) ) {
							$files[] = $source['src'];
						}
					}
				}
			}

			// Get image file
			$image = get_post_meta( $post_id, 'image', true );
			if ( ! empty( $image ) && $this->is_file_local( $image ) ) {
				$files[] = $image;
			}

			// Get subtitle files
			$tracks = get_post_meta( $post_id, 'track' );
			if ( ! empty( $tracks ) && is_array( $tracks ) ) {
				foreach ( $tracks as $track ) {
					$track = maybe_unserialize( $track );

					if ( ! empty( $track['src'] ) && $this->is_file_local( $track['src'] ) ) {
						$files[] = $track['src'];
					}
				}
			}

			// No files found for this video
			if ( empty( $files ) ) {
				continue;
			}

			// Move subtitle, image files to the top of the list and video files to the bottom for efficient ZIP compression.
			// Remove duplicate entries just in case.
			$files = array_reverse( array_unique( $files ) );

			// Process each file
			foreach ( $files as $file_url ) {
				// Convert URL to absolute path				
				if ( substr( $file_url, 0, strlen( $upload_baseurl ) ) === $upload_baseurl ) {
					$file_path = $upload_basedir . substr( $file_url, strlen( $upload_baseurl ) );
				} else {
					$file_path = ABSPATH . str_replace( site_url( '/' ), '', $file_url );
				}

				if ( ! file_exists( $file_path ) ) {
					continue;
				}

				$file_size = filesize( $file_path );

				// Skip single files larger than max size
				if ( $file_size > $max_size ) {
					$skipped[] = $file_url;
					continue;
				}

				// If adding this file exceeds max ZIP size, close current ZIP and start a new one
				if ( ( $added_size + $file_size ) > $max_size ) {
					$zip->close();
					$zip_files[] = trailingslashit( $export_url ) . $zip_name;

					// Schedule cleanup
					$this->schedule_export_directory_cleanup();

					// Start new ZIP
					$zip_index++;

					$added_size = 0;

					$zip_name = 'aiovg-videos-' . $timestamp . '-' . $zip_index . '.zip';
					$zip_path = trailingslashit( $export_dir ) . $zip_name;
					
					$zip = new ZipArchive();

					if ( $zip->open( $zip_path, ZipArchive::CREATE ) !== TRUE ) {
						wp_send_json_error( array( 'error' => __( 'Sorry, we were unable to create the ZIP file. Please check folder permissions and try again.', 'all-in-one-video-gallery' ) ) );
					}				
				}

				$zip->addFile( $file_path, $this->get_exported_filename( $file_path, $post_id, true ) );

				$added_size += $file_size;
				$added_count++;
			}
		}

		// Close last ZIP if files were added
		if ( $added_count > 0 ) {
			$zip->close();
			$zip_files[] = trailingslashit( $export_url ) . $zip_name;

			// Schedule cleanup
			$this->schedule_export_directory_cleanup();
		}

		wp_reset_postdata();

		// Calculate new offset
		$new_offset = $offset + count( $aiovg_query->posts );

		// Batch processing
		if ( $new_offset < $total ) {
			$message = sprintf(
				__( '%d out of %d videos processed', 'all-in-one-video-gallery' ),
				$new_offset,
				$total
			);

			$response = array(
				'step'      => 'process',
				'message'   => $message,
				'offset'    => $new_offset,
				'total'     => $total,
				'zip_index' => $zip_index,
				'zip_files' => $zip_files,
				'skipped'   => $skipped
			);

			wp_send_json_success( $response );
		}

		// Build the final response
		$message = sprintf(
			__( 'Your ZIP export is complete. %s video records processed, and %s skipped.', 'all-in-one-video-gallery' ),
			'%%exported%%',
			'%%skipped%%'
		);

		$response = array(
			'step'      => 'done',
			'message'   => $message,
			'offset'    => $new_offset,
			'total'     => $total,
			'zip_index' => $zip_index,
			'zip_files' => $zip_files,
			'skipped'   => $skipped
		);

		wp_send_json_success( $response ); // Send successful JSON response
	}

	/**
	 * AJAX callback to export email leads as a downloadable CSV file.
	 *
	 * @since 4.7.6
	 */
	public function ajax_callback_export_email_leads_csv() {
		if ( function_exists( 'set_time_limit' ) ) {
			set_time_limit( 1200 );
		}
		
		if ( function_exists( 'ignore_user_abort' ) ) {
			ignore_user_abort( true );
		}

		check_ajax_referer( 'aiovg_ajax_nonce', 'security' );

		if ( ! current_user_can( 'manage_aiovg_options' ) ) {
			wp_send_json_error( array( 'error' => esc_html__( 'You do not have sufficient permissions to do this action.', 'all-in-one-video-gallery' ) ) );
		}

		$video_id  = isset( $_GET['video_id'] ) ? absint( $_GET['video_id'] ) : 0;
		$date_from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '';
		$date_to   = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '';

		$leads = aiovg_email_capture_get_leads( array(
			'video_id'  => $video_id,
			'date_from' => $date_from,
			'date_to'   => $date_to
		) );

		if ( $video_id > 0 ) {
			$post      = get_post( $video_id );
			$slug      = ( $post && ! empty( $post->post_name ) ) ? $post->post_name : 'video-' . $video_id;
			$file_name = 'aiovg-emails-' . $slug . '-' . date( 'Y-m-d' ) . '.csv';
		} else {
			$file_name = 'aiovg-emails-' . date( 'Y-m-d' ) . '.csv';
		}

		aiovg_email_capture_generate_csv( $leads, $file_name );
	}

	/**
	 * Hooked cleanup function to delete all files inside the export directory.
	 *
	 * @since 4.5.2
	 */
	public function cleanup_export_directory() {
		// Ensure WP_Filesystem is available
		if ( ! isset( $GLOBALS['wp_filesystem'] ) || ! is_object( $GLOBALS['wp_filesystem'] ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
			WP_Filesystem();
		}

		global $wp_filesystem;

		// Path to export directory
		$uploads    = wp_upload_dir();
		$export_dir = trailingslashit( $uploads['basedir'] ) . 'all-in-one-video-gallery/exports/';

		if ( $wp_filesystem->is_dir( $export_dir ) ) {
			// Get all items (files and subdirectories) inside the folder
			$items = $wp_filesystem->dirlist( $export_dir );

			foreach ( $items as $item ) {
				$path = trailingslashit( $export_dir ) . $item['name'];
				// Recursive delete: removes files and subfolders
				$wp_filesystem->delete( $path, true );
			}
		}
	}	

	/**
	 * Parse CSV data with mapping, offset, and limit support.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  string         $csv_file   Path or attachment URL of the CSV file.
	 * @param  array          $attributes Import settings.
	 * @return array|WP_Error             Parsed CSV data.
	 */
	private function parse_csv( $csv_file, $attributes = array() ) {
		if ( empty( $csv_file ) ) {
			return new WP_Error( 'invalid_csv', __( 'Sorry, the selected file is not a valid CSV. Please upload a valid CSV file.', 'all-in-one-video-gallery' ) );
		}

		// Default arguments.
		$attributes = wp_parse_args( $attributes, array(
			'columns_separator' => ',',
			'csv_columns'       => array(),
			'headers_only'      => false,
			'offset'            => 0,
			'limit'             => 0
		) );

		$response = array(
			'headers'    => array(),
			'videos'     => array(),
			'has_more'   => false,
			'new_offset' => $attributes['offset']
		);

		$csv_row_number = 0;

		// Resolve CSV file path.
		$attachment_id  = attachment_url_to_postid( $csv_file );
		$csv_file_path  = $attachment_id ? get_attached_file( $attachment_id ) : '';

		if ( ! $csv_file_path || ! file_exists( $csv_file_path ) ) {
			return new WP_Error( 'invalid_csv', __( 'Sorry, the selected file is not a valid CSV. Please upload a valid CSV file.', 'all-in-one-video-gallery' ) );
		}

		if ( ! is_readable( $csv_file_path ) ) {
			chmod( $csv_file_path, 0744 );
			if ( ! is_readable( $csv_file_path ) ) {
				return new WP_Error( 'csv_not_readable', __( 'Sorry, we could not read the selected CSV file. Please check the file and try again.', 'all-in-one-video-gallery' ) );
			}
		}

		if ( function_exists( 'wp_check_filetype' ) ) {
			$filetype = wp_check_filetype( $csv_file_path );
			if ( empty( $filetype['ext'] ) || 'csv' !== strtolower( $filetype['ext'] ) ) {
				return new WP_Error( 'invalid_file_type', __( 'Sorry, this file type is not supported. Please upload a valid CSV file.', 'all-in-one-video-gallery' ) );
			}
		}

		if ( false === ( $handle = fopen( $csv_file_path, 'r' ) ) ) {
			return new WP_Error( 'csv_open_failed', __( 'Sorry, we were unable to open the selected CSV file. Please check the file and try again.', 'all-in-one-video-gallery' ) );
		}

		// Get headers
		$headers = fgetcsv( $handle, 0, $attributes['columns_separator'] );
		$csv_row_number++;

		if ( empty( $headers ) || ! is_array( $headers ) ) {
			fclose( $handle );
			return new WP_Error( 'invalid_csv_headers', __( 'Sorry, the CSV file does not contain the required column headers. Please check the file and try again.', 'all-in-one-video-gallery' ) );
		}

		$response['headers'] = array_map( 'trim', $headers );

		if ( true === $attributes['headers_only'] ) {
			fclose( $handle );
			return $response;
		}

		// Skip until offset
		for ( $i = 0; $i < $attributes['offset']; $i++ ) {
			if ( feof( $handle ) ) break;

			fgetcsv( $handle, 0, $attributes['columns_separator'] );
			$csv_row_number++;
		}

		// Read CSV rows
		while ( ! feof( $handle ) ) {
			if ( $attributes['limit'] > 0 && count( $response['videos'] ) >= $attributes['limit'] ) {
				// Reached limit, stop reading further rows
				break;
			}

			$row = fgetcsv( $handle, 0, $attributes['columns_separator'] );
			$csv_row_number++;

			if ( $row === false ) {
				if ( ! feof( $handle ) ) {
					// Malformed row
					$response['videos'][] = array(
						'number' => $csv_row_number,
						'error'  => __( 'Malformed CSV row', 'all-in-one-video-gallery' )
					);

					continue;
				} else {
					break;
				}
			}

			$post = array( 'number' => $csv_row_number );

			// Include empty rows
			if ( ! array_filter( $row, 'strlen' ) ) {
				$response['videos'][] = $post;
				continue;
			}

			// Map columns
			foreach ( $attributes['csv_columns'] as $i => $field ) {
				if ( ! empty( $field ) && isset( $row[ $i ] ) ) {
					$value = trim( $row[ $i ] );

					$decoded = json_decode( $value, true );
					if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
						$value = $decoded;
					} else {
						$value = htmlspecialchars_decode( $value );
					}

					$post[ $field ] = $value;
				}
			}

			$post = apply_filters( 'aiovg_parse_csv_row', $post, $attributes );
			$response['videos'][] = $post;
		}

		// Peek one extra row to set has_more if needed
		if ( ! feof( $handle ) && $attributes['limit'] > 0 ) {
			while ( ( $next_row = fgetcsv( $handle, 0, $attributes['columns_separator'] ) ) !== false ) {
				if ( array_filter( $next_row, 'strlen' ) ) {
					$response['has_more'] = true;
					break;
				}
			}
		}

		fclose( $handle );

		// Calculate next offset for batch import
		$response['new_offset'] = $csv_row_number - 1;

		return $response;
	}

	/**
	 * Resolve import directory from a ZIP file or a folder path.
	 * 
	 * @since  4.5.2
	 * @access private
	 * @param  string          $zip_file Path or attachment URL of the ZIP file, or folder path.
	 * @return string|WP_Error           Extracted directory path or validated folder path, or WP_Error on failure.
	 */
	private function resolve_import_directory( $zip_file = '' ) {
		if ( empty( $zip_file ) ) {
			return new WP_Error( 'invalid_zip', __( 'Sorry, no ZIP file or folder path was provided. Please select a ZIP file or enter a folder path to continue.', 'all-in-one-video-gallery' ) );
		}

		// CASE 1: Folder path
		$folder_path = $this->resolve_folder_path( $zip_file );

		if ( ! is_wp_error( $folder_path ) ) {
			// Valid folder
			return $folder_path;
		}

		// If folder validation failed with 'invalid_folder', treat it as ZIP
		if ( $folder_path->get_error_code() !== 'invalid_folder' ) {
			// Any other error (e.g., permissions, invalid location) — stop
			return $folder_path;
		}

		// CASE 2: ZIP attachment
		$attachment_id = attachment_url_to_postid( $zip_file );
		$zip_file_path = $attachment_id ? get_attached_file( $attachment_id ) : '';

		if ( empty( $zip_file_path ) || ! is_file( $zip_file_path ) ) {
			return new WP_Error( 'invalid_zip', __( 'Sorry, the selected ZIP file could not be found or read. Please check the file and try again.', 'all-in-one-video-gallery' ) );
		}

		// Ensure WP_Filesystem is available
		if ( ! isset( $GLOBALS['wp_filesystem'] ) || ! is_object( $GLOBALS['wp_filesystem'] ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
			WP_Filesystem();
		}

		$uploads       = wp_upload_dir();
		$zip_file_name = basename( $zip_file_path );
		$folder_name   = substr( $zip_file_name, 0, strrpos( $zip_file_name, '.' ) );
		$default_path  = trailingslashit( $uploads['basedir'] ) . 'all-in-one-video-gallery/imports/' . sanitize_file_name( $folder_name );

		$extract_path = apply_filters( 'aiovg_import_directory_path', $default_path, array(	'zip_file' => $zip_file	) );

		if ( ! wp_mkdir_p( $extract_path ) ) {
			return new WP_Error( 'mkdir_failed', __( 'Sorry, we were unable to create the import directory. Please check your folder permissions and try again.', 'all-in-one-video-gallery' ) );
		}

		// Create protection files safely inside the extract path
		$htaccess_file   = trailingslashit( $extract_path ) . '.htaccess';
		$webconfig_file  = trailingslashit( $extract_path ) . 'web.config';
		$htaccess_rules  = "Deny from all\n";
		$webconfig_rules = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
			. "<configuration>\n"
			. "\t<system.webServer>\n"
			. "\t\t<authorization>\n"
			. "\t\t\t<deny users=\"*\" />\n"
			. "\t\t</authorization>\n"
			. "\t</system.webServer>\n"
			. "</configuration>";

		if ( false === file_put_contents( $htaccess_file, $htaccess_rules ) ) {
			return new WP_Error( 'file_write_failed', __( 'We were unable to complete a required file operation. Please make sure your server allows writing files.', 'all-in-one-video-gallery' ) );
		}

		if ( false === file_put_contents( $webconfig_file, $webconfig_rules ) ) {
			return new WP_Error( 'file_write_failed', __( 'We were unable to complete a required file operation. Please make sure your server allows writing files.', 'all-in-one-video-gallery' ) );
		}

		// Unzip
		$unzip_result = unzip_file( $zip_file_path, $extract_path );
		
		if ( is_wp_error( $unzip_result ) ) {
			// Remove protection files before returning
			unlink( $htaccess_file );
			unlink( $webconfig_file );

			return $unzip_result;
		}

		// Keep only files allowed by WordPress MIME validation
		$iterator = new RecursiveIteratorIterator(
			new RecursiveDirectoryIterator( $extract_path, RecursiveDirectoryIterator::SKIP_DOTS ),
			RecursiveIteratorIterator::CHILD_FIRST
		);

		foreach ( $iterator as $file ) {
			if ( $file->isFile() ) {
				$filetype = wp_check_filetype( $file->getFilename() );
				if ( empty( $filetype['type'] ) ) {
					unlink( $file->getPathname() );
				}
			}
		}

		// Security cleanup
		unlink( $htaccess_file );
		unlink( $webconfig_file );

		return untrailingslashit( $extract_path );
	}

	/**
	 * Validate a folder path inside the WordPress directory.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  string          $folder The folder path to validate (relative or absolute).
	 * @return string|WP_Error         Validated and normalized absolute folder path, or WP_Error on failure.
	 */
	private function resolve_folder_path( $folder ) {
		if ( empty( $folder ) ) {
			return new WP_Error( 'invalid_folder', __( 'Sorry, the folder path provided is invalid. Please check the path and try again.', 'all-in-one-video-gallery' ) );
		}

		// Resolve the absolute path
		$folder_path = realpath( trailingslashit( ABSPATH ) . ltrim( $folder, '/' ) );

		if ( ! $folder_path || ! is_dir( $folder_path ) ) {
			return new WP_Error( 'invalid_folder', __( 'Sorry, the folder path provided is invalid. Please check the path and try again.', 'all-in-one-video-gallery' ) );
		}

		// Normalize and remove trailing slash
		$folder_path = untrailingslashit( wp_normalize_path( $folder_path ) );

		// Ensure folder is inside the WordPress directory
		if ( strpos( $folder_path, wp_normalize_path( ABSPATH ) ) !== 0 ) {
			return new WP_Error( 'invalid_folder_location', __( 'Sorry, the folder must be located inside your WordPress directory. Please choose a valid folder and try again.', 'all-in-one-video-gallery' ) );
		}

		return $folder_path;
	}

	/**
	 * Import videos from folder.
	 * 
	 * @since  4.5.2
	 * @access private
	 * @param  array   $videos     Array of videos to import.
	 * @param  array   $attributes Import settings.
	 * @return array   $response   Array containing import results.
     */
	private function import_videos_from_folder( $videos, $attributes = array() ) {
		$response = array(
			'total'    => count( $videos ),
			'imported' => array(),
			'skipped'  => array(),
			'failed'   => array()
		);

		if ( empty( $videos ) ) {
			return $response;
		}

		// Pre import
		wp_defer_term_counting( true );
		wp_defer_comment_counting( true );
		
		// Import videos
		foreach ( $videos as $video ) {
			// Check if the video post already exists			
			$post_id   = $this->is_video_exists( $video['src'] );
			$video_src = aiovg_sanitize_url( $video['src'] );

			if ( ! empty( $post_id ) ) {
				$response['skipped'][] = $video_src;
				continue;
			}
			
			// Insert post
			$args = array(
				'post_type'      => 'aiovg_videos',
				'post_title'     => wp_strip_all_tags( $video['title'] ),
				'post_status'    => 'publish',
				'comment_status' => $attributes['comment_status']					
			);

			if ( 'random' === $attributes['slug_strategy'] ) {
				$args['post_name'] = $this->generate_random_video_slug( $video_src );
			}

			$post_id = wp_insert_post( $args );

			// Insert post meta
			if ( ! is_wp_error( $post_id ) && ! empty( $post_id ) ) {
				// Categories
				if ( ! empty( $attributes['categories'] ) ) {
					wp_set_object_terms( $post_id, $attributes['categories'], 'aiovg_categories' );
				}
				
				// Tags
				if ( ! empty( $attributes['tags'] ) ) {
					wp_set_object_terms( $post_id, $attributes['tags'], 'aiovg_tags' );
				}

				// Meta
				$meta = array(
					'type'             => 'default',
					'mp4'              => $video_src,
					'duration'         => '',
					'featured'         => 0,
					'views'            => 0,
					'likes'			   => 0,
					'dislikes'         => 0,
					'download'         => $attributes['enable_downloads'],
					'email_capture'    => $attributes['email_capture'],
					'access_control'   => $attributes['access_control'],
					'restricted_roles' => $attributes['restricted_roles'],
					'import_id'        => $attributes['import_id']
				);

				// Image
				if ( ! empty( $video['image'] ) ) {
					$meta['image'] = aiovg_sanitize_url( $video['image'] );

					if ( ! empty( $attributes['set_featured_image'] ) && ! empty( $video['image_path'] ) ) {
						$image_id = $this->create_attachment( $post_id, sanitize_text_field( $video['image_path'] ), $meta['image'] );

						if ( ! empty( $image_id ) ) {
							$meta['image_id'] = $image_id;
							set_post_thumbnail( $post_id, $image_id ); 
						}
					}	
				}			

				// Insert post meta in bulk
				$this->add_post_meta_bulk( $post_id, $meta );
				$response['imported'][] = $video_src;

				// Hook for developers
				do_action( 'aiovg_video_imported', $post_id, $attributes['import_id'] );				
			} else {
				$response['failed'][] = $video_src;
			}
		}

		// Post Import
		wp_defer_term_counting( false );
		wp_defer_comment_counting( false );
		
		return $response;
	}

	/**
	 * Import videos from CSV.
	 * 
	 * @since  4.5.2
	 * @access private
	 * @param  array   $videos     Array of videos to import.
	 * @param  array   $attributes Import settings.
	 * @return array   $response   Array containing import results.
     */
	private function import_videos_from_csv( $videos, $attributes = array() ) {
		$response = array(
			'total'    => count( $videos ),
			'imported' => array(),
			'updated'  => array(),
			'skipped'  => array(),
			'failed'   => array(),
			'errors'   => array()
		);

		if ( empty( $videos ) ) {
			return $response;
		}

		// Pre import
		wp_defer_term_counting( true );
		wp_defer_comment_counting( true );
		
		// Import videos
		foreach ( $videos as $row ) {	
			$csv_row_number = (int) $row['number'];

			if ( isset( $row['error'] ) && ! empty( $row['error'] ) ) {
				$response['failed'][] = $csv_row_number;

				$response['errors'][] = sprintf(
					__( '<strong>Line #%d:</strong> %s', 'all-in-one-video-gallery' ),
					$csv_row_number,
					esc_html( $row['error'] )
				);

				continue;
			}

			if ( empty( $row['post_title'] ) || empty( $row['video'] ) ) {
				$response['failed'][] = $csv_row_number;

				$response['errors'][] = sprintf(
					__( '<strong>Line #%d:</strong> Invalid video title or source.', 'all-in-one-video-gallery' ),
					$csv_row_number
				);

				continue;
			}

			// Parse video data
			$post_title = wp_strip_all_tags( $row['post_title'] );

			$video   = $row['video'];
			$sources = array();			

			if ( is_array( $video ) ) {
				// Multiple videos array
				if ( ! isset( $video['src'] ) ) {
					$sources = $video;
					$video   = array_shift( $sources );
				}							
			} else {	
				$video = array( 'src' => $video );			
			}

			if ( empty( $video['src'] ) ) {
				$response['failed'][] = $csv_row_number;

				$response['errors'][] = sprintf(
					__( '<strong>Line #%d:</strong> %s - Invalid video source.', 'all-in-one-video-gallery' ),
					$csv_row_number,
					$post_title
				);

				continue;
			}	

			// $video['src'] is a simple filename (e.g. video.mp4)
			if ( ! empty( $attributes['folder'] ) && preg_match( '/^[a-zA-Z0-9_\-]+\.[a-zA-Z0-9]+$/i', $video['src'] ) ) {				
				$local_file_path = $attributes['folder'] . '/' . sanitize_file_name( $video['src'] );
				if ( file_exists( $local_file_path ) ) {
					$video['src'] = $this->make_absolute_url( $local_file_path );
				}
			}

			// Check if the video post already exists
			$type    = $this->resolve_video_type( $video['src'] );
			$post_id = isset( $row['post_id'] ) ? (int) $row['post_id'] : 0;

			if ( $post_id > 0 ) {
				unset( $row['post_id'] );

				$post = get_post( $post_id );
				if ( $post && 'aiovg_videos' === $post->post_type ) {
					$row['post_id'] = $post_id;
				}
			}

			if ( ! isset( $row['post_id'] ) ) {
				$post_id = $this->is_video_exists( $video['src'], $type );

				if ( ! empty( $post_id ) ) {
					$response['skipped'][] = $csv_row_number;

					$response['errors'][] = sprintf(
						__( '<strong>Line #%d:</strong> %s - Video already exists.', 'all-in-one-video-gallery' ),
						$csv_row_number,
						$post_title
					);
					
					continue;
				}	
			}

			// Insert / Update post
			$is_existing_post = false;

			$args = array(
				'post_type'  => 'aiovg_videos',
				'post_title' => $post_title				
			);

			if ( isset( $row['post_content'] ) && ! empty( $row['post_content'] ) ) {
				$args['post_content'] = wp_kses_post( $row['post_content'] );
			}

			if ( isset( $row['post_excerpt'] ) && ! empty( $row['post_excerpt'] ) ) {
				$args['post_excerpt'] = wp_kses_post( $row['post_excerpt'] );
			}

			if ( isset( $row['post_date'] ) && ! empty( $row['post_date'] ) ) {
				$timestamp = strtotime( $row['post_date'] );
				if ( false !== $timestamp ) {
					$args['post_date'] = gmdate( 'Y-m-d H:i:s', $timestamp );
				}
			}

			if ( isset( $row['post_status'] ) && ! empty( $row['post_status'] ) ) {
				$args['post_status'] = sanitize_text_field( $row['post_status'] );
			}			

			if ( isset( $row['post_author'] ) && ! empty( $row['post_author'] ) ) {
				$post_author = $this->resolve_user_id( $row['post_author'], $attributes );
				
				if ( ! is_wp_error( $post_author ) ) {
					$args['post_author'] = (int) $post_author;
				} else {
					$response['errors'][] = sprintf(
						__( '<strong>Line #%d:</strong> %s - %s', 'all-in-one-video-gallery' ),
						$csv_row_number,
						$post_title,
						$post_author->get_error_message()
					);
				}
			}

			if ( isset( $row['comment_status'] ) && 'open' === strtolower( $row['comment_status'] ) ) {
				$args['comment_status'] = 'open';
			}

			if ( isset( $row['post_id'] ) ) {
				// Update the existing video post
				$post_id = $row['post_id'];

				$args['ID'] = $row['post_id'];
				wp_update_post( $args );

				$is_existing_post = true;
			} else {
				if ( ! isset( $row['post_status'] ) || empty( $row['post_status'] ) ) {
					$args['post_status'] = 'publish';
				}

				if ( ! isset( $row['comment_status'] ) || empty( $row['comment_status'] ) ) {
					$args['comment_status'] = $attributes['comment_status'];
				}

				$post_id = wp_insert_post( $args );
			}

			// Insert post meta
			if ( ! is_wp_error( $post_id ) && ! empty( $post_id ) ) {
				// Categories
				if ( isset( $row['aiovg_categories'] ) ) {
					wp_set_object_terms( $post_id, null, 'aiovg_categories' );

					if ( ! empty( $row['aiovg_categories'] ) ) {
						$categories = $this->resolve_term_ids( $row['aiovg_categories'], 'aiovg_categories', $attributes );				
						
						if ( ! is_wp_error( $categories ) ) {
							if ( ! empty( $categories ) ) {
								wp_set_object_terms( $post_id, $categories, 'aiovg_categories' );
							}
						} else {
							$response['errors'][] = sprintf(
								__( '<strong>Line #%d:</strong> %s - %s', 'all-in-one-video-gallery' ),
								$csv_row_number,
								$post_title,
								$categories->get_error_message()
							);
						}
					}
				}
				
				// Tags
				if ( isset( $row['aiovg_tags'] ) ) {
					wp_set_object_terms( $post_id, null, 'aiovg_tags' );

					if ( ! empty( $row['aiovg_tags'] ) ) {
						$tags = $this->resolve_term_ids( $row['aiovg_tags'], 'aiovg_tags', $attributes );				
						
						if ( ! is_wp_error( $tags ) ) {
							if ( ! empty( $tags ) ) {
								wp_set_object_terms( $post_id, $tags, 'aiovg_tags' );
							}
						} else {
							$response['errors'][] = sprintf(
								__( '<strong>Line #%d:</strong> %s - %s', 'all-in-one-video-gallery' ),
								$csv_row_number,
								$post_title,
								$tags->get_error_message()
							);
						}
					}
				}

				// Subtitles
				if ( isset( $row['track'] ) ) {
					delete_post_meta( $post_id, 'track' );

					if ( is_array( $row['track'] ) ) {
						foreach ( $row['track'] as $source ) {
							if ( ! empty( $source['src'] ) ) {
								$src = $source['src'];

								if ( ! empty( $attributes['folder'] ) && preg_match( '/^[a-zA-Z0-9_\-]+\.[a-zA-Z0-9]+$/i', $src ) ) {
									// $src is a simple filename (e.g. captions.vtt)
									$local_file_path = $attributes['folder'] . '/' . sanitize_file_name( $src );
									if ( file_exists( $local_file_path ) ) {
										$src = $this->make_absolute_url( $local_file_path );
									}
								}

								if ( ! empty( $attributes['folder'] ) && isset( $source['file'] ) && ! empty( $source['file'] ) ) {
									$local_file_path = $attributes['folder'] . '/' . sanitize_file_name( $source['file'] );
									if ( file_exists( $local_file_path ) ) {
										$src = $this->make_absolute_url( $local_file_path );
									}
								}

								$track = array(
									'src'     => aiovg_sanitize_url( $src ),
									'label'   => '',
									'srclang' => ''
								);

								if ( isset( $source['label'] ) && ! empty( $source['label'] ) ) {
									$track['label'] = sanitize_text_field( $source['label'] );
								}

								if ( isset( $source['srclang'] ) && ! empty( $source['srclang'] ) ) {
									$track['srclang'] = sanitize_text_field( $source['srclang'] );
								}

								add_post_meta( $post_id, 'track', $track );
							}
						}
					}
				}

				// Chapters
				if ( isset( $row['chapter'] ) ) {
					delete_post_meta( $post_id, 'chapter' );

					if ( is_array( $row['chapter'] ) ) {
						foreach ( $row['chapter'] as $chapter ) {
							if ( ! empty( $chapter['time'] ) && ! empty( $chapter['label'] ) ) {
								$chapter = array(
									'time'  => sanitize_text_field( $chapter['time'] ),
									'label' => sanitize_text_field( $chapter['label'] )
								);

								add_post_meta( $post_id, 'chapter', $chapter );
							}
						}					
					}
				}

				// Import ID
				$import_id = sanitize_text_field( $attributes['import_id'] );

				if ( ! metadata_exists( 'post', $post_id, 'import_id' ) ) {
					add_post_meta( $post_id, 'import_id', $import_id );
				} else {
					// Meta exists — check if same value
					$existing = get_post_meta( $post_id, 'import_id' );

					if ( ! in_array( $import_id, $existing ) ) {
						add_post_meta( $post_id, 'import_id', $import_id );
					}
				}

				// Meta
				$meta = array();

				// Type
				$meta['type'] = $type;

				// Video
				if ( in_array( $type, array( 'youtube', 'vimeo', 'dailymotion', 'rumble', 'facebook' ) ) ) {
					$meta[ $type ] = aiovg_sanitize_url( $video['src'] );
				} elseif ( in_array( $type, array( 'hls', 'dash' ) ) ) {
					$meta['type']  = 'adaptive';
					$meta[ $type ] = aiovg_sanitize_url( $video['src'] );
				} elseif ( 'embedcode' == $type ) {
					try {
						add_filter( 'wp_kses_allowed_html', 'aiovg_allow_iframe_script_tags' );
						$meta[ $type ] = wp_kses_post( $video['src'] );
					} finally {
						remove_filter( 'wp_kses_allowed_html', 'aiovg_allow_iframe_script_tags' );
					}
				} else {
					$src = $video['src'];

					if ( ! empty( $attributes['folder'] ) && isset( $video['file'] ) && ! empty( $video['file'] ) ) {
						$local_file_path = $attributes['folder'] . '/' . sanitize_file_name( $video['file'] );
						if ( file_exists( $local_file_path ) ) {
							$src = $this->make_absolute_url( $local_file_path );
						}
					}

					$meta['mp4'] = aiovg_sanitize_url( $src );

					// Quality level
					if ( isset( $video['quality'] ) && ! empty( $video['quality'] ) ) {
						$meta['quality_level'] = sanitize_text_field( $video['quality'] );
					}
				}

				// Sources
				$videos = array();

				foreach ( $sources as $source ) {
					if ( ! empty( $source['src'] ) && ! empty( $source['quality'] ) ) {
						$src = $source['src'];

						if ( ! empty( $attributes['folder'] ) && preg_match( '/^[a-zA-Z0-9_\-]+\.[a-zA-Z0-9]+$/i', $src ) ) {
							// $src is a simple filename (e.g. video.mp4)
							$local_file_path = $attributes['folder'] . '/' . sanitize_file_name( $src );
							if ( file_exists( $local_file_path ) ) {
								$src = $this->make_absolute_url( $local_file_path );
							}
						}

						if ( ! empty( $attributes['folder'] ) && isset( $source['file'] ) && ! empty( $source['file'] ) ) {
							$local_file_path = $attributes['folder'] . '/' . sanitize_file_name( $source['file'] );
							if ( file_exists( $local_file_path ) ) {
								$src = $this->make_absolute_url( $local_file_path );
							}
						}

						$videos[] = array(
							'src'     => aiovg_sanitize_url( $src ),
							'quality' => sanitize_text_field( $source['quality'] )
						);
					}
				}

				$meta['sources'] = $videos;

				// Parse image data
				$image      = '';
				$image_path = '';
				$image_id   = 0;
				$image_alt  = '';

				if ( isset( $row['image'] ) ) {
					if ( is_array( $row['image'] ) ) {
						if ( isset( $row['image']['src'] ) && ! empty( $row['image']['src'] ) ) {
							$image = $row['image']['src'];
						}

						if ( ! empty( $attributes['folder'] ) && isset( $row['image']['file'] ) && ! empty( $row['image']['file'] ) ) {
							$local_file_path = $attributes['folder'] . '/' . sanitize_file_name( $row['image']['file'] );
							if ( file_exists( $local_file_path ) ) {
								$image      = $this->make_absolute_url( $local_file_path );
								$image_path = $local_file_path;
							}
						}

						if ( isset( $row['image']['alt'] ) && ! empty( $row['image']['alt'] ) ) {
							$image_alt = $row['image']['alt'];
						}
					} else {
						$image = $row['image'];
					}

					// $image is a simple filename (e.g. image.jpg)
					if ( ! empty( $image ) && ! empty( $attributes['folder'] ) && preg_match( '/^[a-zA-Z0-9_\-]+\.[a-zA-Z0-9]+$/i', $image ) ) {
						$local_file_path = $attributes['folder'] . '/' . sanitize_file_name( $image );
						if ( file_exists( $local_file_path ) ) {
							$image      = $this->make_absolute_url( $local_file_path );
							$image_path = $local_file_path;
						}
					}
				}

				$meta['image']     = ! empty( $image ) ? aiovg_sanitize_url( $image ) : '';
				$meta['image_alt'] = ! empty( $image_alt ) ? sanitize_text_field( $image_alt ) : '';

				// Featured Image
				if ( ! empty( $attributes['set_featured_image'] ) ) {
					if ( ! empty( $meta['image'] ) ) {
						if ( ! empty( $image_path ) ) {
							$image_id = $this->create_attachment( $post_id, $image_path, $meta['image'] );
						} else {
							$image_id = aiovg_create_attachment_from_external_image_url( $meta['image'], $post_id );
						}
					}

					if ( ! empty( $image_id ) ) {
						$meta['image_id'] = $image_id;
						set_post_thumbnail( $post_id, $image_id ); 
					}
				}

				// Duration
				if ( isset( $row['duration'] ) ) {
					$meta['duration'] = '';

					if ( ! empty( $row['duration'] ) ) {
						$meta['duration'] = sanitize_text_field( $row['duration'] );
					}
				}

				// Views
				if ( isset( $row['views'] ) ) {
					$meta['views'] = 0;

					if ( ! empty( $row['views'] ) ) {
						$meta['views'] = (int) $row['views'];
					}
				}

				// Likes
				if ( isset( $row['likes'] ) ) {
					$meta['likes'] = 0;

					if ( ! empty( $row['likes'] ) ) {
						$meta['likes'] = (int) $row['likes'];
					}
				}

				// Dislikes
				if ( isset( $row['dislikes'] ) ) {
					$meta['dislikes'] = 0;

					if ( ! empty( $row['dislikes'] ) ) {
						$meta['dislikes'] = (int) $row['dislikes'];
					}
				}

				// Download
				if ( isset( $row['download'] ) ) {
					$meta['download'] = 0;

					if ( ! empty( $row['download'] ) ) {
						$meta['download'] = (int) $row['download'];
					}
				}

				// Featured
				if ( isset( $row['featured'] ) ) {
					$meta['featured'] = 0;

					if ( ! empty( $row['featured'] ) ) {
						$meta['featured'] = (int) $row['featured'];
					}
				}

				// Email Capture
				if ( isset( $row['email_capture'] ) ) {
					$meta['email_capture'] = -1;

					if ( '' !== (string) $row['email_capture'] ) {
						$meta['email_capture'] = (int) $row['email_capture'];
					}
				}

				// Access Control
				if ( isset( $row['access_control'] ) ) {
					$meta['access_control'] = -1;

					if ( ! empty( $row['access_control'] ) ) {
						$meta['access_control'] = (int) $row['access_control'];
					}
				}

				// Restricted Roles
				if ( isset( $row['restricted_roles'] ) ) {
					$meta['restricted_roles'] = array();

					if ( ! empty( $row['restricted_roles'] ) ) {
						$separator = ! empty( $attributes['values_separator'] ) ? $attributes['values_separator'] : ';';
						$restricted_roles = array_filter( array_map( 'trim', explode( $separator, $row['restricted_roles'] ) ) );
						$meta['restricted_roles'] = array_map( 'sanitize_text_field', $restricted_roles );
					}
				}

				// Insert / Update post meta in bulk
				if ( $is_existing_post ) {
					$this->update_post_meta_bulk( $post_id, $meta );
					$response['updated'][] = $csv_row_number;
				} else {
					$this->add_post_meta_bulk( $post_id, $meta );
					$response['imported'][] = $csv_row_number;
				}

				// Hook for developers
				do_action( 'aiovg_video_imported', $post_id, $attributes['import_id'] );					
			} else {
				$response['failed'][] = $csv_row_number;

				$response['errors'][] = sprintf(
					__( '<strong>Line #%d:</strong> %s - %s', 'all-in-one-video-gallery' ),
					$csv_row_number,
					$post_title,
					$post_id->get_error_message()
				);
			}
		}

		// Post Import
		wp_defer_term_counting( false );
		wp_defer_comment_counting( false );
		
		return $response;
	}

	/**
	 * Check if the video already exists.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  string  $src  The video source (URL or embed code).
	 * @param  string  $type The video source type.
	 * @return mixed         Video post ID if exists, false if not.
	 */
	private function is_video_exists( $src, $type = 'default' ) {
		if ( empty( $src ) || empty( $type ) ) {
			return false;
		}

		$type = sanitize_key( $type );

		$args = array(
			'post_type'              => 'aiovg_videos',
			'post_status'            => 'any',
			'posts_per_page'         => 1,
			'fields'                 => 'ids',
			'no_found_rows'          => true,
			'update_post_term_cache' => false,
			'update_post_meta_cache' => false
		);

		$meta_queries = array();

		// Match by video type
		$meta_queries[] = array(
			'key'     => 'type',
			'value'   => ( in_array( $type, array( 'hls', 'dash' ) ) ? 'adaptive' : $type ),
			'compare' => '='
		);

		// Match by source URL
		if ( 'embedcode' === $type ) {
			try {
				add_filter( 'wp_kses_allowed_html', 'aiovg_allow_iframe_script_tags' );

				$meta_queries[] = array(
					'key'     => 'embedcode',
					'value'   => wp_kses_post( $src ),
					'compare' => '='
				);
			} finally {
				remove_filter( 'wp_kses_allowed_html', 'aiovg_allow_iframe_script_tags' );
			}
		} else {
			$meta_queries[] = array(
				'key'     => ( 'default' == $type ? 'mp4' : $type ),
				'value'   => aiovg_sanitize_url( $src ),
				'compare' => '='
			);
		}

		// Assign the meta query with 'AND' relation if multiple clauses exist.
		if ( count( $meta_queries ) > 1 ) {
			$meta_queries['relation'] = 'AND';
		}
		$args['meta_query'] = $meta_queries;

		$aiovg_query = new WP_Query( $args );

		if ( $aiovg_query->have_posts() ) {
			// Since 'fields' is 'ids', $aiovg_query->posts is an array of post IDs.
			return $aiovg_query->posts[0];
		}

		return false;
	}

	/**
	 * Generate a secure, deterministic video post slug.
	 *
	 * @since  4.7.0
	 * @access private
	 * @param  string  $video_src Absolute or relative video file path.
	 * @return string
	 */
	private function generate_random_video_slug( $video_src ) {
		// Normalize path for consistency across environments
		$normalized = wp_normalize_path( $video_src );

		// Add site-specific salt
		$hash = hash( 'sha256', $normalized . '|' . wp_salt( 'aiovg_video_slug' ) );

		// Short, URL-safe slug
		return substr( $hash, 0, 18 );
	}

	/**
	 * Resolve video type.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  string  $src Video source (URL, relative path, or embed code).
	 * @return string       Video type ('default', 'youtube', 'vimeo', etc., or 'embedcode').
	 */
	private function resolve_video_type( $src ) {
		if ( empty( $src ) ) {
			return 'default';
		}

		// Normalize source
		$src = trim( $src );

		// Check for any HTML markup
		if ( preg_match( '/<\s*\w+(?:\b[^>]*)?>/i', $src ) ) {
			return 'embedcode';
		}

		// Check known video platforms
		if ( false !== stripos( $src, 'youtube.com' ) || false !== stripos( $src, 'youtu.be' ) ) {
			return 'youtube';
		} elseif ( false !== stripos( $src, 'vimeo.com' ) ) {
			return 'vimeo';
		} elseif ( false !== stripos( $src, 'dailymotion.com' ) ) {
			return 'dailymotion';
		} elseif ( false !== stripos( $src, 'rumble.com' ) ) {
			return 'rumble';
		} elseif ( false !== stripos( $src, 'facebook.com' ) ) {
			return 'facebook';
		} else {
			$file_type = wp_check_filetype( $src );

			if ( 'm3u8' == $file_type['ext'] ) {
				return 'hls';
			}

			if ( 'mpd' == $file_type['ext'] ) {
				return 'dash';
			}
		}

		// Default fallback
		return 'default';
	}

	/**
	 * Resolve term IDs from imported data for a given taxonomy.
	 *
	 * Supports hierarchical chains like "Parent > Child > Subchild".
	 * Can optionally create new terms if they don't exist.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  array           $terms      Imported terms array.
	 * @param  string          $taxonomy   Taxonomy name.
	 * @param  array           $attributes Import settings (expects 'values_separator' and 'add_new_term').
	 * @return array|WP_Error              Array of term IDs, or WP_Error on failure.
	 */
	private function resolve_term_ids( $terms, $taxonomy, $attributes ) {
		$term_ids = array();

		if ( empty( $terms ) ) {
			return $term_ids; // No terms provided, return empty array
		}

		$separator = ! empty( $attributes['values_separator'] ) ? $attributes['values_separator'] : ';';
		$terms_raw = array_filter( array_map( 'trim', explode( $separator, $terms ) ) );

		foreach ( $terms_raw as $term_item ) {
			// Numeric term IDs
			if ( is_numeric( $term_item ) && get_term( (int) $term_item, $taxonomy ) ) {
				$term_ids[] = (int) $term_item;
				continue;
			}

			// Hierarchical chain
			$chain = array_filter( array_map( 'trim', explode( '>', $term_item ) ) );
			if ( empty( $chain ) ) {
				continue;
			}

			$parent_id = 0; // For hierarchy

			foreach ( $chain as $term_name ) {
				$term_name = htmlspecialchars( sanitize_text_field( $term_name ) ); // Handle &amp; etc.

				// Check if term exists
				$term = term_exists( $term_name, $taxonomy, $parent_id );

				if ( $term ) {
					$parent_id = (int) $term['term_id'];
				} else {
					if ( ! empty( $attributes['add_new_term'] ) ) {
						$new_term = wp_insert_term( $term_name, $taxonomy, array( 'parent' => $parent_id ) );
						
						if ( is_wp_error( $new_term ) ) {
							return new WP_Error(
								'term_insert_failed',
								sprintf(
									__( 'Failed to create term "%s" in taxonomy "%s".', 'all-in-one-video-gallery' ),
									$term_name,
									$taxonomy
								)
							);
						}

						$parent_id = (int) $new_term['term_id'];
					} else {
						return new WP_Error(
							'term_not_found',
							sprintf(
								__( 'Term "%s" not found in taxonomy "%s" and adding new terms is disabled.', 'all-in-one-video-gallery' ),
								$term_name,
								$taxonomy
							)
						);
					}
				}

				// Append each term in the chain if not already added
				if ( $parent_id && ! in_array( $parent_id, $term_ids, true ) ) {
					$term_ids[] = $parent_id;
				}
			}
		}

		return $term_ids;
	}

	/**
	 * Resolve user ID from a username or email, optionally creating a new user.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  string       $post_author WordPress username or email address.
	 * @param  array        $attributes  Import settings (expects 'add_new_user' boolean).
	 * @return int|WP_Error              User ID on success, WP_Error on failure.
	 */
	private function resolve_user_id( $post_author, $attributes = array() ) {
		if ( empty( $post_author ) ) {
			return new WP_Error( 'invalid_user', __( 'No username or email provided.', 'all-in-one-video-gallery' ) );
		}

		$post_author = sanitize_text_field( $post_author );

		// Case 1: Numeric user ID
		if ( is_numeric( $post_author ) ) {
			$user = get_user_by( 'ID', (int) $post_author );

			if ( $user ) {
				return $user->ID;
			}
				
			return new WP_Error( 'invalid_user', __( 'User ID does not exist.', 'all-in-one-video-gallery' ) );
		}

		// Case 2: Email
		if ( is_email( $post_author ) ) {
			$email = sanitize_email( $post_author );
			$user  = get_user_by( 'email', $email );

			if ( $user ) {
				return $user->ID;
			}

			if ( ! empty( $attributes['add_new_user'] ) ) {
				$password    = wp_generate_password( 12, true );
				$new_user_id = wp_create_user( $email, $password, $email );

				if ( is_wp_error( $new_user_id ) ) {
					return $new_user_id;
				}

				return $new_user_id;
			}

			return new WP_Error( 'invalid_user', sprintf( __( 'User with email "%s" does not exist.', 'all-in-one-video-gallery' ), $email ) );
		}

		// Case 3: Username
		$user_id = username_exists( $post_author );

		if ( $user_id ) {
			return $user_id;
		}

		return new WP_Error( 'invalid_user', sprintf( __( 'User "%s" does not exist.', 'all-in-one-video-gallery' ), $post_author ) );
	}

	/**
	 * Create a WordPress attachment from a local file if it doesn't already exist.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  int       $post_id   The post ID to attach the media to (e.g., a video post).
	 * @param  string    $file_path Absolute path to the local file.
	 * @param  string    $file_url  Optional. Public URL of the file (for checking duplicates by URL).
	 * @return int|false            Attachment ID on success, false on failure.
	 */
	private function create_attachment( $post_id, $file_path, $file_url = '' ) {
		if ( ! file_exists( $file_path ) ) {
			return false; // File doesn't exist
		}
		
		if ( $file_url ) {
			// Check via WordPress native function (uploads directory)
			if ( $existing_id = attachment_url_to_postid( $file_url ) ) {
				return $existing_id;
			}

			// Check via plugin meta (for external or imported files)
			$existing = get_posts( array(
				'post_type'      => 'attachment',
				'post_status'    => 'inherit',
				'meta_key'       => 'aiovg_source_url',
				'meta_value'     => $file_url,
				'fields'         => 'ids',
				'posts_per_page' => 1
			) );

			if ( ! empty( $existing ) ) {
				return $existing[0];
			}
		}

		// Include required WordPress files
		if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
			require_once ABSPATH . 'wp-admin/includes/image.php';
			require_once ABSPATH . 'wp-admin/includes/file.php';
			require_once ABSPATH . 'wp-admin/includes/media.php';
		}

		// Prepare attachment data
		$file_name = basename( $file_path );
		$file_type = wp_check_filetype( $file_name, null );
		
		// Default to a safe image MIME type if detection fails
		$mime_type = ! empty( $file_type['type'] ) ? $file_type['type'] : 'image/jpeg';

		$attachment = array(
			'guid'           => $file_url,
			'post_mime_type' => $mime_type,
			'post_title'     => sanitize_file_name( preg_replace( '/\.[^.]+$/', '', $file_name ) ),
			'post_content'   => '',
			'post_status'    => 'inherit'
		);

		// Insert attachment into WordPress
		$attach_id = wp_insert_attachment( $attachment, $file_path, $post_id );

		if ( ! $attach_id || is_wp_error( $attach_id ) ) {
			return false;
		}

		// Generate metadata (thumbnails, sizes)
		$attach_data = wp_generate_attachment_metadata( $attach_id, $file_path );
		wp_update_attachment_metadata( $attach_id, $attach_data );

		// Save source URL for plugin-specific imports
		if ( $file_url ) {
			update_post_meta( $attach_id, 'aiovg_source_url', esc_url_raw( $file_url ) );
		}

		return $attach_id;
	}

	/**
	 * Insert multiple post meta at once.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  int     $post_id Post ID.
	 * @param  array   $data    Associative array of meta keys and values.
	 */
	private function add_post_meta_bulk( $post_id, $data ) {
		global $wpdb;

		$meta_table = _get_meta_table( 'post' );

		// Basic sanity check.
		if ( empty( $post_id ) || empty( $data ) ) {
			return;
		}

		// Bulk insert all new meta entries.
		$placeholders = array();
		$values       = array();

		foreach ( $data as $key => $value ) {
			$placeholders[] = "(%d, %s, %s)";
			$values[]       = $post_id;
			$values[]       = $key;
			$values[]       = maybe_serialize( $value );
		}

		$sql  = "INSERT INTO $meta_table (`post_id`, `meta_key`, `meta_value`) VALUES ";
		$sql .= implode( ',', $placeholders );

		$wpdb->query( $wpdb->prepare( $sql, $values ) );
	}

	/**
	 * Update multiple post meta entries at once.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  int     $post_id Post ID.
	 * @param  array   $data    Associative array of meta keys and values.
	 * @return void
	 */
	private function update_post_meta_bulk( $post_id, $data ) {
		global $wpdb;

		$meta_table = _get_meta_table( 'post' );

		// Basic sanity check.
		if ( empty( $post_id ) || empty( $data ) ) {
			return;
		}

		// Determine if the DB engine supports transactions (InnoDB).
		$supports_transactions = false;

		$engine_result = $wpdb->get_row(
			$wpdb->prepare( "SHOW TABLE STATUS WHERE Name = %s", $meta_table )
		);

		if ( $engine_result && isset( $engine_result->Engine ) && strtoupper( $engine_result->Engine ) === 'INNODB' ) {
			$supports_transactions = true;
		}

		// Begin transaction if supported.
		if ( $supports_transactions ) {
			$wpdb->query( 'START TRANSACTION' );
		}

		try {
			// Step 1: Delete existing meta for the given keys.
			$meta_keys = array_keys( $data );

			$placeholders = implode( ', ', array_fill( 0, count( $meta_keys ), '%s' ) );
			$sql_delete   = "DELETE FROM {$meta_table} WHERE post_id = %d AND meta_key IN ($placeholders)";
			$wpdb->query( $wpdb->prepare( $sql_delete, array_merge( array( $post_id ), $meta_keys ) ) );

			// Step 2: Bulk insert all new meta entries.
			$placeholders = array();
			$values       = array();

			foreach ( $data as $key => $value ) {
				$placeholders[] = "(%d, %s, %s)";
				$values[]       = $post_id;
				$values[]       = $key;
				$values[]       = maybe_serialize( $value );
			}

			$sql_insert  = "INSERT INTO {$meta_table} (`post_id`, `meta_key`, `meta_value`) VALUES ";
			$sql_insert .= implode( ',', $placeholders );

			$wpdb->query( $wpdb->prepare( $sql_insert, $values ) );

			// Commit if all went well.
			if ( $supports_transactions ) {
				$wpdb->query( 'COMMIT' );
			}
		} catch ( Exception $e ) {
			// Rollback only works if engine supports transactions.
			if ( $supports_transactions ) {
				$wpdb->query( 'ROLLBACK' );
			}
		}
	}

	/**
	 * Get CSV column mapping fields.
	 *
	 * @since  4.5.2
	 * @access private
	 * @return array   $fields CSV column mapping fields.
	 */
	private function get_csv_column_mapping_fields() {
		$fields = array(
			'post_id'          => __( 'Video ID (existing video)', 'all-in-one-video-gallery' ),
			'post_title'       => __( 'Video Title', 'all-in-one-video-gallery' ),
			'video'            => __( 'Video File', 'all-in-one-video-gallery' ),
			'image'            => __( 'Poster Image', 'all-in-one-video-gallery' ),
			'post_content'     => __( 'Video Description', 'all-in-one-video-gallery' ),
			'post_excerpt'     => __( 'Video Excerpt', 'all-in-one-video-gallery' ),					
			'aiovg_categories' => __( 'Video Categories', 'all-in-one-video-gallery' ),			
			'aiovg_tags'       => __( 'Video Tags', 'all-in-one-video-gallery' ),
			'track'            => __( 'Subtitles', 'all-in-one-video-gallery' ),
			'chapter'          => __( 'Chapters', 'all-in-one-video-gallery' ),
			'duration'         => __( 'Video Duration', 'all-in-one-video-gallery' ),					
			'views'            => __( 'Views Count', 'all-in-one-video-gallery' ),
			'likes'            => __( 'Likes Count', 'all-in-one-video-gallery' ),
			'dislikes'         => __( 'Dislikes Count', 'all-in-one-video-gallery' ),
			'download'         => __( 'Enable Download', 'all-in-one-video-gallery' ),
			'featured'         => __( 'Featured', 'all-in-one-video-gallery' ),
			'email_capture'    => __( 'Email & Lead Capture', 'all-in-one-video-gallery' ),
			'access_control'   => __( 'Access Control', 'all-in-one-video-gallery' ),
			'restricted_roles' => __( 'Allowed User Roles', 'all-in-one-video-gallery' ),
			'comment_status'   => __( 'Comment Status', 'all-in-one-video-gallery' ),
			'post_status'      => __( 'Video Status', 'all-in-one-video-gallery' ),
			'post_author'      => __( 'Video Author', 'all-in-one-video-gallery' ),
			'post_date'        => __( 'Published Date', 'all-in-one-video-gallery' )
		);

		return $fields;
	}

	/**
	 * Check if a given URL is local to the WordPress site.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  string  $url The URL to check.
	 * @return bool         True if local, false if external.
	 */
	private function is_file_local( $url ) {
		// Get the hostname of the WordPress site.
		$site_host = wp_parse_url( get_home_url(), PHP_URL_HOST );

		// Get the hostname from the URL to be checked.
		$file_host = wp_parse_url( $url, PHP_URL_HOST );

		// If there is no host, it's a relative path and thus local.
		if ( null === $file_host ) {
			return true;
		}

		// Compare the hostnames (case-insensitive).
		return strcasecmp( $site_host, $file_host ) === 0;
	}

	/**
	 * Generate a filename for exported local files.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  string       $file_url The file URL.
	 * @param  int          $post_id  The post ID.
	 * @param  bool|null    $is_local Optional. Whether the file is local. If null, it will be determined automatically.
	 * @return string|false           Generated filename or false if external URL.
	 */
	private function get_exported_filename( $file_url, $post_id, $is_local = null ) {
		if ( $is_local === null ) {
			$is_local = $this->is_file_local( $file_url );
		}

		if ( $is_local ) {
			return "$post_id-" . basename( $file_url );
		}

		return false;
	}

	/**
	 * Converts an absolute file system path to a site-relative URL.
	 *
	 * @since  4.5.2
	 * @access private
	 * @param  string  $absolute_path Absolute file path.
	 * @return string                 File URL or empty string if path is invalid.
	 */
	private function make_absolute_url( $absolute_path ) {
		$absolute_path = wp_normalize_path( $absolute_path );
		$abspath       = wp_normalize_path( ABSPATH );

		if ( stripos( $absolute_path, $abspath ) === 0 ) {
			$relative_path = ltrim( substr( $absolute_path, strlen( $abspath ) ), '/' );
			return site_url( $relative_path );
		}

		return '';
	}

	/**
	 * Schedule cleanup of the export directory contents.
	 *
	 * @since  4.5.2
	 * @access private
	 */
	private function schedule_export_directory_cleanup() {
		// Unschedule previous cleanup if exists
		$timestamp = wp_next_scheduled( 'aiovg_cleanup_export_directory' );
		if ( $timestamp ) {
			wp_unschedule_event( $timestamp, 'aiovg_cleanup_export_directory' );
		}

		// Schedule new single-event cleanup
		$cleanup_after = apply_filters( 'aiovg_cleanup_export_directory_time', DAY_IN_SECONDS );
		wp_schedule_single_event( time() + $cleanup_after, 'aiovg_cleanup_export_directory' );
	}

}
