/**
 * AI Alt Text Generator - Frontend scripts
 */
(function($) {
	'use strict';

	const $doc = $(document);

	window.addEventListener('unhandledrejection', function(event) {
		const reason = event.reason;
		if (!reason || reason.name !== 'AbortError') {
			return;
		}

		const message = String(reason.message || '');
		if (
			message.indexOf('Transition was skipped') !== -1
			|| message.indexOf('Transition was aborted') !== -1
		) {
			event.preventDefault();
		}
	});

	function showResult($el, message, isError) {
		$el.removeClass('success error').addClass(isError ? 'error' : 'success').text(message).show();
	}

	function postAjax(action, data, opts) {
		const options = opts || {};
		return $.ajax({
			url: wppsAiAltText.ajaxUrl,
			method: 'POST',
			data: Object.assign({
				action: action,
				nonce: wppsAiAltText.nonce
			}, data || {}),
			timeout: typeof options.timeout === 'number' ? options.timeout : 0
		});
	}

	function ajaxErrorMessage(xhr, fallback) {
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		const defaultMsg = fallback || strings.error || 'Something went wrong.';

		if (!xhr) {
			return defaultMsg;
		}

		const json = xhr.responseJSON;
		if (json && json.data) {
			if (typeof json.data === 'string' && json.data.trim()) {
				return json.data;
			}
			if (json.data.message) {
				const code = json.data.code ? ' [' + json.data.code + ']' : '';
				return json.data.message + code;
			}
		}

		if (xhr.status === 0) {
			return strings.networkError || 'Network error. Check your connection and try again.';
		}
		if (xhr.status === 403) {
			return strings.permissionError || 'Permission denied. Refresh the page and try again.';
		}
		if (xhr.status === 408 || xhr.status === 504) {
			return strings.timeoutError || 'The request timed out. Try a smaller batch size.';
		}
		if (xhr.status >= 500) {
			return strings.serverError || 'Server error. Check PHP limits and AI provider status.';
		}

		return defaultMsg;
	}

	function truncateAltDisplay(text, max) {
		const value = (text || '').trim();
		const limit = max || 80;
		if (!value) {
			return '';
		}
		return value.length > limit ? value.slice(0, limit - 1) + '…' : value;
	}

	function syncMediaAttachmentModel(attachmentId, altText) {
		if (!window.wp || !wp.media || typeof wp.media.attachment !== 'function') {
			return;
		}

		const model = wp.media.attachment(attachmentId);
		if (!model) {
			return;
		}

		model.set('alt', altText);
	}

	function syncAttachmentAltFields(attachmentId, altText) {
		const text = altText || '';

		$('#wpps-ai-alt-field').val(text);
		$('input[name="_wp_attachment_image_alt"]').val(text);
		$('textarea[name="_wp_attachment_image_alt"]').val(text);
		$('.attachment-details textarea[data-setting="alt"]').val(text).trigger('change');
		$('#attachment-details-two-column-alt-text').val(text).trigger('change');
		$('#attachment-details-alt-text').val(text).trigger('change');
		$('textarea[name="attachments[' + attachmentId + '][alt]"]').val(text).trigger('change');
		$('input[name="attachments[' + attachmentId + '][alt]"]').val(text).trigger('change');

		syncMediaAttachmentModel(attachmentId, text);
	}

	function shouldShowMediaGenerate(statusSlug) {
		return ['missing', 'weak', 'too_short', 'keyword_stuffed', 'needs_review'].indexOf(statusSlug) !== -1;
	}

	function renderMediaAltColumnHtml(data) {
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		const altText = data.alt_text || '';
		const display = data.alt_display || truncateAltDisplay(altText);

		if (!display) {
			return '<span class="description">—</span>';
		}

		if (data.is_suggestion) {
			return '<span class="description">' + $('<div>').text(display).html() +
				'</span><br><em class="description">' + (strings.suggestionLabel || '(suggestion)') + '</em>';
		}

		return $('<div>').text(display).html();
	}

	function updateMediaLibraryListRow(attachmentId, data) {
		const $row = $('#post-' + attachmentId);
		if (!$row.length || !data) {
			return;
		}

		const $altCol = $row.find('.column-wpps_ai_alt_text');
		if ($altCol.length) {
			$altCol.html(renderMediaAltColumnHtml(data));
		}

		const $statusCol = $row.find('.column-wpps_ai_alt_status');
		if ($statusCol.length && data.status_label && data.badge_class) {
			let html = '<span class="wpps-ai-alt-badge ' + data.badge_class + '">' +
				$('<div>').text(data.status_label).html() + '</span>';
			const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
			if (shouldShowMediaGenerate(data.status_slug)) {
				html += '<br><button type="button" class="button button-small wpps-ai-alt-col-generate" data-attachment-id="' +
					attachmentId + '">' + (strings.generate || 'Generate') + '</button>';
			} else if (data.status_slug === 'decorative') {
				html += '<br><button type="button" class="button button-small wpps-ai-alt-col-undecorative" data-attachment-id="' +
					attachmentId + '">' + (strings.unmarkDecorative || 'Unmark decorative') + '</button>';
			}
			$statusCol.html(html);
		}
	}

	function applyGenerationResult(attachmentId, data, options) {
		options = options || {};
		if (!data || !data.alt_text) {
			return;
		}

		syncAttachmentAltFields(attachmentId, data.alt_text);
		syncMediaAttachmentModelExtras(attachmentId, data);
		updateMediaLibraryListRow(attachmentId, data);

		getMediaAiAltRoots().each(function() {
			updateMediaModalActionsState($(this), attachmentId, {
				statusSlug: data.status_slug || '',
				isDecorative: data.status_slug === 'decorative',
				hasAlt: !!(data.alt_text || ''),
				forceRegenerate: true
			});
		});

		if (options.$result && options.$result.length) {
			const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
			const message = data.alt_saved
				? (strings.generatedSaved || strings.saved || 'Alt text generated and saved.')
				: (strings.generated || 'Suggestion ready. Review and save when ready.');
			showResult(options.$result, message, false);
		}
	}

	function generateButtonLabel($btn) {
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		const isRegenerate = $btn.data('regenerate') === 1 || $btn.data('regenerate') === '1';
		return isRegenerate
			? (strings.regenerateWithAi || 'Regenerate with AI')
			: (strings.generateWithAi || 'Generate with AI');
	}

	function mediaAiControlsEnabled() {
		return !!(window.wppsAiAltText && wppsAiAltText.canGenerate && wppsAiAltText.hasApiKey);
	}

	function getItemIdFromUrl() {
		try {
			const params = new URLSearchParams(window.location.search);
			const item = parseInt(params.get('item') || params.get('attachment_id') || '0', 10);
			return item > 0 ? item : 0;
		} catch (err) {
			const match = /[?&]item=(\d+)/.exec(window.location.href || '');
			return match ? parseInt(match[1], 10) : 0;
		}
	}

	function getMediaFrameSelectionModel() {
		if (!window.wp || !wp.media) {
			return null;
		}

		const frames = [];
		if (wp.media.frames && wp.media.frames.edit) {
			frames.push(wp.media.frames.edit);
		}
		if (wp.media.frame) {
			frames.push(wp.media.frame);
		}

		for (let i = 0; i < frames.length; i++) {
			const frame = frames[i];
			try {
				if (frame.model && frame.model.id) {
					return frame.model;
				}
				const state = frame.state && frame.state();
				if (!state) {
					continue;
				}
				const selection = state.get('selection');
				if (selection && selection.length) {
					return selection.first();
				}
			} catch (err) {
				// Try next frame reference.
			}
		}

		return null;
	}

	function parseAttachmentIdFromAltSetting($setting) {
		const $field = $setting.find('textarea, input').first();
		const name = $field.attr('name') || '';
		let match = /attachments\[(\d+)\]\[alt\]/.exec(name);
		if (match) {
			return parseInt(match[1], 10);
		}

		match = /attachments\[\]\[alt\]/.exec(name);
		if (match) {
			const model = getMediaFrameSelectionModel();
			if (model && model.id) {
				return parseInt(model.id, 10);
			}
		}

		return 0;
	}

	function resolveMediaModalAttachmentId($modal, overrideId) {
		const forced = parseInt(overrideId, 10);
		if (forced > 0) {
			return forced;
		}

		const model = getMediaFrameSelectionModel();
		if (model && model.id) {
			return parseInt(model.id, 10);
		}

		const urlItem = getItemIdFromUrl();
		if (urlItem) {
			return urlItem;
		}

		const $altSetting = $modal.find('.setting[data-setting="alt"]').first();
		if ($altSetting.length) {
			const parsed = parseAttachmentIdFromAltSetting($altSetting);
			if (parsed) {
				return parsed;
			}
		}

		const $idField = $modal.find('input[type="hidden"][name*="[id]"]').first();
		if ($idField.length) {
			return parseInt($idField.val(), 10) || 0;
		}

		return 0;
	}

	function getAltInjectionAnchor($modal) {
		const $description = $modal.find('#alt-text-description').first();
		if ($description.length) {
			return $description;
		}

		const $altField = $modal.find('#attachment-details-two-column-alt-text, #attachment-details-alt-text').first();
		if ($altField.length) {
			return $altField.closest('.setting[data-setting="alt"]').first();
		}

		return $modal.find('.setting[data-setting="alt"]').first();
	}

	function removeMediaModalAiAltControls($modal) {
		$modal.find('.wpps-ai-alt-media-tools').remove();
		$modal.find('.wpps-ai-alt-media-actions').remove();
	}

	function getAltTextFromModalSetting($modal) {
		const $field = $modal.find('.setting[data-setting="alt"] textarea, .setting[data-setting="alt"] input').first();
		return ($field.val() || '').trim();
	}

	function syncMediaAttachmentModelExtras(attachmentId, data) {
		if (!window.wp || !wp.media || typeof wp.media.attachment !== 'function' || !data) {
			return;
		}

		const model = wp.media.attachment(attachmentId);
		if (!model) {
			return;
		}

		if (data.status_slug) {
			model.set('wppsAiAltStatusSlug', data.status_slug);
			model.set('wppsAiAltDecorative', data.status_slug === 'decorative');
		}
	}

	function updateMediaModalActionsState($modal, attachmentId, options) {
		injectMediaModalControls($modal, attachmentId, options || {});
	}

	function buildMediaModalActionsHtml(attachmentId, options) {
		options = options || {};
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		const isDecorative = options.isDecorative === true || options.statusSlug === 'decorative';
		const hasAlt = !!options.hasAlt;
		const isRegenerate = (hasAlt || !!options.forceRegenerate) && !isDecorative;
		const label = isRegenerate
			? (strings.regenerateWithAi || 'Regenerate with AI')
			: (strings.generateWithAi || 'Generate with AI');

		let html = '<div class="wpps-ai-alt-media-actions">';
		if (isDecorative) {
			html += '<button type="button" class="button wpps-ai-alt-media-undecorative" data-attachment-id="' + attachmentId + '">' +
				$('<div>').text(strings.unmarkDecorative || 'Unmark decorative').html() + '</button>';
			html += '<p class="description wpps-ai-alt-media-decorative-note">' +
				$('<div>').text(strings.decorativeNote || '').html() + '</p>';
		} else {
			html += '<button type="button" class="button wpps-ai-alt-media-generate" data-attachment-id="' + attachmentId +
				'" data-regenerate="' + (isRegenerate ? '1' : '0') + '">' + $('<div>').text(label).html() + '</button>';
		}
		html += '<div class="wpps-ai-alt-media-result wpps-ai-alt-result" style="display:none;"></div>';
		html += '</div>';
		return html;
	}

	/**
	 * Roots where AI Alt controls belong.
	 * Prefer containers that already expose an alt field so we never treat the
	 * empty Media Library grid Manage frame as an inject target.
	 */
	function getMediaAiAltRoots() {
		const roots = [];
		const seen = [];

		$('.setting[data-setting="alt"]:visible').each(function() {
			const el = $(this).closest(
				'.media-modal, .edit-attachment-frame, .media-frame-edit-attachments, .attachment-details, .media-frame'
			).get(0) || this;
			if (el && seen.indexOf(el) === -1) {
				seen.push(el);
				roots.push(el);
			}
		});

		if (roots.length) {
			return $(roots);
		}

		// Fallback for deep-linked edit (?item=ID) before the alt field paints.
		return $(
			'.media-modal:visible, .edit-attachment-frame:visible, .media-frame-edit-attachments:visible'
		);
	}

	function injectMediaModalControls($scope, overrideId, options) {
		if (!mediaAiControlsEnabled()) {
			return;
		}

		let $modal = $scope && $scope.length ? $scope : getMediaAiAltRoots();
		if (!$modal.length) {
			return;
		}

		const $withAlt = $modal.filter(function() {
			return $(this).find('.setting[data-setting="alt"]').length > 0;
		});
		$modal = ($withAlt.length ? $withAlt : $modal).first();

		if (!$modal.find('.setting[data-setting="alt"]').length && !getItemIdFromUrl()) {
			return;
		}

		const attachmentId = resolveMediaModalAttachmentId($modal, overrideId);
		if (!attachmentId) {
			return;
		}

		options = options || {};
		const model = getMediaFrameSelectionModel();
		const statusSlug = options.statusSlug !== undefined
			? options.statusSlug
			: (model ? (model.get('wppsAiAltStatusSlug') || '') : '');
		const isDecorative = options.isDecorative !== undefined
			? options.isDecorative
			: (model ? !!model.get('wppsAiAltDecorative') : (statusSlug === 'decorative'));
		const hasAlt = options.hasAlt !== undefined
			? options.hasAlt
			: (getAltTextFromModalSetting($modal) !== '');
		const $anchor = getAltInjectionAnchor($modal);

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

		removeMediaModalAiAltControls($modal);

		$anchor.after(
			'<div class="wpps-powersuite-media-tools wpps-ai-alt-media-tools">' +
			buildMediaModalActionsHtml(attachmentId, {
				statusSlug: statusSlug,
				isDecorative: isDecorative,
				hasAlt: hasAlt,
				forceRegenerate: !!options.forceRegenerate
			}) +
			'</div>'
		);
	}

	function scheduleMediaAiAltInject(overrideId) {
		window.setTimeout(function() {
			try {
				injectMediaModalControls(getMediaAiAltRoots(), overrideId || 0);
			} catch (err) {
				// Never let control injection break media library browsing.
			}
		}, 0);
	}

	function hookEditAttachmentFrame(frame) {
		if (!frame || frame.__wppsAiAltHooked) {
			return;
		}

		frame.__wppsAiAltHooked = true;
		const schedule = function() {
			const modelId = frame.model && frame.model.id ? parseInt(frame.model.id, 10) : 0;
			scheduleMediaAiAltInject(modelId);
		};

		frame.on('content:render:edit-metadata', schedule);
		frame.on('refresh', schedule);
		frame.on('open', schedule);
		if (frame.modal) {
			frame.modal.on('open', schedule);
		}
	}

	/**
	 * Minimal hooks only — do not replace wp.media(), extend Attachment.Details,
	 * patch Modal.open, or observe the whole document. Those broke Media Library grid.
	 */
	function initMediaModalIntegration() {
		if (!window.wp || !wp.media || wp.media.__wppsAiAltMediaBooted) {
			return;
		}
		wp.media.__wppsAiAltMediaBooted = true;

		if (wp.media.view && wp.media.view.MediaFrame && wp.media.view.MediaFrame.EditAttachments) {
			const EditAttachments = wp.media.view.MediaFrame.EditAttachments;
			if (!EditAttachments.__wppsAiAltPatched) {
				const originalInitialize = EditAttachments.prototype.initialize;
				EditAttachments.prototype.initialize = function() {
					const result = originalInitialize.apply(this, arguments);
					hookEditAttachmentFrame(this);
					return result;
				};
				EditAttachments.__wppsAiAltPatched = true;
			}
		}

		if (wp.media.frames && wp.media.frames.edit) {
			hookEditAttachmentFrame(wp.media.frames.edit);
		}

		// Inject only after the alt field is focused/clicked in a real edit UI.
		$doc.on(
			'focusin click',
			'.setting[data-setting="alt"] textarea, .setting[data-setting="alt"] input, #attachment-details-two-column-alt-text, #attachment-details-alt-text',
			function() {
				const $root = $(this).closest(
					'.media-modal, .edit-attachment-frame, .media-frame-edit-attachments, .attachment-details'
				);
				scheduleMediaAiAltInject(resolveMediaModalAttachmentId($root, 0));
			}
		);

		if (getItemIdFromUrl()) {
			scheduleMediaAiAltInject(getItemIdFromUrl());
			window.setTimeout(function() {
				scheduleMediaAiAltInject(getItemIdFromUrl());
			}, 300);
		}
	}

	// Single image: Generate / Regenerate
	$doc.on('click', '#wpps-ai-alt-generate', function() {
		const $btn = $(this);
		const id = $btn.data('attachment-id');
		const $field = $('#wpps-ai-alt-field');
		const $result = $('#wpps-ai-alt-result');
		const isRegenerate = $btn.data('regenerate') === 1 || $btn.data('regenerate') === '1';

		if (!id || !window.wppsAiAltText) return;

		$btn.prop('disabled', true).text(wppsAiAltText.strings.generating);
		$result.hide();

		const payload = { attachment_id: id };
		if (isRegenerate) {
			payload.force = 1;
		}

		postAjax('wpps_ai_alt_generate', payload)
		.done(function(res) {
			if (res.success && res.data && res.data.alt_text) {
				applyGenerationResult(id, res.data, { $result: $result });
				$btn.data('regenerate', 1).attr('data-regenerate', '1');
			} else {
				showResult($result, (res.data && res.data.message) || wppsAiAltText.strings.error, true);
			}
		})
		.fail(function(xhr) {
			const msg = xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message
				? xhr.responseJSON.data.message
				: wppsAiAltText.strings.error;
			showResult($result, msg, true);
		})
		.always(function() {
			$btn.prop('disabled', false).text(generateButtonLabel($btn));
		});
	});

	// Single image: Save
	$doc.on('click', '#wpps-ai-alt-save', function() {
		const $btn = $(this);
		const id = $btn.data('attachment-id');
		const $field = $('#wpps-ai-alt-field');
		const $result = $('#wpps-ai-alt-result');

		if (!id || !window.wppsAiAltText) return;

		$btn.prop('disabled', true).text(wppsAiAltText.strings.saving);

		postAjax('wpps_ai_alt_save', {
			attachment_id: id,
			alt_text: $field.val()
		})
		.done(function(res) {
			if (res.success && res.data) {
				applyGenerationResult(id, res.data, { $result: $result });
				if (!res.data.alt_text) {
					showResult($result, res.data.message || wppsAiAltText.strings.saved, false);
				}
			} else {
				showResult($result, (res.data && res.data.message) || wppsAiAltText.strings.error, true);
			}
		})
		.fail(function() {
			showResult($result, wppsAiAltText.strings.error, true);
		})
		.always(function() {
			$btn.prop('disabled', false).text('Save Alt Text');
		});
	});

	// Mark / unmark decorative
	function applyDecorativeState(id, data, isDecorative) {
		syncAttachmentAltFields(id, data.alt_text || '');
		updateMediaLibraryListRow(id, data);

		const $generate = $('#wpps-ai-alt-generate');
		if ($generate.length) {
			$generate.data('regenerate', 0).attr('data-regenerate', '0').text(generateButtonLabel($generate));
		}

		$('#wpps-ai-alt-decorative').prop('hidden', isDecorative);
		$('#wpps-ai-alt-undecorative').prop('hidden', !isDecorative);
		$('.wpps-ai-alt-decorative-note').prop('hidden', !isDecorative);

		getMediaAiAltRoots().each(function() {
			updateMediaModalActionsState($(this), id, {
				statusSlug: isDecorative ? 'decorative' : (data.status_slug || ''),
				isDecorative: isDecorative,
				hasAlt: !!(data.alt_text || '')
			});
		});

		const $result = $('#wpps-ai-alt-result');
		if ($result.length) {
			showResult($result, data.message || wppsAiAltText.strings.saved, false);
		}
	}

	$doc.on('click', '#wpps-ai-alt-decorative', function() {
		const id = $(this).data('attachment-id');
		if (!id) return;

		postAjax('wpps_ai_alt_mark_decorative', { attachment_id: id })
		.done(function(res) {
			if (res.success && res.data) {
				applyDecorativeState(id, res.data, true);
			}
		});
	});

	$doc.on('click', '#wpps-ai-alt-undecorative, .wpps-ai-alt-col-undecorative, .wpps-ai-alt-media-undecorative', function() {
		const $btn = $(this);
		const id = $btn.data('attachment-id');
		if (!id || !$btn.is(':visible')) {
			return;
		}

		const $result = $btn.closest('.wpps-ai-alt-media-actions, .wpps-ai-alt-panel').find('.wpps-ai-alt-media-result, #wpps-ai-alt-result').first();

		postAjax('wpps_ai_alt_unmark_decorative', { attachment_id: id })
		.done(function(res) {
			if (res.success && res.data) {
				applyDecorativeState(id, res.data, false);
				return;
			}
			const message = (res.data && res.data.message) || wppsAiAltText.strings.error;
			if ($result.length) {
				showResult($result, message, true);
			} else {
				alert(message);
			}
		})
		.fail(function() {
			if ($result.length) {
				showResult($result, wppsAiAltText.strings.error, true);
			} else {
				alert(wppsAiAltText.strings.error);
			}
		});
	});

	// Review list row actions
	function reviewRowAfterAction($row) {
		$row.fadeOut(200, function() {
			$(this).remove();
			const remaining = $('.wpps-ai-alt-review-page tbody tr').length;
			if (remaining === 0) {
				if (window.wppsAiAltInvalidateTab) {
					window.wppsAiAltInvalidateTab('review');
				}
				if (window.wppsAiAltLoadTab) {
					window.wppsAiAltLoadTab('review', { force: true, pushState: false });
				}
			}
		});
		if (window.wppsAiAltInvalidateTab) {
			window.wppsAiAltInvalidateTab('dashboard');
		}
		if (typeof refreshLibraryStats === 'function') {
			refreshLibraryStats(true).done(function(res) {
				if (res.success && res.data && res.data.library && typeof updateReviewBadge === 'function') {
					updateReviewBadge(res.data.library.pending_review);
				}
			});
		}
	}

	$doc.on('click', '.wpps-ai-alt-approve', function() {
		const $btn = $(this);
		const id = parseInt($btn.data('attachment-id'), 10);
		const $row = $btn.closest('tr');
		const altText = $row.find('.wpps-ai-alt-review-field').val();

		if (!id || !window.wppsAiAltText) {
			return;
		}

		$btn.prop('disabled', true);
		postAjax('wpps_ai_alt_save', { attachment_id: id, alt_text: altText })
		.done(function(res) {
			if (res.success) {
				reviewRowAfterAction($row);
				return;
			}
			alert((res.data && res.data.message) || wppsAiAltText.strings.error);
			$btn.prop('disabled', false);
		})
		.fail(function() {
			alert(wppsAiAltText.strings.error);
			$btn.prop('disabled', false);
		});
	});

	$doc.on('click', '.wpps-ai-alt-reject', function() {
		const $btn = $(this);
		const id = parseInt($btn.data('attachment-id'), 10);
		const $row = $btn.closest('tr');

		if (!id || !window.wppsAiAltText) {
			return;
		}

		$btn.prop('disabled', true);
		postAjax('wpps_ai_alt_reject', { attachment_id: id })
		.done(function(res) {
			if (res.success) {
				reviewRowAfterAction($row);
				return;
			}
			alert((res.data && res.data.message) || wppsAiAltText.strings.error);
			$btn.prop('disabled', false);
		})
		.fail(function() {
			alert(wppsAiAltText.strings.error);
			$btn.prop('disabled', false);
		});
	});

	// Media Library column quick generate
	$doc.on('click', '.wpps-ai-alt-col-generate', function() {
		const $btn = $(this);
		const id = parseInt($btn.data('attachment-id'), 10);
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		if (!id || !window.wppsAiAltText) return;

		const original = $btn.text();
		$btn.prop('disabled', true).text(strings.generating || 'Generating...');
		postAjax('wpps_ai_alt_generate', { attachment_id: id, force: 1 })
		.done(function(res) {
			if (res.success && res.data && res.data.alt_text) {
				applyGenerationResult(id, res.data);
				$btn.text('✓');
				return;
			}
			$btn.prop('disabled', false).text(original);
			alert((res.data && res.data.message) || strings.error);
		})
		.fail(function() {
			$btn.prop('disabled', false).text(original);
		});
	});

	// Media Library grid modal: Generate with AI
	$doc.on('click', '.wpps-ai-alt-media-generate', function() {
		const $btn = $(this);
		const id = parseInt($btn.data('attachment-id'), 10);
		const $modal = $btn.closest('.media-modal');
		const $result = $btn.closest('.wpps-ai-alt-media-actions').find('.wpps-ai-alt-media-result');
		const isRegenerate = $btn.data('regenerate') === 1 || $btn.data('regenerate') === '1';
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};

		if (!id || !window.wppsAiAltText) {
			return;
		}

		$btn.prop('disabled', true).text(strings.generating || 'Generating...');
		$result.hide();

		const payload = { attachment_id: id };
		if (isRegenerate) {
			payload.force = 1;
		}

		postAjax('wpps_ai_alt_generate', payload)
		.done(function(res) {
			if (res.success && res.data && res.data.alt_text) {
				applyGenerationResult(id, res.data);
				const $freshResult = $modal.find('.wpps-ai-alt-media-result').first();
				if ($freshResult.length) {
					const message = res.data.alt_saved
						? (strings.generatedSaved || strings.saved || 'Alt text generated and saved.')
						: (strings.generated || 'Suggestion ready. Review and save when ready.');
					showResult($freshResult, message, false);
				}
				return;
			}

			const message = (res.data && res.data.message) || strings.error;
			if ($result.length) {
				showResult($result, message, true);
			} else {
				alert(message);
			}

			if (message.toLowerCase().indexOf('decorative') !== -1 && $modal.length) {
				updateMediaModalActionsState($modal, id, {
					isDecorative: true,
					statusSlug: 'decorative'
				});
			}
		})
		.fail(function(xhr) {
			const msg = xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message
				? xhr.responseJSON.data.message
				: strings.error;
			if ($result.length) {
				showResult($result, msg, true);
			} else {
				alert(msg);
			}
		})
		.always(function() {
			const $freshBtn = $modal.find('.wpps-ai-alt-media-generate').first();
			if ($freshBtn.length) {
				$freshBtn.prop('disabled', false).text(generateButtonLabel($freshBtn));
			}
		});
	});

	// WooCommerce product panel
	$doc.on('click', '#wpps-ai-alt-product-generate', function() {
		const $btn = $(this);
		const productId = $btn.data('product-id');
		if (!productId || !window.wppsAiAltText) return;

		$btn.prop('disabled', true).text(wppsAiAltText.strings.generating);
		postAjax('wpps_ai_alt_product_generate', { product_id: productId })
		.done(function(res) {
			alert((res.data && res.data.message) || (res.success ? wppsAiAltText.strings.bulkComplete : wppsAiAltText.strings.error));
			if (res.success) {
				window.location.reload();
			} else {
				$btn.prop('disabled', false).text('Generate all product images');
			}
		})
		.fail(function() {
			$btn.prop('disabled', false).text('Generate all product images');
		});
	});

	// Bulk generation
	var bulkTotal = 0;

	function runBulk(ids) {
		if (!ids || !ids.length || !window.wppsAiAltText) {
			$('.wpps-ai-alt-bulk-progress').hide();
			$('.wpps-ai-alt-bulk-notice p').text(wppsAiAltText.strings.bulkComplete);
			return;
		}

		if (bulkTotal === 0) bulkTotal = ids.length;
		$('.wpps-ai-alt-bulk-progress').show();

		postAjax('wpps_ai_alt_bulk', { attachment_ids: ids })
		.done(function(res) {
			if (res.success && res.data) {
				const done = bulkTotal - (res.data.remaining || []).length;
				const pct = bulkTotal > 0 ? Math.min(100, (done / bulkTotal) * 100) : 100;
				$('.wpps-ai-alt-progress-fill').css('width', pct + '%');
				$('.wpps-ai-alt-progress-text').text(done + ' / ' + bulkTotal);

				if (res.data.remaining && res.data.remaining.length) {
					runBulk(res.data.remaining);
				} else {
					$('.wpps-ai-alt-bulk-notice p').text(wppsAiAltText.strings.bulkComplete);
					bulkTotal = 0;
				}
			}
		})
		.fail(function() {
			$('.wpps-ai-alt-bulk-notice p').text(wppsAiAltText.strings.error);
			bulkTotal = 0;
		});
	}

	$(function() {
		const match = /[?&]wpps_ai_alt_bulk=1&media=([^&]+)/.exec(window.location.search);
		if (match && match[1] && window.wppsAiAltText) {
			const ids = match[1].split(',').map(function(s) { return parseInt(s, 10); }).filter(Boolean);
			if (ids.length) {
				bulkTotal = 0;
				runBulk(ids);
			}
		}
	});

	$doc.on('click', '#wpps-ai-alt-regenerate-duplicates', function() {
		const $btn = $(this);
		if (!window.wppsAiAltText || !window.confirm(wppsAiAltText.strings.confirmDuplicates)) return;
		$btn.prop('disabled', true).text(wppsAiAltText.strings.generating);
		postAjax('wpps_ai_alt_regenerate_duplicates', {})
		.done(function(res) {
			alert((res.data && res.data.message) || wppsAiAltText.strings.bulkQueued);
			if (res.success) window.location.reload();
		})
		.fail(function() {
			alert(wppsAiAltText.strings.error);
		})
		.always(function() {
			$btn.prop('disabled', false).text('Regenerate duplicates');
		});
	});

	// Settings page: accordions
	$doc.on('click', '.wpps-ai-alt-accordion-header', function(e) {
		if ($(e.target).closest('.wpps-ai-alt-accordion-tooltip').length) {
			return;
		}
		const $accordion = $(this).closest('.wpps-ai-alt-accordion');
		const isOpen = $accordion.hasClass('is-open');
		$accordion.toggleClass('is-open', !isOpen);
		$(this).attr('aria-expanded', !isOpen ? 'true' : 'false');
	});

	$doc.on('click', '.wpps-ai-alt-accordion-tooltip', function(e) {
		e.stopPropagation();
	});

	function rebuildModelOptions(provider, selected) {
		const $model = $('#' + (wppsAiAltText.moduleId || 'ai-alt-text-generator') + '_model');
		if (!$model.length) return;
		const groups = JSON.parse($model.attr('data-model-groups') || '{}');
		const providerGroups = groups[provider];
		if (!providerGroups) return;
		const current = selected || $model.val();
		$model.empty();
		Object.keys(providerGroups).forEach(function(groupKey) {
			const group = providerGroups[groupKey];
			if (!group || !group.models) return;
			const $optgroup = $('<optgroup>').attr('label', group.label || groupKey);
			Object.keys(group.models).forEach(function(value) {
				const $option = $('<option>').val(value).text(group.models[value]);
				if (value === current) $option.prop('selected', true);
				$optgroup.append($option);
			});
			$model.append($optgroup);
		});
		const hints = wppsAiAltText.modelHints || {};
		const $hint = $('#' + (wppsAiAltText.moduleId || 'ai-alt-text-generator') + '_model_hint');
		if ($hint.length && hints[provider]) {
			$hint.text(hints[provider]);
		}
	}

	$doc.on('change', '#' + (wppsAiAltText.moduleId || 'ai-alt-text-generator') + '_provider', function() {
		const provider = $(this).val();
		rebuildModelOptions(provider, '');
	});

	$doc.on('change', '#' + (wppsAiAltText.moduleId || 'ai-alt-text-generator') + '_prompt_style', function() {
		const style = $(this).val();
		const descriptions = wppsAiAltText.promptStyleDescriptions || {};
		const $hint = $('#' + (wppsAiAltText.moduleId || 'ai-alt-text-generator') + '_prompt_style_hint');
		if ($hint.length && descriptions[style]) {
			$hint.text(descriptions[style]);
		}
	});

	function toggleAdminOnlyAccess() {
		const moduleId = wppsAiAltText.moduleId || 'ai-alt-text-generator';
		const $toggle = $('#' + moduleId + '_admin_only_access');
		const $wrap = $('#' + moduleId + '_role_caps_wrap');
		if (!$toggle.length || !$wrap.length) {
			return;
		}
		$wrap.toggleClass('is-hidden', $toggle.is(':checked'));
	}

	function initPostTypeMultiselects() {
		const form = document.getElementById('wpps-ai-alt-settings-form');
		if (!form) {
			return;
		}

		form.querySelectorAll('.wpps-ct-multiselect').forEach(function(root) {
			if (root.dataset.multiselectInit === '1') {
				return;
			}
			root.dataset.multiselectInit = '1';

			const trigger = root.querySelector('.wpps-ct-multiselect__trigger');
			const triggerText = root.querySelector('.wpps-ct-multiselect__trigger-text');
			const dropdown = root.querySelector('.wpps-ct-multiselect__dropdown');
			const search = root.querySelector('.wpps-ct-multiselect__search');
			const options = root.querySelectorAll('.wpps-ct-multiselect__option');
			if (!trigger || !dropdown || !triggerText) {
				return;
			}

			function countChecked() {
				let count = 0;
				options.forEach(function(option) {
					const checkbox = option.querySelector('input[type="checkbox"]');
					if (checkbox && checkbox.checked) {
						count += 1;
					}
				});
				return count;
			}

			function updateTriggerText() {
				const count = countChecked();
				const none = root.dataset.labelNone || '';
				const single = root.dataset.labelSingle || '';
				const multi = (root.dataset.labelMulti || '').replace('%d', String(count));
				triggerText.textContent = count === 0 ? none : (count === 1 ? single : multi);
			}

			function filterOptions(query) {
				const q = (query || '').toLowerCase().trim();
				options.forEach(function(option) {
					const label = (option.dataset.label || '').toLowerCase();
					const slug = (option.dataset.slug || '').toLowerCase();
					const show = !q || label.indexOf(q) !== -1 || slug.indexOf(q) !== -1;
					option.classList.toggle('wpps-ct-multiselect__option--hidden', !show);
				});
			}

			function toggleDropdown(open) {
				const openNow = open === undefined ? dropdown.hasAttribute('hidden') : !!open;
				if (openNow) {
					dropdown.removeAttribute('hidden');
					trigger.setAttribute('aria-expanded', 'true');
					if (search) {
						search.value = '';
						filterOptions('');
						search.focus();
					}
				} else {
					dropdown.setAttribute('hidden', '');
					trigger.setAttribute('aria-expanded', 'false');
				}
			}

			trigger.addEventListener('click', function() {
				toggleDropdown();
			});

			if (search) {
				search.addEventListener('input', function() {
					filterOptions(this.value);
				});
				search.addEventListener('keydown', function(event) {
					if (event.key === 'Escape') {
						toggleDropdown(false);
					}
				});
			}

			root.querySelectorAll('.wpps-ct-multiselect__action').forEach(function(button) {
				button.addEventListener('click', function() {
					const action = button.dataset.action;
					const visible = Array.prototype.filter.call(options, function(option) {
						return !option.classList.contains('wpps-ct-multiselect__option--hidden');
					});
					if (action === 'select-all') {
						visible.forEach(function(option) {
							const checkbox = option.querySelector('input[type="checkbox"]');
							if (checkbox) {
								checkbox.checked = true;
							}
						});
					} else if (action === 'clear') {
						options.forEach(function(option) {
							const checkbox = option.querySelector('input[type="checkbox"]');
							if (checkbox) {
								checkbox.checked = false;
							}
						});
					}
					updateTriggerText();
				});
			});

			options.forEach(function(option) {
				const checkbox = option.querySelector('input[type="checkbox"]');
				if (checkbox) {
					checkbox.addEventListener('change', updateTriggerText);
				}
			});

			document.addEventListener('click', function(event) {
				if (!root.contains(event.target)) {
					toggleDropdown(false);
				}
			});

			updateTriggerText();
		});
	}

	$doc.on('change', '[id$="_admin_only_access"]', toggleAdminOnlyAccess);

	$doc.on('click', '#' + (wppsAiAltText.moduleId || 'ai-alt-text-generator') + '_test_connection', function() {
		const $btn = $(this);
		const moduleId = wppsAiAltText.moduleId || 'ai-alt-text-generator';
		const $result = $('#' + moduleId + '_test_result');
		const $provider = $('#' + moduleId + '_provider');
		const provider = $provider.length ? $provider.val() : 'openai';
		const data = new window.FormData();
		data.append('action', 'wpps_ai_alt_test_connection');
		data.append('nonce', wppsAiAltText.nonce);
		data.append('provider', provider);
		$btn.prop('disabled', true);
		$result.text(wppsAiAltText.strings.generating || 'Testing...').css('color', '');
		window.fetch(wppsAiAltText.ajaxUrl, { method: 'POST', body: data, credentials: 'same-origin' })
			.then(function(response) { return response.json(); })
			.then(function(json) {
				$result.text((json.data && json.data.message) || wppsAiAltText.strings.error);
				$result.css('color', json.success ? '#008a20' : '#b32d2e');
			})
			.catch(function() {
				$result.text(wppsAiAltText.strings.error).css('color', '#b32d2e');
			})
			.finally(function() {
				$btn.prop('disabled', false);
			});
	});

	$(function() {
		const moduleId = wppsAiAltText.moduleId || 'ai-alt-text-generator';
		toggleAdminOnlyAccess();
		initPostTypeMultiselects();
		if (window.wppsAccordionTooltipsInit) {
			window.wppsAccordionTooltipsInit();
		}
	});

	// Bulk generate tab — live session processing
	let bulkSession = '';
	let bulkRunning = false;
	let bulkCancelRequested = false;
	let bulkStoppedByUser = false;
	let bulkStepRetries = 0;
	let bulkSkipStreak = 0;

	function bulkOptions() {
		const overwrite = $('#wpps-ai-alt-bulk-overwrite').is(':checked');
		return {
			overwrite: overwrite ? 1 : 0,
			only_attached: $('#wpps-ai-alt-bulk-attached').is(':checked') ? 1 : 0,
			skip_processed: $('#wpps-ai-alt-bulk-skip-processed').is(':checked') ? 1 : 0,
			woo_only: $('#wpps-ai-alt-bulk-woo-only').is(':checked') ? 1 : 0,
			woo_main_only: $('#wpps-ai-alt-bulk-woo-main').is(':checked') ? 1 : 0,
			seo_keywords: ($('#wpps-ai-alt-bulk-seo-keywords').val() || '').trim(),
			negative_keywords: ($('#wpps-ai-alt-bulk-negative-keywords').val() || '').trim(),
			target: overwrite ? 'all' : 'missing'
		};
	}

	function updateBulkHeroStats(library) {
		if (!library) {
			return;
		}

		const total = parseInt(library.total, 10) || 0;
		const missing = parseInt(library.missing, 10) || 0;
		const withAlt = Math.max(0, total - missing);
		const missingPct = total > 0 ? Math.round((missing / total) * 1000) / 10 : 0;
		const withPct = total > 0 ? Math.round((withAlt / total) * 1000) / 10 : 0;

		$('#wpps-ai-alt-stat-total').text(total);
		$('#wpps-ai-alt-stat-missing').text(missing);
		$('#wpps-ai-alt-stat-optimized').text(withAlt);
		$('#wpps-ai-alt-stat-weak').text(library.weak || 0);
		$('#wpps-ai-alt-stat-pending').text(library.pending_review || 0);
	}

	function updateBulkFilteredStats(stats) {
		if (!stats) {
			return;
		}

		const total = parseInt(stats.total, 10) || 0;
		const missing = parseInt(stats.missing, 10) || 0;
		const withAlt = parseInt(stats.with_alt, 10);
		const resolvedWithAlt = Number.isFinite(withAlt) ? Math.max(0, withAlt) : Math.max(0, total - missing);
		const missingPct = total > 0 ? Math.round((missing / total) * 1000) / 10 : 0;
		const withPct = total > 0 ? Math.round((resolvedWithAlt / total) * 1000) / 10 : 0;

		$('#wpps-ai-alt-bulk-stat-total').text(total);
		$('#wpps-ai-alt-bulk-stat-missing').text(missing);
		$('#wpps-ai-alt-bulk-stat-missing-pct').text(missingPct + '%');
		$('#wpps-ai-alt-bulk-stat-complete').text(resolvedWithAlt);
		$('#wpps-ai-alt-bulk-stat-complete-pct').text(withPct + '%');
	}

	function formatBulkString(template, count) {
		return String(template || '').replace('%d', String(count));
	}

	function updateLibraryStatsUI(library) {
		updateBulkHeroStats(library);
	}

	function refreshLibraryStats(refresh) {
		return postAjax('wpps_ai_alt_dashboard_stats', { refresh: refresh ? 1 : 0 })
		.done(function(res) {
			if (res.success && res.data && res.data.library) {
				updateLibraryStatsUI(res.data.library);
			}
		});
	}

	function updateBulkCountUI(count, stats) {
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		const $btn = $('#wpps-ai-alt-bulk-start');
		const $match = $('#wpps-ai-alt-bulk-match-count');
		const hasKey = $btn.data('has-api-key') === 1 || $btn.data('has-api-key') === '1';

		$btn.data('count', count);

		if (stats) {
			updateBulkFilteredStats(stats);
		}

		if (count > 0) {
			const label = count === 1
				? formatBulkString(strings.bulkGenerateSingular, count)
				: formatBulkString(strings.bulkGeneratePlural, count);
			$btn.text(label);
			$match.text(
				count === 1
					? formatBulkString(strings.bulkMatchSingular, count)
					: formatBulkString(strings.bulkMatchPlural, count)
			);
			if (hasKey) {
				$btn.prop('disabled', false);
			}
		} else {
			$btn.text(strings.bulkGenerateNone || 'No images match selected filters');
			$match.text(strings.bulkMatchNone || 'No images match your filters');
			$btn.prop('disabled', true);
		}
	}

	let bulkPreviewTimer = null;
	let bulkPreviewRequest = null;

	function refreshBulkPreviewCount() {
		if (!$('#wpps-ai-alt-bulk-start').length || bulkRunning) {
			return;
		}

		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		const $btn = $('#wpps-ai-alt-bulk-start');
		$btn.prop('disabled', true).text(strings.bulkCountUpdating || 'Updating count...');

		if (bulkPreviewRequest && bulkPreviewRequest.abort) {
			bulkPreviewRequest.abort();
		}

		bulkPreviewRequest = postAjax('wpps_ai_alt_bulk_preview', bulkOptions())
		.done(function(res) {
			const count = res.success && res.data ? parseInt(res.data.count, 10) || 0 : 0;
			const stats = res.success && res.data ? res.data.stats : null;
			updateBulkCountUI(count, stats);
		})
		.fail(function() {
			const fallback = parseInt($btn.data('count'), 10) || 0;
			updateBulkCountUI(fallback);
		});
	}

	function scheduleBulkPreviewCount() {
		window.clearTimeout(bulkPreviewTimer);
		bulkPreviewTimer = window.setTimeout(refreshBulkPreviewCount, 250);
	}

	function updateBulkProgress(data) {
		const total = data.total || 0;
		const current = data.current || 0;
		const percent = total > 0 ? Math.round((current / total) * 100) : 0;
		$('#wpps-ai-alt-bulk-progress-fill').css('width', percent + '%');
		$('#wpps-ai-alt-bulk-progress-text').text((current || 0) + ' / ' + total);
		$('#wpps-ai-alt-bulk-progress-pct').text(percent + '%');
		$('#wpps-ai-alt-bulk-stat-processed').text(data.processed || 0);
		$('#wpps-ai-alt-bulk-stat-failed').text(data.failed || 0);
		$('#wpps-ai-alt-bulk-stat-skipped').text(data.skipped || 0);
	}

	function bulkStatusLabel(status) {
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		if (status === 'success') {
			return strings.bulkStatusSuccess || 'Success';
		}
		if (status === 'skipped') {
			return strings.bulkStatusSkipped || 'Skipped';
		}
		return strings.bulkStatusFailed || 'Failed';
	}

	function toggleBulkRetryButton(data) {
		const failedItems = (data && data.failed_items) || [];
		const failedCount = failedItems.length || parseInt((data && data.failed) || 0, 10) || 0;
		const show = !!(data && data.done && failedCount > 0);
		$('#wpps-ai-alt-bulk-retry-failed').prop('hidden', !show);
	}

	function applyBulkSessionStatus(data) {
		if (!data || data.mode !== 'session') {
			return;
		}

		if (bulkStoppedByUser) {
			return;
		}

		updateBulkProgress({
			total: data.total || 0,
			current: data.current || 0,
			processed: data.processed || 0,
			failed: data.failed || 0,
			skipped: data.skipped || 0
		});
		toggleBulkRetryButton(data);

		if (data.done) {
			bulkRunning = false;
			setBulkRunState('complete');
			finishBulkRun();
			return;
		}

		bulkRunning = true;
		setBulkRunState('running');
	}

	function resumeBulkIfNeeded() {
		if (!$('#wpps-ai-alt-bulk-start').length || bulkRunning || bulkStoppedByUser) {
			return;
		}

		postAjax('wpps_ai_alt_bulk_status', {})
		.done(function(res) {
			if (!res.success || !res.data || res.data.mode !== 'session') {
				return;
			}

			const data = res.data;
			bulkSession = data.session || '';

			if (!bulkSession) {
				return;
			}

			if (data.done) {
				applyBulkSessionStatus(data);
				return;
			}

			const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
			$('#wpps-ai-alt-bulk-run-label').text(strings.bulkResume || 'Resuming bulk generation...');
			applyBulkSessionStatus(data);
			runBulkStep();
		});
	}

	function setBulkRunState(state) {
		const $page = $('.wpps-ai-alt-bulk-page');
		const $panel = $('#wpps-ai-alt-bulk-run-panel');
		const $spinner = $('#wpps-ai-alt-bulk-run-spinner');
		const $label = $('#wpps-ai-alt-bulk-run-label');
		const $stop = $('#wpps-ai-alt-bulk-stop');
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};

		if (state === 'running') {
			$page.addClass('is-running');
			$panel.prop('hidden', false).removeClass('is-complete is-stopped');
			$spinner.prop('hidden', false);
			$label.text(strings.bulkRunning || 'Generating alt text...');
			$stop.prop('hidden', false);
			$('#wpps-ai-alt-bulk-retry-failed').prop('hidden', true);
			return;
		}

		$page.removeClass('is-running');
		$spinner.prop('hidden', true);
		$stop.prop('hidden', true);

		if (state === 'complete') {
			$panel.addClass('is-complete').removeClass('is-stopped');
			$label.text(strings.bulkComplete || 'Bulk generation complete.');
			return;
		}

		if (state === 'stopped') {
			$panel.addClass('is-stopped').removeClass('is-complete');
			$label.text(strings.bulkStopped || 'Bulk generation stopped.');
			return;
		}

		$panel.prop('hidden', true).removeClass('is-complete is-stopped');
	}

	function resetBulkResults() {
		$('#wpps-ai-alt-bulk-results tbody').empty();
		$('#wpps-ai-alt-bulk-results-count').text('0');
		$('#wpps-ai-alt-bulk-results-empty').prop('hidden', false);
	}

	function appendBulkResult(item) {
		if (!item) return;

		const $tbody = $('#wpps-ai-alt-bulk-results tbody');
		const status = item.status || 'failed';
		let alt = item.alt_text || '';
		if (!alt && item.message) {
			alt = item.message + (item.code ? ' [' + item.code + ']' : '');
		}
		if (!alt) {
			alt = '—';
		}
		const statusLabel = bulkStatusLabel(status);
		const filename = item.filename || '';
		const id = item.id || '';
		const thumbUrl = item.thumb_url || '';
		const editUrl = item.edit_url || '';
		const $alt = $('<div>').text(alt);
		let keywordsHtml = '';
		if (item.keywords_applied && item.keywords_applied.length) {
			keywordsHtml = '<div class="wpps-ai-alt-bulk-keywords">' +
				$('<div>').text('Keywords: ' + item.keywords_applied.join(', ')).html() +
				'</div>';
		}

		let thumbHtml = '<span class="wpps-ai-alt-bulk-thumb-fallback" aria-hidden="true"><span class="dashicons dashicons-format-image"></span></span>';
		if (thumbUrl) {
			thumbHtml = '<img class="wpps-ai-alt-bulk-thumb" src="' + $('<div>').text(thumbUrl).html() + '" alt="" width="48" height="48" loading="lazy" decoding="async" />';
		}

		let titleHtml = '<span class="wpps-ai-alt-bulk-image-title">#' + id + '</span>';
		if (editUrl) {
			titleHtml = '<a class="wpps-ai-alt-bulk-image-title" href="' + $('<div>').text(editUrl).html() + '" target="_blank" rel="noopener noreferrer">#' + id + '</a>';
		}

		const row = '<tr class="wpps-ai-alt-bulk-row is-' + status + '">' +
			'<td class="col-image">' +
				'<div class="wpps-ai-alt-bulk-image-cell">' +
					thumbHtml +
					'<div class="wpps-ai-alt-bulk-image-meta">' +
						titleHtml +
						(filename ? '<span class="wpps-ai-alt-bulk-image-file">' + $('<div>').text(filename).html() + '</span>' : '') +
					'</div>' +
				'</div>' +
			'</td>' +
			'<td class="col-status"><span class="wpps-ai-alt-bulk-status is-' + status + '">' + statusLabel + '</span></td>' +
			'<td class="col-alt"><span class="wpps-ai-alt-bulk-alt-text" title="' + $('<div>').text(alt).html() + '">' + $alt.html() + '</span>' + keywordsHtml + '</td>' +
			'</tr>';

		$tbody.prepend(row);
		$('#wpps-ai-alt-bulk-results-empty').prop('hidden', true);
		$('#wpps-ai-alt-bulk-results-count').text($tbody.children().length);
	}

	function appendBulkResults(items) {
		if (!items || !items.length) {
			return;
		}
		items.slice().reverse().forEach(appendBulkResult);
	}

	function finishBulkRun() {
		refreshLibraryStats(true).done(function(res) {
			if (res.success && res.data && res.data.library) {
				const pending = parseInt(res.data.library.pending_review, 10) || 0;
				$('#wpps-ai-alt-bulk-review-cta').prop('hidden', pending <= 0);
				updateReviewBadge(pending);
			}
			if (window.wppsAiAltInvalidateTab) {
				window.wppsAiAltInvalidateTab('dashboard');
				window.wppsAiAltInvalidateTab('review');
			}
		});
		scheduleBulkPreviewCount();
		postAjax('wpps_ai_alt_bulk_status', {}).done(function(res) {
			if (res.success && res.data) {
				toggleBulkRetryButton(res.data);
			}
		});
	}

	function handleBulkStepSuccess(data) {
		bulkStepRetries = 0;
		bulkSkipStreak = 0;
		updateBulkProgress(data);
		appendBulkResults(data.items || (data.item ? [data.item] : []));
		toggleBulkRetryButton(data);

		if (data.done) {
			bulkRunning = false;
			setBulkRunState('complete');
			finishBulkRun();
			return;
		}

		window.setTimeout(runBulkStep, 20);
	}

	function runBulkStep(skipCurrent) {
		if (!bulkSession || bulkCancelRequested) {
			bulkRunning = false;
			setBulkRunState('stopped');
			finishBulkRun();
			return;
		}

		const payload = { session: bulkSession };
		if (skipCurrent) {
			payload.skip_current = 1;
		}

		// Client waits longer than the server's capped 25s AI timeout so JSON errors surface cleanly.
		postAjax('wpps_ai_alt_bulk_step', payload, { timeout: 45000 })
		.done(function(res) {
			if (!res.success || !res.data) {
				const retryable = !!(res.data && res.data.retryable);
				if (retryable && bulkStepRetries < 2) {
					bulkStepRetries += 1;
					window.setTimeout(function() { runBulkStep(false); }, 1500 * bulkStepRetries);
					return;
				}
				bulkStepRetries = 0;
				alert((res.data && res.data.message) || wppsAiAltText.strings.error);
				bulkRunning = false;
				setBulkRunState('stopped');
				finishBulkRun();
				return;
			}

			handleBulkStepSuccess(res.data);
		})
		.fail(function(xhr) {
			const status = xhr && xhr.status;
			const retryableStatus = status === 0 || status === 408 || status === 500 || status === 502 || status === 503 || status === 504;
			if (!bulkCancelRequested && bulkSession && retryableStatus && bulkStepRetries < 2) {
				bulkStepRetries += 1;
				const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
				$('#wpps-ai-alt-bulk-run-label').text(
					(strings.bulkRetryingStep || 'Temporary server issue — retrying…') + ' (' + bulkStepRetries + '/2)'
				);
				window.setTimeout(function() { runBulkStep(false); }, 2000 * bulkStepRetries);
				return;
			}

			// After repeated hard 500s, skip the stuck image and keep the run moving.
			if (!bulkCancelRequested && bulkSession && retryableStatus && bulkSkipStreak < 20) {
				bulkSkipStreak += 1;
				bulkStepRetries = 0;
				const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
				$('#wpps-ai-alt-bulk-run-label').text(
					strings.bulkSkippingStuck || 'Skipping stuck image and continuing…'
				);
				window.setTimeout(function() { runBulkStep(true); }, 500);
				return;
			}

			bulkStepRetries = 0;
			alert(ajaxErrorMessage(xhr, wppsAiAltText.strings.error));
			bulkRunning = false;
			setBulkRunState('stopped');
			finishBulkRun();
		});
	}

	$doc.on('change input', '#wpps-ai-alt-bulk-overwrite, #wpps-ai-alt-bulk-attached, #wpps-ai-alt-bulk-skip-processed, #wpps-ai-alt-bulk-woo-only, #wpps-ai-alt-bulk-woo-main, #wpps-ai-alt-bulk-seo-keywords, #wpps-ai-alt-bulk-negative-keywords', function() {
		if (this.id === 'wpps-ai-alt-bulk-woo-main' && $(this).is(':checked')) {
			$('#wpps-ai-alt-bulk-woo-only').prop('checked', true);
		}
		scheduleBulkPreviewCount();
	});

	$doc.on('click', '#wpps-ai-alt-bulk-start', function() {
		if (!window.wppsAiAltText || bulkRunning) return;

		const $btn = $(this);
		bulkCancelRequested = false;
		bulkStoppedByUser = false;
		bulkStepRetries = 0;
		bulkSkipStreak = 0;
		$btn.prop('disabled', true).text(wppsAiAltText.strings.bulkStarting || 'Starting...');
		resetBulkResults();
		setBulkRunState('running');
		$('#wpps-ai-alt-bulk-review-cta').prop('hidden', true);

		postAjax('wpps_ai_alt_bulk_start', bulkOptions())
		.done(function(res) {
			if (!res.success || !res.data || !res.data.session) {
				alert((res.data && res.data.message) || wppsAiAltText.strings.bulkNoImages || wppsAiAltText.strings.error);
				setBulkRunState('idle');
				scheduleBulkPreviewCount();
				return;
			}

			bulkSession = res.data.session;
			bulkRunning = true;
			$btn.text(wppsAiAltText.strings.bulkRunning || 'Generating...');
			const seoKw = (res.data.keywords_seo || []).join(', ');
			if (seoKw) {
				$('#wpps-ai-alt-bulk-run-label').attr('title', 'Keywords: ' + seoKw);
				$('#wpps-ai-alt-bulk-seo-keywords').val(seoKw);
			}
			updateBulkProgress({
				total: res.data.total,
				current: 0,
				processed: 0,
				failed: 0,
				skipped: 0
			});

			runBulkStep();
		})
		.fail(function(xhr) {
			const msg = ajaxErrorMessage(xhr, wppsAiAltText.strings.error);
			alert(msg);
			setBulkRunState('idle');
			scheduleBulkPreviewCount();
		});
	});

	$doc.on('click', '#wpps-ai-alt-bulk-stop', function() {
		bulkCancelRequested = true;
		bulkStoppedByUser = true;
		if (bulkSession) {
			postAjax('wpps_ai_alt_bulk_cancel', { session: bulkSession });
		}
		bulkSession = '';
		bulkRunning = false;
		setBulkRunState('stopped');
		finishBulkRun();
	});

	$doc.on('click', '#wpps-ai-alt-bulk-retry-failed', function() {
		const $btn = $(this);
		const strings = (window.wppsAiAltText && wppsAiAltText.strings) || {};
		if (!window.wppsAiAltText) {
			return;
		}

		$btn.prop('disabled', true).text(strings.bulkRetrying || 'Retrying failed images...');
		bulkCancelRequested = false;
		bulkStoppedByUser = false;
		resetBulkResults();
		setBulkRunState('running');

		postAjax('wpps_ai_alt_bulk_retry_failed', { session: bulkSession })
		.done(function(res) {
			if (!res.success || !res.data) {
				alert((res.data && res.data.message) || strings.error);
				setBulkRunState('idle');
				return;
			}

			bulkSession = res.data.session || bulkSession;
			bulkRunning = true;
			updateBulkProgress({
				total: res.data.total || 0,
				current: 0,
				processed: 0,
				failed: 0,
				skipped: 0
			});

			runBulkStep();
		})
		.fail(function() {
			alert(strings.error);
			setBulkRunState('idle');
		})
		.always(function() {
			$btn.prop('disabled', false).text(strings.bulkRetryFailed || 'Retry failed');
		});
	});

	function prependHistoryLogRow($sourceRow, data) {
		const $tbody = $sourceRow.closest('tbody');
		if (!$tbody.length || !data || !data.alt_text) {
			return false;
		}

		const $newRow = $sourceRow.clone();
		$newRow.removeClass('alternate').addClass('wpps-ai-alt-history-row-new');
		const $cells = $newRow.children('td, th');
		const oldAlt = data.old_alt || '';
		const newAlt = data.alt_text || '';

		if ($cells.length >= 6) {
			$cells.eq(1).text(data.created_at || '');
			$cells.eq(3).text(data.log_status || 'success');
			if (oldAlt !== '') {
				$cells.eq(4).text(oldAlt);
			} else {
				$cells.eq(4).empty().append($('<span class="description"></span>').text('—'));
			}
			$cells.eq(5).text(newAlt);
		}

		$newRow.find('input[type="checkbox"]').prop('checked', false);
		$tbody.prepend($newRow);
		window.setTimeout(function() {
			$newRow.removeClass('wpps-ai-alt-history-row-new');
		}, 2500);
		return true;
	}

	function refreshTabAfterGeneration(tab, data, $sourceRow) {
		if (tab === 'history' && $sourceRow && $sourceRow.length && prependHistoryLogRow($sourceRow, data)) {
			if (window.wppsAiAltInvalidateTab) {
				window.wppsAiAltInvalidateTab('history');
				window.wppsAiAltInvalidateTab('dashboard');
			}
			return;
		}

		if (tab && window.wppsAiAltInvalidateTab) {
			window.wppsAiAltInvalidateTab(tab);
			window.wppsAiAltInvalidateTab('dashboard');
			if (window.wppsAiAltLoadTab) {
				window.wppsAiAltLoadTab(tab, { force: true, pushState: false });
				return;
			}
		}

		window.location.reload();
	}

	$doc.on('click', '.wpps-ai-alt-review-regenerate, .wpps-ai-alt-history-regenerate', function(e) {
		e.preventDefault();
		const $btn = $(this);
		const attachmentId = parseInt($btn.data('attachment-id'), 10);
		if (!attachmentId || !window.wppsAiAltText) {
			return;
		}

		const original = $btn.text();
		const $row = $btn.closest('tr');
		$btn.prop('disabled', true).text(wppsAiAltText.strings.generating || 'Generating...');

		postAjax('wpps_ai_alt_generate', { attachment_id: attachmentId, force: 1 })
		.done(function(res) {
			if (!res.success || !res.data) {
				alert((res.data && res.data.message) || wppsAiAltText.strings.error);
				return;
			}

			const $reviewField = $row.find('.wpps-ai-alt-review-field');
			if ($reviewField.length && res.data.alt_text) {
				$reviewField.val(res.data.alt_text);
				if (window.wppsAiAltInvalidateTab) {
					window.wppsAiAltInvalidateTab('review');
					window.wppsAiAltInvalidateTab('dashboard');
				}
				return;
			}

			if (res.data.alt_text) {
				applyGenerationResult(attachmentId, res.data);
			}

			const tab = $('#wpps-ai-alt-tab-panel').attr('data-current-tab') || '';
			refreshTabAfterGeneration(tab, res.data, $row);
		})
		.fail(function() {
			alert(wppsAiAltText.strings.error);
		})
		.always(function() {
			if (!$btn.prop('disabled')) {
				return;
			}
			$btn.prop('disabled', false).text(original);
		});
	});

	// Dashboard SPA-style tabs (no full page reload).
	const tabCache = {};
	let tabRequest = null;

	const allowedTabs = ['dashboard', 'bulk', 'review', 'history', 'settings'];

	function tabFromHref(href) {
		const match = /[?&]tab=([a-z_]+)/.exec(href || '');
		if (match && allowedTabs.indexOf(match[1]) !== -1) {
			return match[1];
		}
		if ((href || '').indexOf('#wpps-ai-alt-settings') !== -1) {
			return 'settings';
		}
		return 'dashboard';
	}

	function normalizeTab(tab) {
		return allowedTabs.indexOf(tab) !== -1 ? tab : 'dashboard';
	}

	function scrollToSettings() {
		const $target = $('#wpps-ai-alt-settings');
		if (!$target.length) {
			return;
		}
		const top = Math.max(0, $target.offset().top - 48);
		$('html, body').animate({ scrollTop: top }, 280);
	}

	function afterTabShown(tab, options) {
		onTabShown(tab);
		if (options && options.scrollTo === 'settings') {
			window.setTimeout(scrollToSettings, 60);
		}
	}

	const TRANSIENT_URL_PARAMS = ['settings_saved', 'refresh', 'bulk_done', 'bulk_count'];

	function hasTransientUrlParams(url) {
		const target = url || new URL(window.location.href, window.location.origin);
		return TRANSIENT_URL_PARAMS.some(function(param) {
			return target.searchParams.has(param);
		});
	}

	function currentHistoryUrl() {
		const url = new URL(window.location.href, window.location.origin);
		return url.pathname + url.search + url.hash;
	}

	function tabUrl(tab) {
		const base = (window.wppsAiAltText && wppsAiAltText.pageUrl) || window.location.href;
		const url = new URL(base, window.location.origin);
		if (tab && tab !== 'dashboard') {
			url.searchParams.set('tab', tab);
		} else {
			url.searchParams.delete('tab');
		}
		url.searchParams.delete('refresh');
		url.searchParams.delete('settings_saved');
		url.searchParams.delete('bulk_done');
		url.searchParams.delete('bulk_count');
		return url.pathname + url.search;
	}

	function updateReviewBadge(count) {
		const pending = parseInt(count, 10) || 0;
		const $badge = $('#wpps-ai-alt-review-badge');
		if (!$badge.length) {
			return;
		}
		if (pending > 0) {
			$badge.text(pending).prop('hidden', false);
		} else {
			$badge.prop('hidden', true);
		}
	}

	function toggleApiKeyNotice(tab) {
		const $notice = $('[data-wpps-ai-alt-notice="api-key"]');
		if ($notice.length) {
			$notice.toggle(tab === 'dashboard');
		}
	}

	function onTabShown(tab) {
		toggleApiKeyNotice(tab);
		if (tab === 'settings') {
			initSettingsTab();
		}
		if (tab === 'bulk' && typeof scheduleBulkPreviewCount === 'function') {
			scheduleBulkPreviewCount();
			resumeBulkIfNeeded();
		}
	}

	function initSettingsTab() {
		if (!window.wppsAiAltText) {
			return;
		}
		const moduleId = wppsAiAltText.moduleId || 'ai-alt-text-generator';
		const $provider = $('#' + moduleId + '_provider');
		if (!$provider.length) {
			return;
		}
		rebuildModelOptions($provider.val());
		const $promptStyle = $('#' + moduleId + '_prompt_style');
		if ($promptStyle.length) {
			$promptStyle.trigger('change');
		}
		toggleAdminOnlyAccess();
		initPostTypeMultiselects();
		if (window.wppsAccordionTooltipsInit) {
			window.wppsAccordionTooltipsInit();
		}
	}

	function setActiveTab(tab) {
		$('.wpps-ai-alt-tab').removeClass('is-active');
		$('.wpps-ai-alt-tab[data-tab="' + tab + '"]').addClass('is-active');
		$('#wpps-ai-alt-tab-panel').attr('data-current-tab', tab);
	}

	function renderTabPanel(html, tab, options) {
		$('#wpps-ai-alt-tab-panel').html(html);
		tabCache[tab] = html;
		setActiveTab(tab);
		afterTabShown(tab, options || {});
	}

	function loadTab(tab, options) {
		options = options || {};
		if (!window.wppsAiAltText || !$('#wpps-ai-alt-tab-panel').length) {
			return;
		}

		tab = normalizeTab(tab);
		const $panel = $('#wpps-ai-alt-tab-panel');
		const current = $panel.attr('data-current-tab') || 'dashboard';

		if (tab === current && !options.force && !options.scrollTo) {
			return;
		}

		if (tab === current && options.scrollTo) {
			afterTabShown(tab, options);
			return;
		}

		if (options.pushState !== false) {
			history.pushState({ tab: tab }, '', tabUrl(tab));
		}

		if (tabCache[tab] && !options.force) {
			renderTabPanel(tabCache[tab], tab, options);
			return;
		}

		const strings = wppsAiAltText.strings || {};
		$panel.addClass('is-loading');

		if (tabRequest && tabRequest.abort) {
			tabRequest.abort();
		}

		tabRequest = postAjax('wpps_ai_alt_load_tab', { tab: tab })
		.done(function(res) {
			if (!res.success || !res.data || !res.data.html) {
				alert((res.data && res.data.message) || strings.error);
				return;
			}
			const resolved = res.data.tab || tab;
			renderTabPanel(res.data.html, normalizeTab(resolved), options);
			if (typeof res.data.pending_review !== 'undefined') {
				updateReviewBadge(res.data.pending_review);
			}
		})
		.fail(function(xhr, status) {
			if (status === 'abort') {
				return;
			}
			window.location.href = tabUrl(tab);
		})
		.always(function() {
			$panel.removeClass('is-loading');
		});
	}

	window.wppsAiAltLoadTab = loadTab;
	window.wppsAiAltInvalidateTab = function(tab) {
		if (tab) {
			delete tabCache[tab];
		} else {
			Object.keys(tabCache).forEach(function(key) {
				delete tabCache[key];
			});
		}
	};

	$doc.on('click', '.wpps-ai-alt-wrap .wpps-ai-alt-tab, .wpps-ai-alt-wrap .wpps-ai-alt-tab-link', function(e) {
		const href = $(this).attr('href') || '';
		const hasPage = href.indexOf('page=') !== -1;
		const scrollToSettingsSection = $(this).is('.wpps-ai-alt-scroll-settings') || href.indexOf('#wpps-ai-alt-settings') !== -1;
		if (!hasPage && !scrollToSettingsSection) {
			return;
		}
		e.preventDefault();
		const tab = normalizeTab($(this).data('tab') || tabFromHref(href));
		loadTab(tab, scrollToSettingsSection ? { scrollTo: 'settings' } : {});
	});

	$doc.on('click', '.wpps-ai-alt-scroll-settings', function(e) {
		const href = $(this).attr('href') || '';
		if (href.indexOf('page=') !== -1 || $(this).closest('.wpps-ai-alt-wrap').length) {
			return;
		}
		e.preventDefault();
		loadTab('settings', { scrollTo: 'settings' });
	});

	$(function() {
		const $wrap = $('.wpps-ai-alt-wrap');
		if (!$wrap.length || !window.wppsAiAltText) {
			return;
		}

		const $panel = $('#wpps-ai-alt-tab-panel');
		const initialTab = $panel.attr('data-current-tab') || wppsAiAltText.initialTab || 'dashboard';
		tabCache[initialTab] = $panel.html();

		if (!history.state || !history.state.tab) {
			history.replaceState(
				{ tab: initialTab },
				'',
				hasTransientUrlParams() ? currentHistoryUrl() : tabUrl(initialTab)
			);
		}

		onTabShown(initialTab);

		if (window.location.hash === '#wpps-ai-alt-settings') {
			if (initialTab !== 'settings') {
				loadTab('settings', { scrollTo: 'settings', pushState: false });
			} else {
				window.setTimeout(scrollToSettings, 120);
			}
		}
	});

	window.addEventListener('popstate', function(event) {
		if (!$('.wpps-ai-alt-wrap').length) {
			return;
		}
		const tab = (event.state && event.state.tab) || tabFromHref(window.location.href);
		loadTab(tab, { pushState: false });
	});

	$(function() {
		if (!window.wppsAiAltText) {
			return;
		}

		// On Media Library grid browse, wait until an attachment is opened (?item=)
		// or an edit frame exists. Patching views during initial grid load breaks
		// query-attachments and leaves the spinner running forever.
		const isGridBrowse = !!document.getElementById('wp-media-grid') && !getItemIdFromUrl();

		function bootMediaModalIntegration(attempts) {
			attempts = attempts || 0;
			if (window.wp && wp.media) {
				initMediaModalIntegration();
				return;
			}
			if (attempts < 40) {
				window.setTimeout(function() {
					bootMediaModalIntegration(attempts + 1);
				}, 100);
			}
		}

		if (isGridBrowse) {
			const maybeBootFromItem = function() {
				if (getItemIdFromUrl()) {
					bootMediaModalIntegration();
					return true;
				}
				return false;
			};
			$(window).on('hashchange.wppsAiAlt popstate.wppsAiAlt', maybeBootFromItem);
			// Backbone router updates the query string without a full reload.
			const checkItem = window.setInterval(function() {
				if (maybeBootFromItem()) {
					window.clearInterval(checkItem);
				}
			}, 500);
			window.setTimeout(function() {
				window.clearInterval(checkItem);
			}, 120000);
			return;
		}

		bootMediaModalIntegration();
	});

})(jQuery);
