/**
 * Admin JavaScript for WebEquipe PDF Search
 *
 * @package WebEquipe\PDFSearch
 * @since 1.0.0
 */

(function ($) {
	'use strict';

	var reindexing     = false;
	var currentOffset  = 0;
	var totalProcessed = 0;
	var totalFailed    = 0;
	var reindexTotal   = 0;
	var reindexSweep   = 0;
	var reindexPendingOnly = false;

	// PDF List Management.
	var pdfListCurrentPage  = 1;
	var pdfListLimit        = 50;
	var pdfListTotal        = 0;
	var pdfListSearch       = '';
	var pdfListStatusFilter  = 'all';
	var manageListPollTimer  = null;

	// Index Activity page state.
	var activityCurrentPage  = 1;
	var activityLimit        = 20;
	var activityTotal        = 0;
	var activitySearch       = '';
	var activityStatusFilter = 'all';
	var activityPollTimer    = null;

	// Error dialog state.
	var $errorDialog = null;

	/**
	 * Initialize admin functionality
	 */
	function init() {
		// Re-index all button.
		$( '#webequipe-pdf-reindex-all, #webequipe-pdf-manage-reindex-all' ).on( 'click', handleReindexAll );

		// Clear index button.
		$( '#webequipe-pdf-clear-index, #webequipe-pdf-manage-clear-index' ).on( 'click', handleClearIndex );

		// Re-index single PDF (use event delegation for dynamic elements).
		$( document ).on( 'click', '.webequipe-pdf-reindex-single', handleReindexSingle );

		// Unindex single PDF.
		$( document ).on( 'click', '.webequipe-pdf-unindex-single', handleUnindexSingle );

		// Exclude single PDF.
		$( document ).on( 'click', '.webequipe-pdf-exclude-single', handleExcludeSingle );

		// Include single PDF (clear exclusion only).
		$( document ).on( 'click', '.webequipe-pdf-include-single', handleIncludeSingle );

		// Copy shortcode button.
		$( '.copy-shortcode' ).on( 'click', handleCopyShortcode );

		// PDF list actions.
		$( '#refresh-pdf-list, #webequipe-pdf-manage-refresh' ).on( 'click', handleRefreshPDFList );
		$( '#select-all-pdfs, #select-all-pdfs-header' ).on( 'change', handleSelectAll );
		$( '#apply-bulk-action' ).on( 'click', handleBulkAction );
		$( '#webequipe-pdf-search-input' ).on( 'input', debounce( handleSearchPDFs, 500 ) );
		$( '#webequipe-pdf-status-filter' ).on( 'change', handleStatusFilter );

		// Individual actions in PDF list.
		$( document ).on( 'click', '.pdf-action-index', handlePDFListAction );
		$( document ).on( 'click', '.pdf-action-unindex', handlePDFListAction );
		$( document ).on( 'click', '.pdf-action-include', handlePDFListAction );
		$( document ).on( 'click', '.pdf-action-exclude', handlePDFListAction );
		$( document ).on( 'click', '.pdf-action-retry', handlePDFListAction );
		$( document ).on( 'click', '.pdf-action-cancel', handleCancelIndexing );
		$( document ).on( 'click', '.pdf-action-resume', handleResumeIndexing );

		// Error detail buttons (Media Library column + PDF list).
		$( document ).on( 'click', '.webequipe-pdf-error-details', handleErrorDetailsClick );
		$( document ).on( 'click', '.webequipe-pdf-index-warning', handleWarningDetailsClick );

		// Index Activity page.
		if ( $( '#webequipe-pdf-activity-page' ).length ) {
			initActivityPage();
		}

		// Pro launch banner dismiss.
		$( document ).on( 'click', '#webequipe-pro-banner-dismiss', handleProBannerDismiss );

		initIndexingMethodCards();

		initManagePage();
	}

	/**
	 * Whether the Manage PDFs admin page is active.
	 *
	 * @return {boolean}
	 */
	function isManagePage() {
		return $( '#webequipe-pdf-manage-page' ).length > 0;
	}

	/**
	 * Initialize Manage PDFs page (auto-load library, metrics, Pro-style table).
	 */
	function initManagePage() {
		if ( ! isManagePage() ) {
			return;
		}

		$( '#webequipe-pdf-manage-last-scan' ).text( webequipePdfSearch.strings.lastScannedPending );

		pdfListLimit = parseInt( $( '#webequipe-pdf-per-page' ).val(), 10 ) || 50;

		$( '#webequipe-pdf-per-page' ).on(
			'change',
			function () {
				pdfListLimit       = parseInt( $( this ).val(), 10 ) || 50;
				pdfListCurrentPage = 1;
				loadPDFList();
			}
		);

		$( '#webequipe-pdf-manage-page' ).on(
			'click',
			'.pdf-page-link',
			function ( e ) {
				e.preventDefault();
				pdfListCurrentPage = parseInt( $( this ).data( 'page' ), 10 ) || 1;
				loadPDFList();
			}
		);

		loadPDFList();
	}

	/**
	 * Keep PDF indexing method radio cards in sync (one selected at a time).
	 */
	function initIndexingMethodCards() {
		var $grid = $( '.webequipe-pdf-indexing-method-grid' );
		if ( ! $grid.length ) {
			return;
		}

		$grid.on( 'change', '.webequipe-pdf-indexing-method-input', function () {
			$grid.find( '.webequipe-pdf-indexing-method-card' ).removeClass( 'webequipe-pdf-indexing-method-card--selected' );
			$( this ).closest( '.webequipe-pdf-indexing-method-card' ).addClass( 'webequipe-pdf-indexing-method-card--selected' );
		} );

		$grid.find( '.webequipe-pdf-indexing-method-input:checked' ).each( function () {
			$( this ).closest( '.webequipe-pdf-indexing-method-card' ).addClass( 'webequipe-pdf-indexing-method-card--selected' );
		} );
	}

	/**
	 * Dismiss the Pro launch banner permanently for this user.
	 */
	function handleProBannerDismiss() {
		var $banner = $( '#webequipe-pro-banner' );
		if ( ! $banner.length ) {
			return;
		}

		$banner.slideUp( 200 );

		$.post(
			webequipePdfSearch.ajaxUrl,
			{
				action: 'webequipe_dismiss_pro_banner',
				nonce: webequipePdfSearch.dismissProBannerNonce
			}
		);
	}

	/**
	 * Handle re-index all action
	 */
	function handleReindexAll() {
		if (reindexing) {
			return;
		}

		if ( ! confirm( webequipePdfSearch.strings.confirmReindexAll )) {
			return;
		}

		reindexing     = true;
		currentOffset  = 0;
		totalProcessed = 0;
		totalFailed    = 0;
		reindexTotal   = 0;
		reindexSweep       = 0;
		reindexPendingOnly = false;

		$( '#webequipe-pdf-progress' ).show();
		$( '.progress-fill' ).css( 'width', '0%' );
		$( '.progress-text' ).text( webequipePdfSearch.strings.startingReindex );
		showReindexProgressNotice();
		setReindexingControls( true );

		processBatch( false );
	}

	/**
	 * Re-index buttons on dashboard and activity page.
	 */
	function getReindexButtons() {
		return $( '#webequipe-pdf-reindex-all, #webequipe-pdf-manage-reindex-all' );
	}

	/**
	 * Show / hide the do-not-refresh notice under the re-index progress bar.
	 */
	function showReindexProgressNotice() {
		var $progress = $( '#webequipe-pdf-progress' );
		var $notice   = $progress.find( '.webequipe-pdf-progress-notice' );

		if ( ! $notice.length ) {
			$notice = $( '<p class="webequipe-pdf-progress-notice" role="status"></p>' );
			$progress.append( $notice );
		}

		$notice.text( webequipePdfSearch.strings.reindexInProgressNotice ).show();
	}

	function hideReindexProgressNotice() {
		$( '#webequipe-pdf-progress .webequipe-pdf-progress-notice' ).hide();
	}

	/**
	 * Disable other indexing controls while bulk re-index runs.
	 *
	 * @param {boolean} disabled
	 */
	function setReindexingControls(disabled) {
		getReindexButtons().prop( 'disabled', disabled );

		if ( isManagePage() ) {
			$( '#webequipe-pdf-manage-refresh, #webequipe-pdf-manage-clear-index, #apply-bulk-action, #bulk-action-select' ).prop( 'disabled', disabled );
		}

		if ( $( '#webequipe-pdf-dashboard' ).length ) {
			$( '#webequipe-pdf-clear-index' ).prop( 'disabled', disabled );
		}

		if ( $( '#webequipe-pdf-activity-page' ).length ) {
			$( '#webequipe-pdf-activity-refresh, #webequipe-pdf-activity-export' ).prop( 'disabled', disabled );
		}

		if ( disabled ) {
			window.onbeforeunload = function () {
				return webequipePdfSearch.strings.reindexInProgressNotice;
			};
		} else {
			window.onbeforeunload = null;
		}
	}

	/**
	 * Process a batch of PDFs
	 */
	function processBatch(pendingOnly) {
		reindexPendingOnly = !! pendingOnly;

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				timeout: 0,
				data: {
					action: 'webequipe_pdf_reindex_ajax',
					nonce: webequipePdfSearch.nonce,
					offset: currentOffset,
					pending_only: pendingOnly ? 1 : 0
				},
				success: function (response) {
					if (response.success) {
						var data       = response.data;
						var batchCount = parseInt( data.batch_count, 10 ) || 0;

						if ( data.total_pdfs ) {
							reindexTotal = parseInt( data.total_pdfs, 10 ) || reindexTotal;
						}

						totalProcessed += data.success;
						totalFailed    += data.failed;

						var progress = 0;
						if ( reindexTotal > 0 ) {
							progress = ( ( currentOffset + Math.max( batchCount, 1 ) ) / reindexTotal ) * 100;
						}
						updateProgress( progress, totalProcessed, totalFailed );

						var hasMore          = !! data.has_more;
						var remainingPending = parseInt( data.remaining_not_indexed, 10 ) || 0;

						nudgeBackgroundIndexingDuringReindex();

						if ( hasMore ) {
							currentOffset += Math.max( batchCount, 1 );
							setTimeout(
								function () {
									processBatch( reindexPendingOnly );
								},
								300
							);
						} else if ( remainingPending > 0 && reindexSweep < 8 ) {
							reindexSweep += 1;
							currentOffset = 0;
							setTimeout(
								function () {
									processBatch( true );
								},
								500
							);
						} else {
							updateProgress( 100, totalProcessed, totalFailed );
							finishReindexWithResume( remainingPending );
						}
					} else {
						showError( response.data.message || webequipePdfSearch.strings.error );
						completeReindexing();
					}
				},
				error: function () {
					// Server timed out (often a huge PDF) — skip ahead so later files are not left "Not indexed".
					currentOffset += 1;
					if ( reindexTotal > 0 && currentOffset < reindexTotal ) {
						updateProgress(
							( currentOffset / reindexTotal ) * 100,
							totalProcessed,
							totalFailed
						);
						setTimeout(
							function () {
								processBatch( reindexPendingOnly );
							},
							1500
						);
						return;
					}
					showError( webequipePdfSearch.strings.error );
					completeReindexing();
				}
			}
		);
	}

	/**
	 * Process stalled/scheduled page batches while bulk re-index runs.
	 */
	function nudgeBackgroundIndexingDuringReindex() {
		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_resume_indexing',
					nonce: webequipePdfSearch.nonce,
					limit: 3
				}
			}
		);
	}

	/**
	 * Update progress bar
	 */
	function updateProgress(percentage, processed, failed) {
		percentage = Math.min( 100, Math.max( 0, percentage ) );
		$( '.progress-fill' ).css( 'width', percentage + '%' );
		$( '.progress-text' ).text(
			formatString(
				webequipePdfSearch.strings.progressTemplate,
				processed,
				failed,
				Math.round( percentage )
			)
		);
	}

	/**
	 * Resume stalled page batches, then complete re-index UI.
	 *
	 * @param {number} remainingPending PDFs still not fully indexed.
	 */
	function finishReindexWithResume(remainingPending) {
		var rounds = 0;
		var maxRounds = remainingPending > 0 ? 10 : 3;

		function runResumeRound() {
			$.ajax(
				{
					url: webequipePdfSearch.ajaxUrl,
					type: 'POST',
					data: {
						action: 'webequipe_pdf_resume_indexing',
						nonce: webequipePdfSearch.nonce,
						limit: 5
					},
					complete: function () {
						rounds += 1;
						if ( rounds < maxRounds ) {
							setTimeout( runResumeRound, 2000 );
						} else {
							if ( isManagePage() ) {
								loadPDFList( true );
							}
							completeReindexing();
						}
					}
				}
			);
		}

		runResumeRound();
	}

	/**
	 * Complete re-indexing
	 */
	function completeReindexing() {
		reindexing = false;
		hideReindexProgressNotice();
		setReindexingControls( false );
		$( '.progress-text' ).text(
			formatString(
				webequipePdfSearch.strings.completedTemplate,
				totalProcessed,
				totalFailed
			)
		);

		if ( $( '#webequipe-pdf-activity-page' ).length ) {
			setTimeout(
				function () {
					loadActivityList();
				},
				2000
			);
			return;
		}

		if ( isManagePage() ) {
			setTimeout(
				function () {
					$( '#webequipe-pdf-progress' ).hide();
					loadPDFList();
				},
				2000
			);
			return;
		}

		// Reload page after 2 seconds to show updated stats.
		setTimeout(
			function () {
				location.reload();
			},
			2000
		);
	}

	/**
	 * Handle clear index action
	 */
	function handleClearIndex() {
		if ( ! confirm( webequipePdfSearch.strings.confirmClear )) {
			return;
		}

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_clear_index',
					nonce: webequipePdfSearch.nonce
				},
				success: function (response) {
					if (response.success) {
						alert( response.data.message );
						if ( isManagePage() ) {
							$( '#webequipe-pdf-progress' ).hide();
							loadPDFList();
							return;
						}
						location.reload();
					} else {
						alert( response.data.message || webequipePdfSearch.strings.error );
					}
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
				}
			}
		);
	}

	/**
	 * Handle re-index single PDF
	 */
	function handleReindexSingle() {
		var $button    = $( this );
		var postId     = $button.data( 'post-id' );
		var $container = $button.closest( '.webequipe-pdf-index-status' );
		var $spinner   = $button.siblings( '.spinner' );

		$button.prop( 'disabled', true );
		$spinner.addClass( 'is-active' );

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_reindex_single',
					nonce: webequipePdfSearch.nonce,
					post_id: postId
				},
				success: function (response) {
					if (response.success) {
						if ( $container.length ) {
							var now     = new Date();
							var dateStr = now.toLocaleDateString();
							$container.html(
								'<span class="status-badge status-indexed"><span class="dashicons dashicons-yes-alt"></span> ' + webequipePdfSearch.strings.indexed + '</span>' +
								'<br><small>' + dateStr + '</small>' +
								'<div class="webequipe-pdf-actions">' +
								'<button type="button" class="button button-small webequipe-pdf-unindex-single" data-post-id="' + postId + '">' + webequipePdfSearch.strings.unindex + '</button> ' +
								'<button type="button" class="button button-small webequipe-pdf-exclude-single" data-post-id="' + postId + '">' + webequipePdfSearch.strings.excludeAction + '</button>' +
								'<span class="spinner"></span>' +
								'</div>'
							);
						} else {
							location.reload();
						}
					} else {
						alert( response.data.message || webequipePdfSearch.strings.error );
						$button.prop( 'disabled', false );
						$spinner.removeClass( 'is-active' );
					}
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
					$button.prop( 'disabled', false );
					$spinner.removeClass( 'is-active' );
				}
			}
		);
	}

	/**
	 * Handle unindex single PDF
	 */
	function handleUnindexSingle() {
		var $button    = $( this );
		var postId     = $button.data( 'post-id' );
		var $container = $button.closest( '.webequipe-pdf-index-status' );
		var $spinner   = $button.siblings( '.spinner' );

		if ( ! confirm( webequipePdfSearch.strings.confirmUnindex )) {
			return;
		}

		$button.prop( 'disabled', true );
		$spinner.addClass( 'is-active' );

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_unindex_single',
					nonce: webequipePdfSearch.nonce,
					post_id: postId
				},
				success: function (response) {
					if (response.success) {
						// Update UI - show "Not Indexed" badge with Index and Exclude buttons (same design as Settings page).
						$container.html(
							'<span class="status-badge status-not-indexed"><span class="dashicons dashicons-minus"></span> ' + webequipePdfSearch.strings.notIndexed + '</span>' +
							'<div class="webequipe-pdf-actions">' +
							'<button type="button" class="button button-small webequipe-pdf-reindex-single" data-post-id="' + postId + '">' + webequipePdfSearch.strings.index + '</button> ' +
							'<button type="button" class="button button-small webequipe-pdf-exclude-single" data-post-id="' + postId + '">' + webequipePdfSearch.strings.excludeAction + '</button>' +
							'<span class="spinner"></span>' +
							'</div>'
						);
					} else {
						alert( response.data.message || webequipePdfSearch.strings.error );
						$button.prop( 'disabled', false );
						$spinner.removeClass( 'is-active' );
					}
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
					$button.prop( 'disabled', false );
					$spinner.removeClass( 'is-active' );
				}
			}
		);
	}

	/**
	 * Handle exclude single PDF
	 */
	function handleExcludeSingle() {
		var $button    = $( this );
		var postId     = $button.data( 'post-id' );
		var $container = $button.closest( '.webequipe-pdf-index-status' );
		var $spinner   = $button.siblings( '.spinner' );

		if ( ! confirm( webequipePdfSearch.strings.confirmExclude )) {
			return;
		}

		$button.prop( 'disabled', true );
		$spinner.addClass( 'is-active' );

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_exclude_single',
					nonce: webequipePdfSearch.nonce,
					post_id: postId
				},
				success: function (response) {
					if (response.success) {
						// Update UI - show "Excluded" badge with only Include button (same design as Settings page).
						$container.html(
							'<span class="status-badge status-excluded"><span class="dashicons dashicons-hidden"></span> ' + webequipePdfSearch.strings.excluded + '</span>' +
							'<div class="webequipe-pdf-actions">' +
							'<button type="button" class="button button-small webequipe-pdf-include-single" data-post-id="' + postId + '">' + webequipePdfSearch.strings.include + '</button>' +
							'<span class="spinner"></span>' +
							'</div>'
						);
					} else {
						alert( response.data.message || webequipePdfSearch.strings.error );
						$button.prop( 'disabled', false );
						$spinner.removeClass( 'is-active' );
					}
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
					$button.prop( 'disabled', false );
					$spinner.removeClass( 'is-active' );
				}
			}
		);
	}

	/**
	 * Handle include single PDF (clear exclusion only; does not index)
	 */
	function handleIncludeSingle() {
		var $button    = $( this );
		var postId     = $button.data( 'post-id' );
		var $container = $button.closest( '.webequipe-pdf-index-status' );
		var $spinner   = $button.siblings( '.spinner' );

		$button.prop( 'disabled', true );
		$spinner.addClass( 'is-active' );

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_include_single',
					nonce: webequipePdfSearch.nonce,
					post_id: postId
				},
				success: function (response) {
					if (response.success) {
						// Update UI - show "Not Indexed" badge with Index and Exclude buttons (same design as Settings page).
						$container.html(
							'<span class="status-badge status-not-indexed"><span class="dashicons dashicons-minus"></span> ' + webequipePdfSearch.strings.notIndexed + '</span>' +
							'<div class="webequipe-pdf-actions">' +
							'<button type="button" class="button button-small webequipe-pdf-reindex-single" data-post-id="' + postId + '">' + webequipePdfSearch.strings.index + '</button> ' +
							'<button type="button" class="button button-small webequipe-pdf-exclude-single" data-post-id="' + postId + '">' + webequipePdfSearch.strings.excludeAction + '</button>' +
							'<span class="spinner"></span>' +
							'</div>'
						);
					} else {
						alert( response.data.message || webequipePdfSearch.strings.error );
						$button.prop( 'disabled', false );
						$spinner.removeClass( 'is-active' );
					}
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
					$button.prop( 'disabled', false );
					$spinner.removeClass( 'is-active' );
				}
			}
		);
	}

	/**
	 * Show error message
	 */
	function showError(message) {
		$( '.progress-text' ).text( webequipePdfSearch.strings.errorPrefix + ' ' + message ).css( 'color', '#d63638' );
	}

	/**
	 * Handle error details click
	 */
	function handleErrorDetailsClick(e) {
		e.preventDefault();

		var $btn     = $( this );
		var rawError = $btn.data( 'error' ) || $btn.attr( 'title' ) || '';
		var message  = String( rawError );

		if ( ! message) {
			alert( webequipePdfSearch.strings.error );
			return;
		}

		if ( isImageOnlyError( message ) ) {
			showProUpsellErrorDialog(
				message,
				webequipePdfSearch.strings.suggestImageOnlyFreeTip,
				webequipePdfSearch.strings.suggestImageOnlyPro
			);
			return;
		}

		if ( isSecuredPdfError( message ) ) {
			showProUpsellErrorDialog(
				message,
				webequipePdfSearch.strings.suggestSecuredPdf,
				webequipePdfSearch.strings.suggestSecuredPdfPro
			);
			return;
		}

		var suggestion = buildErrorSuggestion( message );

		// Prefer a lightweight modal-style overlay within the admin screen.
		showErrorDialog( message, suggestion );
	}

	/**
	 * Error dialog with free tip + PDF Search Pro upsell block.
	 *
	 * @param {string} message Primary error text.
	 * @param {string} freeTip Secondary free-plugin guidance.
	 * @param {string} proLead Pro upsell paragraph.
	 */
	function showProUpsellErrorDialog(message, freeTip, proLead) {
		showErrorDialog( message, freeTip, null, {
			proUpsell: true,
			proLead: proLead
		} );
	}

	/**
	 * Whether the error text indicates image-only / no extractable text (OCR upsell case).
	 *
	 * @param {string} message Error message.
	 * @return {boolean}
	 */
	function isImageOnlyError( message ) {
		var lower = String( message ).toLowerCase();

		return (
			lower.indexOf( 'could not be indexed' ) !== -1 ||
			lower.indexOf( 'does not contain extractable text' ) !== -1 ||
			lower.indexOf( 'text-based pdf' ) !== -1 ||
			lower.indexOf( 'image-based' ) !== -1 ||
			lower.indexOf( 'no searchable text' ) !== -1 ||
			lower.indexOf( 'cannot be parsed for search' ) !== -1
		);
	}

	/**
	 * Whether the error text indicates a secured / password-protected PDF.
	 *
	 * @param {string} message Error message.
	 * @return {boolean}
	 */
	function isSecuredPdfError( message ) {
		var lower = String( message ).toLowerCase();

		return (
			lower.indexOf( 'secured pdf' ) !== -1 ||
			lower.indexOf( 'password-protected' ) !== -1 ||
			lower.indexOf( 'password protected' ) !== -1 ||
			lower.indexOf( 'encrypted pdf' ) !== -1 ||
			lower.indexOf( 'pdf is encrypted' ) !== -1 ||
			lower.indexOf( 'security restrictions' ) !== -1
		);
	}

	/**
	 * Build a human suggestion based on error text
	 */
	function buildErrorSuggestion(message) {
		var lower = String( message ).toLowerCase();

		// File size exceeded.
		if (lower.indexOf( 'file size exceeds maximum' ) !== -1) {
			return webequipePdfSearch.strings.suggestMaxFileSize;
		}

		// Secured / encrypted PDF (Pro upsell dialog handled in handleErrorDetailsClick).
		if ( isSecuredPdfError( message ) ) {
			return webequipePdfSearch.strings.suggestSecuredPdf;
		}

		// Parser library missing.
		if (lower.indexOf( 'pdf parser library not found' ) !== -1) {
			return webequipePdfSearch.strings.suggestParserMissing;
		}

		// File missing on server.
		if (lower.indexOf( 'pdf file not found' ) !== -1) {
			return webequipePdfSearch.strings.suggestFileMissing;
		}

		// Database / table issues.
		if (lower.indexOf( 'failed to save pdf data to database' ) !== -1 || lower.indexOf( 'database' ) !== -1) {
			return webequipePdfSearch.strings.suggestDatabase;
		}

		// Generic parsing error.
		if (lower.indexOf( 'pdf parsing error' ) !== -1) {
			return webequipePdfSearch.strings.suggestParsing;
		}

		// Image-only errors use Pro upsell dialog (handled in handleErrorDetailsClick).
		if ( isImageOnlyError( message ) ) {
			return '';
		}

		// Fallback generic suggestion.
		return webequipePdfSearch.strings.suggestGeneric;
	}

	/**
	 * Show partial-index warning details.
	 */
	function handleWarningDetailsClick(e) {
		e.preventDefault();

		var rawWarning = $( this ).data( 'warning' ) || $( this ).attr( 'title' ) || '';
		var message    = String( rawWarning );

		if ( ! message) {
			return;
		}

		showErrorDialog( message, '', webequipePdfSearch.strings.warningDetailTitle, {
			proUpsell: true,
			proLead: webequipePdfSearch.strings.suggestPartialIndexPro
		} );
	}

	/**
	 * Render / show a simple dialog for error + suggestion
	 *
	 * @param {string} message   Primary message (technical).
	 * @param {string} suggestion Optional secondary tip (plain text).
	 * @param {string} title     Dialog title.
	 * @param {Object} options   Optional: { proUpsell: boolean, proLead: string }.
	 */
	function showErrorDialog(message, suggestion, title, options) {
		options     = options || {};
		var dialogTitle = title || webequipePdfSearch.strings.errorDetailTitle;
		var upgradeUrl  = webequipePdfSearch.upgradeUrl || '';
		var ctaLabel    = webequipePdfSearch.strings.upgradeToProCta;

		// If a dialog already exists, reuse it.
		if ( ! $errorDialog) {
			$errorDialog = $(
				'<div class="webequipe-pdf-error-dialog-wrapper">' +
					'<div class="webequipe-pdf-error-dialog" role="dialog" aria-modal="true">' +
						'<h2 class="webequipe-pdf-error-dialog-title"></h2>' +
						'<div class="webequipe-pdf-error-dialog-body">' +
							'<p class="webequipe-pdf-error-raw"></p>' +
							'<p class="webequipe-pdf-error-suggestion"></p>' +
							'<div class="webequipe-pdf-error-dialog-pro">' +
								'<p class="webequipe-pdf-error-pro-lead"></p>' +
								'<a class="webequipe-pdf-error-dialog-pro-link"></a>' +
							'</div>' +
						'</div>' +
						'<div class="webequipe-pdf-error-dialog-footer">' +
							'<button type="button" class="button button-primary webequipe-pdf-error-dialog-close">' + escapeHtml( webequipePdfSearch.strings.close ) + '</button>' +
						'</div>' +
					'</div>' +
				'</div>'
			);

			$( document.body ).append( $errorDialog );

			$errorDialog.on(
				'click',
				'.webequipe-pdf-error-dialog-close',
				function () {
					$errorDialog.removeClass( 'is-visible' );
				}
			);

			// Close when clicking backdrop.
			$errorDialog.on(
				'click',
				function (evt) {
					if ($( evt.target ).is( '.webequipe-pdf-error-dialog-wrapper' )) {
						$errorDialog.removeClass( 'is-visible' );
					}
				}
			);
		}

		var $suggestion = $errorDialog.find( '.webequipe-pdf-error-suggestion' );
		var $proBlock   = $errorDialog.find( '.webequipe-pdf-error-dialog-pro' );
		var $proLead    = $errorDialog.find( '.webequipe-pdf-error-pro-lead' );
		var $proLink    = $errorDialog.find( '.webequipe-pdf-error-dialog-pro-link' );

		$errorDialog.find( '.webequipe-pdf-error-dialog-title' ).text( dialogTitle );
		$errorDialog.find( '.webequipe-pdf-error-raw' ).text( message );

		if ( options.proUpsell && options.proLead ) {
			$suggestion.text( suggestion || '' );
			if ( suggestion ) {
				$suggestion.addClass( 'webequipe-pdf-error-suggestion--secondary' ).show();
			} else {
				$suggestion.removeClass( 'webequipe-pdf-error-suggestion--secondary' ).hide();
			}
			$proLead.text( options.proLead );
			$proLink.attr( 'href', upgradeUrl ).text( ctaLabel );
			$proBlock.show();
		} else {
			$suggestion.removeClass( 'webequipe-pdf-error-suggestion--secondary' ).text( suggestion || '' ).show();
			$proBlock.hide();
		}

		$errorDialog.addClass( 'is-visible' );
	}

	/**
	 * Handle copy shortcode
	 */
	function handleCopyShortcode(e) {
		e.preventDefault();
		var shortcode = $( this ).data( 'shortcode' );
		var $button   = $( this );

		// Copy to clipboard.
		if (navigator.clipboard && navigator.clipboard.writeText) {
			navigator.clipboard.writeText( shortcode ).then(
				function () {
					var originalText = $button.html();
					$button.html( '<span class="dashicons dashicons-yes"></span> ' + webequipePdfSearch.strings.copied );
					setTimeout(
						function () {
							$button.html( originalText );
						},
						2000
					);
				}
			);
		} else {
			// Fallback for older browsers.
			var $temp = $( '<input>' );
			$( 'body' ).append( $temp );
			$temp.val( shortcode ).select();
			document.execCommand( 'copy' );
			$temp.remove();
			var originalText = $button.html();
			$button.html( '<span class="dashicons dashicons-yes"></span> ' + webequipePdfSearch.strings.copied );
			setTimeout(
				function () {
					$button.html( originalText );
				},
				2000
			);
		}
	}

	/**
	 * Load PDF list
	 */
	function loadPDFList(silent) {
		var $loading = $( '.webequipe-pdf-list-loading' );
		var $table   = $( '#webequipe-pdf-list-table' );
		var $footer  = $( '#webequipe-pdf-list-footer' );

		if ( ! silent ) {
			$loading.show();
			$table.hide();
			$footer.hide();
		}

		if ( isManagePage() && ! silent ) {
			getReindexButtons().prop( 'disabled', true );
			$( '#webequipe-pdf-manage-refresh, #refresh-pdf-list' ).prop( 'disabled', true );
		}

		var offset = (pdfListCurrentPage - 1) * pdfListLimit;

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_scan_pdfs',
					nonce: webequipePdfSearch.nonce,
					offset: offset,
					limit: pdfListLimit,
					search: pdfListSearch,
					status_filter: pdfListStatusFilter
				},
				success: function (response) {
					if (response.success) {
						renderPDFList( response.data );
					} else if ( ! silent ) {
						alert( response.data.message || webequipePdfSearch.strings.error );
						$loading.hide();
					}
					if ( isManagePage() && ! silent ) {
						getReindexButtons().prop( 'disabled', false );
						$( '#webequipe-pdf-manage-refresh, #refresh-pdf-list' ).prop( 'disabled', false );
					}
				},
				error: function () {
					if ( ! silent ) {
						alert( webequipePdfSearch.strings.error );
						$loading.hide();
					}
					if ( isManagePage() && ! silent ) {
						getReindexButtons().prop( 'disabled', false );
						$( '#webequipe-pdf-manage-refresh, #refresh-pdf-list' ).prop( 'disabled', false );
					}
				}
			}
		);
	}

	/**
	 * Render PDF list
	 */
	function renderPDFList(data) {
		var $tbody   = $( '#pdf-list-tbody' );
		var $table   = $( '#webequipe-pdf-list-table' );
		var $loading = $( '.webequipe-pdf-list-loading' );
		var $footer  = $( '#webequipe-pdf-list-footer' );

		$tbody.empty();
		pdfListTotal = data.total;

		var tableColspan = isManagePage() ? 7 : 6;

		if (data.pdfs.length === 0) {
			$tbody.html( '<tr><td colspan="' + tableColspan + '" class="no-pdfs-message">' + webequipePdfSearch.strings.noPDFs + '</td></tr>' );
		} else {
			$.each(
				data.pdfs,
				function (index, pdf) {
					var statusIcon    = '';
					var statusText    = '';
					var statusClass   = '';
					var actionButtons = '';

					switch (pdf.status) {
						case 'indexed':
							statusIcon    = '<span class="dashicons dashicons-yes-alt status-icon-indexed"></span>';
							statusText    = webequipePdfSearch.strings.indexed;
							statusClass   = 'status-indexed';
							actionButtons = '<button class="button button-small pdf-action-unindex" data-pdf-id="' + pdf.id + '" data-action="unindex">' + webequipePdfSearch.strings.unindex + '</button> ' +
							'<button class="button button-small pdf-action-exclude" data-pdf-id="' + pdf.id + '" data-action="exclude">' + webequipePdfSearch.strings.excludeAction + '</button>';
							break;
						case 'not_indexed':
							statusIcon    = '<span class="dashicons dashicons-minus status-icon-not-indexed"></span>';
							statusText    = webequipePdfSearch.strings.notIndexed;
							statusClass   = 'status-not-indexed';
							actionButtons = '<button class="button button-small pdf-action-index" data-pdf-id="' + pdf.id + '" data-action="index">' + webequipePdfSearch.strings.index + '</button> ' +
							'<button class="button button-small pdf-action-exclude" data-pdf-id="' + pdf.id + '" data-action="exclude">' + webequipePdfSearch.strings.excludeAction + '</button>';
							break;
						case 'excluded':
							statusIcon    = '<span class="dashicons dashicons-hidden status-icon-excluded"></span>';
							statusText    = webequipePdfSearch.strings.excluded;
							statusClass   = 'status-excluded';
							actionButtons = '<button class="button button-small pdf-action-include" data-pdf-id="' + pdf.id + '" data-action="include">' + webequipePdfSearch.strings.include + '</button>';
							break;
						case 'error':
							statusIcon    = '<span class="dashicons dashicons-warning status-icon-error"></span>';
							statusText    = webequipePdfSearch.strings.errorLabel;
							statusClass   = 'status-error';
							actionButtons = '<button class="button button-small pdf-action-retry" data-pdf-id="' + pdf.id + '" data-action="index">' + webequipePdfSearch.strings.retry + '</button> ' +
							'<button class="button button-small pdf-action-exclude" data-pdf-id="' + pdf.id + '" data-action="exclude">' + webequipePdfSearch.strings.excludeAction + '</button>';
							break;
						case 'processing':
							statusIcon    = '<span class="dashicons dashicons-update"></span>';
							statusText    = webequipePdfSearch.strings.processing;
							statusClass   = 'status-processing';
							actionButtons = '<button type="button" class="button button-small pdf-action-cancel" data-pdf-id="' + pdf.id + '">' + webequipePdfSearch.strings.cancelIndexing + '</button>';
							break;
						case 'scheduled':
							statusIcon    = '<span class="dashicons dashicons-update"></span>';
							statusText    = webequipePdfSearch.strings.statusScheduled;
							statusClass   = 'status-scheduled';
							actionButtons = '<button type="button" class="button button-small pdf-action-cancel" data-pdf-id="' + pdf.id + '">' + webequipePdfSearch.strings.cancelIndexing + '</button>';
							break;
						case 'stalled':
							statusIcon    = '<span class="dashicons dashicons-warning"></span>';
							statusText    = webequipePdfSearch.strings.statusStalled;
							statusClass   = 'status-stalled';
							actionButtons = '<button type="button" class="button button-small button-primary pdf-action-resume" data-pdf-id="' + pdf.id + '">' + webequipePdfSearch.strings.resumeIndexing + '</button> ' +
							'<button type="button" class="button button-small pdf-action-cancel" data-pdf-id="' + pdf.id + '">' + webequipePdfSearch.strings.cancelIndexing + '</button>';
							break;
					}

					var isInFlight    = pdf.status === 'processing' || pdf.status === 'scheduled' || pdf.status === 'stalled';
					var statusHint    = '';
					if ( pdf.status === 'scheduled' ) {
						statusHint = '<div class="webequipe-pdf-manage-status-hint">' + escapeHtml( webequipePdfSearch.strings.statusScheduledHint ) + '</div>';
					} else if ( pdf.status === 'stalled' ) {
						statusHint = '<div class="webequipe-pdf-manage-status-hint">' + escapeHtml( webequipePdfSearch.strings.statusStalledHint ) + '</div>';
					}
					var progressLine  = '';
					if ( isInFlight && pdf.page_count > 0 ) {
						progressLine = '<div class="webequipe-pdf-manage-status-progress">' + escapeHtml(
							formatString( webequipePdfSearch.strings.pagesProgress, pdf.pages_indexed || 0, pdf.page_count )
						) + '</div>';
					}
					var checkboxAttrs = isInFlight ? ' disabled aria-disabled="true"' : '';

					var statusTooltip = pdf.error_message ? pdf.error_message : ( pdf.index_warning || '' );
					var errorTooltip  = statusTooltip ? ' title="' + escapeAttr( statusTooltip ) + '"' : '';
					var errorButton   = '';
					if (pdf.status === 'error' && pdf.error_message) {
						errorButton =
						'<button type="button" class="button-link webequipe-pdf-error-details" ' +
						'data-error="' + escapeAttr( pdf.error_message ) + '" ' +
						'aria-label="' + escapeAttr( webequipePdfSearch.strings.errorDetailAriaLabel ) + '">' +
						'<span class="dashicons dashicons-info-outline"></span>' +
						'</button>';
					} else if (pdf.status === 'indexed' && pdf.index_warning) {
						errorButton =
						'<button type="button" class="button-link webequipe-pdf-index-warning" ' +
						'data-warning="' + escapeAttr( pdf.index_warning ) + '" ' +
						'aria-label="' + escapeAttr( webequipePdfSearch.strings.partialIndexAriaLabel ) + '">' +
						'<span class="dashicons dashicons-info-outline"></span>' +
						'</button>';
					}

					var row = '';

					if ( isManagePage() ) {
						var pagesDisplay = pdf.page_count > 0 ? pdf.page_count : webequipePdfSearch.strings.emptyValue;
						var indexedDateLine = '';
						if ( pdf.indexed_date ) {
							indexedDateLine = '<div class="webequipe-pdf-manage-status-date">' + escapeHtml( new Date( pdf.indexed_date ).toLocaleDateString() ) + '</div>';
						}
						row = '<tr data-pdf-id="' + escapeAttr( pdf.id ) + '" data-status="' + escapeAttr( pdf.status ) + '">' +
						'<th class="check-column"><input type="checkbox" class="pdf-checkbox" value="' + escapeAttr( pdf.id ) + '"' + checkboxAttrs + '></th>' +
						'<td class="col-document"><strong><a href="' + escapeAttr( pdf.edit_link ) + '">' + escapeHtml( pdf.title ) + '</a></strong>' +
						'<div class="row-actions"><span class="filename">' + escapeHtml( pdf.filename ) + '</span>' +
						'<span class="webequipe-pdf-manage-file-meta">' + escapeHtml( pdf.file_size_formatted ) + '</span></div></td>' +
						'<td class="col-pages">' + escapeHtml( String( pagesDisplay ) ) + '</td>' +
						'<td class="col-visibility"><span class="webequipe-pdf-manage-type-badge">' + escapeHtml( webequipePdfSearch.strings.visibilityPublic ) + '</span></td>' +
						'<td class="col-extraction"><span class="webequipe-pdf-manage-type-badge">' + escapeHtml( webequipePdfSearch.strings.extractionNative ) + '</span></td>' +
						'<td class="col-status"><span class="status-badge ' + statusClass + '"' + errorTooltip + '>' + statusIcon + ' ' + statusText + '</span>' + errorButton + progressLine + statusHint + indexedDateLine + '</td>' +
						'<td class="col-actions"><div class="pdf-row-actions">' + actionButtons + '<span class="spinner"></span></div></td>' +
						'</tr>';
					} else {
						var indexedDate = pdf.indexed_date ? new Date( pdf.indexed_date ).toLocaleDateString() : webequipePdfSearch.strings.emptyValue;
						row = '<tr data-pdf-id="' + escapeAttr( pdf.id ) + '" data-status="' + escapeAttr( pdf.status ) + '">' +
						'<th class="check-column"><input type="checkbox" class="pdf-checkbox" value="' + escapeAttr( pdf.id ) + '"' + checkboxAttrs + '></th>' +
						'<td><strong><a href="' + escapeAttr( pdf.edit_link ) + '">' + escapeHtml( pdf.title ) + '</a></strong><div class="row-actions"><span class="filename">' + escapeHtml( pdf.filename ) + '</span></div></td>' +
						'<td>' + escapeHtml( pdf.file_size_formatted ) + '</td>' +
						'<td><span class="status-badge ' + statusClass + '"' + errorTooltip + '>' + statusIcon + ' ' + statusText + '</span>' + errorButton + '</td>' +
						'<td>' + indexedDate + '</td>' +
						'<td><div class="pdf-row-actions">' + actionButtons + '<span class="spinner"></span></div></td>' +
						'</tr>';
					}

					$tbody.append( row );
				}
			);
		}

		// Update stats.
		$( '#pdf-count-current' ).text( data.pdfs.length );
		$( '#pdf-count-total' ).text( data.total );

		if ( isManagePage() && data.stats ) {
			$( '#manage-stat-total' ).text( data.stats.total || 0 );
			$( '#manage-stat-indexed' ).text( data.stats.indexed || 0 );
			$( '#manage-stat-not-indexed' ).text( data.stats.not_indexed || 0 );
			$( '#manage-stat-excluded' ).text( data.stats.excluded || 0 );
			$( '#manage-stat-failed' ).text( data.stats.errors || 0 );
			updateManageLastScan( data.scanned_at, data.scanned_at_display );
		}

		// Render pagination.
		renderPagination( data.total, pdfListLimit, pdfListCurrentPage );

		$loading.hide();
		if ( isManagePage() ) {
			$table.css( 'display', 'table' );
			$footer.css( 'display', 'flex' );
		} else {
			$table.show();
			$footer.show();
		}

		maybePollManagePdfList();
	}

	/**
	 * Auto-refresh Manage PDFs while any row is indexing.
	 */
	function maybePollManagePdfList() {
		if ( ! isManagePage() ) {
			return;
		}

		if ( manageListPollTimer ) {
			clearInterval( manageListPollTimer );
			manageListPollTimer = null;
		}

		if (
			$( '#pdf-list-tbody tr[data-status="processing"], #pdf-list-tbody tr[data-status="scheduled"], #pdf-list-tbody tr[data-status="stalled"]' ).length
		) {
			manageListPollTimer = setInterval(
				function () {
					resumeStalledIndexingThenRefresh();
				},
				5000
			);
		}
	}

	/**
	 * Nudge stalled jobs forward, then refresh the list (Manage PDFs poll).
	 */
	function resumeStalledIndexingThenRefresh() {
		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_resume_indexing',
					nonce: webequipePdfSearch.nonce,
					limit: 2
				},
				complete: function () {
					loadPDFList( true );
				}
			}
		);
	}

	/**
	 * Render pagination
	 */
	function renderPagination(total, limit, currentPage) {
		var totalPages  = Math.ceil( total / limit );
		var $pagination = $( '#pdf-list-pagination' );
		$pagination.empty();

		if (totalPages <= 1) {
			return;
		}

		var pagination = '<div class="tablenav-pages">';

		// Previous button.
		if (currentPage > 1) {
			pagination += '<a href="#" class="button pdf-page-link" data-page="' + (currentPage - 1) + '">&laquo;</a>';
		} else {
			pagination += '<span class="button disabled">&laquo;</span>';
		}

		// Page numbers.
		var startPage = Math.max( 1, currentPage - 2 );
		var endPage   = Math.min( totalPages, currentPage + 2 );

		if (startPage > 1) {
			pagination += '<a href="#" class="button pdf-page-link" data-page="1">1</a>';
			if (startPage > 2) {
				pagination += '<span class="dots">...</span>';
			}
		}

		for (var i = startPage; i <= endPage; i++) {
			if (i === currentPage) {
				pagination += '<span class="button disabled current">' + i + '</span>';
			} else {
				pagination += '<a href="#" class="button pdf-page-link" data-page="' + i + '">' + i + '</a>';
			}
		}

		if (endPage < totalPages) {
			if (endPage < totalPages - 1) {
				pagination += '<span class="dots">...</span>';
			}
			pagination += '<a href="#" class="button pdf-page-link" data-page="' + totalPages + '">' + totalPages + '</a>';
		}

		// Next button.
		if (currentPage < totalPages) {
			pagination += '<a href="#" class="button pdf-page-link" data-page="' + (currentPage + 1) + '">&raquo;</a>';
		} else {
			pagination += '<span class="button disabled">&raquo;</span>';
		}

		var rangeStart = (currentPage - 1) * limit + 1;
		var rangeEnd   = Math.min( currentPage * limit, total );
		pagination += '<span class="displaying-num">' +
			formatString( webequipePdfSearch.strings.paginationSummary, rangeStart, rangeEnd, total ) +
			'</span>';
		pagination += '</div>';

		$pagination.html( pagination );
	}

	/**
	 * Handle refresh PDF list
	 */
	function handleRefreshPDFList() {
		loadPDFList();
	}

	/**
	 * Update Manage PDFs "last scanned" label.
	 *
	 * @param {string} mysqlDate        MySQL datetime from server.
	 * @param {string} formattedDisplay Pre-formatted date/time from server.
	 */
	function updateManageLastScan( mysqlDate, formattedDisplay ) {
		var $label = $( '#webequipe-pdf-manage-last-scan' );
		if ( ! $label.length ) {
			return;
		}
		if ( ! mysqlDate ) {
			$label.text( '' );
			return;
		}
		var display = formattedDisplay || '';
		if ( ! display ) {
			var parsed = new Date( mysqlDate.replace( ' ', 'T' ) );
			display    = isNaN( parsed.getTime() ) ? mysqlDate : parsed.toLocaleString();
		}
		var template = webequipePdfSearch.strings.lastScanned;
		$label.text( template.replace( '%s', display ) );
	}

	/**
	 * Handle select all
	 */
	function handleSelectAll() {
		var checked = $( this ).is( ':checked' );
		$( '.pdf-checkbox' ).prop( 'checked', checked );
		$( '#select-all-pdfs, #select-all-pdfs-header' ).prop( 'checked', checked );
	}

	/**
	 * Handle search PDFs
	 */
	function handleSearchPDFs() {
		pdfListSearch      = $( '#webequipe-pdf-search-input' ).val();
		pdfListCurrentPage = 1;
		loadPDFList();
	}

	/**
	 * Handle status filter
	 */
	function handleStatusFilter() {
		pdfListStatusFilter = $( this ).val();
		pdfListCurrentPage  = 1;
		loadPDFList();
	}

	/**
	 * Handle bulk action
	 */
	function handleBulkAction() {
		var action = $( '#bulk-action-select' ).val();
		if ( ! action) {
			alert( webequipePdfSearch.strings.selectAction );
			return;
		}

		var selected = $( '.pdf-checkbox:checked' ).map(
			function () {
				return $( this ).val();
			}
		).get();

		if (selected.length === 0) {
			alert( webequipePdfSearch.strings.selectPDFs );
			return;
		}

		if ( ! confirm( formatString( webequipePdfSearch.strings.confirmBulkAction, selected.length ) )) {
			return;
		}

		$( '#apply-bulk-action' ).prop( 'disabled', true );

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_bulk_action',
					nonce: webequipePdfSearch.nonce,
					action_type: action,
					post_ids: selected
				},
				success: function (response) {
					if (response.success) {
						alert( response.data.message );
						loadPDFList();
					} else {
						alert( response.data.message || webequipePdfSearch.strings.error );
					}
					$( '#apply-bulk-action' ).prop( 'disabled', false );
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
					$( '#apply-bulk-action' ).prop( 'disabled', false );
				}
			}
		);
	}

	/**
	 * Cancel an in-flight indexing job from the PDF list.
	 */
	function handleResumeIndexing() {
		var $button  = $( this );
		var pdfId    = $button.data( 'pdf-id' );
		var $spinner = $button.siblings( '.spinner' );

		$button.prop( 'disabled', true );
		$spinner.addClass( 'is-active' );

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_resume_indexing',
					nonce: webequipePdfSearch.nonce,
					post_id: pdfId
				},
				success: function (response) {
					loadPDFList();
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
					$button.prop( 'disabled', false );
					$spinner.removeClass( 'is-active' );
				}
			}
		);
	}

	function handleCancelIndexing() {
		if ( ! window.confirm( webequipePdfSearch.strings.confirmCancelIndexing ) ) {
			return;
		}

		var $button  = $( this );
		var pdfId    = $button.data( 'pdf-id' );
		var $spinner = $button.siblings( '.spinner' );

		$button.prop( 'disabled', true );
		$spinner.addClass( 'is-active' );

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_cancel_indexing',
					nonce: webequipePdfSearch.nonce,
					post_id: pdfId
				},
				success: function (response) {
					if (response.success) {
						loadPDFList();
					} else {
						alert( response.data.message || webequipePdfSearch.strings.error );
						$button.prop( 'disabled', false );
						$spinner.removeClass( 'is-active' );
					}
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
					$button.prop( 'disabled', false );
					$spinner.removeClass( 'is-active' );
				}
			}
		);
	}

	/**
	 * Handle PDF list individual action
	 */
	function handlePDFListAction() {
		var $button  = $( this );
		var pdfId    = $button.data( 'pdf-id' );
		var action   = $button.data( 'action' );
		var $row     = $button.closest( 'tr' );
		var $spinner = $button.siblings( '.spinner' );

		$button.prop( 'disabled', true );
		$spinner.addClass( 'is-active' );

		// Use existing AJAX handlers.
		var ajaxAction = '';
		if (action === 'index') {
			ajaxAction = 'webequipe_pdf_reindex_single';
		} else if (action === 'unindex') {
			ajaxAction = 'webequipe_pdf_unindex_single';
		} else if (action === 'include') {
			ajaxAction = 'webequipe_pdf_include_single';
		} else if (action === 'exclude') {
			ajaxAction = 'webequipe_pdf_exclude_single';
		}

		if ( ! ajaxAction) {
			$button.prop( 'disabled', false );
			$spinner.removeClass( 'is-active' );
			return;
		}

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: ajaxAction,
					nonce: webequipePdfSearch.nonce,
					post_id: pdfId
				},
				success: function (response) {
					if (response.success) {
						// Refresh the list to show updated status.
						loadPDFList();
					} else {
						alert( response.data.message || webequipePdfSearch.strings.error );
						$button.prop( 'disabled', false );
						$spinner.removeClass( 'is-active' );
					}
				},
				error: function () {
					alert( webequipePdfSearch.strings.error );
					$button.prop( 'disabled', false );
					$spinner.removeClass( 'is-active' );
				}
			}
		);
	}

	/**
	 * Debounce function
	 */
	function debounce(func, wait) {
		var timeout;
		return function () {
			var context = this;
			var args    = arguments;
			clearTimeout( timeout );
			timeout = setTimeout(
				function () {
					func.apply( context, args );
				},
				wait
			);
		};
	}

	/**
	 * Format string with %d placeholders
	 */
	function formatString(template) {
		var args = Array.prototype.slice.call( arguments, 1 );
		var i    = 0;
		return String( template ).replace(
			/%(\d+)\$d/g,
			function (match, index) {
				var argIndex = parseInt( index, 10 ) - 1;
				return typeof args[argIndex] !== 'undefined' ? args[argIndex] : match;
			}
		).replace(
			/%d/g,
			function () {
				var value = typeof args[i] !== 'undefined' ? args[i] : '';
				i        += 1;
				return value;
			}
		);
	}

	/**
	 * Initialize Index Activity page
	 */
	function initActivityPage() {
		$( '#webequipe-pdf-activity-refresh' ).on( 'click', function () {
			loadActivityList();
		} );
		$( '#webequipe-pdf-activity-export' ).on( 'click', handleExportActivity );
		$( '#webequipe-pdf-activity-status-filter' ).on( 'change', handleActivityFilterChange );
		$( '#webequipe-pdf-activity-per-page' ).on( 'change', handleActivityFilterChange );
		$( '#webequipe-pdf-activity-search' ).on( 'input', debounce( handleActivityFilterChange, 500 ) );
		$( '#webequipe-pdf-activity-prev' ).on( 'click', handleActivityPrev );
		$( '#webequipe-pdf-activity-next' ).on( 'click', handleActivityNext );

		activityLimit = parseInt( $( '#webequipe-pdf-activity-per-page' ).val(), 10 ) || 20;
		loadActivityList();
	}

	/**
	 * Load index activity list via AJAX
	 */
	function loadActivityList() {
		var offset = (activityCurrentPage - 1) * activityLimit;

		$( '#webequipe-pdf-activity-loading' ).show();

		$.ajax(
			{
				url: webequipePdfSearch.ajaxUrl,
				type: 'POST',
				data: {
					action: 'webequipe_pdf_activity_list',
					nonce: webequipePdfSearch.nonce,
					offset: offset,
					limit: activityLimit,
					search: activitySearch,
					status_filter: activityStatusFilter
				},
				success: function (response) {
					$( '#webequipe-pdf-activity-loading' ).hide();

					if ( ! response.success ) {
						showError( response.data && response.data.message ? response.data.message : webequipePdfSearch.strings.error );
						return;
					}

					var data = response.data;
					activityTotal = data.total || 0;

					renderActivityStats( data.stats );
					renderActivityTable( data.records || [] );
					updateActivityPagination( data.offset, data.limit, data.total );
					updateActivityLastUpdated( data.updated );

					var inFlight = ( data.stats && data.stats.processing > 0 ) || hasProcessingRecords( data.records );
					if ( inFlight ) {
						scheduleActivityPoll( true );
					} else {
						clearActivityPoll();
					}
				},
				error: function () {
					$( '#webequipe-pdf-activity-loading' ).hide();
					showError( webequipePdfSearch.strings.error );
				}
			}
		);
	}

	/**
	 * Render activity stat cards
	 */
	function renderActivityStats(stats) {
		if ( ! stats) {
			return;
		}

		$( '#activity-stat-total' ).text( stats.total_documents || 0 );
		$( '#activity-stat-pages' ).text( stats.total_pages || 0 );
		$( '#activity-stat-successful' ).text( stats.successful || 0 );
		$( '#activity-stat-processing' ).text( stats.processing || 0 );
		$( '#activity-stat-failed' ).text( stats.failed || 0 );
	}

	/**
	 * Render activity table rows
	 */
	function renderActivityTable(records) {
		var $tbody = $( '#webequipe-pdf-activity-tbody' );
		$tbody.empty();

		if ( ! records.length ) {
			$tbody.append(
				'<tr class="webequipe-pdf-activity-empty"><td colspan="6">' +
				escapeHtml( webequipePdfSearch.strings.noActivity ) +
				'</td></tr>'
			);
			return;
		}

		records.forEach(
			function (record) {
				var serial     = String( record.serial ).padStart( 2, '0' );
				var pages      = formatActivityPages( record );
				var status     = formatActivityStatus( record.display_status || record.index_status );
				var updated    = formatActivityDate( record.display_date || record.completed_at || record.started_at );
				var mediaLabel = webequipePdfSearch.strings.mediaId + ': ' + record.post_id;

				$tbody.append(
					'<tr>' +
					'<td class="col-sl">' + escapeHtml( serial ) + '</td>' +
					'<td class="col-document">' +
					'<span class="webequipe-pdf-activity-doc-title">' + escapeHtml( record.title || record.filename ) + '</span>' +
					'<span class="webequipe-pdf-activity-doc-meta">' + escapeHtml( mediaLabel ) + '</span>' +
					'</td>' +
					'<td class="col-pages">' + escapeHtml( pages ) + '</td>' +
					'<td class="col-status">' + status + '</td>' +
					'<td class="col-updated">' + escapeHtml( updated ) + '</td>' +
					'<td class="col-action">' +
					'<a href="' + escapeAttr( record.edit_link ) + '" class="button button-small">' +
					escapeHtml( webequipePdfSearch.strings.view ) +
					'</a></td>' +
					'</tr>'
				);
			}
		);
	}

	/**
	 * Format page count for activity row
	 */
	function formatActivityPages(record) {
		var status = record.display_status || record.index_status;

		if (
			( 'processing' === status || 'scheduled' === status || 'stalled' === status ) &&
			record.page_count > 0 &&
			record.pages_indexed < record.page_count
		) {
			return formatString(
				webequipePdfSearch.strings.pagesProgress,
				record.pages_indexed,
				record.page_count
			);
		}

		return String( record.page_count || 0 );
	}

	/**
	 * Format status badge HTML
	 */
	function formatActivityStatus(status) {
		var label = status;
		if ( 'completed' === status ) {
			label = webequipePdfSearch.strings.statusCompleted;
		} else if ( 'processing' === status ) {
			label = webequipePdfSearch.strings.processing;
		} else if ( 'scheduled' === status ) {
			label = webequipePdfSearch.strings.statusScheduled;
		} else if ( 'stalled' === status ) {
			label = webequipePdfSearch.strings.statusStalled;
		} else if ( 'pending' === status ) {
			label = webequipePdfSearch.strings.statusPending;
		} else if ( 'failed' === status ) {
			label = webequipePdfSearch.strings.statusFailed;
		} else if ( 'cancelled' === status ) {
			label = webequipePdfSearch.strings.statusCancelled;
		}

		return (
			'<span class="webequipe-pdf-activity-status status-' + escapeAttr( status ) + '">' +
			escapeHtml( label ) +
			'</span>'
		);
	}

	/**
	 * Format MySQL datetime for display
	 */
	function formatActivityDate(mysqlDate) {
		if ( ! mysqlDate) {
			return webequipePdfSearch.strings.emptyValue;
		}

		var date = new Date( mysqlDate.replace( ' ', 'T' ) );
		if ( isNaN( date.getTime() ) ) {
			return mysqlDate;
		}

		return date.toLocaleString();
	}

	/**
	 * Update pagination controls
	 */
	function updateActivityPagination(offset, limit, total) {
		var start   = total > 0 ? offset + 1 : 0;
		var end     = Math.min( offset + limit, total );
		var pages   = Math.max( 1, Math.ceil( total / limit ) );
		var current = activityCurrentPage;

		$( '#webequipe-pdf-activity-summary' ).text(
			formatString( webequipePdfSearch.strings.showingRecords, start, end, total )
		);

		$( '#webequipe-pdf-activity-page-info' ).text(
			formatString( webequipePdfSearch.strings.pageOf, current, pages )
		);

		$( '#webequipe-pdf-activity-prev' ).prop( 'disabled', current <= 1 );
		$( '#webequipe-pdf-activity-next' ).prop( 'disabled', current >= pages || total === 0 );
	}

	/**
	 * Update last refreshed timestamp
	 */
	function updateActivityLastUpdated(mysqlDate) {
		var label = webequipePdfSearch.strings.justNow;
		if (mysqlDate) {
			label = formatActivityDate( mysqlDate );
		}

		var template = webequipePdfSearch.strings.lastUpdated;
		$( '#webequipe-pdf-activity-last-updated' ).text( template.replace( '%s', label ) );
	}

	/**
	 * Handle activity filter changes
	 */
	function handleActivityFilterChange() {
		activityCurrentPage  = 1;
		activitySearch       = $( '#webequipe-pdf-activity-search' ).val().trim();
		activityStatusFilter = $( '#webequipe-pdf-activity-status-filter' ).val();
		activityLimit        = parseInt( $( '#webequipe-pdf-activity-per-page' ).val(), 10 ) || 20;
		loadActivityList();
	}

	/**
	 * Activity pagination previous
	 */
	function handleActivityPrev() {
		if (activityCurrentPage > 1) {
			activityCurrentPage--;
			loadActivityList();
		}
	}

	/**
	 * Activity pagination next
	 */
	function handleActivityNext() {
		var pages = Math.ceil( activityTotal / activityLimit );
		if (activityCurrentPage < pages) {
			activityCurrentPage++;
			loadActivityList();
		}
	}

	/**
	 * Export activity log as CSV
	 */
	function handleExportActivity() {
		var $form = $( '<form>', {
			method: 'POST',
			action: webequipePdfSearch.ajaxUrl,
			target: '_blank'
		} );

		$form.append( $( '<input>', { type: 'hidden', name: 'action', value: 'webequipe_pdf_export_activity' } ) );
		$form.append( $( '<input>', { type: 'hidden', name: 'nonce', value: webequipePdfSearch.nonce } ) );
		$form.append( $( '<input>', { type: 'hidden', name: 'search', value: activitySearch } ) );
		$form.append( $( '<input>', { type: 'hidden', name: 'status_filter', value: activityStatusFilter } ) );

		$( 'body' ).append( $form );
		$form.trigger( 'submit' );
		$form.remove();
	}

	/**
	 * Check if any record is still processing
	 */
	function hasProcessingRecords(records) {
		if ( ! records) {
			return false;
		}

		return records.some(
			function (record) {
				var status = record.display_status || record.index_status;
				return 'processing' === status || 'scheduled' === status || 'stalled' === status || 'pending' === status;
			}
		);
	}

	/**
	 * Poll activity list while jobs are running
	 */
	function scheduleActivityPoll(inFlight) {
		clearActivityPoll();
		activityPollTimer = setTimeout( loadActivityList, inFlight ? 5000 : 30000 );
	}

	/**
	 * Clear activity poll timer
	 */
	function clearActivityPoll() {
		if (activityPollTimer) {
			clearTimeout( activityPollTimer );
			activityPollTimer = null;
		}
	}

	/**
	 * Escape HTML
	 */
	function escapeHtml(text) {
		var map = {
			'&': '&amp;',
			'<': '&lt;',
			'>': '&gt;',
			'"': '&quot;',
			"'": '&#039;'
		};
		return String( text || '' ).replace(
			/[&<>"']/g,
			function (m) {
				return map[m]; }
		);
	}

	/**
	 * Escape attribute values
	 */
	function escapeAttr(text) {
		return escapeHtml( text );
	}

	// Initialize on document ready.
	$( document ).ready( init );

})( jQuery );
