/**
 * Changeloger Releases Admin JavaScript
 */
( function () {
	'use strict';

	const API_BASE = '/wp-json/changeloger/v1/releases';

	// State
	let currentPage = 1;
	let currentSearch = '';
	let currentStatus = 'any';
	let currentProduct = '';
	let editingId = null;
	let editingProduct = null;
	let products = [];
	let releasesCache = {}; // Cache releases per product ID
	let releaseItemsMap = {}; // Store release items by ID for instant modal open

	// DOM Elements
	let modal,
		modalTitle,
		form,
		tableBody,
		statsCards,
		pagination,
		productTabsContainer,
		productSelect;

	// Initialize
	document.addEventListener( 'DOMContentLoaded', init );

	function init() {
		cacheElements();
		bindEvents();
		loadProducts(); // This will load stats and releases after setting first product
		updatePreviewButton();
		updateBlockActionButton();
	}

	/**
	 * Update preview button with correct URL or show Insert button if no page exists
	 */
	function updatePreviewButton() {
		const previewBtn = document.getElementById( 'cha-preview-btn' );
		const embedBtn = document.getElementById( 'cha-embed-btn' );
		if ( ! previewBtn ) return;

		// Always show "View on Frontend" button
		previewBtn.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="16" height="16">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
            </svg>
            View on Frontend
        `;
		previewBtn.style.display = 'inline-flex';

		// Show embed button when preview is shown
		if ( embedBtn ) {
			embedBtn.style.display = 'inline-flex';
		}

		if ( chaReleasesData.previewBaseUrl ) {
			// Page exists - link to it
			previewBtn.href =
				chaReleasesData.previewBaseUrl +
				'#' +
				chaReleasesData.previewHash;
			previewBtn.removeAttribute( 'data-create-page' );
		} else {
			// No page exists - will show confirmation before creating
			previewBtn.href = '#';
			previewBtn.setAttribute( 'data-create-page', 'true' );
		}
	}

	/**
	 * Update the block action button based on whether the Changeloger block exists.
	 *
	 * Shows "View Block Page" (linking to the published page) when the block is
	 * already in use, or "Add Block to Page" (opening the block editor) when not.
	 */
	function updateBlockActionButton() {
		const btn = document.getElementById( 'cha-block-action-btn' );
		if ( ! btn ) {
			return;
		}

		if ( chaReleasesData.blockPageUrl ) {
			// Block is already on a page — show "View Block Page".
			btn.href = chaReleasesData.blockPageUrl;
			btn.target = '_blank';
			btn.onclick = null;
			btn.innerHTML = `
				<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="16" height="16">
					<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
					<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
				</svg>
				View Block Page
			`;
		} else {
			// Block not added yet — show "Add with Block".
			btn.href = '#';
			btn.removeAttribute( 'target' );
			btn.title = 'You can also use the Changeloger block to publish your changelog from plain text format';
			btn.onclick = handleAddBlockToPage;
			btn.innerHTML = `
				<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="16" height="16">
					<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
				</svg>
				Add with Block
			`;
		}

		btn.style.display = 'inline-flex';
	}

	/**
	 * Handle "Add Block to Page" click.
	 *
	 * Creates a new page via the WP REST API with the cha/changeloger block
	 * pre-inserted, then redirects the user to the block editor.
	 */
	async function handleAddBlockToPage( e ) {
		e.preventDefault();

		const btn = document.getElementById( 'cha-block-action-btn' );
		if ( ! btn ) {
			return;
		}

		// Show loading state.
		const originalHtml = btn.innerHTML;
		btn.innerHTML = '<span class="cha-spinner"></span> Creating…';
		btn.style.pointerEvents = 'none';

		try {
			const nonce =
				typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce
					? wpApiSettings.nonce
					: chaReleasesData.nonce;

			const response = await fetch( chaReleasesData.homeUrl + '/wp-json/wp/v2/pages', {
				method: 'POST',
				credentials: 'same-origin',
				headers: {
					'Content-Type': 'application/json',
					'X-WP-Nonce': nonce,
				},
				body: JSON.stringify( {
					title: 'Changelog',
					status: 'draft',
				} ),
			} );

			const data = await response.json();

			if ( response.ok && data.id ) {
				// Redirect to the block editor with auto-insert param.
				window.location.href =
					chaReleasesData.adminUrl +
					'post.php?post=' +
					data.id +
					'&action=edit&cha_add_block=changeloger';
			} else {
				throw new Error( data.message || 'Failed to create page.' );
			}
		} catch ( error ) {
			console.error( 'Add block to page error:', error );
			if ( typeof showToast !== 'undefined' ) {
				showToast( error.message, 'error' );
			} else {
				alert( 'Error: ' + error.message );
			}
			btn.innerHTML = originalHtml;
			btn.style.pointerEvents = '';
		}
	}

	/**
	 * Handle preview/insert button click
	 */
	async function handlePreviewButtonClick( e ) {
		const btn = e.target.closest( '#cha-preview-btn' );
		if ( ! btn || ! btn.hasAttribute( 'data-create-page' ) ) return;

		e.preventDefault();

		// Show confirmation modal
		showCreatePageModal( btn );
	}

	/**
	 * Show custom modal for page creation confirmation
	 */
	function showCreatePageModal( btn ) {
		// Remove existing modal if any
		const existingModal = document.getElementById(
			'cha-create-page-modal'
		);
		if ( existingModal ) existingModal.remove();

		const modal = document.createElement( 'div' );
		modal.id = 'cha-create-page-modal';
		modal.className = 'cha-modal-overlay active';
		modal.innerHTML = `
            <div class="cha-modal cha-modal-sm">
                <div class="cha-modal-header">
                    <h2>Create Release Hub Page</h2>
                    <button type="button" class="cha-modal-close">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
                        </svg>
                    </button>
                </div>
                <div class="cha-modal-body">
                    <p style="margin: 0 0 16px; color: var(--cha-text-secondary); font-size: 14px; line-height: 1.6;">
                        No Release Hub page exists yet.
                    </p>
                    <p style="margin: 0; color: var(--cha-text-secondary); font-size: 14px; line-height: 1.6;">
                        This will create a new page with the <strong>Release Hub block</strong> where visitors can view your releases, feedback, and roadmap.
                    </p>
                </div>
                <div class="cha-modal-footer">
                    <button type="button" class="cha-btn cha-btn-secondary cha-modal-cancel">Cancel</button>
                    <button type="button" class="cha-btn cha-btn-primary cha-modal-confirm">Continue</button>
                </div>
            </div>
        `;

		document.body.appendChild( modal );

		// Bind events
		modal
			.querySelector( '.cha-modal-close' )
			.addEventListener( 'click', () => modal.remove() );
		modal
			.querySelector( '.cha-modal-cancel' )
			.addEventListener( 'click', () => modal.remove() );
		modal.addEventListener( 'click', ( e ) => {
			if ( e.target === modal ) modal.remove();
		} );

		modal
			.querySelector( '.cha-modal-confirm' )
			.addEventListener( 'click', async () => {
				modal.remove();
				await createReleasePage( btn );
			} );
	}

	/**
	 * Create the release hub page
	 */
	async function createReleasePage( btn ) {
		// Show loading state
		const originalHtml = btn.innerHTML;
		btn.innerHTML = '<span class="cha-spinner"></span> Creating...';
		btn.style.pointerEvents = 'none';

		try {
			const nonce =
				typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce
					? wpApiSettings.nonce
					: chaReleasesData.nonce;

			const response = await fetch( chaReleasesData.createPageUrl, {
				method: 'POST',
				credentials: 'same-origin',
				headers: {
					'Content-Type': 'application/json',
					'X-WP-Nonce': nonce,
				},
				body: JSON.stringify( {
					product_id: currentProduct,
					tab: chaReleasesData.previewHash,
				} ),
			} );

			const data = await response.json();

			if ( response.ok && data.success ) {
				// Update button to View mode
				chaReleasesData.previewBaseUrl =
					data.page_url.split( '#' )[ 0 ];
				updatePreviewButton();

				// Open the new page
				window.open( data.page_url, '_blank' );

				if ( typeof showToast !== 'undefined' ) {
					showToast( 'Page created successfully!', 'success' );
				}
			} else {
				throw new Error( data.message || 'Failed to create page' );
			}
		} catch ( error ) {
			console.error( 'Create page error:', error );
			if ( typeof showToast !== 'undefined' ) {
				showToast( error.message, 'error' );
			} else {
				alert( 'Error: ' + error.message );
			}
			btn.innerHTML = originalHtml;
		} finally {
			btn.style.pointerEvents = '';
		}
	}

	/**
	 * Show upgrade modal when product limit is reached
	 */
	function showUpgradeModal( message, upgradeUrl ) {
		// Remove existing modal if any
		const existingModal = document.getElementById( 'cha-upgrade-modal' );
		if ( existingModal ) existingModal.remove();

		const modal = document.createElement( 'div' );
		modal.id = 'cha-upgrade-modal';
		modal.className = 'cha-modal-overlay active';
		modal.innerHTML = `
            <div class="cha-modal cha-modal-variation-1">
                <button type="button" class="cha-modal-close">&times;</button>
                <div class="cha-modal-variation-body">
                    <div class="cha-modal-variation-icon">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="white" width="32" height="32">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
                        </svg>
                    </div>
                    <h2 class="cha-modal-variation-title">Upgrade to Pro</h2>
                    <p class="cha-modal-variation-desc">${ message }</p>
                    <a href="${ upgradeUrl }" class="cha-btn cha-btn-primary cha-modal-cta-main">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
                        </svg>
                        Upgrade Now
                    </a>
                </div>
            </div>
        `;

		document.body.appendChild( modal );

		// Close handlers
		modal
			.querySelector( '.cha-modal-close' )
			.addEventListener( 'click', () => modal.remove() );
		modal.addEventListener( 'click', ( e ) => {
			if ( e.target === modal ) modal.remove();
		} );
	}

	/**
	 * Handle Import button click - check premium status
	 */
	function handleImportClick() {
		if ( ! chaReleasesData.isPremium ) {
			showImportUpgradeModal();
			return;
		}
		openImportModal();
	}

	/**
	 * Show import feature preview modal for free users
	 */
	function showImportUpgradeModal() {
		// Remove existing modal if any
		const existingModal = document.getElementById(
			'cha-import-upgrade-modal'
		);
		if ( existingModal ) existingModal.remove();

		const modal = document.createElement( 'div' );
		modal.id = 'cha-import-upgrade-modal';
		modal.className = 'cha-modal-overlay active';
		modal.innerHTML = `
            <div class="cha-modal cha-upgrade-bg-modal" style="max-width: 600px;">
                <div class="cha-upgrade-modal-header">
                    <button type="button" class="cha-modal-close">&times;</button>
                    <div class="cha-upgrade-modal-icon">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="white" width="32" height="32">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"/>
                        </svg>
                    </div>
                    <h2 class="cha-upgrade-modal-title">Import Releases</h2>
                    <span class="cha-upgrade-modal-badge">PRO FEATURE</span>
                </div>

                <!-- Content -->
                <div class="cha-upgrade-modal-body">
                    <p class="cha-upgrade-modal-desc">
                        Quickly import your changelog from text files. Save time by bulk importing multiple releases at once.
                    </p>

                    <!-- Feature Preview -->
                    <div class="cha-upgrade-features">
                        <div class="cha-upgrade-features-grid">
                            <div class="cha-upgrade-feature">
                                <div class="cha-upgrade-feature-icon">
                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
                                    </svg>
                                </div>
                                <div>
                                    <h4 class="cha-upgrade-feature-title">Text Import</h4>
                                    <p class="cha-upgrade-feature-desc">Upload or paste text</p>
                                </div>
                            </div>
                            <div class="cha-upgrade-feature">
                                <div class="cha-upgrade-feature-icon">
                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
                                    </svg>
                                </div>
                                <div>
                                    <h4 class="cha-upgrade-feature-title">Auto Parsing</h4>
                                    <p class="cha-upgrade-feature-desc">Smart detection</p>
                                </div>
                            </div>
                            <div class="cha-upgrade-feature">
                                <div class="cha-upgrade-feature-icon">
                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
                                    </svg>
                                </div>
                                <div>
                                    <h4 class="cha-upgrade-feature-title">Bulk Import</h4>
                                    <p class="cha-upgrade-feature-desc">Multiple releases</p>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- CTA Button -->
                    <div class="cha-upgrade-cta-wrap">
                        <a href="${ chaReleasesData.upgradeUrl }" class="cha-btn cha-btn-primary cha-upgrade-cta">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
                            </svg>
                            Unlock Import Feature
                        </a>
                    </div>
                </div>
            </div>
        `;

		document.body.appendChild( modal );

		// Close handlers
		modal
			.querySelector( '.cha-modal-close' )
			.addEventListener( 'click', () => modal.remove() );
		modal.addEventListener( 'click', ( e ) => {
			if ( e.target === modal ) modal.remove();
		} );
	}

	/**
	 * Show Embed Upgrade Modal for Free Users
	 */
	function showEmbedUpgradeModal() {
		// Remove existing modal if any
		const existingModal = document.getElementById(
			'cha-embed-upgrade-modal'
		);
		if ( existingModal ) existingModal.remove();

		const modal = document.createElement( 'div' );
		modal.id = 'cha-embed-upgrade-modal';
		modal.className = 'cha-modal-overlay active';
		modal.innerHTML = `
            <div class="cha-modal cha-upgrade-bg-modal" style="max-width: 600px;">
                <div class="cha-upgrade-modal-header">
                    <button type="button" class="cha-modal-close">&times;</button>
                    <div class="cha-upgrade-modal-icon">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="white" width="32" height="32">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
                        </svg>
                    </div>
                    <h2 class="cha-upgrade-modal-title">Embed Changelog</h2>
                    <span class="cha-upgrade-modal-badge">PRO FEATURE</span>
                </div>

                <!-- Content -->
                <div class="cha-upgrade-modal-body">
                    <p class="cha-upgrade-modal-desc">
                        Embed your changelog, feedback board, or roadmap directly on external websites using iframe or SDK.
                    </p>

                    <!-- Feature Preview -->
                    <div class="cha-upgrade-features">
                        <div class="cha-upgrade-features-grid">
                            <div class="cha-upgrade-feature">
                                <div class="cha-upgrade-feature-icon">
                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
                                    </svg>
                                </div>
                                <div>
                                    <h4 class="cha-upgrade-feature-title">Iframe Embed</h4>
                                    <p class="cha-upgrade-feature-desc">Copy & paste code</p>
                                </div>
                            </div>
                            <div class="cha-upgrade-feature">
                                <div class="cha-upgrade-feature-icon">
                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
                                    </svg>
                                </div>
                                <div>
                                    <h4 class="cha-upgrade-feature-title">SDK Embed</h4>
                                    <p class="cha-upgrade-feature-desc">Advanced control</p>
                                </div>
                            </div>
                            <div class="cha-upgrade-feature">
                                <div class="cha-upgrade-feature-icon">
                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"/>
                                    </svg>
                                </div>
                                <div>
                                    <h4 class="cha-upgrade-feature-title">In-app widget</h4>
                                    <p class="cha-upgrade-feature-desc">Embed widget in your app</p>
                                </div>
                            </div>
                        </div>
                    </div>

                    <!-- CTA Button -->
                    <div class="cha-upgrade-cta-wrap">
                        <a href="${ chaReleasesData.upgradeUrl }" class="cha-btn cha-btn-primary cha-upgrade-cta">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
                            </svg>
                            Unlock Embed Feature
                        </a>
                    </div>
                </div>
            </div>
        `;

		document.body.appendChild( modal );

		// Close handlers
		modal
			.querySelector( '.cha-modal-close' )
			.addEventListener( 'click', () => modal.remove() );
		modal.addEventListener( 'click', ( e ) => {
			if ( e.target === modal ) modal.remove();
		} );
	}

	function cacheElements() {
		modal = document.getElementById( 'cha-release-modal' );
		modalTitle = document.getElementById( 'cha-modal-title' );
		form = document.getElementById( 'cha-release-form' );
		tableBody = document.getElementById( 'cha-releases-tbody' );
		statsCards = document.querySelector( '.cha-stats-grid' );
		pagination = document.querySelector( '.cha-pagination' );
		productTabsContainer = document.getElementById( 'cha-product-tabs' );
		productSelect = document.getElementById( 'cha-product' );
	}

	/**
	 * Show product limit modal with options for Free users
	 */
	function showProductLimitModal( upgradeUrl ) {
		// Remove existing modal if any
		const existingModal = document.getElementById( 'cha-product-limit-modal' );
		if ( existingModal ) existingModal.remove();

		const modal = document.createElement( 'div' );
		modal.id = 'cha-product-limit-modal';
		modal.className = 'cha-modal-overlay active';
		modal.innerHTML = `
			<div class="cha-modal cha-modal-variation-2">
				<button type="button" class="cha-modal-close">&times;</button>
				<div class="cha-modal-variation-body">
					<div class="cha-modal-variation-icon">
						<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="white" width="32" height="32">
							<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
						</svg>
					</div>
					<h2 class="cha-modal-variation-title">Product Limit Reached</h2>
					<p class="cha-modal-variation-desc">The free version is limited to 1 product. Choose how you want to proceed:</p>
					
					<div class="cha-modal-options-grid">
						<!-- Option 1: Pro Upgrade -->
						<div class="cha-modal-option-card primary">
							<div class="cha-modal-option-badge">Recommended</div>
							<div class="cha-modal-option-icon">
								<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="24" height="24">
									<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
								</svg>
							</div>
							<h3 class="cha-modal-option-title">Upgrade to Pro</h3>
							<p class="cha-modal-option-desc">Unlock unlimited products, custom domains, text import, and advanced tracking.</p>
							<a href="${ upgradeUrl }" class="cha-btn cha-btn-primary cha-modal-cta-full">Upgrade Now</a>
						</div>

						<!-- Option 2: Free Block -->
						<div class="cha-modal-option-card secondary">
							<div class="cha-modal-option-icon">
								<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="24" height="24">
									<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" />
								</svg>
							</div>
							<h3 class="cha-modal-option-title">Use Free Block</h3>
							<p class="cha-modal-option-desc">Create a dedicated page using the Changeloger block for unlimited changelogs.</p>
							<a href="#" class="cha-btn cha-btn-secondary cha-modal-cta-full cha-create-block-page-link">Create Page</a>
						</div>
					</div>
				</div>
			</div>
		`;

		document.body.appendChild( modal );

		// Close handlers
		modal
			.querySelector( '.cha-modal-close' )
			.addEventListener( 'click', () => modal.remove() );
		modal.addEventListener( 'click', ( e ) => {
			if ( e.target === modal ) modal.remove();
		} );

		// Create Block Page logic
		const createLink = modal.querySelector( '.cha-create-block-page-link' );
		if ( createLink ) {
			createLink.addEventListener( 'click', function( e ) {
				e.preventDefault();
				if ( createLink.dataset.loading ) return;
				createLink.dataset.loading = 'true';
				createLink.textContent = 'Creating…';

				fetch( chaReleasesData.homeUrl + '/wp-json/wp/v2/pages', {
					method: 'POST',
					credentials: 'same-origin',
					headers: {
						'Content-Type': 'application/json',
						'X-WP-Nonce': chaReleasesData.nonce,
					},
					body: JSON.stringify( {
						title: 'Changelog',
						status: 'draft',
					} ),
				} )
				.then( function( r ) { return r.json(); } )
				.then( function( data ) {
					if ( data && data.id ) {
						window.open(
							chaReleasesData.adminUrl + 'post.php?post=' + data.id + '&action=edit&cha_add_block=changeloger',
							'_blank'
						);
						createLink.textContent = 'Page Created ✓';
					} else {
						throw new Error( data.message || 'Failed to create page.' );
					}
				} )
				.catch( function( err ) {
					alert( 'Error: ' + err.message );
					createLink.textContent = 'Create Page';
					delete createLink.dataset.loading;
				} );
			} );
		}
	}

	/**
	 * Handle Add Product button click - check limit for free users
	 */
	function handleAddProductClick() {
		// Check if free user and already has 1 product
		if ( ! chaReleasesData.isPremium ) {
			const currentProductCount =
				document.querySelectorAll( '.cha-product-tab' ).length;

			if ( currentProductCount >= 1 ) {
				// Show new redesigned product limit modal
				showProductLimitModal( chaReleasesData.upgradeUrl );
				return;
			}
		}

		// Premium or under limit - open product modal
		openProductModal();
	}

	function bindEvents() {
		// Add new button
		document
			.getElementById( 'cha-add-new-btn' )
			?.addEventListener( 'click', openAddModal );

		// Preview/Insert button click
		document
			.getElementById( 'cha-preview-btn' )
			?.addEventListener( 'click', handlePreviewButtonClick );

		// Embed button click
		document
			.getElementById( 'cha-embed-btn' )
			?.addEventListener( 'click', openEmbedModal );

		// Modal close - overlay click
		document
			.querySelector( '.cha-modal-overlay' )
			?.addEventListener( 'click', ( e ) => {
				if ( e.target.classList.contains( 'cha-modal-overlay' ) )
					closeModal();
			} );

		// Modal close - close buttons
		document.querySelectorAll( '.cha-modal-close' ).forEach( ( el ) => {
			el.addEventListener( 'click', closeModal );
		} );

		// Form submit
		form?.addEventListener( 'submit', handleFormSubmit );

		// Search
		const searchInput = document.getElementById( 'cha-search-input' );
		let searchTimeout;
		searchInput?.addEventListener( 'input', ( e ) => {
			clearTimeout( searchTimeout );
			searchTimeout = setTimeout( () => {
				currentSearch = e.target.value;
				currentPage = 1;
				loadReleases();
			}, 300 );
		} );

		// Status filter
		document
			.getElementById( 'cha-status-filter' )
			?.addEventListener( 'change', ( e ) => {
				currentStatus = e.target.value;
				currentPage = 1;
				loadReleases();
			} );

		// Add change item buttons
		document.querySelectorAll( '.cha-add-item-btn' ).forEach( ( btn ) => {
			btn.addEventListener( 'click', () =>
				addChangeItem( btn.dataset.group )
			);
		} );

		// Escape key
		document.addEventListener( 'keydown', ( e ) => {
			if ( e.key === 'Escape' ) {
				closeModal();
				closeImportModal();
			}
		} );

		// Add product button (sidebar) - check limit for free users
		document
			.getElementById( 'cha-add-product-btn' )
			?.addEventListener( 'click', handleAddProductClick );

		// Product form submit
		document
			.getElementById( 'cha-product-form' )
			?.addEventListener( 'submit', handleProductFormSubmit );

		// Product modal close
		document
			.querySelectorAll( '#cha-product-modal .cha-modal-close' )
			.forEach( ( el ) => {
				el.addEventListener( 'click', closeProductModal );
			} );
		document
			.getElementById( 'cha-product-modal' )
			?.addEventListener( 'click', ( e ) => {
				if ( e.target.classList.contains( 'cha-modal-overlay' ) )
					closeProductModal();
			} );

		// Import button and modal - check premium status
		document
			.getElementById( 'cha-import-btn' )
			?.addEventListener( 'click', handleImportClick );
		document
			.getElementById( 'cha-import-form' )
			?.addEventListener( 'submit', handleImportFormSubmit );
		document
			.querySelectorAll( '#cha-import-modal .cha-modal-close' )
			.forEach( ( el ) => {
				el.addEventListener( 'click', closeImportModal );
			} );
		document
			.getElementById( 'cha-import-modal' )
			?.addEventListener( 'click', ( e ) => {
				if ( e.target.classList.contains( 'cha-modal-overlay' ) )
					closeImportModal();
			} );

		// File input change handler
		const importFileInput = document.getElementById( 'cha-import-file' );
		if ( importFileInput ) {
			importFileInput.addEventListener( 'change', handleFileSelect );

			// Drag and drop
			const fileUpload = importFileInput.closest( '.cha-file-upload' );
			if ( fileUpload ) {
				fileUpload.addEventListener( 'dragover', ( e ) => {
					e.preventDefault();
					fileUpload.classList.add( 'dragover' );
				} );
				fileUpload.addEventListener( 'dragleave', () => {
					fileUpload.classList.remove( 'dragover' );
				} );
				fileUpload.addEventListener( 'drop', ( e ) => {
					e.preventDefault();
					fileUpload.classList.remove( 'dragover' );
					if ( e.dataTransfer.files.length ) {
						importFileInput.files = e.dataTransfer.files;
						handleFileSelect( { target: importFileInput } );
					}
				} );
			}
		}

		// Select all checkbox
		document
			.getElementById( 'cha-select-all' )
			?.addEventListener( 'change', handleSelectAll );

		// Bulk action buttons
		document.querySelectorAll( '.cha-bulk-btn' ).forEach( ( btn ) => {
			btn.addEventListener( 'click', () =>
				handleBulkAction( btn.dataset.action )
			);
		} );

		// Bulk badge change select
		document
			.getElementById( 'cha-bulk-badge-select' )
			?.addEventListener( 'change', handleBulkBadgeChange );

		// AI Generate button
		document
			.getElementById( 'cha-ai-generate-btn' )
			?.addEventListener( 'click', handleAIGenerateClick );

		// Antimanual modal close
		document
			.querySelectorAll( '#cha-antimanual-modal .cha-modal-close' )
			.forEach( ( el ) => {
				el.addEventListener( 'click', closeAntimanualModal );
			} );
		document
			.getElementById( 'cha-antimanual-modal' )
			?.addEventListener( 'click', ( e ) => {
				if ( e.target.classList.contains( 'cha-modal-overlay' ) )
					closeAntimanualModal();
			} );

		// Featured image upload
		document
			.getElementById( 'cha-featured-image-upload-btn' )
			?.addEventListener( 'click', openFeaturedImageUploader );

		// Featured image remove
		document
			.getElementById( 'cha-featured-image-remove-btn' )
			?.addEventListener( 'click', resetFeaturedImage );
	}

	// ========== AI Parser Upgrade Modal ==========
	/**
	 * Show a compelling upgrade modal for the AI Parser feature.
	 * Includes a demo GIF placeholder, feature highlights, and how-it-works steps.
	 */
	function showAIParserUpgradeModal() {
		// Remove existing modal if any
		const existingModal = document.getElementById( 'cha-ai-parser-upgrade-modal' );
		if ( existingModal ) existingModal.remove();

		const modal = document.createElement( 'div' );
		modal.id = 'cha-ai-parser-upgrade-modal';
		modal.className = 'cha-modal-overlay active';
		modal.innerHTML = `
			<div class="cha-modal cha-upgrade-bg-modal" style="max-width: 600px;">
				<div class="cha-upgrade-modal-header">
					<button type="button" class="cha-modal-close">&times;</button>
					<div class="cha-upgrade-modal-icon">
						<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="32" height="32" viewBox="0 0 488.472 488.472" xml:space="preserve">
							<path d="M331.351 96.061c-5.963-5.963-15.622-5.963-21.585 0L4.472 401.355c-5.963 5.963-5.963 15.622 0 21.585l61.059 61.059c2.981 2.981 6.887 4.472 10.793 4.472s7.811-1.491 10.793-4.472L392.41 178.705c5.963-5.963 5.963-15.622 0-21.585l-61.059-61.059zM76.323 451.622 36.85 412.148l207.385-207.385 39.474 39.474L76.323 451.622zm228.971-228.971-39.474-39.474 54.738-54.738 39.474 39.474zM213.707 122.118c15.265-30.529 30.529-45.794 61.059-61.059C244.237 45.794 228.972 30.53 213.707 0c-15.265 30.529-30.531 45.794-61.059 61.059 30.53 15.265 45.794 30.53 61.059 61.059zM457.942 213.707c-7.632 15.265-15.267 22.897-30.529 30.529 15.265 7.632 22.897 15.265 30.529 30.529 7.632-15.265 15.265-22.897 30.529-30.529-15.264-7.632-22.896-15.265-30.529-30.529zM457.942 45.795C435.045 34.346 423.596 22.898 412.148.001c-11.449 22.897-22.899 34.346-45.794 45.794 22.897 11.449 34.346 22.897 45.794 45.794 11.449-22.897 22.897-34.346 45.794-45.794z" fill="white" opacity="1"></path>
						</svg>
					</div>
					<h2 class="cha-upgrade-modal-title">AI-Powered Changelog Parser</h2>
					<span class="cha-upgrade-modal-badge">PRO FEATURE</span>
				</div>

				<!-- Content -->
				<div class="cha-upgrade-modal-body">
					<p class="cha-upgrade-modal-desc">
						Paste any changelog text &mdash; AI creates structured releases with enhanced titles, descriptions, and categorized change items instantly.
					</p>
					<!-- Demo GIF Placeholder -->
					<div class="cha-ai-upgrade-demo">
						<div class="cha-ai-upgrade-demo-placeholder">
							<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="48" height="48">
								<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
								<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
							</svg>
							<p>See AI Parser in action</p>
						</div>
					</div>

					<!-- How It Works -->
					<div class="cha-ai-how-it-works">
						<h4>How It Works</h4>
						<div class="cha-ai-steps">
							<div class="cha-ai-step">
								<span class="cha-ai-step-num">1</span>
								<h5>Paste Text</h5>
								<p>Paste your raw changelog or upload a .txt file</p>
							</div>
							<div class="cha-ai-step">
								<span class="cha-ai-step-num">2</span>
								<h5>AI Enhances</h5>
								<p>Auto-detects versions, categorizes changes, rewrites titles</p>
							</div>
							<div class="cha-ai-step">
								<span class="cha-ai-step-num">3</span>
								<h5>Publish</h5>
								<p>Review the result and publish your polished release</p>
							</div>
						</div>
					</div>

					<!-- CTA -->
					<div class="cha-upgrade-cta-wrap">
						<a href="${ chaReleasesData.upgradeUrl }" class="cha-btn cha-btn-primary cha-upgrade-cta">
							<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
								<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
							</svg>
							Unlock AI Parser
						</a>
					</div>
				</div>
			</div>
		`;

		document.body.appendChild( modal );

		// Close handlers
		modal
			.querySelector( '.cha-modal-close' )
			.addEventListener( 'click', () => modal.remove() );
		modal.addEventListener( 'click', ( e ) => {
			if ( e.target === modal ) modal.remove();
		} );
	}

	// ========== AI Generate Button (Header - Bulk/General) ==========
	// This handles the main AI Generate button in the table header for bulk/new generation
	function handleAIGenerateClick() {
		// Step 1: Check if premium
		if ( ! chaReleasesData.isPremium ) {
			showAIParserUpgradeModal();
			return;
		}

		// Step 2: Check if Antimanual is active
		if ( chaReleasesData.antimanualActive ) {
			// Open AI Import modal
			openAIImportModal();
			return;
		}

		// Step 3: Show Antimanual promo modal
		openAntimanualModal();
	}

	function openAntimanualModal() {
		const modal = document.getElementById( 'cha-antimanual-modal' );
		const actionBtn = document.getElementById(
			'cha-antimanual-action-btn'
		);
		const actionText = document.getElementById(
			'cha-antimanual-action-text'
		);

		if ( ! modal ) return;

		// Set the correct action based on Antimanual state
		if ( ! chaReleasesData.antimanualInstalled ) {
			// Not installed - show install button
			actionBtn.href = chaReleasesData.antimanualInstallUrl;
			actionText.textContent = 'Install Antimanual Free';
		} else {
			// Installed but not active - show activate button
			actionBtn.href = chaReleasesData.antimanualActivateUrl;
			actionText.textContent = 'Activate Antimanual';
		}

		modal.classList.add( 'active' );
		document.body.style.overflow = 'hidden';
	}

	function closeAntimanualModal() {
		const modal = document.getElementById( 'cha-antimanual-modal' );
		if ( modal ) {
			modal.classList.remove( 'active' );
			document.body.style.overflow = '';
		}
	}

	// ========== AI Import Modal ==========
	let aiImportFileContent = null;

	function openAIImportModal() {
		const modal = document.getElementById( 'cha-ai-import-modal' );
		if ( ! modal ) return;

		// Reset form
		const textarea = document.getElementById( 'cha-ai-import-textarea' );
		const fileInput = document.getElementById( 'cha-ai-import-file' );
		const dropzone = document.getElementById( 'cha-ai-import-dropzone' );
		const generateBtn = document.getElementById( 'cha-ai-import-generate' );
		const textGroup = document.getElementById( 'cha-ai-input-text-group' );
		const fileGroup = document.getElementById( 'cha-ai-input-file-group' );
		const inputMethodRadios = modal.querySelectorAll(
			'input[name="cha-ai-input-method"]'
		);

		if ( textarea ) textarea.value = '';
		if ( fileInput ) fileInput.value = '';
		aiImportFileContent = null;

		// Reset dropzone state
		if ( dropzone ) {
			dropzone.querySelector( '.cha-dropzone-content' ).style.display =
				'flex';
			dropzone.querySelector( '.cha-dropzone-file' ).style.display =
				'none';
		}

		// Reset input method to text (default)
		inputMethodRadios.forEach( ( radio ) => {
			radio.checked = radio.value === 'text';
		} );
		if ( textGroup ) textGroup.style.display = '';
		if ( fileGroup ) fileGroup.style.display = 'none';

		// Setup input method toggle
		setupInputMethodToggle();

		// Populate product dropdown
		populateAIImportProductDropdown();

		// Setup event listeners
		if ( generateBtn ) {
			generateBtn.onclick = handleAIImport;
			generateBtn.disabled = false;
			generateBtn.classList.remove( 'cha-btn-loading' );
		}

		// Setup file dropzone
		setupAIImportDropzone();

		// Setup close buttons
		modal.querySelectorAll( '.cha-modal-close' ).forEach( ( btn ) => {
			btn.onclick = closeAIImportModal;
		} );
		modal.addEventListener( 'click', ( e ) => {
			if ( e.target.classList.contains( 'cha-modal-overlay' ) )
				closeAIImportModal();
		} );

		modal.classList.add( 'active' );
		document.body.style.overflow = 'hidden';
	}

	function setupInputMethodToggle() {
		const inputMethodRadios = document.querySelectorAll(
			'input[name="cha-ai-input-method"]'
		);
		const textGroup = document.getElementById( 'cha-ai-input-text-group' );
		const fileGroup = document.getElementById( 'cha-ai-input-file-group' );

		inputMethodRadios.forEach( ( radio ) => {
			radio.addEventListener( 'change', ( e ) => {
				if ( e.target.value === 'text' ) {
					if ( textGroup ) textGroup.style.display = '';
					if ( fileGroup ) fileGroup.style.display = 'none';
				} else {
					if ( textGroup ) textGroup.style.display = 'none';
					if ( fileGroup ) fileGroup.style.display = '';
				}
			} );
		} );
	}

	function closeAIImportModal() {
		const modal = document.getElementById( 'cha-ai-import-modal' );
		if ( modal ) {
			modal.classList.remove( 'active' );
			document.body.style.overflow = '';
		}
	}

	function populateAIImportProductDropdown() {
		const select = document.getElementById( 'cha-ai-import-product' );
		if ( ! select || ! products.length ) return;

		select.innerHTML = '<option value="">Select a product...</option>';
		products.forEach( ( p ) => {
			const option = document.createElement( 'option' );
			option.value = p.id;
			option.textContent = p.name;
			select.appendChild( option );
		} );

		// Auto-select: use current product or default to first product
		if ( currentProduct ) {
			select.value = currentProduct;
		} else if ( products.length > 0 ) {
			select.value = products[ 0 ].id;
		}
	}

	function setupAIImportDropzone() {
		const dropzone = document.getElementById( 'cha-ai-import-dropzone' );
		const fileInput = document.getElementById( 'cha-ai-import-file' );
		const contentDiv = dropzone?.querySelector( '.cha-dropzone-content' );
		const fileDiv = dropzone?.querySelector( '.cha-dropzone-file' );
		const filenameSpan = dropzone?.querySelector(
			'.cha-dropzone-filename'
		);
		const removeBtn = dropzone?.querySelector( '.cha-dropzone-remove' );

		if ( ! dropzone || ! fileInput ) return;

		// Click to open file dialog
		contentDiv?.addEventListener( 'click', () => fileInput.click() );

		// Drag and drop
		dropzone.addEventListener( 'dragover', ( e ) => {
			e.preventDefault();
			dropzone.classList.add( 'dragover' );
		} );

		dropzone.addEventListener( 'dragleave', () => {
			dropzone.classList.remove( 'dragover' );
		} );

		dropzone.addEventListener( 'drop', ( e ) => {
			e.preventDefault();
			dropzone.classList.remove( 'dragover' );
			const file = e.dataTransfer.files[ 0 ];
			if ( file ) handleFileSelect( file );
		} );

		// File input change
		fileInput.addEventListener( 'change', ( e ) => {
			const file = e.target.files[ 0 ];
			if ( file ) handleFileSelect( file );
		} );

		// Remove file
		removeBtn?.addEventListener( 'click', ( e ) => {
			e.stopPropagation();
			aiImportFileContent = null;
			fileInput.value = '';
			contentDiv.style.display = 'flex';
			fileDiv.style.display = 'none';
		} );

		function handleFileSelect( file ) {
			const isText = file.name.match( /\.(txt|md|markdown)$/i );

			if ( ! isText ) {
				showToast( 'Please select a text file (.txt, .md)', 'error' );
				return;
			}

			const reader = new FileReader();
			reader.onload = ( e ) => {
				aiImportFileContent = e.target.result;
				filenameSpan.textContent = file.name;
				contentDiv.style.display = 'none';
				fileDiv.style.display = 'flex';
			};

			reader.readAsText( file );
		}
	}

	async function handleAIImport() {
		const textarea = document.getElementById( 'cha-ai-import-textarea' );
		const productSelect = document.getElementById(
			'cha-ai-import-product'
		);
		const statusSelect = document.getElementById( 'cha-ai-import-status' );
		const generateBtn = document.getElementById( 'cha-ai-import-generate' );

		// Check which input method is selected
		const selectedMethod =
			document.querySelector(
				'input[name="cha-ai-input-method"]:checked'
			)?.value || 'text';

		// Get content based on selected method
		const textPrompt = textarea?.value?.trim() || '';
		const hasTextFile =
			aiImportFileContent && aiImportFileContent.length > 0;

		// Validate based on selected method
		if ( selectedMethod === 'text' && ! textPrompt ) {
			showToast( 'Please enter changelog text', 'error' );
			return;
		}
		if ( selectedMethod === 'file' && ! hasTextFile ) {
			showToast( 'Please upload a file', 'error' );
			return;
		}

		const productId = productSelect?.value;
		if ( ! productId ) {
			showToast( 'Please select a product', 'error' );
			return;
		}

		const postStatus = statusSelect?.value || 'draft';

		// Show loading state
		if ( generateBtn ) {
			generateBtn.disabled = true;
			generateBtn.classList.add( 'cha-btn-loading' );
		}

		try {
			// Build request payload based on selected method
			const payload = {
				changelog_text:
					selectedMethod === 'file'
						? aiImportFileContent
						: textPrompt,
			};
			// Step 1: Call AI to format the changelog
			const aiResponse = await fetch( chaReleasesData.aiParseUrl, {
				method: 'POST',
				credentials: 'same-origin',
				headers: {
					'Content-Type': 'application/json',
					'X-WP-Nonce': chaReleasesData.nonce,
				},
				body: JSON.stringify( payload ),
			} );

			const aiResult = await aiResponse.json();

			if ( ! aiResponse.ok || ! aiResult.success ) {
				throw new Error( aiResult.message || 'AI formatting failed' );
			}

			const formattedChangelog = aiResult.changelog_text;

			// Step 2: Use existing import endpoint with AI-formatted text
			const importResponse = await fetch( chaReleasesData.importUrl, {
				method: 'POST',
				credentials: 'same-origin',
				headers: {
					'Content-Type': 'application/json',
					'X-WP-Nonce': chaReleasesData.nonce,
				},
				body: JSON.stringify( {
					changelog_text: formattedChangelog,
					product_id: parseInt( productId ),
					publish_posts: postStatus === 'publish',
					duplicate_action: 'create',
				} ),
			} );

			const importResult = await importResponse.json();
			if ( ! importResponse.ok || ! importResult.success ) {
				throw new Error( importResult.message || 'Import failed' );
			}

			const statusLabel =
				postStatus === 'publish' ? 'published' : 'draft';
			showToast( `Release created as ${ statusLabel }!`, 'success' );
			closeAIImportModal();
			invalidateReleasesCache();
			loadReleases( true );
		} catch ( error ) {
			console.error( 'AI Import error:', error );
			showToast( error.message || 'AI import failed', 'error' );
		} finally {
			if ( generateBtn ) {
				generateBtn.disabled = false;
				generateBtn.classList.remove( 'cha-btn-loading' );
			}
		}
	}

	// ========== Products ==========
	async function loadProducts() {
		try {
			// Use wpApiSettings nonce if available, otherwise use our nonce
			const nonce =
				typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce
					? wpApiSettings.nonce
					: chaReleasesData.nonce;

			const response = await fetch( chaReleasesData.productsUrl, {
				credentials: 'same-origin',
				headers: {
					'X-WP-Nonce': nonce,
				},
			} );
			const data = await response.json();

			// Ensure products is always an array
			products = Array.isArray( data ) ? data : [];

			// Auto-select first product if none selected
			if ( products.length > 0 && ! currentProduct ) {
				currentProduct = products[ 0 ].id;
			}

			renderProductTabs();
			populateProductDropdown();

			// Now load stats and releases with the selected product
			if ( products.length > 0 ) {
				loadStats();
				loadReleases();
				// Show preview button
				const previewBtn = document.getElementById( 'cha-preview-btn' );
				if ( previewBtn ) previewBtn.style.display = 'inline-flex';
			} else {
				// Hide preview button when no products
				const previewBtn = document.getElementById( 'cha-preview-btn' );
				if ( previewBtn ) previewBtn.style.display = 'none';
				renderEmptyState();
			}
		} catch ( error ) {
			console.error( 'Failed to load products:', error );
			products = [];
		}
	}

	function renderProductTabs() {
		if ( ! productTabsContainer ) return;

		productTabsContainer.innerHTML = '';

		// Limit to first product only for free users
		const displayProducts = chaReleasesData.isPremium
			? products
			: products.slice( 0, 1 );

		// Add product tabs only (no All Products tab)
		displayProducts.forEach( ( product ) => {
			const tab = document.createElement( 'button' );
			tab.type = 'button';
			tab.className =
				'cha-product-tab' +
				( currentProduct == product.id ? ' active' : '' );
			tab.dataset.productId = product.id;
			tab.innerHTML = `
                <div class="cha-product-tab-content">
                    <span class="tab-name">${ escapeHtml(
						product.name
					) }</span>
                    <div class="cha-product-tab-actions">
                        <button type="button" class="cha-product-action-btn edit" title="Rename Product" data-id="${
							product.id
						}">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="12" height="12">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
                            </svg>
                        </button>
                        <button type="button" class="cha-product-action-btn delete" title="Delete Product" data-id="${
							product.id
						}">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="12" height="12">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
                            </svg>
                        </button>
                    </div>
                    <span class="tab-count">${ product.count }</span>
                </div>
            `;

			// Handle main click - select product
			tab.addEventListener( 'click', ( e ) => {
				// Ignore clicks on action buttons
				if ( e.target.closest( '.cha-product-action-btn' ) ) return;
				selectProduct( product.id );
			} );

			// Handle edit click
			tab.querySelector( '.edit' ).addEventListener( 'click', ( e ) => {
				e.stopPropagation();
				openProductModal( product );
			} );

			// Handle delete click
			tab.querySelector( '.delete' ).addEventListener( 'click', ( e ) => {
				e.stopPropagation();
				deleteProduct( product.id );
			} );

			productTabsContainer.appendChild( tab );
		} );
	}

	/**
	 * Update product tab counts without re-rendering tabs
	 * This is called after add/update/delete operations to refresh counts
	 */
	function updateProductTabCounts() {
		if ( ! productTabsContainer ) return;

		products.forEach( ( product ) => {
			const tab = productTabsContainer.querySelector(
				`.cha-product-tab[data-product-id="${ product.id }"]`
			);
			if ( tab ) {
				const countEl = tab.querySelector( '.tab-count' );
				if ( countEl ) {
					countEl.textContent = product.count;
				}
			}
		} );
	}

	function renderEmptyState() {
		if ( ! productTabsContainer ) return;

		// Clear products list sidebar
		productTabsContainer.innerHTML = 'No Products Found.';

		// Ensure stats/filters/table are visible (or reset display if previously hidden)
		const statsRow = document.querySelector( '.cha-stats-row' );
		const filtersRow = document.querySelector( '.cha-actions-bar' );
		const releasesTable = document.querySelector( '.cha-releases-table' );

		if ( statsRow ) statsRow.style.display = '';
		if ( filtersRow ) filtersRow.style.display = '';
		if ( releasesTable ) releasesTable.style.display = '';

		// Inject empty state into table body
		const tableBody = releasesTable
			? releasesTable.querySelector( 'tbody' )
			: null;
		if ( tableBody ) {
			tableBody.innerHTML = `
                <tr>
                    <td colspan="100%" style="padding: 0; border: none;">
                        <div class="cha-empty-state" style="margin: 40px auto; max-width: 500px; box-shadow: none; background: transparent;">
                            <div class="cha-empty-state-icon">
                                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="48" height="48">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
                                </svg>
                            </div>
                            <h3>No releases found</h3>
                            <p>Create your first product to start planning or import demo data.</p>
                            <div class="cha-empty-state-actions">
                                <button type="button" class="cha-btn" id="cha-add-first-product">
                                    Create Product
                                </button>
                                <button type="button" class="cha-btn cha-btn-secondary" id="cha-import-demo-btn">
                                     Import Demo Data
                                </button>
                            </div>
                        </div>
                    </td>
                </tr>
             `;

			// Bind buttons
			const btn = document.getElementById( 'cha-import-demo-btn' );
			if ( btn ) btn.addEventListener( 'click', handleImportDemo );

			const addBtn = document.getElementById( 'cha-add-first-product' );
			if ( addBtn ) {
				addBtn.addEventListener( 'click', () => {
					openProductModal();
				} );
			}
		}
	}

	async function handleImportDemo() {
		const btn = document.getElementById( 'cha-import-demo-btn' );
		if ( btn ) {
			btn.disabled = true;
			btn.textContent = 'Importing...';
			btn.classList.add( 'cha-btn-loading' );
		}

		try {
			const nonce =
				typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce
					? wpApiSettings.nonce
					: chaReleasesData.nonce;

			const response = await fetch( chaReleasesData.importSampleUrl, {
				method: 'POST',
				headers: {
					'X-WP-Nonce': nonce,
					'Content-Type': 'application/json',
				},
			} );
			const data = await response.json();

			if ( response.ok ) {
				if ( typeof showToast !== 'undefined' ) {
					showToast( 'Demo data imported successfully!', 'success' );
				} else {
					alert( 'Demo data imported successfully!' );
				}
				// Reload page to refresh all data & views
				setTimeout( () => window.location.reload(), 1500 );
			} else {
				throw new Error( data.message || 'Import failed' );
			}
		} catch ( error ) {
			console.error( 'Import error:', error );
			if ( typeof showToast !== 'undefined' ) {
				showToast( error.message, 'error' );
			} else {
				alert( 'Import error: ' + error.message );
			}
			if ( btn ) {
				btn.disabled = false;
				btn.textContent = 'Import Demo Data';
				btn.classList.remove( 'cha-btn-loading' );
			}
		}
	}
	function populateProductDropdown() {
		if ( ! productSelect ) return;

		const currentValue = productSelect.value;
		productSelect.innerHTML =
			'<option value="" disabled>Select Product</option>';

		products.forEach( ( product ) => {
			const option = document.createElement( 'option' );
			option.value = product.id;
			option.textContent = product.name;
			productSelect.appendChild( option );
		} );

		productSelect.value = currentValue;
	}

	function selectProduct( productId ) {
		currentProduct = productId;
		currentPage = 1;

		// Update active tab
		document.querySelectorAll( '.cha-product-tab' ).forEach( ( tab ) => {
			tab.classList.toggle(
				'active',
				tab.dataset.productId == productId
			);
		} );

		loadReleases();
		loadStats();
		fetchPreviewUrl( productId );
	}

	/**
	 * Fetch preview URL for a specific product
	 */
	async function fetchPreviewUrl( productId ) {
		const previewBtn = document.getElementById( 'cha-preview-btn' );
		if ( ! previewBtn || ! chaReleasesData.previewUrlApi ) return;

		try {
			const nonce =
				typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce
					? wpApiSettings.nonce
					: chaReleasesData.nonce;

			const response = await fetch(
				chaReleasesData.previewUrlApi + '?product_id=' + productId,
				{
					credentials: 'same-origin',
					headers: { 'X-WP-Nonce': nonce },
				}
			);
			const data = await response.json();

			// Update the previewBaseUrl and refresh button state
			chaReleasesData.previewBaseUrl = data.url || '';
			updatePreviewButton();
		} catch ( error ) {
			console.error( 'Failed to fetch preview URL:', error );
		}
	}

	// ========== Product Modal ==========
	// Delegate open/close to global admin scripts
	function openProductModal( product = null ) {
		chaAdmin.openProductModal( product, ( p ) => {
			editingProduct = p;
		} );
	}

	function closeProductModal() {
		chaAdmin.closeProductModal();
	}

	async function handleProductFormSubmit( e ) {
		e.preventDefault();
		const formData = new FormData( e.target );
		const name = formData.get( 'name' )?.trim();

		if ( ! name ) return;

		const submitBtn = e.target.querySelector( 'button[type="submit"]' );
		const originalText = submitBtn.innerHTML;
		submitBtn.innerHTML = '<span class="cha-spinner"></span> Saving...';
		submitBtn.disabled = true;

		try {
			const url = editingProduct
				? `${ chaReleasesData.productsUrl }/${ editingProduct.id }`
				: chaReleasesData.productsUrl;

			const response = await fetch( url, {
				method: 'POST',
				credentials: 'same-origin',
				headers: {
					'Content-Type': 'application/json',
					'X-WP-Nonce': chaReleasesData.nonce,
				},
				body: JSON.stringify( { name } ),
			} );

			if ( ! response.ok ) {
				const error = await response.json();

				// Check if it's a product limit error
				if ( error.code === 'product_limit_reached' ) {
					closeProductModal();
					showUpgradeModal(
						error.message,
						error.data?.upgrade_url || chaReleasesData.upgradeUrl
					);
					return;
				}

				throw new Error( error.message || 'Failed to save product' );
			}

			// showToast(editingProduct ? 'Product updated successfully' : 'Product created successfully', 'success');
			// Assuming showToast is globally available or defined elsewhere, keeping internal consistency if it is not
			if ( typeof showToast !== 'undefined' ) {
				showToast(
					editingProduct
						? 'Product updated successfully'
						: 'Product created successfully',
					'success'
				);
			} else {
				// alert(editingProduct ? 'Product updated successfully' : 'Product created successfully');
			}

			closeProductModal();
			loadProducts();
			editingProduct = null;
		} catch ( error ) {
			if ( typeof showToast !== 'undefined' ) {
				showToast( error.message, 'error' );
			} else {
				alert( error.message );
			}
		} finally {
			submitBtn.innerHTML = originalText;
			submitBtn.disabled = false;
		}
	}

	function deleteProduct( id ) {
		chaAdmin.deleteProduct( id, {
			productsUrl: chaReleasesData.productsUrl,
			nonce: chaReleasesData.nonce,
			onSuccess: function() {
				if ( currentProduct == id ) {
					currentProduct = '';
				}
				loadProducts();
				if ( typeof showToast !== 'undefined' ) {
					showToast( 'Product deleted successfully', 'success' );
				}
			},
			onError: function( msg ) {
				if ( typeof showToast !== 'undefined' ) {
					showToast( msg, 'error' );
				} else {
					alert( msg );
				}
			}
		} );
	}

	// ========== Select All & Bulk Actions ==========
	function handleSelectAll( e ) {
		const isChecked = e.target.checked;
		const checkboxes = tableBody.querySelectorAll( '.cha-checkbox' );
		checkboxes.forEach( ( cb ) => ( cb.checked = isChecked ) );
		updateBulkActionsBar();
	}

	function updateBulkActionsBar() {
		const checkboxes = tableBody.querySelectorAll(
			'.cha-checkbox:checked'
		);
		const bulkBar = document.getElementById( 'cha-bulk-actions' );
		const countSpan = document.getElementById( 'cha-selected-count' );

		if ( checkboxes.length > 0 ) {
			if ( bulkBar ) bulkBar.style.display = 'flex';
			if ( countSpan ) countSpan.textContent = checkboxes.length;
		} else {
			if ( bulkBar ) bulkBar.style.display = 'none';
			// Reset bulk badge select when bar is hidden.
			const badgeSelect = document.getElementById( 'cha-bulk-badge-select' );
			if ( badgeSelect ) badgeSelect.value = '';
		}
	}

	function getSelectedIds() {
		const checkboxes = tableBody.querySelectorAll(
			'.cha-checkbox:checked'
		);
		return Array.from( checkboxes ).map( ( cb ) => cb.value );
	}

	async function handleBulkAction( action ) {
		const ids = getSelectedIds();
		if ( ! ids.length ) return;

		// Handle AI rewrite separately
		if ( action === 'ai-rewrite' ) {
			await handleBulkAIRewrite( ids );
			return;
		}

		let confirmMsg = '';
		if ( action === 'delete' ) {
			confirmMsg = `Are you sure you want to delete ${ ids.length } release(s)? This cannot be undone.`;
		} else if ( action === 'publish' ) {
			confirmMsg = `Publish ${ ids.length } release(s)?`;
		} else if ( action === 'draft' ) {
			confirmMsg = `Set ${ ids.length } release(s) to draft?`;
		}

		if ( ! confirm( confirmMsg ) ) return;

		try {
			if ( action === 'delete' ) {
				await Promise.all(
					ids.map( ( id ) =>
						apiRequest( '/' + id, { method: 'DELETE' } )
					)
				);
				showToast( `${ ids.length } release(s) deleted`, 'success' );
			} else {
				const status = action === 'publish' ? 'publish' : 'draft';
				await Promise.all(
					ids.map( ( id ) =>
						apiRequest( '/' + id, {
							method: 'PUT',
							body: JSON.stringify( { status } ),
						} )
					)
				);
				showToast( `${ ids.length } release(s) updated`, 'success' );
			}

			document.getElementById( 'cha-select-all' ).checked = false;
			tableBody.querySelectorAll('.cha-checkbox').forEach(cb => cb.checked = false);
			updateBulkActionsBar();
			invalidateReleasesCache();
			loadProducts();
			loadStats();
			loadReleases( true );
		} catch ( error ) {
			showToast( 'Operation failed', 'error' );
		}
	}

	/**
	 * Handle bulk badge change from the badge select dropdown
	 */
	async function handleBulkBadgeChange( e ) {
		const select = e.target;
		const badgeSlug = select.value;

		if ( ! badgeSlug ) {
			return;
		}

		const ids = getSelectedIds();
		if ( ! ids.length ) {
			select.value = '';
			return;
		}

		const badgeName = select.options[ select.selectedIndex ].text;
		const confirmMsg = `Change badge to "${ badgeName }" for ${ ids.length } release(s)?`;

		if ( ! confirm( confirmMsg ) ) {
			select.value = '';
			return;
		}

		try {
			await Promise.all(
				ids.map( ( id ) =>
					apiRequest( '/' + id, {
						method: 'PUT',
						body: JSON.stringify( { badge_id: badgeSlug } ),
					} )
				)
			);
			showToast( `${ ids.length } release(s) badge updated to "${ badgeName }"`, 'success' );

			select.value = '';
			document.getElementById( 'cha-select-all' ).checked = false;
			tableBody.querySelectorAll( '.cha-checkbox' ).forEach( ( cb ) => cb.checked = false );
			updateBulkActionsBar();
			invalidateReleasesCache();
			loadReleases( true );
		} catch ( error ) {
			showToast( 'Failed to update badges', 'error' );
			select.value = '';
		}
	}

	/**
	 * Handle bulk AI rewrite for selected releases - Modal based flow
	 */
	async function handleBulkAIRewrite( ids ) {
		// Step 1: Check if premium
		if ( ! chaReleasesData.isPremium ) {
			showUpgradeModal(
				'AI-powered changelog generation is a Pro feature. Upgrade to unlock AI capabilities.',
				chaReleasesData.upgradeUrl
			);
			return;
		}

		// Step 2: Check if Antimanual is active
		if ( ! chaReleasesData.antimanualActive ) {
			openAntimanualModal();
			return;
		}

		// Step 3: Gather release data from cache first, fetch only cache misses.
		const releases = [];
		const uncachedIds = [];

		for ( const id of ids ) {
			const cached = releaseItemsMap[ id ];
			if ( cached ) {
				releases.push( cached );
			} else {
				uncachedIds.push( id );
			}
		}

		// Fetch any cache misses in parallel (edge case — items not in current page).
		if ( uncachedIds.length ) {
			const fetched = await Promise.allSettled(
				uncachedIds.map( ( id ) => apiRequest( '/' + id ) )
			);
			fetched.forEach( ( result ) => {
				if ( result.status === 'fulfilled' ) {
					releases.push( result.value );
				}
			} );
		}

		if ( releases.length === 0 ) {
			showToast( 'No releases found', 'error' );
			return;
		}

		// Step 4: Open modal and populate version list
		openBulkAIModal( releases );
	}

	/**
	 * Open and populate the bulk AI modal
	 */
	function openBulkAIModal( releases ) {
		const modal = document.getElementById( 'cha-bulk-ai-modal' );
		const list = document.getElementById( 'cha-bulk-ai-list' );
		const confirmBtn = document.getElementById( 'cha-bulk-ai-confirm' );
		const footer = document.getElementById( 'cha-bulk-ai-footer' );
		const desc = document.getElementById( 'cha-bulk-ai-desc' );

		if ( ! modal || ! list ) return;

		// Reset modal state
		desc.style.display = 'block';
		footer.style.display = 'flex';
		confirmBtn.disabled = false;

		// Populate version list with pending items
		list.innerHTML = releases
			.map(
				( release ) => `
            <div class="cha-bulk-ai-item" data-id="${ release.id }">
                <div class="cha-bulk-ai-item-info">
                    <span class="cha-bulk-ai-version">${ escapeHtml(
						release.version || '—'
					) }</span>
                    <span class="cha-bulk-ai-title">${ escapeHtml(
						release.title || 'Untitled'
					) }</span>
                </div>
                <div class="cha-bulk-ai-status pending">
                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="18" height="18">
                        <circle cx="12" cy="12" r="10" stroke-width="2"/>
                    </svg>
                </div>
            </div>
        `
			)
			.join( '' );

		// Show modal
		modal.classList.add( 'active' );
		document.body.style.overflow = 'hidden';

		// Setup confirm button
		confirmBtn.onclick = async () => {
			confirmBtn.disabled = true;
			desc.textContent = 'Processing... Please wait.';
			await executeBulkAIRewrite( releases );
		};

		// Setup close buttons
		modal.querySelectorAll( '.cha-modal-close' ).forEach( ( btn ) => {
			btn.onclick = () => closeBulkAIModal();
		} );
		modal.addEventListener( 'click', ( e ) => {
			if ( e.target.classList.contains( 'cha-modal-overlay' ) )
				closeBulkAIModal();
		} );
	}

	/**
	 * Close the bulk AI modal
	 */
	function closeBulkAIModal() {
		const modal = document.getElementById( 'cha-bulk-ai-modal' );
		if ( modal ) {
			modal.classList.remove( 'active' );
			document.body.style.overflow = '';
		}
	}

	/**
	 * Execute the bulk AI rewrite with visual progress
	 */
	async function executeBulkAIRewrite( releases ) {
		let successCount = 0;
		let failCount = 0;

		for ( const release of releases ) {
			const itemEl = document.querySelector(
				`.cha-bulk-ai-item[data-id="${ release.id }"]`
			);
			const statusEl = itemEl?.querySelector( '.cha-bulk-ai-status' );

			// Set loading state
			if ( statusEl ) {
				statusEl.className = 'cha-bulk-ai-status loading';
				statusEl.innerHTML = '<div class="cha-spinner-sm"></div>';
			}

			try {
				const changeItems = release.change_items || [];
				if ( changeItems.length === 0 ) {
					throw new Error( 'No change items' );
				}

				// Call AI rewrite API
				const response = await fetch( chaReleasesData.aiRewriteUrl, {
					method: 'POST',
					credentials: 'same-origin',
					headers: {
						'Content-Type': 'application/json',
						'X-WP-Nonce': chaReleasesData.nonce,
					},
					body: JSON.stringify( {
						release_id: release.id,
						change_items: changeItems,
						current_title: release.title || '',
						current_notes: release.notes || '',
						version: release.version || '',
					} ),
				} );

				const data = await response.json();

				if ( response.ok && data.success ) {
					// Update release with new title and description
					await apiRequest( '/' + release.id, {
						method: 'PUT',
						body: JSON.stringify( {
							title: data.title,
							notes: data.description,
						} ),
					} );
					successCount++;

					// Show success icon
					if ( statusEl ) {
						statusEl.className = 'cha-bulk-ai-status success';
						statusEl.innerHTML = `
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
                            </svg>
                        `;
					}
				} else {
					throw new Error( data.message || 'AI rewrite failed' );
				}
			} catch ( error ) {
				console.error(
					'AI rewrite error for release',
					release.id,
					error
				);
				failCount++;

				// Show error icon
				if ( statusEl ) {
					statusEl.className = 'cha-bulk-ai-status error';
					statusEl.innerHTML = `
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="20" height="20">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
                        </svg>
                    `;
				}
			}
		}

		// Update description with result
		const desc = document.getElementById( 'cha-bulk-ai-desc' );
		if ( desc ) {
			desc.textContent = `Complete! ${ successCount } succeeded, ${ failCount } failed.`;
		}

		// Auto-close after delay and refresh
		setTimeout( () => {
			closeBulkAIModal();
			if ( successCount > 0 ) {
				document.getElementById( 'cha-select-all' ).checked = false;
				tableBody.querySelectorAll('.cha-checkbox').forEach(cb => cb.checked = false);
				updateBulkActionsBar();
				invalidateReleasesCache();
				loadReleases( true );
			}
		}, 1500 );
	}

	// API Functions
	async function apiRequest( endpoint, options = {} ) {
		const url = chaReleasesData.restUrl + endpoint;

		// Use wpApiSettings nonce if available, otherwise use our nonce
		const nonce =
			typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce
				? wpApiSettings.nonce
				: chaReleasesData.nonce;

		const response = await fetch( url, {
			...options,
			credentials: 'same-origin',
			headers: {
				'Content-Type': 'application/json',
				'X-WP-Nonce': nonce,
				...options.headers,
			},
		} );

		if ( ! response.ok ) {
			const error = await response.json();
			throw new Error( error.message || 'API request failed' );
		}

		return response.json();
	}

	// Load Stats
	async function loadStats() {
		try {
			const params = new URLSearchParams();
			if ( currentProduct ) params.set( 'product', currentProduct );

			const stats = await apiRequest( '/stats?' + params.toString() );
			updateStatsUI( stats );
		} catch ( error ) {
			console.error( 'Failed to load stats:', error );
		}
	}

	function updateStatsUI( stats ) {
		document.getElementById( 'stat-total' ).textContent = stats.total || 0;
		document.getElementById( 'stat-published' ).textContent =
			stats.published || 0;
		document.getElementById( 'stat-drafts' ).textContent =
			stats.drafts || 0;

		// Latest release with version and product name
		const latestEl = document.getElementById( 'stat-latest' );
		const latestLabel = document.getElementById( 'stat-latest-label' );

		if ( stats.latest_version ) {
			latestEl.textContent = stats.latest_version;
			if ( latestLabel && stats.latest_product ) {
				latestLabel.textContent = stats.latest_product;
			} else {
				latestLabel.textContent = 'Latest Release';
			}
		} else {
			latestEl.textContent = '—';
			if ( latestLabel ) latestLabel.textContent = 'Latest Release';
		}
	}

	// Load Releases
	async function loadReleases( forceRefresh = false ) {
		// Create cache key based on current filters
		const cacheKey = `${
			currentProduct || 'all'
		}_${ currentPage }_${ currentStatus }_${ currentSearch }`;

		// Check cache first (unless force refresh)
		if ( ! forceRefresh && releasesCache[ cacheKey ] ) {
			renderReleases( releasesCache[ cacheKey ].releases );
			renderPagination( releasesCache[ cacheKey ] );
			return;
		}

		showLoading();

		try {
			const params = new URLSearchParams( {
				page: currentPage,
				per_page: 20,
				status: currentStatus,
				search: currentSearch,
			} );

			if ( currentProduct ) {
				params.set( 'product', currentProduct );
			}

			const data = await apiRequest( '?' + params.toString() );

			// Store in cache
			releasesCache[ cacheKey ] = data;

			renderReleases( data.releases );
			renderPagination( data );
		} catch ( error ) {
			console.error( 'Failed to load releases:', error );
			showToast( 'Failed to load releases', 'error' );
		}
	}

	// Invalidate cache (call after create/update/delete)
	function invalidateReleasesCache() {
		releasesCache = {};
		releaseItemsMap = {}; // Clear cached items so next list load provides fresh data.
	}

	function showLoading() {
		tableBody.innerHTML = `
            <tr>
                <td colspan="6">
                    <div class="cha-loading">
                        <div class="cha-loading-spinner"></div>
                    </div>
                </td>
            </tr>
        `;
	}

	function renderReleases( releases ) {
		// Store each release in the map for instant quick edit modal.
		releases.forEach( ( release ) => {
			releaseItemsMap[ release.id ] = release;
		} );

		if ( ! releases.length ) {
			tableBody.innerHTML = `
                <tr>
                    <td colspan="5">
                        <div class="cha-empty-state">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
                            </svg>
                            <h3>No releases found</h3>
                            <p>Create your first release to get started</p>
                        </div>
                    </td>
                </tr>
            `;
			return;
		}

		tableBody.innerHTML = releases
			.map(
				( release ) => `
            <tr data-id="${ release.id }">
                <td><input type="checkbox" class="cha-checkbox" value="${
					release.id
				}"></td>
                <td>
                    <span class="cha-version">${ escapeHtml(
						release.version || '—'
					) }</span>
                    ${
						release.badge
							? `<span class="cha-type-badge" style="background-color: ${
									release.badge.color
							  }20; color: ${
									release.badge.color
							  }; margin-left: 8px;">${ escapeHtml(
									release.badge.name
							  ) }</span>`
							: ''
					}
                </td>
                <td>
                    <span class="cha-release-title">${ escapeHtml(
						release.title || 'Untitled'
					) }</span>
                    <div class="cha-actions">
                        <button type="button" class="cha-action-btn" onclick="chaReleases.quickEdit(${
							release.id
						})" title="Quick Edit">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
                            </svg>
                        </button>
                        <a href="${ chaReleasesData.adminUrl }post.php?post=${
							release.id
						}&action=edit" target="_blank" class="cha-action-btn" title="Edit">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
                            </svg>
                        </a>
                        <button type="button" class="cha-action-btn delete" onclick="chaReleases.delete(${
							release.id
						})" title="Delete">
                            <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
                            </svg>
                        </button>
                        <button type="button" class="cha-action-btn ai cha-tooltip" onclick="chaReleases.aiGenerate(${
							release.id
						})" data-tooltip="AI Rewrite: Generate improved title and description based on your change items">
                            <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 488.472 488.472" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><path d="M331.351 96.061c-5.963-5.963-15.622-5.963-21.585 0L4.472 401.355c-5.963 5.963-5.963 15.622 0 21.585l61.059 61.059c2.981 2.981 6.887 4.472 10.793 4.472s7.811-1.491 10.793-4.472L392.41 178.705c5.963-5.963 5.963-15.622 0-21.585l-61.059-61.059zM76.323 451.622 36.85 412.148l207.385-207.385 39.474 39.474L76.323 451.622zm228.971-228.971-39.474-39.474 54.738-54.738 39.474 39.474zM213.707 122.118c15.265-30.529 30.529-45.794 61.059-61.059C244.237 45.794 228.972 30.53 213.707 0c-15.265 30.529-30.531 45.794-61.059 61.059 30.53 15.265 45.794 30.53 61.059 61.059zM457.942 213.707c-7.632 15.265-15.267 22.897-30.529 30.529 15.265 7.632 22.897 15.265 30.529 30.529 7.632-15.265 15.265-22.897 30.529-30.529-15.264-7.632-22.896-15.265-30.529-30.529zM457.942 45.795C435.045 34.346 423.596 22.898 412.148.001c-11.449 22.897-22.899 34.346-45.794 45.794 22.897 11.449 34.346 22.897 45.794 45.794 11.449-22.897 22.897-34.346 45.794-45.794z" fill="currentColor" opacity="1"
                        </button>
                    </div>
                </td>
                <td><span class="cha-status-badge ${ release.status }">${
					release.status === 'publish' ? 'published' : 'draft'
				}</span></td>
                <td><span class="cha-release-date">${ formatDate(
					release.release_date
				) }</span></td>
            </tr>
        `
			)
			.join( '' );

		// Add checkbox change listeners
		tableBody.querySelectorAll( '.cha-checkbox' ).forEach( ( cb ) => {
			cb.addEventListener( 'change', () => {
				updateBulkActionsBar();
				// Update select-all checkbox state
				const allChecked =
					tableBody.querySelectorAll( '.cha-checkbox' ).length ===
					tableBody.querySelectorAll( '.cha-checkbox:checked' )
						.length;
				document.getElementById( 'cha-select-all' ).checked =
					allChecked;
			} );
		} );
	}

	function renderPagination( data ) {
		const paginationInfo = document.querySelector( '.cha-pagination-info' );
		const prevBtn = document.getElementById( 'cha-prev-btn' );
		const nextBtn = document.getElementById( 'cha-next-btn' );

		if ( paginationInfo ) {
			const start = ( data.page - 1 ) * 20 + 1;
			const end = Math.min( data.page * 20, data.total );
			paginationInfo.textContent = `Page ${ data.page } of ${
				data.total_pages || 1
			}`;
		}

		if ( prevBtn ) {
			prevBtn.disabled = data.page <= 1;
			prevBtn.onclick = () => {
				if ( data.page > 1 ) {
					currentPage = data.page - 1;
					loadReleases();
				}
			};
		}

		if ( nextBtn ) {
			nextBtn.disabled = data.page >= data.total_pages;
			nextBtn.onclick = () => {
				if ( data.page < data.total_pages ) {
					currentPage = data.page + 1;
					loadReleases();
				}
			};
		}
	}

	// Modal Functions
	function openAddModal() {
		editingId = null;
		modalTitle.textContent = 'Add New Release';
		form.reset();
		clearChangeItems();
		resetFeaturedImage();

		// Auto-select the current product from sidebar
		if ( productSelect && currentProduct ) {
			productSelect.value = currentProduct;
		}

		// Hide edit link for new releases
		const editLink = document.getElementById( 'cha-modal-edit-link' );
		if ( editLink ) editLink.style.display = 'none';

		modal.classList.add( 'active' );
	}

	async function openEditModal( id ) {
		editingId = id;
		modalTitle.textContent = 'Edit Release';

		// Show edit link
		const editLink = document.getElementById( 'cha-modal-edit-link' );
		if ( editLink ) {
			editLink.href =
				chaReleasesData.adminUrl +
				'post.php?post=' +
				id +
				'&action=edit';
			editLink.style.display = 'flex';
		}

		// Check releaseItemsMap first — skip API if data is already cached.
		const cachedRelease = releaseItemsMap[ id ] || null;
		if ( cachedRelease ) {
			populateForm( cachedRelease );
			modal.classList.add( 'active' );
			return;
		}

		// Fallback: fetch from API (edge case — item not in the current list).
		try {
			const release = await apiRequest( '/' + id );
			populateForm( release );
			modal.classList.add( 'active' );
		} catch ( error ) {
			showToast( 'Failed to load release', 'error' );
		}
	}

	function closeModal() {
		modal.classList.remove( 'active' );
		editingId = null;

		// Hide edit link
		const editLink = document.getElementById( 'cha-modal-edit-link' );
		if ( editLink ) editLink.style.display = 'none';
	}

	function populateForm( release ) {
		form.version.value = release.version || '';
		if ( form.badge_id ) {
			form.badge_id.value = release.badge ? release.badge.slug : '';
		}
		form.title.value = release.title || '';
		form.notes.value = release.notes || '';
		form.release_date.value = release.release_date || '';
		form.status.value = release.status || 'draft';

		// Set product
		if ( form.product ) {
			form.product.value = release.product ? release.product.id : '';
		}

		// Populate featured image
		if ( release.featured_image && release.featured_image.id ) {
			setFeaturedImage(
				release.featured_image.id,
				release.featured_image.url
			);
		} else {
			resetFeaturedImage();
		}

		// Populate change items
		clearChangeItems();
		if ( release.change_items && Array.isArray( release.change_items ) ) {
			release.change_items.forEach( ( group ) => {
				if ( group.items && Array.isArray( group.items ) ) {
					group.items.forEach( ( item ) => {
						addChangeItem( group.group, item );
					} );
				}
			} );
		}
	}

	function clearChangeItems() {
		document
			.querySelectorAll( '.cha-change-items-list' )
			.forEach( ( list ) => {
				list.innerHTML = '';
			} );
	}

	function addChangeItem( group, value = '' ) {
		const list = document.querySelector(
			`.cha-change-items-list[data-group="${ group }"]`
		);
		if ( ! list ) return;

		const item = document.createElement( 'div' );
		item.className = 'cha-change-item';
		item.innerHTML = `
            <input type="text" name="change_items[${ group }][]" value="${ escapeHtml(
				value
			) }" placeholder="Enter change item...">
            <button type="button" class="cha-remove-item-btn" onclick="this.parentElement.remove()">
                <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
                </svg>
            </button>
        `;
		list.appendChild( item );
	}

	/**
	 * Validate category selection and quality for change items
	 */
	function validateChangeItemsQuality() {
		const issues = [];
		const groupLists = form.querySelectorAll(
			'.cha-change-items-list[data-group]'
		);

		// Define keywords for each category
		const keywords = {
			fixes: [
				'fixed',
				'bug',
				'issue',
				'crash',
				'resolved',
				'corrected',
				'patch',
				'error',
				'failure',
			],
			features: [
				'added',
				'new',
				'feature',
				'introduced',
				'support for',
				'integration',
			],
			improvements: [
				'improved',
				'optimized',
				'enhanced',
				'updated',
				'refactored',
				'better',
				'tweak',
				'performance',
			],
		};

		// Check each group
		groupLists.forEach( ( list ) => {
			const currentGroup = list.dataset.group;
			const inputs = list.querySelectorAll( 'input[type="text"]' );

			inputs.forEach( ( input ) => {
				const text = input.value.trim().toLowerCase();
				if ( ! text ) return;

				// Rule 1: Fix-like items in non-Fix groups
				if (
					currentGroup !== 'fixes' &&
					keywords.fixes &&
					keywords.fixes.some( ( k ) => text.includes( k ) )
				) {
					issues.push( {
						element: input,
						message: `"${ input.value }" sounds like a bug fix.\nConsider moving it to the "Fixes" category.`,
					} );
					return; // Stop checking this item
				}

				// Rule 2: Feature-like items in Fixes group
				if ( currentGroup === 'fixes' ) {
					if (
						keywords.features &&
						keywords.features.some( ( k ) => text.includes( k ) )
					) {
						issues.push( {
							element: input,
							message: `"${ input.value }" sounds like a new feature.\nConsider moving it to the "Features" or "Improvements" category.`,
						} );
						return;
					}
					if (
						keywords.improvements &&
						keywords.improvements.some( ( k ) =>
							text.includes( k )
						)
					) {
						issues.push( {
							element: input,
							message: `"${ input.value }" sounds like an improvement.\nConsider moving it to the "Improvements" category.`,
						} );
						return;
					}
				}

				// Rule 3: Vague or Jargon check
				const vagueTerms = [
					'fixed bugs',
					'bug fixes',
					'updates',
					'misc',
					'refactoring',
					'maintenance',
					'improvements',
				];
				if (
					vagueTerms.includes( text ) ||
					( text.length < 15 &&
						( text.startsWith( 'fix' ) ||
							text.startsWith( 'update' ) ) )
				) {
					issues.push( {
						element: input,
						message: `"${ input.value }" is a bit vague.\n\nTry adding more detail like what was fixed or improved (e.g., "Fixed login issue on mobile").`,
					} );
					return;
				}

				// Rule 4: Technical Jargon check
				const jargonMap = {
					refactored: 'Improved code structure',
					deprecated: 'Marked as outdated',
					bumped: 'Updated',
				};
				for ( const [ term, suggestion ] of Object.entries(
					jargonMap
				) ) {
					if ( text.startsWith( term ) ) {
						issues.push( {
							element: input,
							message: `"${ term }" is technical jargon.\nConsider using "${ suggestion }" or explaining the user benefit.`,
						} );
						return;
					}
				}

				// Rule 5: Passive Voice Check (NEW)
				const passivePatterns = [
					'was fixed',
					'were fixed',
					'has been',
					'have been',
					'was added',
					'were added',
				];
				for ( const pattern of passivePatterns ) {
					if ( text.includes( pattern ) ) {
						issues.push( {
							element: input,
							message: `"${ input.value }" uses passive voice.\nActive voice is clearer (e.g., use "Fixed..." instead of "...was fixed").`,
						} );
						return;
					}
				}

				// Rule 6: Starts with Article (NEW)
				const articleStarts = [ 'the ', 'this ', 'a ', 'an ' ];
				if (
					articleStarts.some( ( prefix ) =>
						text.startsWith( prefix )
					)
				) {
					issues.push( {
						element: input,
						message: `"${ input.value }" starts with an article.\nStart with a verb for punchier changelogs (e.g., "Added...", "Fixed...").`,
					} );
					return;
				}

				// Rule 7: All Caps (NEW)
				if (
					text.length > 10 &&
					text === text.toUpperCase() &&
					/[A-Z]/.test( text )
				) {
					issues.push( {
						element: input,
						message: `"${ input.value }" is in ALL CAPS.\nPlease use sentence case (e.g., "Fixed the bug" instead of "FIXED THE BUG").`,
					} );
					return;
				}

				// Rule 8: Too Short (NEW)
				if ( text.length < 10 && text.length > 0 ) {
					issues.push( {
						element: input,
						message: `"${ input.value }" is very short.\nPlease add more detail (e.g., "Fixed login issue" instead of "Fixed").`,
					} );
					return;
				}

				// Rule 9: Missing User Benefit Context in Features
				if ( currentGroup === 'features' ) {
					const benefitConnectors = [ ' to ', ' for ', ' by ', ' so ', ' allowing ', ' which ', ' that ', ' with ' ];
					if ( ! benefitConnectors.some( connector => text.includes( connector ) ) ) {
						issues.push( {
							element: input,
							message: `"${ input.value }" might be missing context.\nFor features, try explaining the user benefit (e.g., "Added dark mode TO reduce eye strain").`,
						} );
						return;
					}
				}
			} );
		} );

		// Show confirmation for first issue
		if ( issues.length > 0 ) {
			const item = issues[ 0 ];
			// Highlight the field
			item.element.style.transition =
				'box-shadow 0.3s, border-color 0.3s';
			const originalBorder = item.element.style.borderColor;
			item.element.style.borderColor = '#f59e0b';
			item.element.style.boxShadow = '0 0 0 3px rgba(245, 158, 11, 0.2)';
			item.element.scrollIntoView( {
				behavior: 'smooth',
				block: 'center',
			} );
			item.element.focus();

			// Ask user
			if (
				! confirm(
					`💡 Quality Tip: ${ item.message }\n\nClick Cancel to improve it, or OK to save anyway.`
				)
			) {
				// User clicked Cancel (Review) -> keep focus
				setTimeout( () => {
					item.element.style.borderColor = originalBorder;
					item.element.style.boxShadow = '';
				}, 5000 );
				return false;
			} else {
				// User clicked OK (Save anyway) -> remove highlight immediately
				item.element.style.borderColor = originalBorder;
				item.element.style.boxShadow = '';
			}
		}

		return true;
	}

	/**
	 * Validate version number format (Semantic Versioning)
	 */
	function validateVersionSemVer() {
		const versionInput = form.querySelector( '[name="version"]' );
		const version = versionInput.value.trim();

		// Regex for semantic versioning: allows optional 'v' or 'V' prefix, X.Y.Z format, and optional pre-release/build suffix
		const semVerRegex = /^[vV]?\d+\.\d+\.\d+([-+].+)?$/;

		if ( version.length > 0 && ! semVerRegex.test( version ) ) {
			if (
				! confirm(
					`Quality Tip: Your version number "${ version }" doesn't follow standard Semantic Versioning (e.g., 1.0.0 or v2.1.3).\n\nStandardizing versions helps users understand the scale of updates (Major.Minor.Patch).\n\nClick Cancel to adjust it, or OK to save anyway.`
				)
			) {
				versionInput.focus();
				return false;
			}
		}

		return true;
	}

	/**
	 * Validate release content for quality
	 */
	function validateReleaseContent() {
		const titleInput = form.querySelector( '[name="title"]' );
		const title = titleInput.value.trim();
		const genericTitles = [
			'update',
			'new version',
			'bug fixes',
			'fixes',
			'release',
			'maintenance',
			'improvements',
			'patch',
		];

		// Check for generic titles
		if (
			genericTitles.includes( title.toLowerCase() ) ||
			/^[vV]?\d+(\.\d+)*$/.test( title )
		) {
			if (
				! confirm(
					'Quality Tip: Your release title seems generic.\n\nA descriptive title (e.g., "Performance Boost & Login Fixes") helps users understand value instantly.\n\nClick OK to save anyway, or Cancel to improve it.'
				)
			) {
				// User clicked Cancel (Improve) -> return focus to title
				titleInput.focus();
				return false;
			}
			// User clicked OK (Save anyway) -> proceed
		}

		// Check for missing or short release notes
		const notesInput = form.querySelector( '[name="notes"]' );
		const notes = notesInput.value.trim();

		if ( notes.length === 0 || ( notes.length < 20 && notes.length > 0 ) ) {
			if (
				! confirm(
					'Quality Tip: Your release notes are very short or empty.\n\nA brief summary (2-3 sentences) helps users understand the "why" behind this release.\n\nClick OK to save anyway, or Cancel to add more details.'
				)
			) {
				notesInput.focus();
				return false;
			}
		}

		// Auto-capitalize and normalize punctuation for change items
		const changeInputs = form.querySelectorAll( '.cha-change-item input' );
		changeInputs.forEach( ( input ) => {
			let val = input.value.trim();
			if ( val.length > 0 ) {
				// Strip Conventional Commit prefixes (e.g., 'feat:', 'fix(core):')
				val = val.replace(
					/^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9_-]+\))?:\s*/i,
					''
				);

				// Capitalize first letter
				val = val.charAt( 0 ).toUpperCase() + val.slice( 1 );

				// Remove trailing period if present (for consistency)
				if ( val.endsWith( '.' ) ) {
					val = val.slice( 0, -1 );
				}

				if ( input.value !== val ) {
					input.value = val;
				}
			}
		} );

		if ( ! validateChangeItemsQuality() ) {
			return false;
		}

		if ( ! validateVersionSemVer() ) {
			return false;
		}

		return true;
	}

	// Form Submit
	async function handleFormSubmit( e ) {
		e.preventDefault();

		if ( ! validateReleaseContent() ) {
			return;
		}

		const submitBtn = form.querySelector( 'button[type="submit"]' );
		const originalText = submitBtn.innerHTML;
		submitBtn.innerHTML = '<span class="cha-spinner"></span> Saving...';
		submitBtn.disabled = true;

		try {
			const data = getFormData();

			if ( editingId ) {
				await apiRequest( '/' + editingId, {
					method: 'PUT',
					body: JSON.stringify( data ),
				} );
				showToast( 'Release updated successfully', 'success' );
			} else {
				await apiRequest( '', {
					method: 'POST',
					body: JSON.stringify( data ),
				} );
				showToast( 'Release created successfully', 'success' );
			}

			closeModal();
			invalidateReleasesCache();
			loadProducts();
			loadStats();
			loadReleases( true );
		} catch ( error ) {
			showToast( error.message || 'Failed to save release', 'error' );
		} finally {
			submitBtn.innerHTML = originalText;
			submitBtn.disabled = false;
		}
	}

	function getFormData() {
		const formData = new FormData( form );
		const data = {
			version: formData.get( 'version' ),
			badge_id: formData.get( 'badge_id' ) || '',
			title: formData.get( 'title' ),
			notes: formData.get( 'notes' ),
			release_date: formData.get( 'release_date' ),
			status: formData.get( 'status' ),
			product: formData.get( 'product' ) || '',
			featured_image: formData.get( 'featured_image' )
				? parseInt( formData.get( 'featured_image' ), 10 ) || 0
				: 0,
			change_items: [],
		};

		// Collect change items dynamically from all groups in the DOM
		const groupLists = form.querySelectorAll(
			'.cha-change-items-list[data-group]'
		);
		groupLists.forEach( ( list ) => {
			const group = list.dataset.group;
			const inputs = list.querySelectorAll( 'input[type="text"]' );
			const items = Array.from( inputs )
				.map( ( i ) => i.value.trim() )
				.filter( Boolean );
			if ( items.length ) {
				data.change_items.push( { group, items } );
			}
		} );

		return data;
	}

	// ========== Featured Image Functions ==========
	let featuredImageFrame = null;

	/**
	 * Open WordPress Media Library to select a featured image.
	 */
	function openFeaturedImageUploader() {
		if ( featuredImageFrame ) {
			featuredImageFrame.open();
			return;
		}

		featuredImageFrame = wp.media( {
			title: 'Select Featured Image',
			button: { text: 'Use this image' },
			multiple: false,
			library: { type: 'image' },
		} );

		featuredImageFrame.on( 'select', function () {
			const attachment = featuredImageFrame
				.state()
				.get( 'selection' )
				.first()
				.toJSON();
			const imgUrl =
				attachment.sizes && attachment.sizes.medium
					? attachment.sizes.medium.url
					: attachment.url;
			setFeaturedImage( attachment.id, imgUrl );
		} );

		featuredImageFrame.open();
	}

	/**
	 * Set the featured image preview and hidden input.
	 */
	function setFeaturedImage( id, url ) {
		const hiddenInput = document.getElementById( 'cha-featured-image' );
		const preview = document.getElementById( 'cha-featured-image-preview' );
		const previewImg = document.getElementById( 'cha-featured-image-img' );
		const uploadBtn = document.getElementById(
			'cha-featured-image-upload-btn'
		);

		if ( hiddenInput ) hiddenInput.value = id;
		if ( previewImg ) previewImg.src = url;
		if ( preview ) preview.style.display = '';
		if ( uploadBtn ) uploadBtn.style.display = 'none';
	}

	/**
	 * Reset the featured image field.
	 */
	function resetFeaturedImage() {
		const hiddenInput = document.getElementById( 'cha-featured-image' );
		const preview = document.getElementById( 'cha-featured-image-preview' );
		const previewImg = document.getElementById( 'cha-featured-image-img' );
		const uploadBtn = document.getElementById(
			'cha-featured-image-upload-btn'
		);

		if ( hiddenInput ) hiddenInput.value = '';
		if ( previewImg ) previewImg.src = '';
		if ( preview ) preview.style.display = 'none';
		if ( uploadBtn ) uploadBtn.style.display = '';
	}

	// Delete
	async function deleteRelease( id ) {
		if (
			! confirm(
				'Are you sure you want to delete this release? This action cannot be undone.'
			)
		) {
			return;
		}

		try {
			await apiRequest( '/' + id, { method: 'DELETE' } );
			showToast( 'Release deleted successfully', 'success' );
			invalidateReleasesCache();
			loadProducts(); // Refresh products to update tab counts
			loadStats();
			loadReleases( true );
		} catch ( error ) {
			showToast( 'Failed to delete release', 'error' );
		}
	}

	// Quick Edit (opens modal)
	function quickEditRelease( id ) {
		openEditModal( id );
	}

	// Toast
	function showToast( message, type = 'success' ) {
		const existing = document.querySelector( '.cha-toast' );
		if ( existing ) existing.remove();

		const toast = document.createElement( 'div' );
		toast.className = `cha-toast ${ type }`;
		toast.innerHTML = `
            ${ message }
            <span class="cha-toast-close" onclick="this.parentElement.remove()">×</span>
        `;
		document.body.appendChild( toast );

		setTimeout( () => toast.remove(), 4000 );
	}

	// Utilities
	function escapeHtml( text ) {
		if ( ! text ) return '';
		const div = document.createElement( 'div' );
		div.textContent = text;
		return div.innerHTML;
	}

	function formatDate( dateStr ) {
		if ( ! dateStr ) return '—';
		try {
			const date = new Date( dateStr );
			return date.toLocaleDateString( 'en-US', {
				month: 'short',
				day: 'numeric',
				year: 'numeric',
			} );
		} catch {
			return dateStr;
		}
	}

	// ========== Import Functions ==========
	function openImportModal() {
		const modal = document.getElementById( 'cha-import-modal' );
		if ( modal ) {
			modal.classList.add( 'active' );
			document.body.style.overflow = 'hidden';
			resetImportModal();
		}
	}

	function closeImportModal() {
		const modal = document.getElementById( 'cha-import-modal' );
		if ( modal ) {
			modal.classList.remove( 'active' );
			document.body.style.overflow = '';
			resetImportModal();
		}
	}

	function resetImportModal() {
		const form = document.getElementById( 'cha-import-form' );
		const fileUpload = document.querySelector(
			'#cha-import-modal .cha-file-upload'
		);
		const progress = document.querySelector(
			'#cha-import-modal .cha-import-progress'
		);
		const result = document.querySelector(
			'#cha-import-modal .cha-import-result'
		);
		const submitBtn = document.getElementById( 'cha-import-submit' );
		const fileName = document.querySelector(
			'#cha-import-modal .cha-file-name'
		);

		if ( form ) form.reset();
		if ( fileUpload ) fileUpload.classList.remove( 'has-file' );
		if ( progress ) progress.style.display = 'none';
		if ( result ) {
			result.style.display = 'none';
			result.className = 'cha-import-result';
			result.innerHTML = '';
		}
		if ( submitBtn ) submitBtn.disabled = false;
		if ( submitBtn ) submitBtn.disabled = false;
		if ( fileName )
			fileName.textContent = 'Choose a .txt file or drag and drop';

		const publishCheckbox = document.getElementById( 'cha-import-publish' );
		if ( publishCheckbox ) publishCheckbox.checked = false;
	}

	function handleFileSelect( e ) {
		const input = e.target;
		const fileUpload = input.closest( '.cha-file-upload' );
		const fileName = fileUpload?.querySelector( '.cha-file-name' );

		if ( input.files && input.files[ 0 ] ) {
			fileUpload?.classList.add( 'has-file' );
			if ( fileName ) fileName.textContent = input.files[ 0 ].name;
		} else {
			fileUpload?.classList.remove( 'has-file' );
			if ( fileName )
				fileName.textContent = 'Choose a .txt file or drag and drop';
		}
	}

	async function handleImportFormSubmit( e ) {
		e.preventDefault();

		const fileInput = document.getElementById( 'cha-import-file' );
		const progress = document.querySelector(
			'#cha-import-modal .cha-import-progress'
		);
		const progressFill = document.querySelector(
			'#cha-import-modal .cha-progress-fill'
		);
		const progressText = document.querySelector(
			'#cha-import-modal .cha-progress-text'
		);
		const result = document.querySelector(
			'#cha-import-modal .cha-import-result'
		);
		const submitBtn = document.getElementById( 'cha-import-submit' );
		const publishCheckbox = document.getElementById( 'cha-import-publish' );
		const duplicateAction = document.querySelector(
			'input[name="duplicate_action"]:checked'
		);

		if ( ! fileInput.files || ! fileInput.files[ 0 ] ) {
			alert( 'Please select a file' );
			return;
		}

		// Get the currently selected product
		const productId = currentProduct || 0;
		if ( ! productId ) {
			alert( 'Please select a product from the sidebar first' );
			return;
		}

		// Show progress
		if ( progress ) progress.style.display = 'block';
		if ( progressFill ) progressFill.style.width = '30%';
		if ( result ) result.style.display = 'none';
		if ( submitBtn ) submitBtn.disabled = true;

		let responseData = null;

		try {
			// Read file content
			const file = fileInput.files[ 0 ];
			const text = await file.text();

			if ( progressFill ) progressFill.style.width = '60%';
			if ( progressText )
				progressText.textContent = 'Processing changelog...';

			// Send to API
			const response = await fetch( chaReleasesData.importUrl, {
				method: 'POST',
				credentials: 'same-origin',
				headers: {
					'Content-Type': 'application/json',
					'X-WP-Nonce': chaReleasesData.nonce,
				},
				body: JSON.stringify( {
					changelog_text: text,
					product_id: productId,
					publish_posts: publishCheckbox
						? publishCheckbox.checked
						: false,
					duplicate_action: duplicateAction
						? duplicateAction.value
						: 'skip',
				} ),
			} );

			responseData = await response.json();

			if ( progressFill ) progressFill.style.width = '100%';

			if ( response.ok && responseData.requires_mapping ) {
				// Pause progress and show mapping modal — check this BEFORE success
				// because the response may have both success:true and requires_mapping:true.
				if ( progressText )
					progressText.textContent = 'Mapping Categories...';

				// Construct the payload we used for this request to pass along
				const originalPayload = {
					changelog_text: text,
					product_id: productId,
					publish_posts: publishCheckbox
						? publishCheckbox.checked
						: false,
					duplicate_action: duplicateAction
						? duplicateAction.value
						: 'skip',
				};

				showMappingModal(
					responseData.unmatched_categories,
					responseData.existing_categories,
					originalPayload
				);
			} else if ( response.ok && responseData.success ) {
				if ( result ) {
					result.style.display = 'block';
					result.className = 'cha-import-result success';

					// Build detailed result message
					let resultParts = [];
					if ( responseData.imported > 0 ) {
						resultParts.push(
							`<strong>${
								responseData.imported
							}</strong> new release${
								responseData.imported !== 1 ? 's' : ''
							} imported`
						);
					}
					if ( responseData.replaced > 0 ) {
						resultParts.push(
							`<strong>${
								responseData.replaced
							}</strong> release${
								responseData.replaced !== 1 ? 's' : ''
							} replaced`
						);
					}
					if ( responseData.skipped > 0 ) {
						resultParts.push(
							`<strong>${
								responseData.skipped
							}</strong> duplicate${
								responseData.skipped !== 1 ? 's' : ''
							} skipped`
						);
					}

					const resultMessage =
						resultParts.length > 0
							? resultParts.join( ', ' ) + '.'
							: 'No changes made.';

					result.innerHTML = `
                        <h4>Import Complete!</h4>
                        <p>${ resultMessage }</p>
                    `;
				}

				// Reload releases after short delay
				setTimeout( () => {
					invalidateReleasesCache();
					loadProducts();
					loadReleases();
					loadStats();
					closeImportModal();
				}, 1500 );
			} else {
				throw new Error( responseData.message || 'Import failed' );
			}
		} catch ( error ) {
			console.error( 'Import error:', error );
			if ( result ) {
				result.style.display = 'block';
				result.className = 'cha-import-result error';
				result.innerHTML = `
                    <h4>Import Failed</h4>
                    <p>${ error.message }</p>
                `;
			}
			if ( submitBtn ) submitBtn.disabled = false;
		} finally {
			if (
				progress &&
				( ! responseData || ! responseData.requires_mapping )
			) {
				setTimeout( () => {
					progress.style.display = 'none';
					if ( progressFill ) progressFill.style.width = '0%';
				}, 500 );
			}
		}
	}

	// ========== Category Mapping Modal ==========
	let currentMappingPayload = null;

	function showMappingModal(
		unmatchedCategories,
		existingCategories,
		originalPayload
	) {
		currentMappingPayload = originalPayload;

		// Create or reuse the overlay wrapper (matches existing modal pattern)
		let overlay = document.getElementById( 'cha-mapping-modal-overlay' );
		if ( ! overlay ) {
			overlay = document.createElement( 'div' );
			overlay.id = 'cha-mapping-modal-overlay';
			overlay.className = 'cha-modal-overlay';

			const modal = document.createElement( 'div' );
			modal.id = 'cha-mapping-modal';
			modal.className = 'cha-modal cha-modal-md';
			overlay.appendChild( modal );
			document.body.appendChild( overlay );
		}

		const mappingModal = document.getElementById( 'cha-mapping-modal' );
		mappingModal.innerHTML = buildMappingModalHTML(
			unmatchedCategories,
			existingCategories
		);

		// Setup event listeners
		mappingModal
			.querySelectorAll( '.cha-modal-close' )
			.forEach( ( btn ) => {
				btn.addEventListener( 'click', closeMappingModal );
			} );

		// Handle radio changes to show/hide the select dropdowns
		// and dynamically add/remove "keep" items from other dropdowns.
		mappingModal
			.querySelectorAll( 'input[type="radio"]' )
			.forEach( ( radio ) => {
				radio.addEventListener( 'change', ( e ) => {
					const targetId = e.target.name.replace(
						'action_',
						'target_'
					);
					const selectContainer = document.getElementById(
						targetId + '_container'
					);
					if ( selectContainer ) {
						selectContainer.style.display =
							e.target.value === 'map' ? 'block' : 'none';
					}

					// Get this item's original name and display name.
					const thisIndex = e.target.name.replace( 'action_', '' );
					const originalInput = mappingModal.querySelector(
						`[name="original_${ thisIndex }"]`
					);
					if ( ! originalInput ) return;

					const originalName = originalInput.value;
					const keptValue = 'kept:' + originalName;
					const displayName =
						originalInput
							.closest( '.cha-mapping-item' )
							?.querySelector( 'span[style*="color"]' )
							?.textContent?.replace( /"/g, '' ) ||
						originalName;

					// Collect all select elements in the modal (other items' dropdowns).
					const allSelects = mappingModal.querySelectorAll(
						'select[name^="target_"]'
					);

					if ( e.target.value === 'keep' ) {
						// Add this item as an option in all OTHER selects.
						allSelects.forEach( ( select ) => {
							if ( select.name === `target_${ thisIndex }` ) {
								return; // Skip own select.
							}
							// Don't add if already present.
							if (
								select.querySelector(
									`option[value="${ CSS.escape(
										keptValue
									) }"]`
								)
							) {
								return;
							}
							const option = document.createElement( 'option' );
							option.value = keptValue;
							option.textContent = `${ displayName } (new)`;
							option.dataset.kept = 'true';
							select.appendChild( option );
						} );
					} else {
						// Remove this item's option from all selects.
						allSelects.forEach( ( select ) => {
							const opt = select.querySelector(
								`option[value="${ CSS.escape( keptValue ) }"]`
							);
							if ( opt ) opt.remove();
						} );
					}
				} );
			} );

		const form = document.getElementById( 'cha-mapping-form' );
		form.addEventListener( 'submit', submitMappingModal );

		// Close on overlay (backdrop) click
		overlay.addEventListener( 'click', ( e ) => {
			if ( e.target === overlay ) closeMappingModal();
		} );

		// Pre-populate dropdowns: since all items default to 'keep',
		// add each one as an option in all OTHER items' selects.
		const allItems = mappingModal.querySelectorAll( '.cha-mapping-item' );
		const allSelects = mappingModal.querySelectorAll(
			'select[name^="target_"]'
		);
		allItems.forEach( ( item, idx ) => {
			const originalInput = item.querySelector(
				`[name="original_${ idx }"]`
			);
			if ( ! originalInput ) return;

			const originalName = originalInput.value;
			const keptValue = 'kept:' + originalName;
			const displayName =
				item
					.querySelector( 'span[style*="color"]' )
					?.textContent?.replace( /"/g, '' ) || originalName;

			allSelects.forEach( ( select ) => {
				if ( select.name === `target_${ idx }` ) return;
				const option = document.createElement( 'option' );
				option.value = keptValue;
				option.textContent = `${ displayName } (new)`;
				option.dataset.kept = 'true';
				select.appendChild( option );
			} );
		} );

		overlay.classList.add( 'active' );
	}
	function buildMappingModalHTML( unmatchedCategories, existingCategories ) {
		let categoryListHTML = '';

		// Build the dropdown options for existing categories
		let optionsHTML =
			'<option value="">-- Select an existing group --</option>';
		for ( const [ slug, label ] of Object.entries( existingCategories ) ) {
			optionsHTML += `<option value="${ escapeHtml(
				slug
			) }">${ escapeHtml( label ) }</option>`;
		}

		unmatchedCategories.forEach( ( cat, index ) => {
			const safeName = escapeHtml( cat.original );
			const idPrefix = `cat_${ index }`;
			categoryListHTML += `
                <div class="cha-mapping-item" style="margin-bottom: 20px; padding: 15px; border: 1px solid #e2e8f0; border-radius: 6px;">
                    <div style="font-weight: 600; margin-bottom: 10px; font-size: 15px;">New Group Found: <span style="color: #4f46e5;">"${ escapeHtml(
						cat.display
					) }"</span></div>
                    <input type="hidden" name="original_${ index }" value="${ safeName }">
                    
                    <div style="margin-bottom: 10px;">
                        <label style="display: flex; align-items: center; gap: 8px; cursor: pointer; margin-bottom: 8px;">
                            <input type="radio" name="action_${ index }" value="keep" checked>
                            <span>Keep and register as a new group</span>
                        </label>
                        <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
                            <input type="radio" name="action_${ index }" value="map">
                            <span>Map to an existing group</span>
                        </label>
                    </div>
                    
                    <div id="target_${ index }_container" style="display: none; padding-left: 24px; margin-top: 8px;">
                        <select name="target_${ index }" class="cha-input" style="width: 100%; max-width: 300px;">
                            ${ optionsHTML }
                        </select>
                    </div>
                </div>
            `;
		} );

		return `
            <div class="cha-modal-content">
                <div class="cha-modal-header">
                    <h2 class="cha-modal-title">Unknown Change Groups Detected</h2>
                    <button class="cha-modal-close">×</button>
                </div>
                <div class="cha-modal-body">
                    <p style="margin-bottom: 20px; color: #475569;">The changelog contains group names that don't match your configured Change Groups. How would you like to handle them?</p>
                    <form id="cha-mapping-form">
                        <input type="hidden" id="mapping-count" value="${ unmatchedCategories.length }">
                        <div class="cha-mapping-list" style="max-height: 400px; overflow-y: auto;">
                            ${ categoryListHTML }
                        </div>
                    </form>
                </div>
                <div class="cha-modal-footer">
                    <button type="button" class="cha-btn cha-btn-secondary cha-modal-close">Cancel Import</button>
                    <button type="submit" form="cha-mapping-form" class="cha-btn cha-btn-primary">Continue Import</button>
                </div>
            </div>
        `;
	}

	function closeMappingModal() {
		const overlay = document.getElementById( 'cha-mapping-modal-overlay' );
		if ( overlay ) {
			overlay.classList.remove( 'active' );
		}

		// Also close the main import modal
		closeImportModal();

		currentMappingPayload = null;
	}

	async function submitMappingModal( e ) {
		e.preventDefault();

		if ( ! currentMappingPayload ) return;

		const formElement = e.target;
		const count = parseInt(
			formElement.querySelector( '#mapping-count' ).value,
			10
		);
		const categoryMapping = {};

		// First pass: collect all items set to 'keep' so we know their slug.
		const keptSlugs = {};
		for ( let i = 0; i < count; i++ ) {
			const action = formElement.querySelector(
				`[name="action_${ i }"]:checked`
			).value;
			if ( action === 'keep' ) {
				const original = formElement.querySelector(
					`[name="original_${ i }"]`
				).value;
				// Match PHP's sanitize_title_with_dashes: lowercase, replace spaces with dashes.
				keptSlugs[ original ] = original
					.toLowerCase()
					.replace( /\s+/g, '-' )
					.replace( /[^a-z0-9-]/g, '' );
			}
		}

		// Second pass: build the final mapping object.
		for ( let i = 0; i < count; i++ ) {
			const original = formElement.querySelector(
				`[name="original_${ i }"]`
			).value;
			const action = formElement.querySelector(
				`[name="action_${ i }"]:checked`
			).value;
			const target = formElement.querySelector(
				`[name="target_${ i }"]`
			).value;

			if ( action === 'map' && ! target ) {
				alert( 'Please select a target group for all mapped items.' );
				return;
			}

			// Handle mapping to a 'kept' item from this same session.
			if ( action === 'map' && target.startsWith( 'kept:' ) ) {
				const keptOriginal = target.substring( 5 ); // Remove 'kept:' prefix.
				const resolvedSlug = keptSlugs[ keptOriginal ] || keptOriginal;
				categoryMapping[ original ] = {
					action: 'map',
					target: resolvedSlug,
				};
			} else {
				categoryMapping[ original ] = {
					action: action,
					target: action === 'map' ? target : null,
				};
			}
		}

		// Close the mapping modal overlay
		const mappingOverlay = document.getElementById(
			'cha-mapping-modal-overlay'
		);
		if ( mappingOverlay ) mappingOverlay.classList.remove( 'active' );

		// Update payload and send original import request again
		const payload = {
			...currentMappingPayload,
			category_mapping: categoryMapping,
		};

		// Re-show progress on main modal
		const progressFill = document.querySelector(
			'#cha-import-modal .cha-progress-fill'
		);
		const progressText = document.querySelector(
			'#cha-import-modal .cha-progress-text'
		);
		const result = document.querySelector(
			'#cha-import-modal .cha-import-result'
		);
		const progress = document.querySelector(
			'#cha-import-modal .cha-import-progress'
		);

		if ( progressFill ) progressFill.style.width = '80%';
		if ( progressText ) progressText.textContent = 'Finalizing import...';

		try {
			const response = await fetch( chaReleasesData.importUrl, {
				method: 'POST',
				credentials: 'same-origin',
				headers: {
					'Content-Type': 'application/json',
					'X-WP-Nonce': chaReleasesData.nonce,
				},
				body: JSON.stringify( payload ),
			} );

			const data = await response.json();

			if ( progressFill ) progressFill.style.width = '100%';

			if ( response.ok && data.success ) {
				if ( result ) {
					result.style.display = 'block';
					result.className = 'cha-import-result success';

					let resultParts = [];
					if ( data.imported > 0 )
						resultParts.push(
							`<strong>${ data.imported }</strong> new release${
								data.imported !== 1 ? 's' : ''
							} imported`
						);
					if ( data.replaced > 0 )
						resultParts.push(
							`<strong>${ data.replaced }</strong> release${
								data.replaced !== 1 ? 's' : ''
							} replaced`
						);
					if ( data.skipped > 0 )
						resultParts.push(
							`<strong>${ data.skipped }</strong> duplicate${
								data.skipped !== 1 ? 's' : ''
							} skipped`
						);

					const resultMessage =
						resultParts.length > 0
							? resultParts.join( ', ' ) + '.'
							: 'No changes made.';
					result.innerHTML = `<h4>Import Complete!</h4><p>${ resultMessage }</p>`;
				}

				setTimeout( () => {
					invalidateReleasesCache();
					loadProducts();
					loadReleases();
					loadStats();
					closeImportModal();
				}, 1500 );
			} else {
				throw new Error( data.message || 'Import failed' );
			}
		} catch ( error ) {
			console.error( 'Import error:', error );
			if ( result ) {
				result.style.display = 'block';
				result.className = 'cha-import-result error';
				result.innerHTML = `<h4>Import Failed</h4><p>${ error.message }</p>`;
			}
			const submitBtn = document.getElementById( 'cha-import-submit' );
			if ( submitBtn ) submitBtn.disabled = false;
		} finally {
			if ( progress ) {
				setTimeout( () => {
					progress.style.display = 'none';
					if ( progressFill ) progressFill.style.width = '0%';
				}, 500 );
			}
			currentMappingPayload = null;
		}
	}

	// ========== AI Generate for Individual Release (Row Action Button) ==========
	// This handles the AI magic wand button on each release row for single-release actions
	async function aiGenerateRelease( releaseId ) {
		// Step 1: Check if premium
		if ( ! chaReleasesData.isPremium ) {
			showUpgradeModal(
				'AI-powered changelog generation is a Pro feature. Upgrade to unlock AI capabilities.',
				chaReleasesData.upgradeUrl
			);
			return;
		}

		// Step 2: Check if Antimanual is active
		if ( ! chaReleasesData.antimanualActive ) {
			// Show Antimanual promo modal
			openAntimanualModal();
			return;
		}

		// Step 3: Open the modal and load release data (like quickEdit)
		editingId = releaseId;
		document.getElementById( 'cha-modal-title' ).textContent =
			'AI Rewrite Release';

		// Update edit link
		const editLink = document.getElementById( 'cha-modal-edit-link' );
		if ( editLink ) {
			editLink.href =
				chaReleasesData.adminUrl +
				'post.php?post=' +
				releaseId +
				'&action=edit';
			editLink.style.display = 'inline-flex';
		}

		try {
			const release = await apiRequest( '/' + releaseId );
			populateForm( release );
			modal.classList.add( 'active' );

			// Wait for the modal to be visible and form to be populated
			await new Promise( ( resolve ) => setTimeout( resolve, 100 ) );

			// Step 4: Collect current form data
			const titleField = form.querySelector( '#cha-title' );
			const notesField = form.querySelector( '#cha-notes' );
			const versionField = form.querySelector( '[name="version"]' );

			if ( ! titleField || ! notesField ) {
				showToast( 'Form fields not found', 'error' );
				return;
			}

			// Collect change items from the form
			const changeItems = collectChangeItemsFromForm();

			if ( changeItems.length === 0 ) {
				showToast(
					'Add some change items first before using AI rewrite',
					'error'
				);
				return;
			}

			// Step 5: Show loading overlays on title and notes
			addAILoadingOverlay( titleField.parentElement );
			addAILoadingOverlay( notesField.parentElement );

			// Step 6: Call the AI rewrite API
			try {
				const response = await fetch( chaReleasesData.aiRewriteUrl, {
					method: 'POST',
					credentials: 'same-origin',
					headers: {
						'Content-Type': 'application/json',
						'X-WP-Nonce': chaReleasesData.nonce,
					},
					body: JSON.stringify( {
						release_id: releaseId,
						change_items: changeItems,
						current_title: titleField.value || '',
						current_notes: notesField.value || '',
						version: versionField ? versionField.value : '',
					} ),
				} );

				const data = await response.json();

				if ( response.ok && data.success ) {
					// Step 7: Update the fields with AI-generated content
					titleField.value = data.title || titleField.value;
					notesField.value = data.description || notesField.value;
					showToast( 'AI rewrite complete!', 'success' );
				} else {
					throw new Error( data.message || 'AI rewrite failed' );
				}
			} catch ( error ) {
				console.error( 'AI rewrite error:', error );
				showToast( error.message || 'AI rewrite failed', 'error' );
			} finally {
				// Remove loading overlays
				removeAILoadingOverlay( titleField.parentElement );
				removeAILoadingOverlay( notesField.parentElement );
			}
		} catch ( error ) {
			showToast( 'Failed to load release', 'error' );
		}
	}

	/**
	 * Collect change items from the modal form
	 */
	function collectChangeItemsFromForm() {
		const changeItems = [];

		// Dynamically collect from all groups in the DOM
		const groupLists = form.querySelectorAll(
			'.cha-change-items-list[data-group]'
		);
		groupLists.forEach( ( list ) => {
			const group = list.dataset.group;
			const inputs = list.querySelectorAll( 'input[type="text"]' );
			const items = Array.from( inputs )
				.map( ( input ) => input.value.trim() )
				.filter( Boolean );

			if ( items.length > 0 ) {
				changeItems.push( { group, items } );
			}
		} );

		return changeItems;
	}

	/**
	 * Add loading overlay to a form group
	 */
	function addAILoadingOverlay( element ) {
		if ( ! element ) return;
		element.classList.add( 'cha-ai-loading' );
	}

	/**
	 * Remove loading overlay from a form group
	 */
	function removeAILoadingOverlay( element ) {
		if ( ! element ) return;
		element.classList.remove( 'cha-ai-loading' );
	}

	// ========== Embed Modal Functions ==========
	let currentEmbedProductId = null;
	let currentEmbedType = 'releases';

	function openEmbedModal() {
		// Check if user has Pro
		const embedBtn = document.getElementById( 'cha-embed-btn' );

		if ( ! chaReleasesData.isPremium ) {
			// Show upgrade modal for free users
			showEmbedUpgradeModal();
			return;
		}

		// Get current product from the active tab
		const activeTab = document.querySelector( '.cha-product-tab.active' );
		if ( ! activeTab ) {
			showToast( 'Please select a product first', 'error' );
			return;
		}

		currentEmbedProductId = activeTab.dataset.productId;

		// Get embed type from button data attribute (auto-detected based on current page)
		currentEmbedType = embedBtn?.dataset.embedType || 'releases';

		const modal = document.getElementById( 'cha-embed-modal' );
		if ( modal ) {
			modal.classList.add( 'active' );

			// Set the hidden embed type input
			const embedTypeInput = document.getElementById( 'cha-embed-type' );
			if ( embedTypeInput ) {
				embedTypeInput.value = currentEmbedType;
			}

			// Hide SDK tab for feedback and roadmap (only iframe is supported)
			const sdkTab = modal.querySelector(
				'.cha-embed-tab[data-tab="sdk"]'
			);
			const sdkTabContent = modal.querySelector(
				'.cha-embed-tab-content[data-tab="sdk"]'
			);
			if (
				currentEmbedType === 'feedback' ||
				currentEmbedType === 'roadmap'
			) {
				if ( sdkTab ) sdkTab.style.display = 'none';
				if ( sdkTabContent ) sdkTabContent.style.display = 'none';
				// Ensure iframe tab is active
				const iframeTab = modal.querySelector(
					'.cha-embed-tab[data-tab="iframe"]'
				);
				const iframeTabContent = modal.querySelector(
					'.cha-embed-tab-content[data-tab="iframe"]'
				);
				if ( iframeTab ) iframeTab.classList.add( 'active' );
				if ( iframeTabContent )
					iframeTabContent.classList.add( 'active' );
			} else {
				if ( sdkTab ) sdkTab.style.display = '';
				if ( sdkTabContent ) sdkTabContent.style.display = '';
			}

			// Initialize tabs
			initEmbedTabs();

			// Generate initial codes
			generateEmbedCode();
			if ( currentEmbedType === 'releases' ) {
				generateSDKCode();
			}

			// Bind change events for iframe tab
			document
				.getElementById( 'cha-embed-height' )
				?.addEventListener( 'input', generateEmbedCode );
			document
				.getElementById( 'cha-copy-embed' )
				?.addEventListener( 'click', copyEmbedCode );

			// Bind copy buttons for SDK
			modal.querySelectorAll( '.cha-copy-btn' ).forEach( ( btn ) => {
				btn.addEventListener( 'click', () => {
					const targetId = btn.dataset.target;
					copyToClipboard( targetId, btn );
				} );
			} );

			modal.querySelectorAll( '.cha-copy-inline' ).forEach( ( btn ) => {
				btn.addEventListener( 'click', ( e ) => {
					e.preventDefault();
					e.stopPropagation();
					const code = btn.getAttribute( 'data-code' );
					if ( ! code ) {
						console.error( 'No data-code attribute found' );
						return;
					}

					// Use fallback method for copying
					const textArea = document.createElement( 'textarea' );
					textArea.value = code;
					textArea.style.position = 'fixed';
					textArea.style.left = '-9999px';
					textArea.style.top = '-9999px';
					document.body.appendChild( textArea );
					textArea.select();
					try {
						document.execCommand( 'copy' );
						showToast( 'Copied!', 'success' );
						const originalText = btn.textContent;
						btn.textContent = 'Copied!';
						setTimeout( () => {
							btn.textContent = originalText;
						}, 1500 );
					} catch ( err ) {
						console.error( 'Copy failed:', err );
						showToast( 'Copy failed', 'error' );
					}
					document.body.removeChild( textArea );
				} );
			} );

			// Close button handlers
			modal.querySelectorAll( '.cha-modal-close' ).forEach( ( btn ) => {
				btn.addEventListener( 'click', closeEmbedModal );
			} );

			// Click outside to close
			modal.addEventListener( 'click', ( e ) => {
				if ( e.target === modal ) closeEmbedModal();
			} );
		}
	}

	function initEmbedTabs() {
		const tabs = document.querySelectorAll( '.cha-embed-tab' );
		const contents = document.querySelectorAll( '.cha-embed-tab-content' );

		tabs.forEach( ( tab ) => {
			tab.addEventListener( 'click', () => {
				const tabName = tab.dataset.tab;

				// Update active tab
				tabs.forEach( ( t ) => t.classList.remove( 'active' ) );
				tab.classList.add( 'active' );

				// Update active content
				contents.forEach( ( c ) => {
					c.classList.toggle( 'active', c.dataset.tab === tabName );
				} );
			} );
		} );
	}

	function closeEmbedModal() {
		const modal = document.getElementById( 'cha-embed-modal' );
		if ( modal ) {
			modal.classList.remove( 'active' );
		}
	}

	function generateEmbedCode() {
		const type = currentEmbedType || 'releases';
		const height =
			document.getElementById( 'cha-embed-height' )?.value || 600;

		// Derive base URL from admin URL to support subdirectory installs.
		const baseUrl = window.location.href.split( '/wp-admin' )[ 0 ];
		const embedUrl = `${ baseUrl }/?cha_embed=1&product=${ currentEmbedProductId }&type=${ type }`;

		const iframeCode = `<iframe src="${ embedUrl }" width="100%" height="${ height }" frameborder="0" style="border: none; border-radius: 8px;"></iframe>`;

		const codeTextarea = document.getElementById( 'cha-embed-code' );
		if ( codeTextarea ) {
			codeTextarea.value = iframeCode;
		}
	}

	function generateSDKCode() {
		// Derive base URL from admin URL to support subdirectory installs.
		const baseUrl =
			typeof chaReleasesData !== 'undefined' && chaReleasesData.homeUrl
				? chaReleasesData.homeUrl.replace( /\/$/, '' )
				: window.location.href.split( '/wp-admin' )[ 0 ];

		const sdkScript = `<!-- Changeloger Embed SDK -->
<script src="${ baseUrl }/wp-content/plugins/changeloger-pro/assets/js/cha-embed-sdk.min.js"></script>
<script>
CHA.init({
    product: ${ currentEmbedProductId },
    site: '${ baseUrl }'
});
</script>`;

		const scriptTextarea = document.getElementById( 'cha-sdk-script' );
		if ( scriptTextarea ) {
			scriptTextarea.value = sdkScript;
		}
	}

	function copyEmbedCode() {
		const codeTextarea = document.getElementById( 'cha-embed-code' );
		if ( codeTextarea ) {
			codeTextarea.select();
			document.execCommand( 'copy' );
			showToast( 'Embed code copied to clipboard!', 'success' );

			// Update button text temporarily
			const copyBtn = document.getElementById( 'cha-copy-embed' );
			if ( copyBtn ) {
				const originalHTML = copyBtn.innerHTML;
				copyBtn.innerHTML =
					'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="14" height="14"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> Copied!';
				setTimeout( () => {
					copyBtn.innerHTML = originalHTML;
				}, 2000 );
			}
		}
	}

	function copyToClipboard( textareaId, btn ) {
		const textarea = document.getElementById( textareaId );
		if ( textarea ) {
			textarea.select();
			document.execCommand( 'copy' );
			showToast( 'Copied to clipboard!', 'success' );

			const originalHTML = btn.innerHTML;
			btn.innerHTML =
				'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" width="14" height="14"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg> Copied!';
			setTimeout( () => {
				btn.innerHTML = originalHTML;
			}, 1500 );
		}
	}

	// Expose public API
	window.chaReleases = {
		quickEdit: quickEditRelease,
		delete: deleteRelease,
		addItem: addChangeItem,
		aiGenerate: aiGenerateRelease,
		openEmbed: openEmbedModal,
	};
} )();
