/**
	Admin Appointments Calendar (React + FullCalendar)
	- Renders the admin calendar and wires appointments/rules modals
	- Integrates Server-Sent Events (SSE) for live updates
	- Gates non-SSE refetches to prevent duplicate event flashes on create

	============================================================================
	TIMEZONE ARCHITECTURE - CRITICAL FOR DEVELOPERS
	============================================================================

	This file uses UTC methods (getUTCHours, getUTCDate, etc.) INTENTIONALLY.

	WHY: FullCalendar expects ISO strings in UTC format. Our timestamps are stored
	as UTC Unix timestamps that SEMANTICALLY REPRESENT site timezone times.

	EXAMPLE:
	- Stored timestamp: 1705320000 (for site in Europe/Ljubljana, UTC+1)
	- Represents: 2024-01-15 14:00 Ljubljana time
	- When we call new Date(ts * 1000).getUTCHours() → 14 (correct!)
	- If we called getHours() → would vary by browser timezone (WRONG!)

	RULE FOR THIS FILE:
	✅ USE: getUTCHours(), getUTCDate(), getUTCMonth(), getUTCFullYear()
	❌ DO NOT USE: getHours(), getDate(), getMonth() for timestamp extraction

	This is the OPPOSITE of appointment-admin-modal.js which uses local methods.
	See docs/TIMEZONE_ARCHITECTURE.md for full explanation.

	DO NOT "FIX" THIS BY ADDING TIMEZONE CONVERSIONS - IT WILL BREAK THE CALENDAR!
	============================================================================
*/
( function() {
/* global wcAppointmentsReactCalendar, wp */
	'use strict';

	// Admin Calendar (React): integrates FullCalendar with WooCommerce Appointments
	// Handles i18n, REST fetching, availability rendering, and appointment modals
	if ( !window.wcAppointmentsReactCalendar ) {
		return;
	}
	var h = wp.element.createElement;
	var useState = wp.element.useState;
	var useEffect = wp.element.useEffect;
	var useRef = wp.element.useRef;
	var i18n = ( window.wcAppointmentsReactCalendar && window.wcAppointmentsReactCalendar.i18n ) || {};

	/**
	 * WCA Modal Hooks - WordPress-style filters for modal content customization.
	 *
	 * Allows developers to filter/modify modal sections or add custom content.
	 * Lightweight implementation with zero overhead when no hooks are registered.
	 *
	 * Usage (in external snippet):
	 *   // Filter existing section content
	 *   window.wcaModalHooks.addFilter( 'appointment_modal_customer_section', function( content, appt, context ) {
	 *       content.push( h( 'div', { className: 'my-custom-field' }, 'Custom: ' + appt.custom_field ) );
	 *       return content;
	 *   }, 10 );
	 *
	 *   // Add content to a position
	 *   window.wcaModalHooks.addFilter( 'appointment_modal_after_customer', function( content, appt, context ) {
	 *       return [ h( 'div', { className: 'my-addon' }, 'My addon content' ) ];
	 *   } );
	 *
	 * Available filters:
	 *   - appointment_modal_header: Filter header row content
	 *   - appointment_modal_customer_section: Filter customer section rows
	 *   - appointment_modal_schedule_section: Filter schedule (when/duration) rows
	 *   - appointment_modal_staff_section: Filter staff section rows
	 *   - appointment_modal_addons_section: Filter addons section
	 *   - appointment_modal_pricing_section: Filter pricing/order section
	 *   - appointment_modal_footer_actions: Filter footer action buttons
	 *   - appointment_modal_after_header: Insert content after header
	 *   - appointment_modal_after_customer: Insert content after customer section
	 *   - appointment_modal_after_schedule: Insert content after schedule section
	 *   - appointment_modal_after_staff: Insert content after staff section
	 *   - appointment_modal_after_addons: Insert content after addons
	 *   - appointment_modal_after_pricing: Insert content after pricing
	 *   - appointment_modal_before_footer: Insert content before footer
	 *
	 * Context object contains: { h, t, i18n, appt, orderInfo, staffList, products, adminBase }
	 */
	var wcaModalHooks = ( function() {
		var filters = {};

		return {
			/**
			 * Add a filter callback.
			 * @param {string} hook - Hook name
			 * @param {Function} callback - Function( value, appt, context ) => filteredValue
			 * @param {number} priority - Lower runs first (default: 10)
			 */
			addFilter: function( hook, callback, priority ) {
				if ( 'function' !== typeof callback ) { return; }
				priority = 'number' === typeof priority ? priority : 10;
				if ( !filters[ hook ] ) { filters[ hook ] = []; }
				filters[ hook ].push( { fn: callback, p: priority } );
				filters[ hook ].sort( function( a, b ) { return a.p - b.p; } );
			},

			/**
			 * Remove a filter callback.
			 * @param {string} hook - Hook name
			 * @param {Function} callback - The callback to remove
			 */
			removeFilter: function( hook, callback ) {
				if ( !filters[ hook ] ) { return; }
				filters[ hook ] = filters[ hook ].filter( function( f ) { return f.fn !== callback; } );
			},

			/**
			 * Apply filters to a value.
			 * @param {string} hook - Hook name
			 * @param {*} value - Value to filter
			 * @param {object} appt - Appointment data
			 * @param {object} context - Additional context
			 * @returns {*} Filtered value
			 */
			applyFilters: function( hook, value, appt, context ) {
				if ( !filters[ hook ] || !filters[ hook ].length ) { return value; }
				var result = value;
				for ( var i = 0; i < filters[ hook ].length; i++ ) {
					try {
						result = filters[ hook ][ i ].fn( result, appt, context );
					} catch ( e ) {
						if ( window.console && window.console.error ) {
							window.console.error( 'wcaModalHooks filter error (' + hook + '):', e );
						}
					}
				}
				return result;
			},

			/**
			 * Check if a hook has any filters registered.
			 * @param {string} hook - Hook name
			 * @returns {boolean}
			 */
			hasFilters: function( hook ) {
				return !!( filters[ hook ] && filters[ hook ].length );
			},

			/**
			 * Get content for an insertion point (returns array of elements).
			 * @param {string} hook - Hook name
			 * @param {object} appt - Appointment data
			 * @param {object} context - Additional context
			 * @returns {Array} Array of React elements (empty if no filters)
			 */
			getContent: function( hook, appt, context ) {
				return this.applyFilters( hook, [], appt, context );
			}
		};
	} )();

	// Expose hooks API globally for external snippets
	window.wcaModalHooks = wcaModalHooks;

	// Utility functions
	function pad( n ) {
		return 10 > n ? '0' + n : String( n );
	}

	function t( key, fallback ) {
		var v = i18n && i18n[ key ];
		return ( 'string' === typeof v && v ) ? v : ( fallback || key );
	}

	function formatDateByPattern( d, pattern ) {
		if ( !d || !( d instanceof Date ) ) { return ''; }
		var mIndex = d.getUTCMonth();
		var day = d.getUTCDate();
		var year = d.getUTCFullYear();
		var monthsFull = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.monthsFull ) || [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ];
		var monthsShort = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.monthsShort ) || [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
		var tokens = {
			'F': monthsFull[ mIndex ],
			'M': monthsShort[ mIndex ],
			'm': pad( mIndex + 1 ),
			'n': String( mIndex + 1 ),
			'd': pad( day ),
			'j': String( day ),
			'Y': String( year ),
			'y': String( year ).slice( -2 )
		};
		return String( pattern || '' ).replace( /F|M|m|n|d|j|Y|y/g, function( tok ) {
			return tokens[ tok ] || tok;
		} );
	}

	function formatTimeByPattern( d, pattern ) {
		if ( !d || !( d instanceof Date ) ) { return ''; }
		var Hnum = d.getUTCHours();
		var H = pad( Hnum );
		var gnum = ( Hnum % 12 ) || 12;
		var g = String( gnum );
		var h = pad( gnum );
		var i = pad( d.getUTCMinutes() );
		var s = pad( d.getUTCSeconds() );
		var a = 12 > Hnum ? 'am' : 'pm';
		var tokens = {
			'g': g,
			'h': h,
			'H': H,
			'i': i,
			's': s,
			'a': a
		};
		return String( pattern || '' ).replace( /g|h|H|i|s|a/g, function( tok ) {
			return tokens[ tok ] || tok;
		} );
	}

	function formatTimeByPatternLocal( d, pattern ) {
		if ( !d || !( d instanceof Date ) ) { return ''; }
		var local = new Date( Date.UTC(
			d.getFullYear(),
			d.getMonth(),
			d.getDate(),
			d.getHours(),
			d.getMinutes(),
			d.getSeconds()
		) );
		return formatTimeByPattern( local, pattern );
	}

	function buildFullCalendarTimeFormat( pattern ) {
		var fmt = {};
		var hasHours = /[gGhH]/.test( pattern || '' );
		var hasLeadingHour = /[Hh]/.test( pattern || '' );
		var hasMinutes = /i/.test( pattern || '' );
		var hasSeconds = /s/.test( pattern || '' );
		var is24Hour = /[GH]/.test( pattern || '' );
		var is12Hour = /[gh]/.test( pattern || '' ) || /a/.test( pattern || '' );
		if ( hasHours ) {
			fmt.hour = hasLeadingHour ? '2-digit' : 'numeric';
		}
		if ( hasMinutes ) {
			fmt.minute = '2-digit';
		}
		if ( hasSeconds ) {
			fmt.second = '2-digit';
		}
		if ( is24Hour ) {
			fmt.hour12 = false;
		} else if ( is12Hour ) {
			fmt.hour12 = true;
		}
		return fmt;
	}

	function tsToNaiveLocalISO( ts ) {
		if ( !ts ) { return ''; }
		var d = new Date( ( ts || 0 ) * 1000 );
		var y = d.getUTCFullYear();
		var m = pad( d.getUTCMonth() + 1 );
		var day = pad( d.getUTCDate() );
		var hh = pad( d.getUTCHours() );
		var mm = pad( d.getUTCMinutes() );
		var ss = pad( d.getUTCSeconds() );
		return y + '-' + m + '-' + day + 'T' + hh + ':' + mm + ':' + ss;
	}

	function tsToNaiveLocalDate( ts ) {
		if ( !ts ) { return ''; }
		var d = new Date( ( ts || 0 ) * 1000 );
		var y = d.getUTCFullYear();
		var m = pad( d.getUTCMonth() + 1 );
		var day = pad( d.getUTCDate() );
		return y + '-' + m + '-' + day;
	}

	function dateToYmd( date ) {
		if ( !date || !( date instanceof Date ) ) { return ''; }
		return date.getFullYear() + '-' + pad( date.getMonth() + 1 ) + '-' + pad( date.getDate() );
	}

	function mapInitialView( v ) {
		// Map query/view setting to FullCalendar view identifiers
		switch ( ( v || '' ).toLowerCase() ) {
			case 'month':
				return 'dayGridMonth';
			case 'day':
				return 'timeGridDay';
			case 'list':
				return 'listWeek';
			case 'week':
			default:
				return 'timeGridWeek';
		}
	}

	var __wcaActiveGetControllers = [];
	var EXCLUDED_STATUSES = { 'trash': 1, 'in-cart': 1, 'was-in-cart': 1 };
	var __wcaRestApiAvailable = null; // null = unknown, true = available, false = unavailable
	var __wcaRestCheckInProgress = false;

	function isExcludedStatus( status ) {
		return !!( status && EXCLUDED_STATUSES[ String( status ).toLowerCase() ] );
	}

	/**
	 * Check if REST API is available with cached result
	 * @returns {Promise<boolean>} Resolves to true if REST available, false otherwise
	 */
	function checkRestApiAvailability() {
		// Return cached result if available
		if ( null !== __wcaRestApiAvailable ) {
			return Promise.resolve( __wcaRestApiAvailable );
		}

		// Prevent multiple simultaneous checks
		if ( __wcaRestCheckInProgress ) {
			return new Promise( function( resolve ) {
				var checkInterval = setInterval( function() {
					if ( !__wcaRestCheckInProgress ) {
						clearInterval( checkInterval );
						resolve( __wcaRestApiAvailable );
					}
				}, 50 );
			} );
		}

		__wcaRestCheckInProgress = true;

		// Try lightweight GET request to REST index endpoint (HEAD may not be supported)
		var base = ( window.wcAppointmentsReactCalendar && window.wcAppointmentsReactCalendar.restUrl ) ? window.wcAppointmentsReactCalendar.restUrl : '';
		if ( !base ) {
			__wcaRestApiAvailable = false;
			__wcaRestCheckInProgress = false;
			return Promise.resolve( false );
		}

		if ( '/' !== base.slice( -1 ) ) {
			base += '/';
		}

		// Use GET with minimal params instead of HEAD for better compatibility
		var controller = ( window && 'function' === typeof window.AbortController ) ? new window.AbortController() : null;
		var signal = controller ? controller.signal : undefined;

		return fetch( base + 'wc-appointments/v2/index?per_page=1', {
			method: 'GET',
			headers: {
				'X-WP-Nonce': wcAppointmentsReactCalendar.nonce
			},
			signal: signal
		} ).then( function( r ) {
			// If we get any HTTP response (even 401/403), the API endpoint exists and is working
			// Only mark as unavailable for 404 (endpoint not found) or 5xx (server errors)
			// 2xx = success, 4xx (except 404) = API exists but may have permission issues
			var isAvailable = r.ok || ( r.status !== 404 && r.status < 500 );
			__wcaRestApiAvailable = isAvailable;
			__wcaRestCheckInProgress = false;
			return isAvailable;
		} ).catch( function( e ) {
			// Only mark as unavailable on actual network errors (no HTTP response received)
			// If we get an HTTP error response, it means the API exists
			var isNetworkError = e && e.message && ( /Failed to fetch/i.test( e.message ) || /NetworkError/i.test( e.message ) );
			var isAbortError = e && 'AbortError' === e.name;
			// Network errors mean API is unavailable; abort errors are intentional, so don't mark as unavailable
			__wcaRestApiAvailable = !isNetworkError;
			__wcaRestCheckInProgress = false;
			return __wcaRestApiAvailable;
		} );
	}

	function sortByText( a, b ) {
		var at = String( a.text || '' ).toLowerCase();
		var bt = String( b.text || '' ).toLowerCase();
		return at < bt ? -1 : ( at > bt ? 1 : 0 );
	}

	function ensureArray( val ) {
		return Array.isArray( val ) ? val : [];
	}

	// Toast notification system for calendar updates
	var __wcaToasts = [];
	var __wcaToastListeners = [];
	var __wcaToastIdCounter = 0;
	var __wcaToastSeen = {}; // Deduplication for toasts by resource ID + content hash
	var __wcaToastTTL = 3000; // 3 second deduplication window for toasts
	var __wcaApptStateCache = {}; // Cache of appointment states for change detection
	var __wcaPendingCheckout = {}; // Track appointments transitioning from cart to checkout
	var __wcaCheckoutTTL = 5000; // 5 second window to suppress cart-removal notices after checkout

	function showToast( message, type ) {
		type = type || 'info';
		var id = ++__wcaToastIdCounter;
		var toast = { id: id, message: message, type: type, visible: true, hover: false };
		__wcaToasts.push( toast );
		__wcaToastListeners.forEach( function( fn ) {
			try { fn( __wcaToasts.slice() ); } catch ( _ ) {}
		} );
		// Auto-dismiss after 4 seconds, but check hover state
		var dismissTimer = setTimeout( function() {
			var currentToast = __wcaToasts.find( function( t ) { return t.id === id; } );
			if ( currentToast && !currentToast.hover ) {
				dismissToast( id );
			}
		}, 4000 );
		// Store timer ID for potential cleanup
		toast.timerId = dismissTimer;
		return id;
	}

	function dismissToast( id ) {
		var toastIndex = __wcaToasts.findIndex( function( t ) { return t.id === id; } );
		if ( toastIndex !== -1 ) {
			var toast = __wcaToasts[ toastIndex ];
			// Clear any pending timer
			if ( toast.timerId ) {
				clearTimeout( toast.timerId );
			}
			__wcaToasts.splice( toastIndex, 1 );
		}
		__wcaToastListeners.forEach( function( fn ) {
			try { fn( __wcaToasts.slice() ); } catch ( _ ) {}
		} );
	}

	function subscribeToasts( fn ) {
		__wcaToastListeners.push( fn );
		return function() {
			__wcaToastListeners = __wcaToastListeners.filter( function( f ) { return f !== fn; } );
		};
	}

	// Toast container component
	function ToastContainer() {
		var [ toasts, setToasts ] = useState( [] );

		useEffect( function() {
			return subscribeToasts( setToasts );
		}, [] );

		if ( !toasts.length ) {
			return null;
		}

		return h( 'div', { className: 'wca-toast-container' },
			toasts.map( function( toast ) {
				var iconClass = 'dashicons ';
				if ( 'success' === toast.type ) {
					iconClass += 'dashicons-yes-alt';
				} else if ( 'error' === toast.type ) {
					iconClass += 'dashicons-warning';
				} else {
					iconClass += 'dashicons-info';
				}
				return h( 'div', {
					key: toast.id,
					className: 'wca-toast wca-toast--' + toast.type,
					onMouseEnter: function() {
						// Set hover state and clear timer
						toast.hover = true;
						if ( toast.timerId ) {
							clearTimeout( toast.timerId );
							toast.timerId = null;
						}
					},
					onMouseLeave: function() {
						// Clear hover state and set new timer
						toast.hover = false;
						if ( !toast.timerId ) {
							toast.timerId = setTimeout( function() {
								dismissToast( toast.id );
							}, 1000 ); // Give 1 second after hover ends
						}
					}
				},
					h( 'span', { className: iconClass } ),
					h( 'span', { className: 'wca-toast-message' }, toast.message ),
					h( 'button', {
						className: 'wca-toast-dismiss',
						onClick: function() { dismissToast( toast.id ); },
						'aria-label': t( 'dismiss', 'Dismiss' )
					}, h( 'span', { className: 'dashicons dashicons-no-alt' } ) )
				);
			} )
		);
	}

	// Helper to cache appointment state for change detection
	function cacheApptState( id, status, start, end, customerName ) {
		if ( !id ) { return; }
		__wcaApptStateCache[ id ] = {
			status: status || '',
			start: parseInt( start, 10 ) || 0,
			end: parseInt( end, 10 ) || 0,
			customerName: customerName || ''
		};
	}

	// Mark an appointment as having just completed checkout (suppress cart-removal notice)
	function markCheckoutComplete( id ) {
		if ( !id ) { return; }
		__wcaPendingCheckout[ id ] = Date.now();
	}

	// Check if an appointment recently completed checkout
	function isRecentCheckout( id ) {
		if ( !id || !__wcaPendingCheckout[ id ] ) { return false; }
		var elapsed = Date.now() - __wcaPendingCheckout[ id ];
		if ( elapsed > __wcaCheckoutTTL ) {
			delete __wcaPendingCheckout[ id ];
			return false;
		}
		return true;
	}

	// Helper to format date/time for toast messages using WordPress site settings
	function formatToastDateTime( ts ) {
		try {
			if ( !ts ) { return ''; }
			var d = new Date( ts * 1000 );
			var dateFmt = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.dateFormat ) || 'M j, Y';
			var timeFmt = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.timeFormat ) || 'g:i a';
			// Format using WordPress date/time formats
			var dateStr = formatDateByPattern( d, dateFmt );
			var timeStr = formatTimeByPattern( d, timeFmt );
			return dateStr + ', ' + timeStr;
		} catch ( _ ) {
			return '';
		}
	}

	// Helper to get customer display name from payload
	function getCustomerDisplayName( payload ) {
		try {
			if ( !payload ) { return ''; }
			var name = payload.customer_name || payload.customer_full_name || '';
			if ( !name && payload.customer_first_name ) {
				name = ( payload.customer_first_name + ' ' + ( payload.customer_last_name || '' ) ).trim();
			}
			return name;
		} catch ( _ ) {
			return '';
		}
	}

	// Build semantic toast message with proper i18n structure
	function buildSemanticToastMessage( template, replacements ) {
		var msg = template;
		Object.keys( replacements || {} ).forEach( function( key ) {
			var val = replacements[ key ] || '';
			msg = msg.replace( new RegExp( '\\{' + key + '\\}', 'g' ), val );
			msg = msg.replace( new RegExp( '%' + key + '%', 'g' ), val );
		} );
		return msg;
	}

	// Status map for humanizing status strings
	function humanizeStatus( status, statusMap ) {
		if ( !status ) { return ''; }
		if ( statusMap && statusMap[ status ] ) { return statusMap[ status ]; }
		return status.split( '-' ).map( function( p ) {
			return p.charAt( 0 ).toUpperCase() + p.slice( 1 );
		} ).join( ' ' );
	}

	// Build toast message for SSE events
	function getToastMessageForEvent( topic, payload, i18n, resourceId ) {
		var isAvailability = topic && topic.indexOf( 'availability.' ) === 0;
		var id = ( payload && payload.id ) || resourceId || 0;
		if ( !id && !isAvailability ) { return ''; }

		var newStatus = payload && payload.status ? String( payload.status ) : '';
		var newStart = payload && payload.start ? parseInt( payload.start, 10 ) : 0;
		var newEnd = payload && payload.end ? parseInt( payload.end, 10 ) : 0;
		var prev = id ? __wcaApptStateCache[ id ] : null;
		var resolvedStart = newStart || ( prev ? prev.start : 0 );
		var resolvedEnd = newEnd || ( prev ? prev.end : 0 );
		var customerName = getCustomerDisplayName( payload ) || ( prev && prev.customerName ) || '';
		var customerLabel = customerName || t( 'guest', 'Guest' );
		var dateTimeStr = resolvedStart ? formatToastDateTime( resolvedStart ) : '';
		var replacements = { customer: customerLabel, datetime: dateTimeStr };
		var statusMap = ( i18n && i18n.statuses ) || {};
		var message = '';

		// Simple availability messages
		if ( isAvailability ) {
			if ( topic.indexOf( '.created' ) > 0 ) {
				message = t( 'toastAvailabilityCreated', 'Availability rule created' );
			} else if ( topic.indexOf( '.updated' ) > 0 ) {
				message = t( 'toastAvailabilityUpdated', 'Availability rule updated' );
			} else if ( topic.indexOf( '.deleted' ) > 0 ) {
				message = t( 'toastAvailabilityDeleted', 'Availability rule deleted' );
			}
		// Appointment events
		} else if ( topic.indexOf( 'appointment.' ) === 0 ) {
			var action = topic.replace( 'appointment.', '' );
			var prevStatus = prev ? prev.status : '';
			var statusChanged = newStatus && newStatus !== prevStatus;
			var startChanged = newStart && newStart !== ( prev ? prev.start : 0 );

			if ( action === 'created' ) {
				// Skip toast for in-cart additions
				if ( newStatus !== 'in-cart' ) {
					message = buildSemanticToastMessage(
						t( 'toastAppointmentCreatedSemantic', 'Appointment with {customer} created for {datetime}' ),
						replacements
					);
				}

			} else if ( action === 'cancelled' ) {
				message = buildSemanticToastMessage(
					t( 'toastAppointmentCancelledSemantic', 'Appointment with {customer} for {datetime} was cancelled' ),
					replacements
				);

			} else if ( action === 'deleted' || action === 'trashed' ) {
				message = buildSemanticToastMessage(
					t( 'toastAppointmentDeletedSemantic', 'Appointment with {customer} was removed' ),
					replacements
				);

			} else if ( action === 'rescheduled' ) {
				message = buildSemanticToastMessage(
					t( 'toastAppointmentRescheduledSemantic', 'Appointment with {customer} rescheduled to {datetime}' ),
					replacements
				);

			} else if ( action === 'updated' ) {
				// Checkout: in-cart → paid/unpaid/pending-confirmation/confirmed
				var checkoutStatuses = { 'unpaid': 1, 'pending-confirmation': 1, 'paid': 1, 'confirmed': 1 };
				var isCheckout = statusChanged && prevStatus === 'in-cart' && checkoutStatuses[ newStatus ];
				var isCartRemoval = newStatus === 'was-in-cart';
				var isCartAdd = newStatus === 'in-cart' && prevStatus !== 'in-cart';

				if ( isCheckout ) {
					markCheckoutComplete( id );
					message = buildSemanticToastMessage(
						t( 'toastAppointmentBooked', '{customer} booked an appointment for {datetime}' ),
						replacements
					);
				} else if ( isCartRemoval ) {
					// Skip toast for cart removals (including after checkout)
					message = '';
				} else if ( isCartAdd ) {
					// Skip toast for cart additions
					message = '';
				} else if ( newStatus === 'cancelled' && statusChanged ) {
					message = buildSemanticToastMessage(
						t( 'toastAppointmentCancelledSemantic', 'Appointment with {customer} for {datetime} was cancelled' ),
						replacements
					);
				} else if ( startChanged && !statusChanged ) {
					message = buildSemanticToastMessage(
						t( 'toastAppointmentRescheduledSemantic', 'Appointment with {customer} rescheduled to {datetime}' ),
						replacements
					);
				} else if ( statusChanged && newStatus ) {
					replacements.status = humanizeStatus( newStatus, statusMap );
					message = buildSemanticToastMessage(
						t( 'toastAppointmentStatusChanged', 'Appointment with {customer} status changed to {status}' ),
						replacements
					);
				}
			}
		}

		// Update state cache
		if ( id ) {
			if ( topic && ( topic.indexOf( '.deleted' ) > 0 || topic.indexOf( '.trashed' ) > 0 ) ) {
				delete __wcaApptStateCache[ id ];
			} else if ( newStatus || newStart || newEnd || customerName ) {
				cacheApptState( id, newStatus || ( prev ? prev.status : '' ), newStart || resolvedStart, newEnd || resolvedEnd, customerName );
			}
		}

		if ( !message ) { return ''; }

		// Deduplicate by message content within TTL window
		var toastKey = isAvailability
			? 'avail:' + ( resourceId || id ) + ':' + topic
			: 'appt:' + id + ':' + message.substring( 0, 40 );
		var now = Date.now();
		if ( __wcaToastSeen[ toastKey ] && ( now - __wcaToastSeen[ toastKey ] ) < __wcaToastTTL ) {
			return '';
		}
		__wcaToastSeen[ toastKey ] = now;

		// Periodic cleanup (10% chance)
		if ( Math.random() < 0.1 ) {
			var cutoff = now - __wcaToastTTL * 3;
			var checkoutCutoff = now - __wcaCheckoutTTL * 2;
			for ( var k in __wcaToastSeen ) {
				if ( __wcaToastSeen[ k ] < cutoff ) { delete __wcaToastSeen[ k ]; }
			}
			for ( var c in __wcaPendingCheckout ) {
				if ( __wcaPendingCheckout[ c ] < checkoutCutoff ) { delete __wcaPendingCheckout[ c ]; }
			}
		}

		return message;
	}

	function abortAllGetRequests() {
		__wcaActiveGetControllers.forEach( function( c ) {
			try {
				c.abort();
			} catch ( _ ) {}
		} );
		__wcaActiveGetControllers = [];
	}

	function isRequestsBlocked() {
		try {
			return !!window.__wcaBlockRequests;
		} catch ( _ ) {
			return false;
		}
	}

	function setRequestsBlocked( on ) {
		try {
			window.__wcaBlockRequests = !!on;
		} catch ( _ ) {}
		if ( on ) {
			abortAllGetRequests();
		}
	}

	/**
	 * Retry wrapper for API calls that handles 502/503 errors
	 * @param {Function} fetchFn - Function that returns a Promise
	 * @param {number} maxRetries - Maximum number of retry attempts (default: 2)
	 * @returns {Promise} Promise that resolves/rejects after retries
	 */
	function retryOnServerError( fetchFn, maxRetries ) {
		maxRetries = maxRetries || 2;
		var attempt = 0;

		function attemptRequest() {
			return fetchFn().catch( function( error ) {
				// Check if error is a 502 or 503
				var isRetryableError = false;
				var statusCode = null;

				if ( error && error.message ) {
					var statusMatch = error.message.match( /HTTP (\d+)/ );
					if ( statusMatch ) {
						statusCode = parseInt( statusMatch[ 1 ], 10 );
						isRetryableError = 502 === statusCode || 503 === statusCode;
					}
				}

				// Also check for network errors that might be retryable
				if ( !isRetryableError && error && 'TypeError' === error.name && error.message && ( error.message.includes( 'fetch' ) || error.message.includes( 'network' ) ) ) {
					isRetryableError = true;
				}

				// Don't retry if:
				// - Not a retryable error
				// - Request was aborted
				// - Max retries reached
				if ( !isRetryableError || 'AbortError' === error.name || attempt >= maxRetries ) {
					throw error;
				}

				// Increment attempt and retry with exponential backoff
				attempt += 1;
				var delay = Math.min( 500 * Math.pow( 2, attempt - 1 ), 2000 ); // 500ms, 1000ms, max 2000ms

				return new Promise( function( resolve ) {
					setTimeout( function() {
						resolve( attemptRequest() );
					}, delay );
				} );
			} );
		}

		return attemptRequest();
	}

	function apiGet( path, params ) {
		if ( isRequestsBlocked() ) {
			return Promise.reject( new Error( 'Blocked during drag/resize' ) );
		}

		// If REST is known to be unavailable, reject immediately
		if ( false === __wcaRestApiAvailable ) {
			return Promise.reject( new Error( 'REST API unavailable' ) );
		}

		var base = ( window.wcAppointmentsReactCalendar && window.wcAppointmentsReactCalendar.restUrl ) ? window.wcAppointmentsReactCalendar.restUrl : '';
		if ( '/' !== base.slice( -1 ) ) {
			base += '/';
		}
		var u = new URL( base + String( path ).replace( /^\//, '' ) );
		if ( params ) {
			var filter = params.filter;
			Object.keys( params ).forEach( function( k ) {
				if ( 'filter' === k ) {
					return;
				}
				var v = params[ k ];
				if ( undefined === v || null === v || '' === v ) {
					return;
				}
				if ( Array.isArray( v ) ) {
					// Check if array contains objects - if so, serialize as JSON
					var hasObjects = 0 < v.length && 'object' === typeof v[ 0 ] && null !== v[ 0 ] && !Array.isArray( v[ 0 ] );
					if ( hasObjects ) {
						// Serialize array of objects as JSON string
						u.searchParams.append( k, JSON.stringify( v ) );
					} else {
						// Handle simple arrays (strings, numbers, etc.)
						var key = ( 'include' === k ) ? 'include[]' : ( k.endsWith( '[]' ) ? k : ( k + '[]' ) );
						v.forEach( function( item ) {
							u.searchParams.append( key, item );
						} );
					}
				} else {
					u.searchParams.append( k, v );
				}
			} );
			if ( Array.isArray( filter ) ) {
				for ( var i = 0; i < filter.length; i++ ) {
					var f = filter[ i ] || {};
					if ( f.key !== undefined ) {
						u.searchParams.append( 'filter[' + i + '][key]', f.key );
					}
					if ( f.compare !== undefined ) {
						u.searchParams.append( 'filter[' + i + '][compare]', f.compare );
					}
					if ( f.value !== undefined ) {
						if ( Array.isArray( f.value ) ) {
							for ( var fi = 0; fi < f.value.length; fi++ ) {
								u.searchParams.append( 'filter[' + i + '][value][]', f.value[ fi ] );
							}
						} else {
							u.searchParams.append( 'filter[' + i + '][value]', f.value );
						}
					}
				}
			}
		}
		var controller = ( window && 'function' === typeof window.AbortController ) ? new window.AbortController() : null;
		var signal = controller ? controller.signal : undefined;
		if ( controller ) {
			__wcaActiveGetControllers.push( controller );
		}
		return retryOnServerError( function() {
			return fetch( u.toString(), {
				headers: {
					'X-WP-Nonce': wcAppointmentsReactCalendar.nonce
				},
				signal: signal
			} ).then( function( r ) {
				if ( controller ) {
					__wcaActiveGetControllers = __wcaActiveGetControllers.filter( function( c ) {
						return c !== controller;
					} );
				}
				if ( !r.ok ) {
					throw new Error( 'HTTP ' + r.status );
				}
				return r.json();
			} ).catch( function( e ) {
				if ( controller ) {
					__wcaActiveGetControllers = __wcaActiveGetControllers.filter( function( c ) {
						return c !== controller;
					} );
				}
				// Mark REST as unavailable on consistent failures
				if ( e && e.message && ( /Failed to fetch/i.test( e.message ) || /NetworkError/i.test( e.message ) ) ) {
					__wcaRestApiAvailable = false;
				}
				throw e;
			} );
		} );
	}

	async function apiGetAllAppointments( params ) {
		// Use the optimized calendar endpoint which returns all data in a single query
		// including order_info, customer_avatar, staff_avatar, staff_name, product_title
		// This eliminates N+1 queries and avoids separate API calls when opening modal
		var calendarParams = Object.assign( {}, params, {
			include_order_details: true,
			_fields: 'id,start,end,product_id,product_title,staff_id,staff_ids,staff_name,staff_avatar,status,customer_id,order_id,order_item_id,order_info,cost,all_day,qty,customer_status,customer_name,customer_first_name,customer_last_name,customer_full_name,customer_avatar,customer_email,customer_phone,cal_color'
		} );

		// The calendar endpoint returns all results in one call (no pagination needed)
		var results = await apiGet( 'wc-appointments/v2/calendar', calendarParams );
		return Array.isArray( results ) ? results : [];
	}

	// Determine if a [start_ts, end_ts] range spans full days (all-day block)
	function isAllDayRangeTs( start_ts, end_ts ) {
		var day = 86400;
		if ( !start_ts || !end_ts ) {
			return false;
		}
		var startDay = Math.floor( start_ts / day );
		var endDayInclusive = Math.floor( ( end_ts - 1 ) / day );
		var spansMultipleDays = endDayInclusive > startDay;
		if ( spansMultipleDays ) {
			return true;
		}
		var startsAtMidnight = 0 === ( start_ts % day );
		var coversFullSingleDay = startsAtMidnight && ( end_ts - start_ts ) >= ( day - 1 ) && endDayInclusive === startDay;
		return coversFullSingleDay;
	}

	function Toolbar( props ) {
		// Admin calendar toolbar: navigation, filters and view toggles
		var canManageOthers = props.canManageOthers;
		var productId = props.productId;
		var setProductId = props.setProductId;
		var staffId = props.staffId;
		var setStaffId = props.setStaffId;
		var title = props.title || '';
		var onPrev = props.onPrev;
		var onNext = props.onNext;
		var onToday = props.onToday;
		var changeView = props.changeView;
		var currentView = props.currentView || '';

		// Minimal async select (Select2-like) used for Product and Staff
		function AsyncSelectInner( opts ) {
			var value = opts.value || '';
			var onChange = opts.onChange || function() {};
			var placeholder = opts.placeholder || '';
			var search = opts.search; // async (query, page) => [{ id, text }]
			var resolveById = opts.resolveById; // async (id) => { id, text }
			var allowUnassigned = !!opts.allowUnassigned;
			var containerRef = useRef( null );
			var [ inputVal, setInputVal ] = useState( '' );
			var [ selectedLabel, setSelectedLabel ] = useState( '' );
			var [ selectedAvatar, setSelectedAvatar ] = useState( '' );
			var [ open, setOpen ] = useState( false );
			var [ loading, setLoading ] = useState( false );
			var [ items, setItems ] = useState( [] );
			var debounceRef = useRef( null );
			var openRef = useRef( false );

			useEffect( function() {
				var was = openRef.current;
				if ( open && !was ) {
					try {
						window.__wcaAsyncSelectOpen = ( window.__wcaAsyncSelectOpen || 0 ) + 1;
					} catch ( _ ) {}
				} else if ( !open && was ) {
					try {
						window.__wcaAsyncSelectOpen = Math.max( 0, ( window.__wcaAsyncSelectOpen || 0 ) - 1 );
					} catch ( _ ) {}
				}
				openRef.current = open;
			}, [ open ] );
			useEffect( function() {
				return function() {
					if ( openRef.current ) {
						try {
							window.__wcaAsyncSelectOpen = Math.max( 0, ( window.__wcaAsyncSelectOpen || 0 ) - 1 );
						} catch ( _ ) {}
					}
				};
			}, [] );

			// Resolve label for preselected value from querystring without loading full lists
			useEffect( function() {
				if ( !value ) {
					setSelectedLabel( '' );
					setSelectedAvatar( '' );
					return;
				}
				var v = String( value );
				if ( allowUnassigned && 'unassigned' === v ) {
					setSelectedLabel( t( 'unassigned', 'Unassigned' ) );
					setSelectedAvatar( '' );
					return;
				}
				var cancelled = false;
				if ( resolveById ) {
					resolveById( v ).then( function( row ) {
						if ( !cancelled ) {
							setSelectedLabel( ( row && ( row.text || row.name ) ) || ( '#' + v ) );
							setSelectedAvatar( ( row && row.avatar ) || '' );
						}
					} ).catch( function() {
						if ( !cancelled ) {
							setSelectedLabel( '#' + v );
							setSelectedAvatar( '' );
						}
					} );
				}
				return function() {
					cancelled = true;
				};
			}, [ value ] );

			// Close dropdown on outside click
			useEffect( function() {
				function onDocClick( e ) {
					try {
						if ( !containerRef.current ) {
							return;
						}
						if ( !containerRef.current.contains( e.target ) ) {
							setOpen( false );
						}
					} catch ( _ ) {}
				}
				document.addEventListener( 'click', onDocClick );
				return function() {
					try {
						document.removeEventListener( 'click', onDocClick );
					} catch ( e ) {}
				};
			}, [] );

			function triggerSearch( q, nextPage ) {
				if ( !search ) {
					return;
				}
				setLoading( true );
				search( q || '', nextPage || 1 ).then( function( list ) {
					setItems( ensureArray( list ) );
					setLoading( false );
				} ).catch( function() {
					setItems( [] );
					setLoading( false );
				} );
			}

			function onInput( e ) {
				var q = e.target.value || '';
				setInputVal( q );
				setOpen( true );
				if ( debounceRef.current ) {
					try {
						clearTimeout( debounceRef.current );
					} catch ( _ ) {}
				}
				debounceRef.current = setTimeout( function() {
					triggerSearch( q, 1 );
				}, 250 );
			}

			function onFocus() {
				setOpen( true );
				if ( !items.length ) {
					triggerSearch( '', 1 );
				}
			}

			function onClear() {
				setInputVal( '' );
				setSelectedLabel( '' );
				setSelectedAvatar( '' );
				onChange( '' );
				setOpen( false );
			}

			function choose( it ) {
				if ( !it ) {
					return;
				}
				setSelectedLabel( it.text || '' );
				setSelectedAvatar( it.avatar || '' );
				setInputVal( '' );
				onChange( it.id );
				setOpen( false );
			}

			// Display value: selection label when not typing; input text when typing
			var shownVal = open ? inputVal : ( selectedLabel || '' );

			return h( 'div',
				{
					className: 'wca-async-select',
					ref: containerRef
				},
				h( 'div',
					{
						className: 'wca-async-select__control' + ( open ? ' is-open' : '' )
					},

					( selectedAvatar && !open ? h( 'span', { className: 'wca-async-select__selected-avatar' },
						h( 'img', { className: 'wca-avatar', src: selectedAvatar, alt: '' } )
					) : null ),
					h( 'input',
						{
							type: 'text',
							value: shownVal,
							placeholder: placeholder,
							onChange: onInput,
							onFocus: onFocus
						}
					),
					( value ? h( 'button',
						{
							type: 'button',
							className: 'wca-async-select__clear',
							title: t( 'clear', 'Clear' ),
							onClick: onClear
						}, '×'
					) : null ),
					h( 'span',
						{
							className: 'wca-async-select__caret',
							onClick: function() {
								setOpen( true );
								if ( !items.length ) {
									triggerSearch( '', 1 );
								}
							}
						}, '▾'
					)
				),
				open ? h( 'div',
					{
						className: 'wca-async-select__menu'
					},
					loading ? h( 'div',
						{
							className: 'wca-async-select__loading'
						}, t( 'loading', 'Loading…' )
					) : ( (
						function() {
							var list = items.slice().sort( sortByText );
							if ( allowUnassigned ) {
								list = [ {
									id: 'unassigned',
									text: t( 'unassigned', 'Unassigned' )
								} ].concat( list );
							}
							if ( !list.length ) {
								return h( 'div',
									{
										className: 'wca-async-select__empty'
									}, t( 'typeToSearch', 'Type to search' )
								);
							}
							return h( 'div',
								{
									className: 'wca-async-select__list'
								}, list.map( function( it ) {
									return h( 'div',
										{
											key: String( it.id || it.text || Math.random() ),
											className: 'wca-async-select__option',
											onMouseDown: function( e ) {
												e.preventDefault();
											},
											onClick: function() {
												choose( it );
											}
										},
										h( 'span', { className: 'wca-async-select__option-avatar' },
											it.avatar ? h( 'img', { className: 'wca-avatar', src: it.avatar, alt: '' } ) : null
										),
										h( 'span', { className: 'wca-async-select__option-label' }, String( it.text || '' ) )
									);
								} )
							);
						}
					)() )
				) : null
			);
		}

		var AsyncSelect = useRef( AsyncSelectInner ).current;

		// Product search/resolve helpers
		function searchProducts( query, page ) {
			var params = {
				type: 'appointment',
				status: 'publish',
				per_page: 20,
				page: page || 1
			};
			if ( query ) {
				params.search = query;
			}
			return apiGet( 'wc-appointments/v1/products', params ).then( function( items ) {
				var mapped = ( items || [] ).map( function( p ) {
					return {
						id: String( p.id ),
						text: p.name || ( '#' + p.id )
					};
				} );
				mapped.sort( sortByText );
				return mapped;
			} );
		}

		function resolveProductById( id ) {
			return apiGet( 'wc-appointments/v1/products', {
				include: [ id ],
				per_page: 1
			} ).then( function( items ) {
				var p = ( Array.isArray( items ) && items[ 0 ] ) || null;
				return p ? {
					id: String( p.id ),
					text: p.name || ( '#' + p.id )
				} : {
					id: String( id ),
					text: '#' + String( id )
				};
			} );
		}

		// Staff search/resolve helpers
		function searchStaff( query, page ) {
			var params = {
				per_page: 20,
				page: page || 1
			};
			if ( query ) {
				params.search = query;
			}
			// Performance optimization: Only fetch the fields we need (name and avatar)
			// This avoids loading expensive availability and products data
			var optimizedParams = Object.assign( {}, params, {
				_fields: 'id,display_name,full_name,avatar'
			} );
			return apiGet( 'wc-appointments/v1/staff', optimizedParams ).then( function( items ) {
				var mapped = ( items || [] ).map( function( s ) {
					var nm = s.full_name || s.display_name || ( '#' + s.id );
					return {
						id: String( s.id ),
						text: nm,
						avatar: s.avatar || ''
					};
				} );
				mapped.sort( sortByText );
				return mapped;
			} );
		}

		function resolveStaffById( id ) {
			if ( 'unassigned' === String( id ) ) {
				return Promise.resolve( {
					id: 'unassigned',
					text: t( 'unassigned', 'Unassigned' )
				} );
			}
			// Performance optimization: Only fetch the fields we need (name and avatar)
			// This avoids loading expensive availability and products data
			return apiGet( 'wc-appointments/v1/staff', {
				include: [ id ],
				per_page: 1,
				_fields: 'id,display_name,full_name,avatar'
			} ).then( function( items ) {
				var s = ( Array.isArray( items ) && items[ 0 ] ) || null;
				var nm = s ? ( s.full_name || s.display_name || ( '#' + s.id ) ) : ( '#' + String( id ) );
				return {
					id: String( id ),
					text: nm,
					avatar: s && s.avatar ? s.avatar : ''
				};
			} );
		}

		return h( 'div',
			{
				className: 'wca-react-toolbar'
			},
			// Left nav + title
			h( 'div',
				{
					className: 'wca-toolbar-left'
				},
				h( 'button',
					{
						className: 'button',
						onClick: function() {
							if ( onPrev ) {
								onPrev();
							}
						},
						title: t( 'prev', 'Prev' ),
						'aria-label': t( 'prev', 'Prev' )
					}, '‹'
				),
				h( 'button',
					{
						className: 'button',
						onClick: function() {
							if ( onToday ) {
								onToday();
							}
						}
					}, t( 'today', 'Today' )
				),
				h( 'button',
					{
						className: 'button',
						onClick: function() {
							if ( onNext ) {
								onNext();
							}
						},
						title: t( 'next', 'Next' ),
						'aria-label': t( 'next', 'Next' )
					}, '›'
				),
				h( 'span',
					{
						className: 'wca-toolbar-title'
					}, title || ''
				)
			),
			// Filters
			h( 'div',
				{
					className: 'wca-toolbar-filters'
				},
				h( AsyncSelect, {
					value: productId || '',
					placeholder: t( 'allProducts', 'All Products' ),
					resolveById: resolveProductById,
					search: searchProducts,
					onChange: function( val ) {
						try {
							window.wcAppointmentsReactCalendar.productId = val || '';
						} catch ( _ ) {}
						setProductId( val || '' );
					}
				} ),
				canManageOthers ? h( AsyncSelect,
					{
						value: staffId || '',
						placeholder: t( 'allStaff', 'All Staff' ),
						allowUnassigned: true,
						resolveById: resolveStaffById,
						search: searchStaff,
						onChange: function( val ) {
							try {
								window.wcAppointmentsReactCalendar.staffId = val || '';
							} catch ( _ ) {}
							setStaffId( val || '' );
						}
					}
				) : null
			),
			// View buttons
			h( 'div',
				{
					className: 'wca-toolbar-views'
				},
				h( 'button',
					{
						className: 'button' + ( 'dayGridMonth' === currentView ? ' is-current' : '' ),
						onClick: function() {
							changeView( 'dayGridMonth' );
						}
					}, t( 'month', 'Month' )
				),
				h( 'button',
					{
						className: 'button' + ( 'timeGridWeek' === currentView ? ' is-current' : '' ),
						onClick: function() {
							changeView( 'timeGridWeek' );
						}
					}, t( 'week', 'Week' )
				),
				h( 'button',
					{
						className: 'button' + ( 'timeGridDay' === currentView ? ' is-current' : '' ),
						onClick: function() {
							changeView( 'timeGridDay' );
						}
					}, t( 'day', 'Day' )
				),
				h( 'button',
					{
						className: 'button' + ( 'listWeek' === currentView ? ' is-current' : '' ),
						onClick: function() {
							changeView( 'listWeek' );
						}
					}, t( 'list', 'List' )
				)
			)
		);
	}

	function AppointmentModal( props ) {
		// Read-only details modal for a selected appointment
		var appt = props.appt || {};
		var products = props.products || [];
		var staffList = props.staff || [];
		var start = appt.start_ts ? new Date( appt.start_ts * 1000 ) : null;
		var end = appt.end_ts ? new Date( appt.end_ts * 1000 ) : null;
		var useState = wp.element.useState;
		var useEffect = wp.element.useEffect;
		var [ customer, setCustomer ] = useState( null );
		var [ orderInfo, setOrderInfo ] = useState( null );
		var staffIdStr = ( appt && null != appt.staff_id ) ? String( appt.staff_id ) : '';
		var [ resolvedStaffName, setResolvedStaffName ] = useState( '' );
		var [ resolvedProductName, setResolvedProductName ] = useState( '' );
		var [ productDetails, setProductDetails ] = useState( null );
		var [ customerAvatar, setCustomerAvatar ] = useState( '' );
		var [ staffAvatar, setStaffAvatar ] = useState( '' );
		var [ localStatus, setLocalStatus ] = useState( String( ( appt && appt.status ) || '' ).toLowerCase() );
		var [ isUpdating, setIsUpdating ] = useState( false );

		var product = null;
		for ( var j0 = 0; j0 < products.length; j0++ ) {
			if ( String( products[ j0 ].id ) === String( appt.product_id ) ) {
				product = products[ j0 ];
				break;
			}
		}
		// productName computed label is unused; rely on order item/product resolved names
		var orderItemName = '';
		if ( orderInfo && appt && appt.order_item_id && Array.isArray( orderInfo.line_items ) ) {
			for ( var j1 = 0; j1 < orderInfo.line_items.length; j1++ ) {
				var it0 = orderInfo.line_items[ j1 ];
				if ( String( it0 && it0.id ) === String( appt.order_item_id ) ) {
					orderItemName = it0 && it0.name ? String( it0.name ) : '';
					break;
				}
			}
		}
		var nameForLabel = ( ( product && product.name ) || resolvedProductName || orderItemName );
		var durationUnit = ( productDetails && productDetails.duration_unit ) ? String( productDetails.duration_unit ) : ( product && product.duration_unit ? String( product.duration_unit ) : '' );
		var durationVal = ( productDetails && productDetails.duration ) ? parseInt( productDetails.duration, 10 ) : ( product && product.duration ? parseInt( product.duration, 10 ) : 0 );
		var adminPost = wcAppointmentsReactCalendar.adminPost || '';
		var adminBase = adminPost.replace( /post\.php.*/, '' );

		useEffect( function() {
			if ( !staffIdStr || '0' === staffIdStr ) {
				return;
			}
			// Use preloaded staff_name if available and truthy from optimized calendar API
			if ( appt.staff_name ) {
				setResolvedStaffName( appt.staff_name );
				return;
			}
			// Check local staffList
			var foundLocal = false;
			for ( var j = 0; j < staffList.length; j++ ) {
				if ( String( staffList[ j ].id ) === staffIdStr ) {
					var nm0 = staffList[ j ].full_name || staffList[ j ].display_name || '';
					if ( nm0 ) {
						setResolvedStaffName( nm0 );
					}
					foundLocal = true;
					break;
				}
			}
			if ( foundLocal ) {
				return;
			}
			// If staff_name key exists in appt (even if empty), data was preloaded - skip API
			if ( 'staff_name' in appt ) {
				// Truthy check failed, local check failed, but key exists -> Staff name is empty/unknown.
				// Set a placeholder to remove skeleton.
				setResolvedStaffName( t( 'unknown_staff', 'Staff' ) + ' #' + staffIdStr );
				return;
			}
			var cancelled = false;
			// Performance optimization: Only fetch the fields we need (name fields only)
			// This avoids loading expensive availability and products data
			apiGet( 'wc-appointments/v1/staff', {
				include: [ staffIdStr ],
				per_page: 1,
				_fields: 'id,display_name,full_name'
			} ).then( function( items ) {
				if ( cancelled ) {
					return;
				}
				var s = ( Array.isArray( items ) && items[ 0 ] ) || null;
				var nm = s ? ( s.full_name || s.display_name || '' ) : '';
				if ( nm ) {
					setResolvedStaffName( nm );
				}
			} ).catch( function() {} );
			return function() {
				cancelled = true;
			};
		}, [ staffIdStr, appt.staff_name, staffList ] );

		useEffect( function() {
			var pidStr = ( appt && null != appt.product_id ) ? String( appt.product_id ) : '';
			if ( !pidStr ) {
				return;
			}
			// Use preloaded product_title if available and truthy from optimized calendar API
			if ( appt.product_title ) {
				setResolvedProductName( appt.product_title );
				return;
			}
			// Check local products array
			var foundLocal = false;
			for ( var i = 0; i < products.length; i++ ) {
				if ( String( products[ i ].id ) === pidStr ) {
					var nm1 = products[ i ].name || '';
					if ( nm1 ) {
						setResolvedProductName( nm1 );
					}
					foundLocal = true;
					break;
				}
			}
			if ( foundLocal ) {
				return;
			}
			// If product_title key exists in appt (even if empty), data was preloaded - skip API
			if ( 'product_title' in appt ) {
				return;
			}
			var cancelled = false;
			apiGet( 'wc-appointments/v1/products', {
				include: [ pidStr ],
				per_page: 1
			} ).then( function( items ) {
				if ( cancelled ) {
					return;
				}
				var p = ( Array.isArray( items ) && items[ 0 ] ) || null;
				var nm = p ? ( p.name || '' ) : '';
				if ( nm ) {
					setResolvedProductName( nm );
				}
				if ( p ) {
					setProductDetails( p );
				}
			} ).catch( function() {} );
			return function() {
				cancelled = true;
			};
		}, [ appt.product_id, appt.product_title, products ] );

		function formatDuration( start, end, unit, val ) {
			if ( 'day' === unit ) {
				var dayMs = 86400 * 1000;
				var d = Math.max( 1, Math.ceil( ( end - start ) / dayMs ) );
				return d + ' ' + ( 1 === d ? t( 'durationDay', 'day' ) : t( 'durationDays', 'days' ) );
			}
			if ( 'month' === unit ) {
				var m = Math.max( 1, val || 1 );
				return m + ' ' + ( 1 === m ? t( 'durationMonth', 'month' ) : t( 'durationMonths', 'months' ) );
			}
			var isAllDay = isAllDayRangeTs( appt.start_ts || 0, appt.end_ts || 0 );
			if ( !unit && isAllDay ) {
				var dayMs2 = 86400 * 1000;
				var d2 = Math.max( 1, Math.ceil( ( end - start ) / dayMs2 ) );
				return d2 + ' ' + ( 1 === d2 ? t( 'durationDay', 'day' ) : t( 'durationDays', 'days' ) );
			}
			var mins = start && end ? Math.max( 0, Math.round( ( end - start ) / 60000 ) ) : 0;
			if ( 60 <= mins && 0 === mins % 60 ) {
				var hrs = Math.round( mins / 60 );
				return hrs + ' ' + ( 1 === hrs ? t( 'durationHour', 'hour' ) : t( 'durationHours', 'hours' ) );
			}
			if ( 60 <= mins ) {
				var h = Math.floor( mins / 60 );
				var mm = mins % 60;
				return h + ' ' + ( 1 === h ? t( 'durationHr', 'hr' ) : t( 'durationHrs', 'hrs' ) ) + ' ' + mm + ' ' + t( 'durationMin', 'min' );
			}
			return mins + ' ' + t( 'durationMin', 'min' );
		}

		function formatRange( start, end, unit ) {
			if ( !start || !end ) {
				return '';
			}
			var dateFmt = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.dateFormat ) ? wcAppointmentsReactCalendar.dateFormat : 'F j, Y';
			var timeFmt = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.timeFormat ) ? wcAppointmentsReactCalendar.timeFormat : 'g:i a';
			var showDaysOnly = ( 'day' === unit || 'month' === unit ) || ( !unit && isAllDayRangeTs( appt.start_ts || 0, appt.end_ts || 0 ) );
			if ( showDaysOnly ) {
				var endInclusive = new Date( ( ( appt.end_ts || 0 ) - 1 ) * 1000 );
				var s = formatDateByPattern( start, dateFmt );
				var e = formatDateByPattern( endInclusive, dateFmt );
				if ( 'month' === unit && 1 === durationVal ) {
					return s;
				}
				if ( 'day' === unit ) {
					return ( s === e ) ? s : ( s + ' – ' + e );
				}
				return ( s === e ) ? s : ( s + ' – ' + e );
			}
			var sDate = formatDateByPattern( start, dateFmt );
			var eDate = formatDateByPattern( end, dateFmt );
			var t1 = formatTimeByPattern( start, timeFmt );
			var t2 = formatTimeByPattern( end, timeFmt );
			var sameDay = sDate === eDate;
			if ( sameDay ) {
				return sDate + ', ' + t1 + ' – ' + t2;
			}
			return sDate + ', ' + t1 + ' – ' + eDate + ', ' + t2;
		}

		var durationText = ( start && end ) ? formatDuration( start, end, durationUnit, durationVal ) : '';
		var rangeText = ( start && end ) ? formatRange( start, end, durationUnit ) : '';

		useEffect( function() {
			if ( appt && ( appt.customer_first_name || appt.customer_last_name ) ) {
				setCustomer( {
					first_name: appt.customer_first_name || '',
					last_name: appt.customer_last_name || ''
				} );
			}
		}, [ appt.customer_first_name, appt.customer_last_name ] );

		// Performance optimization: Use preloaded data from calendar API when available
		// Falls back to API calls only if data is not preloaded
		useEffect( function() {
			var promises = [];
			var cancelled = false;

			// Order info handling:
			// - Preloaded order_info contains basic fields AND line_items for addons (added in recent update)
			// - Use preloaded data if available to avoid API calls
			// - If product_title is present, it's a V2 response, so we trust it (even if order_info is missing/null)
			var isV2Response = 'order_info' in appt || 'product_title' in appt;

			if ( isV2Response ) {
				if ( appt.order_info ) {
					setOrderInfo( appt.order_info );
				} else if ( appt.order_id ) {
					apiGet( 'wc-appointments/v2/orders/' + String( appt.order_id ) )
						.then( function( data ) {
							if ( !cancelled ) {
								setOrderInfo( data || null );
							}
						} )
						.catch( function() {
							if ( !cancelled ) {
								setOrderInfo( null );
							}
						} );
				}
			} else if ( appt.order_id ) {
				// Only fallback if not a V2 response (legacy/cached response)
				var orderPromise = apiGet( 'wc-appointments/v2/orders/' + String( appt.order_id ), {
					_fields: 'id,customer_email,customer_phone,currency,total,discount_total,discount_tax,coupon_lines,billing,line_items'
				} )
					.then( function( data ) {
						if ( !cancelled ) {
							setOrderInfo( data || null );
						}
					} )
					.catch( function() {
						/* ignore */
					} );
				promises.push( orderPromise );
			}

			// Use preloaded customer_avatar if available, otherwise fetch from API
			// Check for key existence (not truthy) since empty string means "loaded but no avatar"
			if ( 'customer_avatar' in appt ) {
				// Avatar URL already preloaded from optimized calendar API
				setCustomerAvatar( appt.customer_avatar || '' );
			} else {
				var raw = ( appt && null != appt.customer_id ) ? appt.customer_id : '';
				var uidNum = parseInt( raw, 10 );
				if ( uidNum && uidNum > 0 ) {
					var uid = String( uidNum );
					var customerAvatarPromise = apiGet( 'wp/v2/users/' + uid, {
						context: 'edit',
						_fields: 'id,avatar_urls'
					} ).then( function( u ) {
						if ( !cancelled ) {
							var av = ( u && u.avatar_urls ) ? ( u.avatar_urls['48'] || u.avatar_urls['96'] || u.avatar_urls['24'] || '' ) : '';
							setCustomerAvatar( av || '' );
						}
					} ).catch( function() {} );
					promises.push( customerAvatarPromise );
				} else {
					setCustomerAvatar( '' );
				}
			}

			// Use preloaded staff_avatar if available, otherwise fetch from API
			// Check for key existence (not truthy) since empty string means "loaded but no avatar"
			if ( 'staff_avatar' in appt ) {
				// Avatar URL already preloaded from optimized calendar API
				setStaffAvatar( appt.staff_avatar || '' );
			} else {
				var sidNum = parseInt( staffIdStr, 10 );
				if ( sidNum && sidNum > 0 ) {
					var sid = String( sidNum );
					var staffAvatarPromise = apiGet( 'wp/v2/users/' + sid, {
						context: 'edit',
						_fields: 'id,avatar_urls'
					} ).then( function( u ) {
						if ( !cancelled ) {
							var av = ( u && u.avatar_urls ) ? ( u.avatar_urls['48'] || u.avatar_urls['96'] || u.avatar_urls['24'] || '' ) : '';
							setStaffAvatar( av || '' );
						}
					} ).catch( function() {} );
					promises.push( staffAvatarPromise );
				} else {
					setStaffAvatar( '' );
				}
			}

			// Execute remaining API calls in parallel (only if not preloaded)
			if ( promises.length > 0 ) {
				Promise.all( promises ).catch( function() {
					// Individual errors are already handled, this is just for cleanup
				} );
			}

			return function() {
				cancelled = true;
			};
		}, [ appt.order_id, appt.order_info, appt.customer_id, appt.customer_avatar, appt.staff_avatar, staffIdStr ] );

		var firstName = ( appt && appt.customer_first_name ) ? appt.customer_first_name : ( ( customer && customer.first_name ) ? customer.first_name : ( ( orderInfo && orderInfo.billing && orderInfo.billing.first_name ) ? orderInfo.billing.first_name : '' ) );
		var lastName = ( appt && appt.customer_last_name ) ? appt.customer_last_name : ( ( customer && customer.last_name ) ? customer.last_name : ( ( orderInfo && orderInfo.billing && orderInfo.billing.last_name ) ? orderInfo.billing.last_name : '' ) );
		var fullName = ( firstName || lastName ) ? ( ( firstName + ' ' + lastName ).trim() ) : '';
		var displayName = fullName || ( ( customer && customer.name ) ? customer.name : ( appt.customer_name || ( appt.customer_id ? ( '#' + appt.customer_id ) : t( 'guest', 'Guest' ) ) ) );
		var custStatus = appt.customer_status || '';
		var currencySymbols = ( i18n && i18n.currencySymbols ) || {};
		var currencyFormat = ( i18n && i18n.currencyFormat ) || {};
		var currencySymbol = ( orderInfo && currencySymbols[ orderInfo.currency ] ) || '';
		var toNum = function( v ) { var n = parseFloat( v ); return isNaN( n ) ? 0 : n; };
		var formatNumber = function( val, decimals, decSep, thouSep ) {
			var n = Number( val );
			if ( isNaN( n ) ) { n = 0; }
			var fixed = n.toFixed( 'number' === typeof decimals ? decimals : 2 );
			var parts = fixed.split( '.' );
			var integer = parts[ 0 ];
			var frac = 1 < parts.length ? parts[ 1 ] : '';
			var rgx = /(\d+)(\d{3})/;
			while ( rgx.test( integer ) ) {
				integer = integer.replace( rgx, '$1' + ( thouSep || ',' ) + '$2' );
			}
			return frac ? integer + ( decSep || '.' ) + frac : integer;
		};
		var fmt = function( v ) {
			var decs = ( 'number' === typeof currencyFormat.priceDecimals ) ? currencyFormat.priceDecimals : 2;
			var num = formatNumber( v, decs, currencyFormat.decimalSeparator, currencyFormat.thousandSeparator );
			var pos = String( currencyFormat.currencyPosition || 'left' );
			var space = ( 'left_space' === pos || 'right_space' === pos ) ? ' ' : '';
			if ( currencySymbol ) {
				switch ( pos ) {
					case 'right': return num + space + currencySymbol;
					case 'right_space': return num + space + currencySymbol;
					case 'left_space': return currencySymbol + space + num;
					case 'left':
					default: return currencySymbol + space + num;
				}
			}
			return num;
		};
		var apptLineItem = null;
		var actionsChildren = isUpdating ? h( 'span', { className: 'wca-skeleton' } ) : ( function() {
			var slug = String( localStatus || '' ).toLowerCase();
			var hs = humanizeStatus( slug );
			return hs ? h( 'mark', { className: 'wca-status status-' + slug }, hs ) : h( 'span', { className: 'wca-skeleton' } );
		} )();
		var headerRow = h( 'div',
			{
				className: 'wca-modal-header-row'
			},
			h( 'div',
				{
					className: 'wca-modal-header-main'
				},
				h( 'h1', { className: 'wca-appointment-title' }, t( 'appointment', 'Appointment' ) + ' #' + String( appt.id || '' ) )
			),
			h( 'div',
				{
					className: 'wca-modal-header-status'
				},
				actionsChildren
			)
		);

		function humanizeStatus( s ) {
			if ( !s ) {
				return '';
			}
			var map = ( i18n && i18n.statuses ) || {};
			if ( map[ s ] ) {
				return map[ s ];
			}
			return String( s ).split( '-' ).map( function( w ) {
				return w.charAt( 0 ).toUpperCase() + w.slice( 1 );
			} ).join( ' ' );
		}

		var addons = [];
		if ( orderInfo && appt && appt.order_item_id && Array.isArray( orderInfo.line_items ) ) {
			for ( var jj = 0; jj < orderInfo.line_items.length; jj++ ) {
				var it = orderInfo.line_items[ jj ];
				if ( String( it && it.id ) === String( appt.order_item_id ) ) {
					apptLineItem = it;
					break;
				}
			}
			if ( apptLineItem && Array.isArray( apptLineItem.meta_data ) ) {
				apptLineItem.meta_data.forEach( function( m ) {
					if ( !m ) {
						return;
					}
					var keyRaw = m.key;
					var keyDisp = m.display_key;
					var key = keyDisp || keyRaw;
					if ( !key || '_' === String( key ).charAt( 0 ) ) {
						return;
					}
					var keyLc = String( keyRaw || key ).toLowerCase();
					var keyDispLc = String( keyDisp || '' ).toLowerCase();
					if ( 'appointment_id' === keyLc || 'appointment id' === keyDispLc ) {
						return;
					}
					var val = ( null != m.display_value ) ? m.display_value : m.value;
					if ( val === undefined || null === val || '' === val ) {
						return;
					}
					var valStr = Array.isArray( val ) ? val.join( ', ' ) : ( 'object' === typeof val ? JSON.stringify( val ) : String( val ) );
					addons.push( {
						k: String( key ),
						v: valStr
					} );
				} );
			}
		}

		// Build display rows: alternating Key cell then Value cell to match 2-column grid
		var gridChildren = [];

		// Context object for hooks - provides utilities and data to filter callbacks
		var hookContext = {
			h: h,
			t: t,
			i18n: i18n,
			appt: appt,
			orderInfo: orderInfo,
			staffList: staffList,
			products: products,
			adminBase: adminBase,
			adminPost: adminPost,
			customerAvatar: customerAvatar,
			staffAvatar: staffAvatar,
			displayName: displayName,
			resolvedStaffName: resolvedStaffName,
			rangeText: rangeText,
			durationText: durationText
		};

		function row( label, valueEl, key ) {
			gridChildren.push( h( 'div', {
				className: 'wca-key',
				key: ( key || label ) + '-k'
			}, label ) );
			gridChildren.push( h( 'div', {
				className: 'wca-val',
				key: ( key || label ) + '-v'
			}, valueEl ) );
		}

		// Customer summary row at top of body
		( function() {
			var uid = appt && appt.customer_id ? String( appt.customer_id ) : '';
			var link = uid ? ( adminBase + 'user-edit.php?user_id=' + uid ) : '';
			var avatarNode = link ? h( 'a', { key: 'avatar', href: link, className: 'wca-avatar-link', title: t( 'editCustomer', 'Edit Customer' ), 'aria-label': t( 'editCustomer', 'Edit Customer' ) },
				customerAvatar ? h( 'img', { className: 'wca-avatar', src: customerAvatar, alt: '' } ) : h( 'span', { className: 'wca-avatar-skeleton' } )
			) : null;
			function humanizeCustomerStatusBody( s ) {
				if ( !s ) { return ''; }
				var map = ( i18n && ( i18n.customer_statuses || i18n.customerStatuses || i18n.customer_status || i18n.customerStatusesMap ) ) || {};
				if ( map[ s ] ) { return map[ s ]; }
				return String( s ).split( '-' ).map( function( w ) { return w.charAt( 0 ).toUpperCase() + w.slice( 1 ); } ).join( ' ' );
			}
			var custLabel = humanizeCustomerStatusBody( custStatus );
			var nameNode = link && displayName ? h( 'a', { key: 'name', href: link, className: 'wca-customer-name' }, displayName ) : h( 'span', { key: 'name', className: 'wca-customer-name' }, displayName || h( 'span', { className: 'wca-skeleton' } ) );
			var badgeNode = custLabel ? h( 'mark', { key: 'cust-status', className: 'wca-badge' }, custLabel ) : null;

			// Customer contact icons (email/phone) - check multiple sources
			var customerEmail = ( orderInfo && orderInfo.billing && orderInfo.billing.email ) ||
							  ( customer && customer.email ) ||
							  ( appt && appt.customer_email ) ||
							  '';
			var customerPhone = ( orderInfo && orderInfo.billing && orderInfo.billing.phone ) ||
							   ( customer && customer.phone ) ||
							   ( appt && appt.customer_phone ) ||
							   '';

			var emailIconNode = customerEmail ? h( 'a', {
				key: 'email-icon',
				href: 'mailto:' + customerEmail,
				className: 'wca-contact-icon wca-contact-email',
				title: customerEmail,
				'aria-label': t( 'emailCustomer', 'Email customer' ) + ': ' + customerEmail
			}, h( 'span', { className: 'dashicons dashicons-email' } ) ) : null;

			var phoneIconNode = customerPhone ? h( 'a', {
				key: 'phone-icon',
				href: 'tel:' + customerPhone.replace( /[^\d+]/g, '' ),
				className: 'wca-contact-icon wca-contact-phone',
				title: customerPhone,
				'aria-label': t( 'callCustomer', 'Call customer' ) + ': ' + customerPhone
			}, h( 'span', { className: 'dashicons dashicons-phone' } ) ) : null;

			var contactIcons = ( emailIconNode || phoneIconNode ) ? h( 'span', { key: 'contact-icons', className: 'wca-contact-icons' }, emailIconNode, phoneIconNode ) : null;

			var summary = h( 'div', { className: 'wca-customer-summary' }, avatarNode, nameNode, contactIcons, badgeNode );

			// Build customer section rows and apply filter
			var customerRows = [ { label: t( 'customer', 'Customer' ), value: summary, key: 'customer' } ];
			customerRows = wcaModalHooks.applyFilters( 'appointment_modal_customer_section', customerRows, appt, hookContext );
			customerRows.forEach( function( r ) { row( r.label, r.value, r.key ); } );

			// Insertion point after customer section
			var afterCustomer = wcaModalHooks.getContent( 'appointment_modal_after_customer', appt, hookContext );
			if ( afterCustomer && afterCustomer.length ) {
				afterCustomer.forEach( function( el, idx ) {
					gridChildren.push( h( 'div', { key: 'after-customer-' + idx, className: 'wca-custom-content wca-full-width' }, el ) );
				} );
			}

			// Schedule section (when/duration)
			var scheduleRows = [];
			if ( rangeText ) {
				scheduleRows.push( { label: t( 'when', 'When' ), value: rangeText, key: 'when' } );
			}
			if ( durationText ) {
				scheduleRows.push( { label: t( 'duration', 'Duration' ), value: durationText, key: 'duration' } );
			}
			scheduleRows = wcaModalHooks.applyFilters( 'appointment_modal_schedule_section', scheduleRows, appt, hookContext );
			scheduleRows.forEach( function( r ) { row( r.label, r.value, r.key ); } );

			// Insertion point after schedule section
			var afterSchedule = wcaModalHooks.getContent( 'appointment_modal_after_schedule', appt, hookContext );
			if ( afterSchedule && afterSchedule.length ) {
				afterSchedule.forEach( function( el, idx ) {
					gridChildren.push( h( 'div', { key: 'after-schedule-' + idx, className: 'wca-custom-content wca-full-width' }, el ) );
				} );
			}

			// Staff section
			( function() {
				var sid = staffIdStr;
				if ( '' === sid || '0' === sid ) {
					return;
				}
				var sObj = null;
				for ( var k = 0; k < staffList.length; k++ ) {
					if ( String( staffList[ k ].id ) === sid ) {
						sObj = staffList[ k ];
						break;
					}
				}
				var baseName = sObj ? ( sObj.full_name || sObj.display_name || '' ) : '';
				var hasName = !!( resolvedStaffName || baseName );
				var sName = hasName ? ( resolvedStaffName || baseName ) : '';
				var staffHref = adminBase + 'user-edit.php?user_id=' + sid;
				var staffNameNode = hasName ? h( 'a', { key: 'name', href: staffHref }, sName ) : h( 'span', { key: 'name', className: 'wca-skeleton' } );
				var staffAvatarNode = h( 'a', { key: 'avatar', href: staffHref, className: 'wca-avatar-link', 'aria-label': t( 'staff', 'Staff' ) },
					staffAvatar ? h( 'img', { className: 'wca-avatar', src: staffAvatar, alt: '' } ) : h( 'span', { className: 'wca-avatar-skeleton' } )
				);
				var staffSummary = h( 'div', { className: 'wca-staff-summary' }, staffAvatarNode, staffNameNode );

				// Build staff section rows and apply filter
				var staffRows = [ { label: t( 'staff', 'Staff' ), value: staffSummary, key: 'staff' } ];
				staffRows = wcaModalHooks.applyFilters( 'appointment_modal_staff_section', staffRows, appt, hookContext );
				staffRows.forEach( function( r ) { row( r.label, r.value, r.key ); } );

				// Insertion point after staff section
				var afterStaff = wcaModalHooks.getContent( 'appointment_modal_after_staff', appt, hookContext );
				if ( afterStaff && afterStaff.length ) {
					afterStaff.forEach( function( el, idx ) {
						gridChildren.push( h( 'div', { key: 'after-staff-' + idx, className: 'wca-custom-content wca-full-width' }, el ) );
					} );
				}
			} )();
		} )();

		var renderItemsArea = !!( orderInfo && appt && appt.order_item_id );

		// Addons section - filterable
		if ( addons && addons.length ) {
			var addonsBlock = h( 'div', {
				className: 'wca-addons'
			}, addons.map( function( a, idx ) {
				return h( 'div', {
					key: 'adline-' + idx
				}, a.k + ': ' + a.v );
			} ) );

			// Build addons section rows and apply filter
			var addonsRows = [ { label: t( 'addons', 'Add-ons' ), value: addonsBlock, key: 'addons' } ];
			addonsRows = wcaModalHooks.applyFilters( 'appointment_modal_addons_section', addonsRows, appt, hookContext );
			addonsRows.forEach( function( r ) { row( r.label, r.value, r.key ); } );

			// Insertion point after addons
			var afterAddons = wcaModalHooks.getContent( 'appointment_modal_after_addons', appt, hookContext );
			if ( afterAddons && afterAddons.length ) {
				afterAddons.forEach( function( el, idx ) {
					gridChildren.push( h( 'div', { key: 'after-addons-' + idx, className: 'wca-custom-content wca-full-width' }, el ) );
				} );
			}
		}

		var itemsNode = null;
		if ( renderItemsArea ) {
			var itemName = ( apptLineItem && apptLineItem.name ) ? String( apptLineItem.name ) : ( nameForLabel || '' );
			var qty = ( apptLineItem && null != apptLineItem.quantity ) ? parseInt( apptLineItem.quantity, 10 ) : ( appt && appt.qty ? parseInt( appt.qty, 10 ) : 1 );
			if ( isNaN( qty ) || 0 >= qty ) { qty = 1; }
			var costExTax = apptLineItem ? toNum( apptLineItem.total ) : 0;
			var taxAmt = apptLineItem ? toNum( apptLineItem.total_tax ) : 0;
			var totalAmt = costExTax + taxAmt;
			var prodLink = appt && appt.product_id ? ( adminPost + '?post=' + String( appt.product_id ) + '&action=edit' ) : '';
			var orderLink = appt && appt.order_id ? ( adminPost + '?post=' + String( appt.order_id ) + '&action=edit' ) : '';
			var orderTotal = orderInfo ? toNum( orderInfo.total ) : 0;

			// Get discount information
			var tolerance = 0.01;
			var itemDisc = 0;
			var itemDiscNode = null;
			if ( apptLineItem ) {
				var ex = Math.max( 0, toNum( apptLineItem.subtotal ) - toNum( apptLineItem.total ) );
				var tx = Math.max( 0, toNum( apptLineItem.subtotal_tax ) - toNum( apptLineItem.total_tax ) );
				itemDisc = ex + tx;
				if ( itemDisc > tolerance ) {
					itemDiscNode = h( 'div', { className: 'wca-item-discount' }, '-' + fmt( itemDisc ) + ' ' + t( 'discount', 'Discount' ) );
				}
			}

			// Get order discount information
			var orderDiscNode = null;
			if ( orderInfo ) {
				var odEx = toNum( orderInfo.discount_total ) || 0;
				var odTax = toNum( orderInfo.discount_tax ) || 0;
				var od = odEx + odTax;
				var codes = [];
				if ( Array.isArray( orderInfo.coupon_lines ) ) {
					for ( var ci = 0; ci < orderInfo.coupon_lines.length; ci++ ) {
						var cl = orderInfo.coupon_lines[ ci ];
						if ( cl && cl.code ) { codes.push( String( cl.code ) ); }
					}
				}
				if ( od > tolerance && codes.length ) {
					var lab = ( 1 === codes.length ) ? t( 'coupon', 'Coupon' ) : t( 'coupons', 'Coupons' );
					var couponElements = [];
					couponElements.push( '-' + fmt( od ) + ' ' + lab + ': ' );
					for ( var codeIdx = 0; codeIdx < codes.length; codeIdx++ ) {
						if ( 0 < codeIdx ) {
							couponElements.push( ', ' );
						}
						couponElements.push( h( 'span', { key: 'coupon-' + codeIdx }, codes[ codeIdx ] ) );
					}
					orderDiscNode = h( 'div', { className: 'wca-item-discount' }, couponElements );
				}
			}

			// Format product name with quantity
			var productNameWithQty = itemName;
			if ( 1 < qty ) {
				productNameWithQty = itemName + ' ×' + String( qty );
			}

			// Combined pricing section with minimal rows
			itemsNode = h( 'div', { className: 'wca-appointment-items' },
				h( 'div', { className: 'wca-pricing-section' },
					// Product name with quantity
					h( 'div', { className: 'wca-product-name' },
						prodLink ? h( 'a', { href: prodLink }, productNameWithQty ) : h( 'div', null, productNameWithQty || h( 'span', { className: 'wca-skeleton' } ) )
					),
					// Cost and Tax on one line
					h( 'div', { className: 'wca-cost-tax-row' },
						h( 'div', { className: 'wca-cost-tax-group' },
							h( 'span', null, t( 'cost', 'Cost' ) + ': ' + fmt( costExTax ) ),
							taxAmt > tolerance ? h( 'span', null, t( 'tax', 'Tax' ) + ': ' + fmt( taxAmt ) ) : null
						),
						h( 'div', { className: 'wca-appointment-total' }, fmt( totalAmt ) )
					),
					// Appointment discount if exists
					itemDiscNode,
					// Order total (if order exists)
					orderInfo ? h( 'div', { className: 'wca-order-total-row' },
						h( 'div', { className: 'wca-order-total-label' },
							orderLink ? h( 'a', { href: orderLink }, t( 'order_total', 'Order Total' ) ) : t( 'order_total', 'Order Total' )
						),
						h( 'div', { className: 'amount' }, fmt( orderTotal ) )
					) : null,
					// Order discount if exists
					orderDiscNode
				)
			);
		} else if ( appt && appt.order_id && !orderInfo ) {
			itemsNode = h( 'div', { className: 'wca-appointment-items' },
				h( 'div', { className: 'wca-pricing-section' },
					h( 'div', { className: 'wca-product-name' },
						h( 'span', { className: 'wca-skeleton wca-skeleton-product' } )
					),
					h( 'div', { className: 'wca-cost-tax-row' },
						h( 'div', { className: 'wca-cost-tax-group' },
							h( 'span', { className: 'wca-skeleton wca-skeleton-cost' } ),
							h( 'span', { className: 'wca-skeleton wca-skeleton-tax' } )
						),
						h( 'span', { className: 'wca-skeleton wca-skeleton-total' } )
					),
					h( 'div', { className: 'wca-order-total-row' },
						h( 'div', { className: 'wca-order-total-label' }, t( 'order_total', 'Order Total' ) ),
						h( 'span', { className: 'wca-skeleton wca-skeleton-order-total' } )
					)
				)
			);
		}

		// Apply pricing section filter
		itemsNode = wcaModalHooks.applyFilters( 'appointment_modal_pricing_section', itemsNode, appt, hookContext );

		// Insertion point after pricing
		var afterPricing = wcaModalHooks.getContent( 'appointment_modal_after_pricing', appt, hookContext );
		var afterPricingNodes = afterPricing && afterPricing.length ? afterPricing.map( function( el, idx ) {
			return h( 'div', { key: 'after-pricing-' + idx, className: 'wca-custom-content' }, el );
		} ) : null;

		// Insertion point before footer
		var beforeFooter = wcaModalHooks.getContent( 'appointment_modal_before_footer', appt, hookContext );
		var beforeFooterNodes = beforeFooter && beforeFooter.length ? h( 'div', { className: 'wca-before-footer' },
			beforeFooter.map( function( el, idx ) {
				return h( 'div', { key: 'before-footer-' + idx, className: 'wca-custom-content' }, el );
			} )
		) : null;

		// Apply header filter
		var filteredHeaderRow = wcaModalHooks.applyFilters( 'appointment_modal_header', headerRow, appt, hookContext );

		// Insertion point after header
		var afterHeader = wcaModalHooks.getContent( 'appointment_modal_after_header', appt, hookContext );
		var afterHeaderNodes = afterHeader && afterHeader.length ? afterHeader.map( function( el, idx ) {
			return h( 'div', { key: 'after-header-' + idx, className: 'wca-custom-content' }, el );
		} ) : null;

		return h( 'div',
			{
				className: 'wca-appointment-modal-backdrop',
				onClick: function( e ) {
					if ( e.target === e.currentTarget ) {
						props.onClose();
					}
				}
			},
			h( 'div',
				{
					className: 'wca-appointment-modal'
				},
				h( 'div',
					{
						className: 'wca-appointment-modal-header'
					},
					h( 'button',
						{
							className: 'wca-appointment-modal-close',
							onClick: function() {
								props.onClose();
							},
							title: t( 'close', 'Close' ),
							'aria-label': t( 'close', 'Close' )
						}, '×'
					),
					filteredHeaderRow
				),
				afterHeaderNodes,
				h( 'div',
					{
						className: 'wca-appointment-modal-body'
					}, gridChildren, itemsNode, afterPricingNodes
				),
				beforeFooterNodes,
				h( 'div',
					{
						className: 'wca-appointment-modal-footer'
					},
					h( 'div',
						{
							className: 'wc-action-button-group'
						},
						( function() {
							var s = String( ( localStatus || '' ) ).toLowerCase();
							var canCancel = appt && appt.id && 'cancelled' !== s;
							var needsConfirm = 'pending-confirmation' === s;
							if ( isUpdating ) {
								return h( 'div', { className: 'wc-action-button-group__items' },
									h( 'a', {
										key: 'sk',
										className: 'button wc-action-button wca-skeleton',
										title: '',
										'aria-hidden': 'true',
										href: '#',
										role: 'button',
										onClick: function( e ) { if ( e && e.preventDefault ) { e.preventDefault(); } },
										style: { display: 'inline-block', width: '176px', border: '0' }
									}, ' ' )
								);
							}
							var children = [];
							if ( needsConfirm ) {
								children.push( h( 'a', {
									key: 'confirm',
									className: 'button wc-action-button wca-button-confirm',
									title: t( 'confirmAppointmentTitle', 'Confirm this appointment' ),
									'aria-label': t( 'confirmAppointmentTitle', 'Confirm this appointment' ),
									href: '#',
									role: 'button',
									onClick: function( e ) {
										if ( e && e.preventDefault ) { e.preventDefault(); }
										if ( isUpdating ) { return; }
										setIsUpdating( true );
										try {
											var base = wcAppointmentsReactCalendar.restUrl;
											if ( '/' !== base.slice( -1 ) ) { base += '/'; }
											var url = base + 'wc-appointments/v2/appointments/' + String( appt.id );
											retryOnServerError( function() {
												return fetch( url, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': wcAppointmentsReactCalendar.nonce }, body: JSON.stringify( { status: 'confirmed' } ) } ).then( function( r ) { if ( !r.ok ) { throw new Error( 'HTTP ' + r.status ); } return r.json(); } );
											} ).then( function() { try { window.dispatchEvent( new Event( 'wc_appointments_updated' ) ); } catch ( _ ) {} try { if ( window.wcAppointmentsRefreshCalendar && 'function' === typeof window.wcAppointmentsRefreshCalendar ) { window.wcAppointmentsRefreshCalendar(); } } catch ( __ ) {} setLocalStatus( 'confirmed' ); setIsUpdating( false ); } ).catch( function() { setIsUpdating( false ); } );
										} catch ( _ ) { setIsUpdating( false ); }
									}
								}, t( 'confirm', 'Confirm' ) ) );
							}
							if ( canCancel ) {
								children.push( h( 'a', {
									key: 'cancel',
									className: 'button wc-action-button wca-button-cancel',
									title: t( 'cancelAppointmentTitle', 'Cancel this appointment' ),
									'aria-label': t( 'cancelAppointmentTitle', 'Cancel this appointment' ),
									href: '#',
									role: 'button',
									onClick: function( e ) {
										if ( e && e.preventDefault ) { e.preventDefault(); }
										if ( isUpdating ) { return; }
										try {
											var msg = t( 'confirmCancel', 'Are you sure you want to cancel this appointment?' );
											if ( !window.confirm( msg ) ) { return; }
											setIsUpdating( true );
											var base = wcAppointmentsReactCalendar.restUrl;
											if ( '/' !== base.slice( -1 ) ) { base += '/'; }
											var url = base + 'wc-appointments/v2/appointments/' + String( appt.id );
											retryOnServerError( function() {
												return fetch( url, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': wcAppointmentsReactCalendar.nonce }, body: JSON.stringify( { status: 'cancelled' } ) } ).then( function( r ) { if ( !r.ok ) { throw new Error( 'HTTP ' + r.status ); } return r.json(); } );
											} ).then( function() { try { window.dispatchEvent( new Event( 'wc_appointments_updated' ) ); } catch ( _ ) {} try { if ( window.wcAppointmentsRefreshCalendar && 'function' === typeof window.wcAppointmentsRefreshCalendar ) { window.wcAppointmentsRefreshCalendar(); } } catch ( __ ) {} try { var ord = appt && appt.order_id ? appt.order_id : 0; if ( ord ) { try { if ( window.__wcaAddOrderNote ) { window.__wcaAddOrderNote( ord, t( 'appointmentCancelledNote', 'Appointment #' + String( appt.id ) + ' cancelled.' ), false ); } } catch ( ____ ) {} } } catch ( ___ ) {} setLocalStatus( 'cancelled' ); setIsUpdating( false ); } ).catch( function() { try { window.alert( t( 'cancelFailed', 'Failed to cancel appointment.' ) ); } catch ( _ ) {} setIsUpdating( false ); } );
										} catch ( _ ) { setIsUpdating( false ); }
									}
								}, t( 'cancelAppointment', 'Cancel' ) ) );
							}
							// Apply footer actions filter
							children = wcaModalHooks.applyFilters( 'appointment_modal_footer_actions', children, appt, hookContext );
							return h( 'div', { className: 'wc-action-button-group__items' }, children );
						} )()
					),
					h( 'div',
						{
							className: 'wca-footer-right'
						},
						appt.id ? h( 'a', {
							key: 'edit',
							className: 'button button-primary',
							title: t( 'editAppointment', 'Edit Appointment' ),
							'aria-label': t( 'editAppointment', 'Edit Appointment' ),
							href: '#',
							role: 'button',
							onClick: function( e ) {
								if ( e && e.preventDefault ) { e.preventDefault(); }
								props.onEdit( appt.id );
							}
						}, t( 'editAppointment', 'Edit Appointment' ) ) : null,
						h( 'a', {
							className: 'button',
							title: t( 'close', 'Close' ),
							'aria-label': t( 'close', 'Close' ),
							href: '#',
							role: 'button',
							onClick: function( e ) {
								if ( e && e.preventDefault ) { e.preventDefault(); }
								props.onClose();
							}
						}, t( 'close', 'Close' ) )
					)
				)
			)
		);
	}

	function RuleModal( props ) {
		// Simple read-only modal for unavailable rule blocks
		var rule = props.rule || {};
		var start = rule.start_ts ? new Date( rule.start_ts * 1000 ) : null;
		var end = rule.end_ts ? new Date( rule.end_ts * 1000 ) : null;
		return h( 'div',
			{
				className: 'wca-appointment-modal-backdrop',
				onClick: function( e ) {
					if ( e.target === e.currentTarget ) {
						props.onClose();
					}
				}
			},
			h( 'div',
				{
					className: 'wca-appointment-modal'
				},
				h( 'div',
					{
						className: 'wca-appointment-modal-header'
					}, t( 'unavailableRule', 'Unavailable Rule' )
				),
				h( 'div',
					{
						className: 'wca-appointment-modal-body'
					},
					rule.id ? h( 'p', null, t( 'ruleId', 'Rule ID' ) + ': ', String( rule.id ) ) : null,
					rule.scope ? h( 'p', null, t( 'scope', 'Scope' ) + ': ', String( rule.scope ) ) : null,
					rule.product_id ? h( 'p', null, t( 'product', 'Product' ) + ': #', String( rule.product_id ) ) : null,
					rule.staff_id ? h( 'p', null, t( 'staff', 'Staff' ) + ': #', String( rule.staff_id ) ) : null,
					start ? h( 'p', null, t( 'start', 'Start' ) + ': ', start.toLocaleString() ) : null,
					end ? h( 'p', null, t( 'end', 'End' ) + ': ', end.toLocaleString() ) : null
				),
				h( 'div',
					{
						className: 'wca-appointment-modal-footer'
					},
					h( 'button',
						{
							className: 'button',
							onClick: function() {
								props.onClose();
							}
						}, t( 'close', 'Close' )
					)
				)
			)
		);
	}

	function App() {
		// Root app: loads data, initializes FullCalendar, and wires handlers
		var canManageOthers = !!wcAppointmentsReactCalendar.canManageOthers;
		var qs0 = ( function() {
			try {
				return new URLSearchParams( window.location.search );
			} catch ( e ) {
				return null;
			}
		} )();
		var qsView = qs0 ? ( qs0.get( 'view' ) || '' ) : '';
		var initialView = mapInitialView( qsView || wcAppointmentsReactCalendar.initialView );
		var initialDateParam = qs0 ? ( qs0.get( 'calendar_day' ) || '' ) : '';
		var calElRef = useRef( null );
		var calRef = useRef( null );
		var __productsState = useState( [] );
		var products = __productsState[ 0 ];
		var __staffState = useState( [] );
		var staff = __staffState[ 0 ];
		var [ productId, setProductId ] = useState( qs0 ? ( qs0.get( 'product_id' ) || '' ) : '' );
		var [ staffId, setStaffId ] = useState( qs0 ? ( qs0.get( 'staff_id' ) || '' ) : '' );
		var [ error, setError ] = useState( '' );
		var __emptyTipState = useState( false );
		var setEmptyTip = __emptyTipState[ 1 ];
		var [ loadingEvents, setLoadingEvents ] = useState( false );
		var [ showModal, setShowModal ] = useState( false );
		var [ selectedAppt, setSelectedAppt ] = useState( null );
		var [ showRuleModal, setShowRuleModal ] = useState( false );
		var [ selectedRule, setSelectedRule ] = useState( null );
		var [ viewTitle, setViewTitle ] = useState( '' );
		var [ currentView, setCurrentView ] = useState( initialView );
		var dragInProgressRef = useRef( false );

		// Refs to store current filter values for use in events function (avoids stale closures)
		var productIdRef = useRef( productId );
		var staffIdRef = useRef( staffId );

		// Refs to control efficient reloads
		var skipAvailRef = useRef( true );
		var appointmentsDirtyRef = useRef( true );
		var availabilitiesDirtyRef = useRef( true );
		var sseReadyRef = useRef( false );
		var pendingCreateRef = useRef( 0 );
		var sseInjectedIdsRef = useRef( {} );

		// Cache for appointments only (availabilities load fresh each time)
		var appointmentsCacheRef = useRef( {} );
		var preloadedDataRef = useRef( null );
		var backgroundPreloadDoneRef = useRef( false );

		function getRangeKey( startTs, endTs, productId, staffId ) {
			return startTs + '-' + endTs + '-' + ( productId || '' ) + '-' + ( staffId || '' );
		}

		// Normalize filter values for consistent comparison
		function normalizeFilterId( id ) {
			if ( null === id || id === undefined || '' === id ) {
				return '';
			}
			if ( 0 === id || '0' === id || 'unassigned' === id ) {
				return '0';
			}
			return String( id );
		}

		// Check if cached data can be used for requested filters
		function canUseCachedData( cachedProductId, cachedStaffId, reqProductId, reqStaffId ) {
			// Exact match
			if ( cachedProductId === reqProductId && cachedStaffId === reqStaffId ) {
				return true;
			}
			// If cached has a product filter but requested doesn't, we can't use it (incomplete)
			if ( '' !== cachedProductId && '' === reqProductId ) {
				return false;
			}
			// If cached has no product filter but requested does, we can use it (filter client-side)
			if ( '' === cachedProductId && '' !== reqProductId ) {
				// Product part is OK, check staff
			} else if ( cachedProductId === reqProductId ) {
				// Product filters match, check staff
			} else {
				return false;
			}
			// Check staff filters
			if ( '' !== cachedStaffId && '' === reqStaffId ) {
				return false;
			}
			if ( '' === cachedStaffId && '' !== reqStaffId ) {
				return true;
			}
			if ( '' !== cachedStaffId && '' !== reqStaffId ) {
				return cachedStaffId === reqStaffId;
			}
			return true;
		}

		// Filter appointments by product/staff
		function filterAppointmentsByFilters( appointments, reqProductId, reqStaffId ) {
			if ( !appointments || !appointments.length ) {
				return appointments;
			}
			return appointments.filter( function( appt ) {
				if ( reqProductId ) {
					if ( String( appt.product_id || '' ) !== String( reqProductId ) ) {
						return false;
					}
				}
				if ( reqStaffId ) {
					var apptStaffId = appt.staff_id || appt.staff_ids;
					if ( 'unassigned' === reqStaffId || 0 === reqStaffId || '0' === reqStaffId ) {
						if ( apptStaffId && 0 !== apptStaffId && '0' !== apptStaffId ) {
							if ( Array.isArray( apptStaffId ) ? 0 < apptStaffId.length : true ) {
								return false;
							}
						}
					} else {
						var reqStaffIdStr = String( reqStaffId );
						if ( Array.isArray( apptStaffId ) ) {
							if ( -1 === apptStaffId.indexOf( parseInt( reqStaffId, 10 ) ) && -1 === apptStaffId.indexOf( reqStaffIdStr ) ) {
								return false;
							}
						} else if ( String( apptStaffId || '' ) !== reqStaffIdStr ) {
							return false;
						}
					}
				}
				return true;
			} );
		}

		// Check if cache has full coverage for a range
		function hasFullCacheCoverage( startTs, endTs, productId, staffId ) {
			var cache = appointmentsCacheRef.current;
			var normProductId = normalizeFilterId( productId );
			var normStaffId = normalizeFilterId( staffId );
			for ( var key in cache ) {
				if ( !cache.hasOwnProperty( key ) ) {
					continue;
				}
				var cached = cache[ key ];
				var cachedProductId = normalizeFilterId( cached.productId );
				var cachedStaffId = normalizeFilterId( cached.staffId );
				if ( canUseCachedData( cachedProductId, cachedStaffId, normProductId, normStaffId ) &&
					 cached.startTs <= startTs && cached.endTs >= endTs ) {
					return true;
				}
			}
			// Also check preloaded data
			var preloaded = preloadedDataRef.current;
			if ( preloaded ) {
				var preloadProductId = normalizeFilterId( preloaded.productId );
				var preloadStaffId = normalizeFilterId( preloaded.staffId );
				if ( canUseCachedData( preloadProductId, preloadStaffId, normProductId, normStaffId ) &&
					 preloaded.startTs <= startTs && preloaded.endTs >= endTs ) {
					return true;
				}
			}
			return false;
		}

		function getCachedAppointments( startTs, endTs, productId, staffId ) {
			var normProductId = normalizeFilterId( productId );
			var normStaffId = normalizeFilterId( staffId );

			// Check preloaded data first
			var preloaded = preloadedDataRef.current;
			if ( preloaded ) {
				var preloadProductId = normalizeFilterId( preloaded.productId );
				var preloadStaffId = normalizeFilterId( preloaded.staffId );
				if ( canUseCachedData( preloadProductId, preloadStaffId, normProductId, normStaffId ) ) {
					// Check if ranges overlap
					if ( preloaded.startTs < endTs && preloaded.endTs > startTs ) {
						var filtered = ( preloaded.appointments || [] ).filter( function( appt ) {
							var apptStart = parseInt( appt.start, 10 ) || 0;
							var apptEnd = parseInt( appt.end, 10 ) || 0;
							return apptStart < endTs && apptEnd > startTs;
						} );
						filtered = filterAppointmentsByFilters( filtered, normProductId, normStaffId );
						// If preloaded range fully contains requested range, return immediately
						if ( preloaded.startTs <= startTs && preloaded.endTs >= endTs ) {
							return filtered;
						}
						// Otherwise, continue to check cache for additional data
					}
				}
			}

			// Check cache for overlapping ranges - collect appointments from all compatible cache entries
			var cache = appointmentsCacheRef.current;
			var collectedAppointments = [];
			var foundFullCoverage = false;
			// Track the actual coverage we have - start with no coverage
			var coverageStart = endTs; // Start with endTs so any valid coverage will be less
			var coverageEnd = startTs; // Start with startTs so any valid coverage will be greater

			for ( var key in cache ) {
				if ( !cache.hasOwnProperty( key ) ) {
					continue;
				}
				var cached = cache[ key ];
				var cachedProductId = normalizeFilterId( cached.productId );
				var cachedStaffId = normalizeFilterId( cached.staffId );
				if ( canUseCachedData( cachedProductId, cachedStaffId, normProductId, normStaffId ) ) {
					// Check if ranges overlap
					if ( cached.startTs < endTs && cached.endTs > startTs ) {
						// Filter appointments that fall within requested range
						var matchingAppts = ( cached.appointments || [] ).filter( function( appt ) {
							var apptStart = parseInt( appt.start, 10 ) || 0;
							var apptEnd = parseInt( appt.end, 10 ) || 0;
							return apptStart < endTs && apptEnd > startTs;
						} );

						matchingAppts = filterAppointmentsByFilters( matchingAppts, normProductId, normStaffId );

						// Merge appointments (avoid duplicates by ID)
						var existingIds = {};
						collectedAppointments.forEach( function( a ) {
							if ( a.id ) {
								existingIds[ String( a.id ) ] = true;
							}
						} );
						matchingAppts.forEach( function( appt ) {
							var apptId = String( appt.id || '' );
							if ( !apptId || !existingIds[ apptId ] ) {
								collectedAppointments.push( appt );
								if ( apptId ) {
									existingIds[ apptId ] = true;
								}
							}
						} );

						// Track if this cache entry fully covers the requested range
						if ( cached.startTs <= startTs && cached.endTs >= endTs ) {
							foundFullCoverage = true;
						}

						// Update coverage tracking to reflect actual coverage
						// coverageStart should be the earliest timestamp we have coverage for
						// coverageEnd should be the latest timestamp we have coverage for
						var overlapStart = Math.max( cached.startTs, startTs );
						var overlapEnd = Math.min( cached.endTs, endTs );
						if ( overlapStart < overlapEnd ) {
							coverageStart = Math.min( coverageStart, overlapStart );
							coverageEnd = Math.max( coverageEnd, overlapEnd );
						}
					}
				}
			}

			// Also check preloaded data if we haven't found full coverage
			if ( !foundFullCoverage && preloaded ) {
				// Reuse variables already defined above
				preloadProductId = normalizeFilterId( preloaded.productId );
				preloadStaffId = normalizeFilterId( preloaded.staffId );
				if ( canUseCachedData( preloadProductId, preloadStaffId, normProductId, normStaffId ) ) {
					if ( preloaded.startTs < endTs && preloaded.endTs > startTs ) {
						// Reuse existingIds from above, but need to check for duplicates
						var preloadExistingIds = {};
						collectedAppointments.forEach( function( a ) {
							if ( a.id ) {
								preloadExistingIds[ String( a.id ) ] = true;
							}
						} );
						var preloadFiltered = ( preloaded.appointments || [] ).filter( function( appt ) {
							var apptStart = parseInt( appt.start, 10 ) || 0;
							var apptEnd = parseInt( appt.end, 10 ) || 0;
							if ( apptStart >= endTs || apptEnd <= startTs ) {
								return false;
							}
							var apptId = String( appt.id || '' );
							if ( apptId && preloadExistingIds[ apptId ] ) {
								return false;
							}
							if ( apptId ) {
								preloadExistingIds[ apptId ] = true;
							}
							return true;
						} );
						preloadFiltered = filterAppointmentsByFilters( preloadFiltered, normProductId, normStaffId );
						collectedAppointments = collectedAppointments.concat( preloadFiltered );

						if ( preloaded.startTs <= coverageStart && preloaded.endTs >= coverageEnd ) {
							foundFullCoverage = true;
						}
					}
				}
			}

			// Check if we have full coverage of the requested range
			// We have full coverage if any single cache entry fully contains the requested range
			var hasFullCoverage = foundFullCoverage;

			// If we have full coverage from a single cache entry, return the appointments
			if ( hasFullCoverage ) {
				return collectedAppointments;
			}

			// Check if combined coverage fully contains requested range
			// We need to verify that coverageStart <= startTs and coverageEnd >= endTs
			if ( 0 < collectedAppointments.length ) {
				// Check all cache entries again to see if any single one fully covers the range
				var cache2 = appointmentsCacheRef.current;
				for ( var key2 in cache2 ) {
					if ( !cache2.hasOwnProperty( key2 ) ) {
						continue;
					}
					var cached2 = cache2[ key2 ];
					var cachedProductId2 = normalizeFilterId( cached2.productId );
					var cachedStaffId2 = normalizeFilterId( cached2.staffId );
					if ( canUseCachedData( cachedProductId2, cachedStaffId2, normProductId, normStaffId ) ) {
						if ( cached2.startTs <= startTs && cached2.endTs >= endTs ) {
							// This cache entry fully covers the requested range
							var fullyCoveredAppts = ( cached2.appointments || [] ).filter( function( appt ) {
								var apptStart = parseInt( appt.start, 10 ) || 0;
								var apptEnd = parseInt( appt.end, 10 ) || 0;
								return apptStart < endTs && apptEnd > startTs;
							} );
							fullyCoveredAppts = filterAppointmentsByFilters( fullyCoveredAppts, normProductId, normStaffId );
							return fullyCoveredAppts;
						}
					}
				}

				// Check preloaded data for full coverage
				if ( preloaded ) {
					var preloadProductId2 = normalizeFilterId( preloaded.productId );
					var preloadStaffId2 = normalizeFilterId( preloaded.staffId );
					if ( canUseCachedData( preloadProductId2, preloadStaffId2, normProductId, normStaffId ) ) {
						if ( preloaded.startTs <= startTs && preloaded.endTs >= endTs ) {
							var preloadFiltered2 = ( preloaded.appointments || [] ).filter( function( appt ) {
								var apptStart = parseInt( appt.start, 10 ) || 0;
								var apptEnd = parseInt( appt.end, 10 ) || 0;
								return apptStart < endTs && apptEnd > startTs;
							} );
							preloadFiltered2 = filterAppointmentsByFilters( preloadFiltered2, normProductId, normStaffId );
							return preloadFiltered2;
						}
					}
				}

				// Check if combined coverage from multiple cache entries fully covers the range
				// coverageStart and coverageEnd track the actual coverage we have
				// We have full coverage if coverageStart <= startTs and coverageEnd >= endTs
				// But we initialized coverageStart to endTs, so if we have no coverage, coverageStart will still be endTs
				// So we need to check if coverageStart was actually updated (meaning we have some coverage)
				if ( coverageStart <= startTs && coverageEnd >= endTs && coverageStart < endTs ) {
					// Combined coverage fully covers the requested range
					return collectedAppointments;
				}
			}

			// Only return cached data if we have full coverage
			// If we have partial data but not full coverage, return null to trigger API fetch
			// This ensures we always get complete data for the requested range
			return null;
		}

		function setCachedAppointments( startTs, endTs, productId, staffId, appointments ) {
			var normProductId = normalizeFilterId( productId );
			var normStaffId = normalizeFilterId( staffId );
			var key = getRangeKey( startTs, endTs, normProductId, normStaffId );
			appointmentsCacheRef.current[ key ] = {
				startTs: startTs,
				endTs: endTs,
				productId: normProductId,
				staffId: normStaffId,
				appointments: appointments || []
			};
		}

		/**
		 * Calculate month date range for month view (accounting for week start offsets)
		 * @param {Date} date - Date within the month
		 * @returns {Object} Object with startTs and endTs
		 */
		function calculateMonthRange( date ) {
			var year = date.getFullYear();
			var month = date.getMonth();
			var startOfWeek = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.firstDay !== undefined ) ? wcAppointmentsReactCalendar.firstDay : 0;
			var firstDay = new Date( year, month, 1 );
			var lastDay = new Date( year, month + 1, 0 );
			var dayOffset = ( firstDay.getDay() - startOfWeek + 7 ) % 7;
			var endDayOffset = 7 - ( ( lastDay.getDate() + dayOffset ) % 7 );
			if ( 7 === endDayOffset ) {
				endDayOffset = 0;
			}
			var startTs = Math.floor( ( firstDay.getTime() - ( dayOffset * 86400 * 1000 ) ) / 1000 );
			var endTs = Math.floor( ( lastDay.getTime() + ( ( endDayOffset + 1 ) * 86400 * 1000 ) ) / 1000 );
			return { startTs: startTs, endTs: endTs };
		}

		/**
		 * Calculate week date range for week view (accounting for week start offsets)
		 * @param {Date} date - Date within the week
		 * @returns {Object} Object with startTs and endTs
		 */
		function calculateWeekRange( date ) {
			var startOfWeek = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.firstDay !== undefined ) ? wcAppointmentsReactCalendar.firstDay : 0;
			var day = date.getDay();
			var diff = ( day - startOfWeek + 7 ) % 7;
			var weekStart = new Date( date );
			weekStart.setDate( date.getDate() - diff );
			weekStart.setHours( 0, 0, 0, 0 );
			var weekEnd = new Date( weekStart );
			weekEnd.setDate( weekStart.getDate() + 7 );
			var startTs = Math.floor( weekStart.getTime() / 1000 );
			var endTs = Math.floor( weekEnd.getTime() / 1000 );
			return { startTs: startTs, endTs: endTs };
		}

		/**
		 * Background preload appointments for adjacent periods (months or weeks)
		 * Runs in idle time to avoid blocking the UI
		 * @param {Date} currentDate - Current date being viewed
		 * @param {string} viewType - Current view type (dayGridMonth, timeGridWeek, etc.)
		 * @param {string} productId - Current product filter
		 * @param {string|number} staffId - Current staff filter
		 */
		function backgroundPreloadAdjacentPeriods( currentDate, viewType, productId, staffId ) {
			// Use requestIdleCallback if available, otherwise setTimeout with delay
			var scheduleIdle = function( callback, options ) {
				if ( window.requestIdleCallback ) {
					return window.requestIdleCallback( callback, options || {} );
				} else {
					// Fallback: use setTimeout with a delay
					var delay = ( options && options.timeout ) ? Math.min( options.timeout, 1000 ) : 1000;
					return setTimeout( callback, delay );
				}
			};

			scheduleIdle( function() {
				// Don't preload if requests are blocked (e.g., during drag/resize)
				if ( isRequestsBlocked() ) {
					return;
				}

				// Determine staff parameter
				var staffParam = null;
				if ( 'unassigned' === staffId ) {
					staffParam = 0;
				} else if ( staffId ) {
					staffParam = staffId;
				}

				// Build API params
				var buildParams = function( startTs, endTs ) {
					var params = {
						date_from: '@' + startTs,
						date_to: '@' + endTs,
						_fields: 'id,start,end,product_id,staff_id,staff_ids,status,customer_id,order_id,order_item_id,cost,all_day,customer_status,customer_name,customer_first_name,customer_last_name,customer_full_name,cal_color',
						orderby: 'date',
						order: 'asc'
					};
					if ( productId ) {
						params.product_id = productId;
					}
					if ( null !== staffParam ) {
						if ( 0 === staffParam ) {
							params.staff_id = 0;
						} else {
							params.staff_id = staffParam;
						}
					}
					return params;
				};

				// Preload function
				var preloadRange = function( startTs, endTs, label, delay ) {
					// Check if already fully cached
					if ( hasFullCacheCoverage( startTs, endTs, productId, staffParam ) ) {
						return; // Already fully cached
					}

					setTimeout( function() {
						if ( isRequestsBlocked() ) {
							return;
						}
						apiGetAllAppointments( buildParams( startTs, endTs ) ).then( function( appointments ) {
							// Filter excluded statuses
							var apptsFiltered = appointments.filter( function( row ) {
								return row && !isExcludedStatus( row.status );
							} );
							// Store in cache
							setCachedAppointments( startTs, endTs, productId, staffParam, apptsFiltered );
						} ).catch( function( err ) {
							// Silently fail - this is background preloading, don't show errors
							console.debug( 'Background preload (' + label + ') failed:', err );
						} );
					}, delay || 500 );
				};

				// Handle month view: preload prev and next months (skip current month)
				// Also preload month after next to ensure smooth navigation
				if ( 'dayGridMonth' === viewType ) {
					var currentMonth = new Date( currentDate.getFullYear(), currentDate.getMonth(), 1 );
					var prevMonth = new Date( currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1 );
					var nextMonth = new Date( currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1 );
					var nextNextMonth = new Date( currentMonth.getFullYear(), currentMonth.getMonth() + 2, 1 );

					var prevRange = calculateMonthRange( prevMonth );
					var nextRange = calculateMonthRange( nextMonth );
					var nextNextRange = calculateMonthRange( nextNextMonth );

					// Preload previous month
					preloadRange( prevRange.startTs, prevRange.endTs, 'prev month', 500 );
					// Preload next month (prioritize for immediate navigation)
					preloadRange( nextRange.startTs, nextRange.endTs, 'next month', 500 );
					// Preload month after next (for smooth subsequent navigation)
					preloadRange( nextNextRange.startTs, nextNextRange.endTs, 'next next month', 1000 );
				} else if ( 'timeGridWeek' === viewType || 'timeGridDay' === viewType || 'listWeek' === viewType ) {
					// Handle week/day views: preload prev and next weeks, plus previous, current, and next months
					var prevWeekDate = new Date( currentDate );
					prevWeekDate.setDate( currentDate.getDate() - 7 );
					var nextWeekDate = new Date( currentDate );
					nextWeekDate.setDate( currentDate.getDate() + 7 );

					var prevWeekRange = calculateWeekRange( prevWeekDate );
					var nextWeekRange = calculateWeekRange( nextWeekDate );

					// Preload previous week
					preloadRange( prevWeekRange.startTs, prevWeekRange.endTs, 'prev week', 500 );
					// Preload next week
					preloadRange( nextWeekRange.startTs, nextWeekRange.endTs, 'next week', 1000 );

					// Preload current, previous, and next months (helps when switching to month view)
					var currentMonthForWeek = new Date( currentDate.getFullYear(), currentDate.getMonth(), 1 );
					var prevMonthForWeek = new Date( currentMonthForWeek.getFullYear(), currentMonthForWeek.getMonth() - 1, 1 );
					var nextMonthForWeek = new Date( currentMonthForWeek.getFullYear(), currentMonthForWeek.getMonth() + 1, 1 );

					var prevMonthRange = calculateMonthRange( prevMonthForWeek );
					var currentMonthRange = calculateMonthRange( currentMonthForWeek );
					var nextMonthRange = calculateMonthRange( nextMonthForWeek );

					// Preload previous month
					preloadRange( prevMonthRange.startTs, prevMonthRange.endTs, 'prev month', 1500 );
					// Preload current month
					preloadRange( currentMonthRange.startTs, currentMonthRange.endTs, 'current month', 2000 );
					// Preload next month
					preloadRange( nextMonthRange.startTs, nextMonthRange.endTs, 'next month', 2500 );
				}
			}, { timeout: 3000 } ); // Fallback timeout of 3 seconds
		}

		function recordSseInjection( id, eventObj ) {
			try {
				if ( !id ) {
					return;
				}
				var key = String( id );
				sseInjectedIdsRef.current[ key ] = true;
				if ( eventObj && 'function' === typeof eventObj.setExtendedProp ) {
					eventObj.setExtendedProp( '__wcaSseInjected', true );
				} else if ( eventObj && eventObj.extendedProps ) {
					eventObj.extendedProps.__wcaSseInjected = true;
				}
			} catch ( _ ) {}
		}

		function clearSseInjectionFlag( id ) {
			try {
				if ( !id ) {
					return;
				}
				delete sseInjectedIdsRef.current[ String( id ) ];
			} catch ( _ ) {}
		}

		function cleanupSseInjectedEvents() {
			try {
				var ids = Object.keys( sseInjectedIdsRef.current || {} );
				if ( !ids.length ) {
					return;
				}
				var cal = calRef.current;
				if ( !cal || 'function' !== typeof cal.getEventById ) {
					return;
				}
				ids.forEach( function( id ) {
					try {
						var ev = cal.getEventById( 'appt-' + String( id ) );
						if ( ev && ev.extendedProps && ev.extendedProps.__wcaSseInjected ) {
							ev.remove();
						}
					} catch ( _e ) {}
					clearSseInjectionFlag( id );
				} );
			} catch ( _ ) {}
		}

		useEffect( function() {
			// Update refs synchronously before triggering refetch (for use in events function)
			productIdRef.current = productId;
			staffIdRef.current = staffId;
			// Don't clear cache when filters change - we can use cached data and filter client-side
			// This allows instant switching between filters without re-fetching
			preloadedDataRef.current = null;
			// Reset background preload flag so it runs again with new filters
			backgroundPreloadDoneRef.current = false;

			try {
				if ( !window.wcAppointmentsReactCalendar ) {
					return;
				}
				window.wcAppointmentsReactCalendar.productId = productId || '';
				window.wcAppointmentsReactCalendar.staffId = staffId || '';
			} catch ( e ) {}
			// Filters changed: appointments and availability should refresh
			appointmentsDirtyRef.current = true;
			availabilitiesDirtyRef.current = true;
			// Trigger refetch when filters change (calendar is already initialized)
			// Refs are updated above, so events function will use current values
			try {
				if ( calRef.current && 'function' === typeof calRef.current.refetchEvents ) {
					calRef.current.refetchEvents();
				}
			} catch ( _ ) {}
		}, [ productId, staffId ] );

		// Do not load product/staff lists on page load; resolve on-demand via async selects

		useEffect( function() {
			setEmptyTip( false );
			if ( !calElRef.current || !window.FullCalendar || !window.FullCalendar.Calendar ) {
				setError( t( 'calendarLibFailed', 'Calendar library failed to load.' ) );
				return;
			}
			var timePattern = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.timeFormat ) ? wcAppointmentsReactCalendar.timeFormat : 'g:i a';
			var fcTimeFormat = buildFullCalendarTimeFormat( timePattern );

			// Check REST API availability on mount
			checkRestApiAvailability().then( function( available ) {
				if ( !available ) {
					setError( t( 'restApiUnavailable', 'REST API is not available. Calendar will display preloaded appointments only. Please contact your administrator if this persists.' ) );
				}
			} ).catch( function() {
				// Silently continue - preloaded appointments will still work
			} );

			// Build an order-note-friendly range that matches the appointment modal
			function __formatNoteRange( startTs, endTs, allDay ) {
				var dateFmt = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.dateFormat ) ? wcAppointmentsReactCalendar.dateFormat : 'F j, Y';
				var timeFmt = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.timeFormat ) ? wcAppointmentsReactCalendar.timeFormat : 'g:i a';
				var showDaysOnly = !!allDay || isAllDayRangeTs( startTs || 0, endTs || 0 );
				var sD = new Date( ( startTs || 0 ) * 1000 );
				if ( showDaysOnly ) {
					var eInc = new Date( ( Math.max( 0, ( endTs || 0 ) - 1 ) ) * 1000 );
					var s = formatDateByPattern( sD, dateFmt );
					var e = formatDateByPattern( eInc, dateFmt );
					return ( s === e ) ? s : ( s + ' – ' + e );
				}
				var eD = new Date( ( endTs || 0 ) * 1000 );
				var sDate = formatDateByPattern( sD, dateFmt );
				var eDate = formatDateByPattern( eD, dateFmt );
				var t1 = formatTimeByPattern( sD, timeFmt );
				var t2 = formatTimeByPattern( eD, timeFmt );
				if ( sDate === eDate ) {
					return sDate + ', ' + t1 + ' – ' + t2;
				}
				return sDate + ', ' + t1 + ' – ' + eDate + ', ' + t2;
			}

			function hexToRgb( hex ) {
				if ( !hex || 'string' !== typeof hex ) {
					return null;
				}
				var h = hex.trim();
				if ( '#' === h.charAt( 0 ) ) {
					h = h.slice( 1 );
				}
				if ( 3 === h.length ) {
					var r3 = parseInt( h.charAt( 0 ) + h.charAt( 0 ), 16 );
					var g3 = parseInt( h.charAt( 1 ) + h.charAt( 1 ), 16 );
					var b3 = parseInt( h.charAt( 2 ) + h.charAt( 2 ), 16 );
					if ( isNaN( r3 ) || isNaN( g3 ) || isNaN( b3 ) ) {
						return null;
					}
					return [ r3, g3, b3 ];
				}
				if ( 6 === h.length ) {
					var r = parseInt( h.slice( 0, 2 ), 16 );
					var g = parseInt( h.slice( 2, 4 ), 16 );
					var b = parseInt( h.slice( 4, 6 ), 16 );
					if ( isNaN( r ) || isNaN( g ) || isNaN( b ) ) {
						return null;
					}
					return [ r, g, b ];
				}
				return null;
			}

			function relLum( rgb ) {
				function comp( c ) {
					var s = c / 255;
					return 0.03928 >= s ? ( s / 12.92 ) : Math.pow( ( s + 0.055 ) / 1.055, 2.4 );
				}
				return ( 0.2126 * comp( rgb[ 0 ] ) ) + ( 0.7152 * comp( rgb[ 1 ] ) ) + ( 0.0722 * comp( rgb[ 2 ] ) );
			}

			function chooseTextColor( bgHex ) {
				var rgb = hexToRgb( bgHex );
				if ( !rgb ) {
					return '#000000';
				}
				var l = relLum( rgb );
				var crWhite = ( 1 + 0.05 ) / ( l + 0.05 );
				var crBlack = ( l + 0.05 ) / 0.05;
				return crWhite >= crBlack ? '#ffffff' : '#000000';
			}

			// Convert a Date's local wall-clock into a naive epoch by composing UTC
			// from local components. This prevents timezone offsets from shifting
			// the saved time when persisting drag/resize operations.

			// Build absolute REST URL from plugin base
			function restUrl( path ) {
				var base = wcAppointmentsReactCalendar.restUrl;
				if ( '/' !== base.slice( -1 ) ) {
					base += '/';
				}
				return base + String( path ).replace( /^\//, '' );
			}

			// PUT JSON helper for updating appointments via REST
			function apiPutJSON( path, payload ) {
				return retryOnServerError( function() {
					return fetch( restUrl( path ), {
						method: 'PUT',
						headers: {
							'Content-Type': 'application/json',
							'X-WP-Nonce': wcAppointmentsReactCalendar.nonce
						},
						body: JSON.stringify( payload )
					} ).then( function( r ) {
						if ( !r.ok ) {
							throw new Error( 'HTTP ' + r.status );
						}
						return r.json();
					} );
				} );
			}

			// POST JSON helper
			function apiPostJSON( path, payload ) {
				return retryOnServerError( function() {
					return fetch( restUrl( path ), {
						method: 'POST',
						headers: {
							'Content-Type': 'application/json',
							'X-WP-Nonce': wcAppointmentsReactCalendar.nonce
						},
						body: JSON.stringify( payload )
					} ).then( function( r ) {
						if ( !r.ok ) {
							throw new Error( 'HTTP ' + r.status );
						}
						return r.json();
					} );
				} );
			}

			function addOrderNote( orderId, message, customerNote ) {
				if ( !orderId ) {
					return Promise.resolve();
				}
				var path = 'wc/v3/orders/' + String( orderId ) + '/notes';
				var payload = {
					note: String( message || '' ),
					customer_note: !!customerNote
				};
				return apiPostJSON( path, payload ).catch( function() {} );
			}
			try {
				window.__wcaAddOrderNote = addOrderNote;
			} catch ( __ ) {}

			// Compute start/end/allDay for a FullCalendar event object, preserving
			// duration when end is missing (drag timeGrid week/day without resize).
			function computeTimesFromEvent( ev, prev ) {
				var startTs = dateToNaiveEpoch( ev.start );
				var endTs = 0;
				if ( ev.allDay ) {
					var day = 86400;
					if ( ev.end ) {
						var endExclusive = dateToNaiveEpoch( ev.end );
						endTs = Math.max( startTs, endExclusive - 1 ); //remove a second so it does not add 1 day.
					} else {
						endTs = startTs + day - 1; //remove a second so it does not add 1 day.
					}
				} else {
					endTs = ev.end ? dateToNaiveEpoch( ev.end ) : ( startTs + Math.max( 1, ( ( prev && prev.end_ts ) || 0 ) - ( ( prev && prev.start_ts ) || 0 ) ) );
				}
				return {
					start: startTs,
					end: endTs,
					all_day: !!ev.allDay
				};
			}

			// Update appointment schedule via REST and refresh calendar
			function updateAppointmentSchedule( apptId, payload, revert, ctx ) {
				apiPutJSON( 'wc-appointments/v2/appointments/' + String( apptId ), payload )
					.then( function() {
						try {
							// Update modal if it's currently open for this appointment
							if ( selectedAppt && selectedAppt.id === apptId ) {
								setSelectedAppt( {
									...selectedAppt,
									start_ts: payload.start,
									end_ts: payload.end,
									all_day: payload.all_day || selectedAppt.all_day
								} );
							}

							var note = '';
							if ( ctx && ctx.prevStart ) {
								var prevTxt = __formatNoteRange( ( ctx.prevStart || 0 ), ( ctx.prevEnd || 0 ), isAllDayRangeTs( ( ctx.prevStart || 0 ), ( ctx.prevEnd || 0 ) ) );
								var nextTxt = __formatNoteRange( ( payload.start || 0 ), ( payload.end || 0 ), !!payload.all_day );
								note = t( 'appointmentUpdatedNoteWithChanges', 'Appointment #' + String( apptId ) + ' updated: ' + prevTxt + ' → ' + nextTxt );
							} else {
								note = t( 'appointmentUpdatedNote', 'Appointment #' + String( apptId ) + ' updated.' );
							}
							if ( ctx && ctx.orderId ) {
								addOrderNote( ctx.orderId, note, false );
							}
						} catch ( __ ) {}
					} )
					.catch( function() {
						try {
							if ( 'function' === typeof revert ) {
								revert();
							}
						} catch ( _ ) {}
					} );
			}

			// Interval helpers used by availability rendering (kept here for reuse/testing)
			// clampIntervalToWindow not used directly; available for reuse via window export
			function clampIntervalToWindow( s, e, winStart, winEnd ) {
				var ss = Math.max( winStart, s || 0 );
				var ee = Math.min( winEnd, e || 0 );
				return ee > ss ? [ ss, ee ] : null;
			}
			try {
				window.__wcaClampIntervalToWindow = clampIntervalToWindow;
			} catch ( _ce ) {}

			function mergeIntervals( list ) {
				if ( !list || !list.length ) { return []; }
				list.sort( function( a, b ) {
					return a[ 0 ] - b[ 0 ];
				} );
				var out = [];
				var cur = list[ 0 ].slice();
				for ( var i = 1; i < list.length; i++ ) {
					var it = list[ i ];
					if ( it[ 0 ] <= cur[ 1 ] ) {
						cur[ 1 ] = Math.max( cur[ 1 ], it[ 1 ] );
					} else {
						out.push( cur );
						cur = it.slice();
					}
				}
				out.push( cur );
				return out;
			}

			// intersectIntervals function removed - not used

			function complementFrom( baseS, baseE, availMerged ) {
				var gaps = [];
				var prev = baseS;
				for ( var i = 0; i < availMerged.length; i++ ) {
					var s = Math.max( baseS, availMerged[ i ][ 0 ] );
					var e = Math.min( baseE, availMerged[ i ][ 1 ] );
					if ( e <= baseS || s >= baseE ) {
						continue;
					}
					if ( s > prev ) {
						gaps.push( [ prev, s ] );
					}
					prev = Math.max( prev, e );
				}
				if ( prev < baseE ) {
					gaps.push( [ prev, baseE ] );
				}
				return gaps;
			}

			// Override base intervals with override intervals
			// Where override has intervals, use those; where override doesn't, keep base
			function overrideIntervals( base, override, baseS, baseE ) {
				if ( !override || !override.length ) { return base || []; }
				if ( !base || !base.length ) { return override || []; }
				// Get gaps in override (where override doesn't have intervals)
				var overrideGaps = complementFrom( baseS, baseE, override );
				// Intersect base with override gaps (base intervals that fall in override gaps)
				var baseInGaps = [];
				for ( var i = 0; i < overrideGaps.length; i++ ) {
					var gap = overrideGaps[ i ];
					for ( var j = 0; j < base.length; j++ ) {
						var baseInt = base[ j ];
						var s = Math.max( gap[ 0 ], baseInt[ 0 ] );
						var e = Math.min( gap[ 1 ], baseInt[ 1 ] );
						if ( e > s ) {
							baseInGaps.push( [ s, e ] );
						}
					}
				}
				// Union override intervals with base intervals in gaps
				return mergeIntervals( override.concat( baseInGaps ) );
			}

			// Create a FullCalendar event object from an appointment row
			function buildAppointmentEvent( row, usersIndex, colorsIndex ) {
				var st = row.start;
				var en = row.end;
				var staffArr = Array.isArray( row.staff_ids ) ? row.staff_ids : ( Array.isArray( row.staff_id ) ? row.staff_id : ( row.staff_id ? [ row.staff_id ] : [] ) );
				var staffSingle = staffArr && staffArr.length ? staffArr[ 0 ] : '';
				var isAllDay = !!row.all_day || isAllDayRangeTs( st, en );
				var idAppt = 'appt-' + String( row.id || ( st + '-' + en ) );
				var custObj = ( usersIndex || {} )[ row.customer_id ] || {};
				var hasCustomer = 0 < parseInt( row.customer_id, 10 );
				// Prefer customer name fields directly from row (included in v2 API response), fallback to index
				var fn = row.customer_first_name || custObj.first_name || '';
				var ln = row.customer_last_name || custObj.last_name || '';
				var full = row.customer_full_name || ( ( fn || ln ) ? ( ( fn + ' ' + ln ).trim() ) : '' );
				var custName = full || row.customer_name || custObj.name || ( hasCustomer ? t( 'customer', 'Customer' ) : t( 'guest', 'Guest' ) );
				if ( isAllDay ) {
					var day = 86400;
					var startDate = tsToNaiveLocalDate( Math.floor( st / day ) * day );
					var endExclusiveTs = ( Math.floor( ( en - 1 ) / day ) + 1 ) * day;
					var endDateExclusive = tsToNaiveLocalDate( endExclusiveTs );
					var evt0 = {
						id: idAppt,
						title: custName,
						start: startDate,
						end: endDateExclusive,
						allDay: true,
						extendedProps: {
							appointmentId: row.id,
							productId: row.product_id,
							productTitle: row.product_title || '',
							staffId: staffSingle,
							staffName: row.staff_name || '',
							staffAvatar: row.staff_avatar || '',
							status: row.status || '',
							start_ts: st,
							end_ts: en,
							customerId: row.customer_id,
							customerName: custName || '',
							customerFirstName: fn || '',
							customerLastName: ln || '',
							customerEmail: row.customer_email || '',
							customerPhone: row.customer_phone || '',
							customerStatus: row.customer_status || '',
							customerAvatar: row.customer_avatar || '',
							orderId: row.order_id,
							orderItemId: row.order_item_id,
							orderInfo: row.order_info || null,
							cost: row.cost
						}
					};
					// Prefer cal_color directly from row (included in v2 API response), fallback to index
					var clr0 = row.cal_color || ( colorsIndex && colorsIndex[ String( row.product_id ) ] ? String( colorsIndex[ String( row.product_id ) ] ) : '' );
					if ( clr0 ) {
						evt0.backgroundColor = clr0;
						evt0.borderColor = clr0;
						evt0.color = clr0;
						evt0.textColor = chooseTextColor( clr0 );
					}
					// Cache state for change detection in toast notifications
					cacheApptState( row.id, row.status, st, en );
					return evt0;
				}
				var evt1 = {
					id: idAppt,
					title: custName,
					start: tsToNaiveLocalISO( st ),
					end: tsToNaiveLocalISO( en ),
					allDay: false,
					extendedProps: {
						appointmentId: row.id,
						productId: row.product_id,
						productTitle: row.product_title || '',
						staffId: staffSingle,
						staffName: row.staff_name || '',
						staffAvatar: row.staff_avatar || '',
						status: row.status || '',
						start_ts: st,
						end_ts: en,
						customerId: row.customer_id,
						customerName: custName || '',
						customerFirstName: fn || '',
						customerLastName: ln || '',
						customerEmail: row.customer_email || '',
						customerPhone: row.customer_phone || '',
						customerStatus: row.customer_status || '',
						customerAvatar: row.customer_avatar || '',
						orderId: row.order_id,
						orderItemId: row.order_item_id,
						orderInfo: row.order_info || null,
						cost: row.cost
					}
				};
				// Prefer cal_color directly from row (included in v2 API response), fallback to index
				var clr1 = row.cal_color || ( colorsIndex && colorsIndex[ String( row.product_id ) ] ? String( colorsIndex[ String( row.product_id ) ] ) : '' );
				if ( clr1 ) {
					evt1.backgroundColor = clr1;
					evt1.borderColor = clr1;
					evt1.color = clr1;
					evt1.textColor = chooseTextColor( clr1 );
				}
				// Cache state for change detection in toast notifications
				cacheApptState( row.id, row.status, st, en );
				return evt1;
			}
			try {
				window.__wcaBuildAppointmentEvent = buildAppointmentEvent;
			} catch ( _ ) {}

			// Open the WP Admin "Add Appointment" modal, prefilled for selection,
			// honoring current product/staff filters and ensuring labels are correct.
			function openAddAppointmentModal( startTs, endTs, productId, staffId, products ) {
				if ( window.__wcaOpeningAddModal ) {
					return;
				}
				window.__wcaOpeningAddModal = true;
				setTimeout( function() {
					window.__wcaOpeningAddModal = false;
				}, 500 );
				try {
					var existing = document.querySelector( '.wc-backbone-modal' );
					var wpAdminExisting = window.wc_appointments_writepanel;
					// Prime selected range to ensure modal honors it during initial field setup
					try {
						if ( wpAdminExisting ) {
							wpAdminExisting._prefillRange = {
								startTs: startTs,
								endTs: endTs
							};
						}
					} catch ( _pf ) {}
					if ( existing && wpAdminExisting ) {
						// Ensure modal is open before prefill to avoid falling back to defaults
						if ( 'function' === typeof wpAdminExisting.openModal ) {
							wpAdminExisting.openModal();
						}
						// Rebuild for product/staff to match current filters
						var staffNumExisting = parseInt( staffId, 10 );
						if ( 'function' === typeof wpAdminExisting.rebuildModalForProduct && productId ) {
							var pName0 = '';
							try {
								for ( var i0 = 0; i0 < ( products || [] ).length; i0++ ) {
									if ( String( products[ i0 ].id ) === String( productId ) ) {
										pName0 = products[ i0 ].name || ( '#' + productId );
										break;
									}
								}
							} catch ( _ ) {}
							wpAdminExisting.rebuildModalForProduct( productId, pName0, staffNumExisting );
							if ( !pName0 ) {
								ensureProductSelectLabel( productId );
							}
						}
						// Prefill selected range from calendar
						if ( 'function' === typeof wpAdminExisting.prefillSelectedRange ) {
							wpAdminExisting.prefillSelectedRange( startTs, endTs );
						}
						if ( 0 < staffNumExisting ) {
							setStaffSelectValue( staffNumExisting );
						}
						return;
					}
				} catch ( __ ) {}
				var wpAdmin = window.wc_appointments_writepanel;
				if ( !wpAdmin ) {
					return;
				}
				var staffNum = parseInt( staffId, 10 );
				if ( 'function' === typeof wpAdmin.rebuildModalForProduct && productId ) {
					var pName = '';
					try {
						for ( var i = 0; i < ( products || [] ).length; i++ ) {
							if ( String( products[ i ].id ) === String( productId ) ) {
								pName = products[ i ].name || ( '#' + productId );
								break;
							}
						}
					} catch ( _ ) {}
					wpAdmin.rebuildModalForProduct( productId, pName, staffNum );
					if ( !pName ) {
						ensureProductSelectLabel( productId );
					}
				} else if ( 'function' === typeof wpAdmin.openModal ) {
					wpAdmin.openModal();
				}
				if ( 'function' === typeof wpAdmin.prefillSelectedRange ) {
					wpAdmin.prefillSelectedRange( startTs, endTs );
				}
				if ( 0 < staffNum ) {
					setStaffSelectValue( staffNum );
				}
			}

			// Ensure product select label shows correct name if modal rebuild lacked it
			function ensureProductSelectLabel( productId ) {
				try {
					apiGet( 'wc-appointments/v1/products', {
						include: [ productId ],
						per_page: 1
					} ).then( function( items ) {
						var nm = ( Array.isArray( items ) && items.length ) ? ( items[ 0 ].name || '' ) : '';
						if ( !nm ) {
							return;
						}
						var $ = window.jQuery || window.$;
						var $sel = $ ? $( '#appointment_product_id' ) : null;
						if ( $sel && $sel.length ) {
							var $opt = $sel.find( 'option:selected' );
							if ( $opt.length ) {
								$opt.text( nm );
								try {
									var $container = $sel.next( '.select2' ).find( '.select2-selection__rendered' );
									if ( $container.length ) {
										$container.text( nm );
									}
								} catch ( _ ) {}
							}
						}
					} ).catch( function() {} );
				} catch ( _ ) {}
			}

			// Set staff select field value once options exist (Select2 or native)
			function setStaffSelectValue( staffNum ) {
				var tries = 0;
				var maxTries = 40;
				var iv = setInterval( function() {
					tries++;
					var $ = window.jQuery || window.$;
					var $sel = $ ? $( '#appointment_staff_id' ) : null;
					var hasOpts = ( $sel && $sel.length && $sel.find( 'option' ).length ) || ( document.querySelector( '#appointment_staff_id' ) && document.querySelector( '#appointment_staff_id' ).options && document.querySelector( '#appointment_staff_id' ).options.length );
					if ( hasOpts ) {
						if ( $sel && $sel.length ) {
							$sel.val( String( staffNum ) ).trigger( 'change' );
						} else {
							var el = document.querySelector( '#appointment_staff_id' );
							el.value = String( staffNum );
							var evt = document.createEvent( 'HTMLEvents' );
							evt.initEvent( 'change', true, false );
							el.dispatchEvent( evt );
						}
						clearInterval( iv );
						return;
					}
					if ( tries >= maxTries ) {
						clearInterval( iv );
					}
				}, 100 );
			}

			function dateToNaiveEpoch( d ) {
				if ( !d || !( d instanceof Date ) ) {
					return 0;
				}
				var y = d.getFullYear();
				var m = d.getMonth();
				var day = d.getDate();
				var hh = d.getHours();
				var mm = d.getMinutes();
				var ss = d.getSeconds();
				return Math.floor( Date.UTC( y, m, day, hh, mm, ss ) / 1000 );
			}

			// Store preloaded appointments for use in events function
			try {
				var preloaded = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.preloadedAppointments ) ? wcAppointmentsReactCalendar.preloadedAppointments : [];
				if ( Array.isArray( preloaded ) && 0 < preloaded.length ) {
					// Calculate preloaded range
					var initialViewType = initialView;
					var initialDate = initialDateParam || '';
					var dayTs = initialDate ? ( new Date( initialDate ).getTime() / 1000 ) : ( Date.now() / 1000 );
					var preloadStartTs = 0;
					var preloadEndTs = 0;
					var bufferDays = 7;

					if ( 'timeGridDay' === initialViewType || 'dayGridDay' === initialViewType ) {
						preloadStartTs = Math.floor( dayTs / 86400 ) * 86400;
						preloadEndTs = preloadStartTs + 86400;
					} else if ( 'dayGridMonth' === initialViewType ) {
						var d = new Date( dayTs * 1000 );
						var year = d.getFullYear();
						var month = d.getMonth();
						var startOfWeek = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.firstDay !== undefined ) ? wcAppointmentsReactCalendar.firstDay : 0;
						var firstDay = new Date( year, month, 1 );
						var lastDay = new Date( year, month + 1, 0 );
						var dayOffset = ( firstDay.getDay() - startOfWeek + 7 ) % 7;
						var endDayOffset = 7 - ( ( lastDay.getDate() + dayOffset ) % 7 );
						if ( 7 === endDayOffset ) {
							endDayOffset = 0;
						}
						preloadStartTs = Math.floor( ( firstDay.getTime() - ( dayOffset * 86400 * 1000 ) ) / 1000 );
						preloadEndTs = Math.floor( ( lastDay.getTime() + ( ( endDayOffset + 1 ) * 86400 * 1000 ) ) / 1000 );
					} else if ( 'timeGridWeek' === initialViewType || 'listWeek' === initialViewType ) {
						var dm = new Date( dayTs * 1000 );
						var range = calculateMonthRange( dm );
						preloadStartTs = range.startTs;
						preloadEndTs = range.endTs;
					} else {
						startOfWeek = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.firstDay !== undefined ) ? wcAppointmentsReactCalendar.firstDay : 0;
						d = new Date( dayTs * 1000 );
						var day = d.getDay();
						var diff = ( day - startOfWeek + 7 ) % 7;
						preloadStartTs = Math.floor( ( d.getTime() - ( diff * 86400 * 1000 ) ) / 1000 );
						preloadEndTs = preloadStartTs + ( 7 * 86400 );
					}

					preloadStartTs = preloadStartTs - ( bufferDays * 86400 );
					preloadEndTs = preloadEndTs + ( bufferDays * 86400 );

					var initialProductId = productIdRef.current || '';
					var initialStaffId = staffIdRef.current || '';
					var initialStaffParam = null;
					if ( 'unassigned' === initialStaffId ) {
						initialStaffParam = 0;
					} else if ( initialStaffId ) {
						initialStaffParam = initialStaffId;
					}

					// Filter preloaded appointments
					var apptsFiltered = preloaded.filter( function( row ) {
						return row && !isExcludedStatus( row.status );
					} );

					// Store preloaded data for events function
					preloadedDataRef.current = {
						startTs: preloadStartTs,
						endTs: preloadEndTs,
						productId: initialProductId,
						staffId: initialStaffParam,
						appointments: apptsFiltered
					};
				}
			} catch ( _ ) {}

			// FullCalendar initialization: editing enabled; header hidden (custom toolbar)
			var CalendarCtor = ( window.FullCalendar && window.FullCalendar.Calendar ) ? window.FullCalendar.Calendar : null;
			if ( !CalendarCtor ) {
				return;
			}
			var calendar = new CalendarCtor( calElRef.current, {
				initialView: initialView,
				initialDate: initialDateParam || undefined,
				locale: ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.locale ) ? wcAppointmentsReactCalendar.locale : 'en',
				headerToolbar: false,
				expandRows: true,
				eventMaxStack: 4, //time views only
				dayMaxEvents: true, //month view only
				nowIndicator: true,
				eventColor: '#0073aa',
				eventTextColor: chooseTextColor( '#0073aa' ),
				slotLabelFormat: fcTimeFormat,
				eventTimeFormat: fcTimeFormat,
				eventDataTransform: function( raw ) {
					try {
						var p = raw && raw.extendedProps ? raw.extendedProps : {};
						var cid = p.customerId || p.customer_id || null;
						var idx = window.__wcaUsersIndex || {};
						var u = ( idx && cid ) ? idx[ cid ] : null;
						if ( u ) {
							var fn = u.first_name || '';
							var ln = u.last_name || '';
							var full = ( fn || ln ) ? ( ( fn + ' ' + ln ).trim() ) : ( u.name || '' );
							if ( full ) {
								raw.title = full;
							}
						}
					} catch ( _ ) {}
					return raw;
				},
				selectable: false !== __wcaRestApiAvailable,
				selectMirror: false !== __wcaRestApiAvailable,
				editable: false !== __wcaRestApiAvailable,
				eventStartEditable: false !== __wcaRestApiAvailable,
				eventDurationEditable: false !== __wcaRestApiAvailable,
				firstDay: ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.firstDay !== undefined ) ? wcAppointmentsReactCalendar.firstDay : 0,
				navLinks: true,
				views: {
					dayGridMonth: {
						fixedWeekCount: true
					}
				},

				datesSet: function( info ) {
					var vtype = info && info.view && info.view.type ? info.view.type : '';
					setCurrentView( vtype || currentView );
					setViewTitle( info && info.view && info.view.title ? info.view.title : '' );
					try {
						var calObj = ( info && info.view ) ? info.view.calendar : null;
						if ( calObj && 'function' === typeof calObj.setOption ) {
							if ( 'timeGridWeek' === vtype || 'timeGridDay' === vtype || 'listWeek' === vtype ) {
								calObj.setOption( 'contentHeight', 'auto' );
							} else {
								calObj.setOption( 'contentHeight', undefined );
							}
						}
					} catch ( _ ) {}
					// Persist current view and anchor date in querystring to survive reloads
					try {
						setQSParam( 'view', mapViewToParam( vtype || currentView ) );
						var anchor = ( info && info.view && info.view.currentStart ) ? info.view.currentStart : ( info && info.start ? info.start : null );
						if ( anchor ) {
							setQSParam( 'calendar_day', dateToYmd( anchor ) );
						}
					} catch ( __ ) {}
					// New date window: mark as dirty so events function will refetch
					// Note: We don't call refetchEvents() here because FullCalendar automatically
					// calls the events function when dates change, so calling it here would cause duplicates
					appointmentsDirtyRef.current = true;
					availabilitiesDirtyRef.current = true;

					// Background preload adjacent periods (months or weeks) for instant navigation
					// Always preload when view changes - the function will check cache and skip if already fully cached
					try {
						var anchorDate = ( info && info.view && info.view.currentStart ) ? info.view.currentStart : ( info && info.start ? new Date() : null );
						if ( anchorDate ) {
							var currentProductId = productIdRef.current || '';
							var currentStaffId = staffIdRef.current || '';
							// For month view, also preload using FullCalendar's actual range to ensure exact match
							if ( 'dayGridMonth' === vtype && info && info.start && info.end ) {
								// Use FullCalendar's actual range to calculate next month more accurately
								var fcStartTs = Math.floor( new Date( info.start ).getTime() / 1000 );
								var fcEndTs = Math.floor( new Date( info.end ).getTime() / 1000 );
								// Calculate next month's range based on current range
								var rangeDays = Math.floor( ( fcEndTs - fcStartTs ) / 86400 );
								var nextMonthStartTs = fcEndTs;
								var nextMonthEndTs = fcEndTs + ( rangeDays * 86400 );
								// Preload next month immediately using FullCalendar's range calculation
								var staffParam = null;
								if ( 'unassigned' === currentStaffId ) {
									staffParam = 0;
								} else if ( currentStaffId ) {
									staffParam = currentStaffId;
								}
								if ( !hasFullCacheCoverage( nextMonthStartTs, nextMonthEndTs, currentProductId, staffParam ) ) {
									setTimeout( function() {
										if ( !isRequestsBlocked() ) {
											var buildParams = function( startTs, endTs ) {
												var params = {
													date_from: '@' + startTs,
													date_to: '@' + endTs,
													_fields: 'id,start,end,product_id,staff_id,staff_ids,status,customer_id,order_id,order_item_id,cost,all_day,customer_status,customer_name,customer_first_name,customer_last_name,customer_full_name,cal_color',
													orderby: 'date',
													order: 'asc'
												};
												if ( currentProductId ) {
													params.product_id = currentProductId;
												}
												if ( null !== staffParam ) {
													if ( 0 === staffParam ) {
														params.staff_id = 0;
													} else {
														params.staff_id = staffParam;
													}
												}
												return params;
											};
											apiGetAllAppointments( buildParams( nextMonthStartTs, nextMonthEndTs ) ).then( function( appointments ) {
												var apptsFiltered = appointments.filter( function( row ) {
													return row && !isExcludedStatus( row.status );
												} );
												setCachedAppointments( nextMonthStartTs, nextMonthEndTs, currentProductId, staffParam, apptsFiltered );
											} ).catch( function( err ) {
												console.debug( 'Background preload (next month from FC range) failed:', err );
											} );
										}
									}, 100 );
								}
							}
							// Always trigger preload check when view changes
							// The backgroundPreloadAdjacentPeriods function will check cache and only preload if needed
							backgroundPreloadAdjacentPeriods( anchorDate, vtype, currentProductId, currentStaffId );
						}
					} catch ( _ ) {}
				},

				eventClassNames: function( arg ) {
					try {
						var p = ( arg && arg.event ) ? ( arg.event.extendedProps || {} ) : {};
						if ( !p || !p.appointmentId ) {
							return [];
						}
						var now = Math.floor( Date.now() / 1000 );
						var classes = [];
						if ( ( p.end_ts || 0 ) < now ) {
							classes.push( 'is-past' );
						}
						if ( 'cancelled' === String( p.status || '' ).toLowerCase() ) {
							classes.push( 'is-cancelled' );
						}
						return classes;
					} catch ( e ) {
						return [];
					}
				},

				eventDidMount: function( info ) {
					// Toggle .is-past class live to avoid stale opacity after time passes
					try {
						var p = ( info && info.event ) ? ( info.event.extendedProps || {} ) : {};
						if ( !p || !p.appointmentId ) {
							return;
						}
						var now = Math.floor( Date.now() / 1000 );
						if ( ( p.end_ts || 0 ) < now ) {
							info.el.classList.add( 'is-past' );
						} else {
							info.el.classList.remove( 'is-past' );
						}
					} catch ( e ) {}
				},

				eventClick: function( info ) {
					// Open appointment or rule modal depending on event type
					if ( info && info.jsEvent ) {
						info.jsEvent.preventDefault();
					}
					var p = ( info && info.event ) ? ( info.event.extendedProps || {} ) : {};
					if ( p && p.appointmentId ) {
						setSelectedAppt( {
							id: p.appointmentId,
							product_id: p.productId,
							product_title: p.productTitle || '',
							staff_id: p.staffId,
							staff_name: p.staffName || '',
							staff_avatar: p.staffAvatar || '',
							status: p.status,
							start_ts: p.start_ts,
							end_ts: p.end_ts,
							customer_id: p.customerId,
							order_id: p.orderId,
							order_item_id: p.orderItemId,
							order_info: p.orderInfo || null,
							customer_status: p.customerStatus || '',
							customer_name: p.customerName || '',
							customer_first_name: p.customerFirstName || '',
							customer_last_name: p.customerLastName || '',
							customer_full_name: p.customerFullName || '',
							customer_email: p.customerEmail || '',
							customer_phone: p.customerPhone || '',
							customer_avatar: p.customerAvatar || ''
						} );
						setShowModal( true );
					} else if ( p && p.block ) {
						setSelectedRule( {
							id: p.rule_id,
							scope: p.scope,
							product_id: p.product_id,
							staff_id: p.staff_id,
							start_ts: p.start_ts,
							end_ts: p.end_ts
						} );
						setShowRuleModal( true );
					}
				},

				dateClick: function( info ) {
					// Use local epoch seconds so modal formats correctly
					try {
						var startTs = Math.floor( info.date.getTime() / 1000 );
						var endTs = info.allDay ? ( startTs + 86400 ) : 0;
						// Use refs to get current filter values (avoids stale closures)
						openAddAppointmentModal( startTs, endTs, productIdRef.current, staffIdRef.current, products );
					} catch ( _ ) {}
				},

				select: function( info ) {
					// Use local epoch seconds so modal formats correctly
					try {
						var startTs = Math.floor( info.start.getTime() / 1000 );
						var selSecs = Math.floor( ( info.end.getTime() - info.start.getTime() ) / 1000 );
						var endTs   = 1800 === selSecs ? 0 : Math.floor( info.end.getTime() / 1000 );
						// Use refs to get current filter values (avoids stale closures)
						openAddAppointmentModal( startTs, endTs, productIdRef.current, staffIdRef.current, products );
					} catch ( _ ) {}
				},

				eventDragStart: function() {
					try {
						setRequestsBlocked( true );
						dragInProgressRef.current = true;
					} catch ( e ) {}
				},

				eventResizeStart: function() {
					try {
						setRequestsBlocked( true );
						dragInProgressRef.current = true;
					} catch ( e ) {}
				},

				eventDrop: function( info ) {
					// Persist drag move using naive wall-clock timestamps
					try {
						// Revert if REST API unavailable
						if ( false === __wcaRestApiAvailable ) {
							if ( info && 'function' === typeof info.revert ) {
								info.revert();
							}
							return;
						}
						var p = info.event.extendedProps || {};
						if ( !p.appointmentId ) {
							if ( info && 'function' === typeof info.revert ) {
								info.revert();
							}
							return;
						}
						var payload = computeTimesFromEvent( info.event, p );
						updateAppointmentSchedule( p.appointmentId, payload, info.revert, {
							orderId: p.orderId,
							prevStart: p.start_ts,
							prevEnd: p.end_ts
						} );
					} catch ( _ ) {
						try {
							if ( info && 'function' === typeof info.revert ) {
								info.revert();
							}
						} catch ( __ ) {}
					}
				},

				eventResize: function( info ) {
					// Persist duration changes using naive wall-clock timestamps
					try {
						// Revert if REST API unavailable
						if ( false === __wcaRestApiAvailable ) {
							if ( info && 'function' === typeof info.revert ) {
								info.revert();
							}
							return;
						}
						var p = info.event.extendedProps || {};
						if ( !p.appointmentId ) {
							if ( info && 'function' === typeof info.revert ) {
								info.revert();
							}
							return;
						}
						var payload = computeTimesFromEvent( info.event, p );
						updateAppointmentSchedule( p.appointmentId, payload, info.revert, {
							orderId: p.orderId,
							prevStart: p.start_ts,
							prevEnd: p.end_ts
						} );
					} catch ( _ ) {
						try {
							if ( info && 'function' === typeof info.revert ) {
								info.revert();
							}
						} catch ( __ ) {}
					}
				},

				eventDragStop: function() {
					try {
						setRequestsBlocked( false );
						dragInProgressRef.current = false;
					} catch ( e ) {}
				},

				eventResizeStop: function() {
					try {
						setRequestsBlocked( false );
						dragInProgressRef.current = false;
					} catch ( e ) {}
				},

				navLinkDayClick: function( date, jsEvent ) {
					// Jump into day view and remember ymd in query params
					try {
						if ( jsEvent ) {
							jsEvent.preventDefault();
						}
						setQSParam( 'calendar_day', dateToYmd( date ) );
						changeView( 'timeGridDay', date );
					} catch ( _ ) {}
				},

				navLinkWeekClick: function( weekStart, jsEvent ) {
					// Jump into week view at the clicked week start
					try {
						if ( jsEvent ) {
							jsEvent.preventDefault();
						}
						setQSParam( 'calendar_day', dateToYmd( weekStart ) );
						changeView( 'timeGridWeek', weekStart );
					} catch ( _ ) {}
				},

				events: function( info, success, failure ) {
					if ( isRequestsBlocked() ) {
						setLoadingEvents( false );
						if ( 'function' === typeof failure ) {
							failure( new Error( 'Blocked during drag/resize' ) );
						}
						return;
					}

					// If REST API is unavailable, only use cached/preloaded data
					if ( false === __wcaRestApiAvailable ) {
						var startTs = Math.floor( new Date( info.startStr ).getTime() / 1000 );
						var endTs = Math.floor( new Date( info.endStr ).getTime() / 1000 );
						var currentProductId = productIdRef.current;
						var currentStaffId = staffIdRef.current;
						var staffParam = null;
						if ( 'unassigned' === currentStaffId ) {
							staffParam = 0;
						} else if ( currentStaffId ) {
							staffParam = currentStaffId;
						}

						// Try to use cached appointments only
						var cachedAppts = getCachedAppointments( startTs, endTs, currentProductId, staffParam );
						if ( null !== cachedAppts ) {
							var apptsFiltered = cachedAppts.filter( function( row ) {
								return row && !isExcludedStatus( row.status );
							} );

							var idx = {};
							var usersIdx = {};
							apptsFiltered.forEach( function( row ) {
								if ( row.product_id && row.cal_color ) {
									idx[ String( row.product_id ) ] = String( row.cal_color );
								}
								if ( row.customer_id ) {
									var cid = parseInt( row.customer_id, 10 );
									if ( 0 < cid && !usersIdx[ cid ] ) {
										usersIdx[ cid ] = {
											name: row.customer_name || row.customer_full_name || ( '#' + cid ),
											email: '',
											first_name: row.customer_first_name || '',
											last_name: row.customer_last_name || ''
										};
									}
								}
							} );

							try {
								window.__wcaUsersIndex = usersIdx || {};
								window.__wcaProductColorsIndex = idx || {};
							} catch ( _ ) {}

							var evs = [];
							apptsFiltered.forEach( function( row ) {
								evs.push( buildAppointmentEvent( row, usersIdx, idx ) );
							} );

							success( evs );
							setLoadingEvents( false );
							setEmptyTip( 0 === evs.length );
							appointmentsDirtyRef.current = false;
							// Skip availability rendering when REST is unavailable
							skipAvailRef.current = true;
							availabilitiesDirtyRef.current = false;
							return;
						}

						// No cached data and REST unavailable - show empty
						success( [] );
						setLoadingEvents( false );
						setEmptyTip( true );
						return;
					}

					var startTs = Math.floor( new Date( info.startStr ).getTime() / 1000 );
					var endTs = Math.floor( new Date( info.endStr ).getTime() / 1000 );
					var currentProductId = productIdRef.current;
					var currentStaffId = staffIdRef.current;
					var staffParam = null;
					if ( 'unassigned' === currentStaffId ) {
						staffParam = 0;
					} else if ( currentStaffId ) {
						staffParam = currentStaffId;
					}

					// Define processAvailabilityResults function early so it's available for both preloaded and API-fetched appointments
					// This function processes availability rules and renders them on the calendar
					var processAvailabilityResults = function( res, useStartTs, useEndTs, useProductId, useStaffParam ) {
						// Use provided parameters or fall back to closure variables
						useStartTs = useStartTs !== undefined ? useStartTs : startTs;
						useEndTs = useEndTs !== undefined ? useEndTs : endTs;
						useProductId = useProductId !== undefined ? useProductId : currentProductId;
						useStaffParam = useStaffParam !== undefined ? useStaffParam : staffParam;

						// res format: [unavGlobal, unavProduct, unavStaff, availGlobal, availProduct, availStaff]
						try {
							var __parseYmd = function( s ) {
								var p = String( s || '' ).split( '-' );
								return { y: parseInt( p[ 0 ], 10 ) || 0, m: parseInt( p[ 1 ], 10 ) || 0, d: parseInt( p[ 2 ], 10 ) || 0 };
							};
							var __parseHHMM = function( s ) {
								var p = String( s || '00:00' ).split( ':' );
								return { h: parseInt( p[ 0 ], 10 ) || 0, m: parseInt( p[ 1 ], 10 ) || 0 };
							};
							var __ymdToTs = function( ymd, hm ) {
								var d = __parseYmd( ymd );
								var t = __parseHHMM( hm );
								var dt = new Date( d.y, d.m - 1, d.d, t.h, t.m, 0 );
								return Math.floor( dt.getTime() / 1000 );
							};
							var __toYmd = function( d ) {
								var y = d.getFullYear();
								var m = d.getMonth() + 1;
								var day = d.getDate();
								return String( y ) + '-' + ( 10 > m ? '0' : '' ) + String( m ) + '-' + ( 10 > day ? '0' : '' ) + String( day );
							};
							var __clampPush = function( out, s, e ) {
								if ( s < useStartTs ) {
									s = useStartTs;
								}
								if ( e > useEndTs ) {
									e = useEndTs;
								}
								if ( e > s ) {
									out.push( [ s, e ] );
								}
							};
							var __forViewDays = function( out, dayFilter, hStart, hEnd ) {
								var sD = new Date( useStartTs * 1000 );
								var eD = new Date( ( useEndTs - 1 ) * 1000 );
								var d0 = new Date( sD.getFullYear(), sD.getMonth(), sD.getDate() );
								var dayMs = 86400 * 1000;
								for ( var dd = d0; dd.getTime() <= eD.getTime(); dd = new Date( dd.getTime() + dayMs ) ) {
									var ymd = __toYmd( dd );
									if ( dayFilter && !dayFilter( dd.getDay() ) ) { continue; }
									var s = __ymdToTs( ymd, hStart );
									var e = __ymdToTs( ymd, hEnd );
									__clampPush( out, s, e );
								}
							};
							var __forRangeDays = function( out, ymdStart, ymdEnd, hStart, hEnd ) {
								var d0 = __parseYmd( ymdStart );
								var d1 = __parseYmd( ymdEnd );
								var startDate = new Date( d0.y, d0.m - 1, d0.d );
								var endDate = new Date( d1.y, d1.m - 1, d1.d );
								var dayMs = 86400 * 1000;
								for ( var dd = startDate; dd.getTime() <= endDate.getTime(); dd = new Date( dd.getTime() + dayMs ) ) {
									var ymd = __toYmd( dd );
									var s = __ymdToTs( ymd, hStart );
									var e = __ymdToTs( ymd, hEnd );
									__clampPush( out, s, e );
								}
							};
							var __expandRRuleLocal = function( r ) {
								var out = [];
								var lines = String( r.rrule || '' ).split( /\n+/ );
								var freq = 'DAILY';
								var intervalN = 1;
								var byDays = null;
								var untilTs = 0;
								var exDates = {};
								var dtstartYmd = '';
								var dtstartHm = '';
								for ( var i = 0; i < lines.length; i++ ) {
									var ln = String( lines[ i ] || '' ).trim();
									if ( !ln ) { continue; }
									if ( 0 === ln.indexOf( 'DTSTART:' ) ) {
										var v0 = ln.split( ':' )[ 1 ] || '';
										var p0 = __parseDateTimeToYmdHm( v0 );
										dtstartYmd = p0.ymd || dtstartYmd;
										dtstartHm = p0.hm || dtstartHm;
										continue;
									}
									if ( 0 === ln.indexOf( 'RRULE:' ) ) {
										var args = ln.split( ':' )[ 1 ] || '';
										var parts = args.split( ';' );
										for ( var j = 0; j < parts.length; j++ ) {
											var kv = parts[ j ].split( '=' );
											var k = ( kv[ 0 ] || '' ).toUpperCase();
											var v = kv[ 1 ] || '';
											if ( 'FREQ' === k ) { freq = String( v || 'DAILY' ).toUpperCase(); } else if ( 'INTERVAL' === k ) { intervalN = Math.max( 1, parseInt( v, 10 ) || 1 ); } else if ( 'BYDAY' === k ) {
												var arr = v.split( ',' );
												var map = { SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6 };
												byDays = {};
												for ( var q = 0; q < arr.length; q++ ) {
													var code = arr[ q ].toUpperCase();
													if ( map.hasOwnProperty( code ) ) { byDays[ map[ code ] ] = 1; }
												}
											} else if ( 'UNTIL' === k ) {
												var val = v;
												var iso = '';
												if ( /\d{8}T\d{6}Z/.test( val ) ) {
													iso = val.slice( 0, 4 ) + '-' + val.slice( 4, 6 ) + '-' + val.slice( 6, 8 ) + 'T' + val.slice( 9, 11 ) + ':' + val.slice( 11, 13 ) + ':' + val.slice( 13, 15 ) + 'Z';
												} else if ( /\d{8}/.test( val ) ) {
													iso = val.slice( 0, 4 ) + '-' + val.slice( 4, 6 ) + '-' + val.slice( 6, 8 ) + 'T00:00:00Z';
												}
												try { untilTs = Math.floor( new Date( iso ).getTime() / 1000 ); } catch ( __u ) {}
											}
										}
										continue;
									}
									if ( 0 === ln.indexOf( 'EXDATE' ) ) {
										var v1 = ln.split( ':' )[ 1 ] || '';
										var arr2 = v1.split( ',' );
										for ( var w = 0; w < arr2.length; w++ ) {
											var p = __parseDateTimeToYmdHm( arr2[ w ] );
											if ( p.ymd ) { exDates[ p.ymd ] = 1; }
										}
										continue;
									}
								}
								if ( !dtstartYmd ) {
									var pFrom = __parseDateTimeToYmdHm( r.from_range || r.from_date || r.from || '' );
									var pTo = __parseDateTimeToYmdHm( r.to_range || r.to_date || r.to || '' );
									dtstartYmd = pFrom.ymd || ( pTo.ymd || __toYmd( new Date( useStartTs * 1000 ) ) );
									dtstartHm = pFrom.hm || '';
								}
								var hmStart = ( r.from || r.from_range || '' );
								var hmEnd = ( r.to || r.to_range || '' );
								var pStart = __parseDateTimeToYmdHm( hmStart );
								var pEnd = __parseDateTimeToYmdHm( hmEnd );
								var hStart = pStart.hm || '00:00';
								var hEnd = pEnd.hm || '23:59';
								var dtStartMid = __ymdToTs( dtstartYmd, '00:00' );
								var sD = new Date( useStartTs * 1000 );
								var eD = new Date( ( useEndTs - 1 ) * 1000 );
								var d0 = new Date( sD.getFullYear(), sD.getMonth(), sD.getDate() );
								var dayMs = 86400 * 1000;
								for ( var dd = d0; dd.getTime() <= eD.getTime(); dd = new Date( dd.getTime() + dayMs ) ) {
									var ymd = __toYmd( dd );
									if ( exDates[ ymd ] ) { continue; }
									var dayTs = __ymdToTs( ymd, '00:00' );
									if ( 0 < untilTs && untilTs < dayTs ) { break; }
									var diffDays = Math.floor( ( dayTs - dtStartMid ) / 86400 );
									if ( 0 > diffDays ) { continue; }
									if ( 'WEEKLY' === freq ) {
										var weeks = Math.floor( diffDays / 7 );
										if ( 0 !== ( weeks % intervalN ) ) { continue; }
										var dow = dd.getDay();
										if ( byDays && !byDays[ dow ] ) { continue; }
									} else if ( 'DAILY' === freq ) {
										if ( 0 !== ( diffDays % intervalN ) ) { continue; }
									} else {
										continue;
									}
									var s = __ymdToTs( ymd, hStart );
									var e = __ymdToTs( ymd, hEnd );
									__clampPush( out, s, e );
								}
								return out;
							};
							var __parseDateTimeToYmdHm = function( s ) {
								if ( !s ) { return { ymd: '', hm: '' }; }
								if ( -1 !== s.indexOf( 'T' ) ) {
									var d = new Date( s );
									var ymd = __toYmd( d );
									var hh = d.getHours();
									var mm = d.getMinutes();
									var ph = 10 > hh ? ( '0' + hh ) : String( hh );
									var pmn = 10 > mm ? ( '0' + mm ) : String( mm );
									return { ymd: ymd, hm: ph + ':' + pmn };
								}
								if ( -1 !== s.indexOf( ':' ) ) {
									return { ymd: '', hm: s };
								}
								return { ymd: s, hm: '' };
							};
							var expandRuleToIntervalsLocal = function( r ) {
								var type = String( ( r && ( r.type || r.range_type ) ) || '' ).toLowerCase();
								var out = [];
								if ( 'custom' === type ) {
									// For custom rules, dates can be in from_date/to_date OR from_range/to_range
									// Check both locations to handle different data formats
									var f0 = r.from || r.from_date || r.from_range || '';
									var t0 = r.to || r.to_date || r.to_range || '';
									if ( f0 && t0 ) {
										var s = __ymdToTs( f0, '00:00' );
										var e = __ymdToTs( t0, '23:59' );
										__clampPush( out, s, e );
									}
								} else if ( 'custom:daterange' === type || 'time:range' === type ) {
									var fd = r.from_date || '';
									var td = r.to_date || '';
									var fh = r.from || r.from_range || '00:00';
									var th = r.to || r.to_range || '23:59';
									if ( fd && td ) {
										__forRangeDays( out, fd, td, fh, th );
									}
								} else if ( 'rrule' === type ) {
									out = __expandRRuleLocal( r );
								} else if ( 'time' === type ) {
									var fh2 = r.from || r.from_range || '00:00';
									var th2 = r.to || r.to_range || '23:59';
									__forViewDays( out, null, fh2, th2 );
								} else if ( 0 === type.indexOf( 'time:' ) ) {
									var dnum = parseInt( type.split( ':' )[ 1 ], 10 );
									var fh3 = r.from || r.from_range || '00:00';
									var th3 = r.to || r.to_range || '23:59';
									var map = { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 0 };
									__forViewDays( out, function( dow ) { return dow === map[ dnum ]; }, fh3, th3 );
								} else if ( 'days' === type ) {
									var fN = parseInt( ( r.from || r.from_range || '1' ), 10 );
									var tN = parseInt( ( r.to || r.to_range || '7' ), 10 );
									var seq = {};
									var cur = fN;
									for ( var i = 0; 7 > i; i++ ) {
										seq[ cur ] = 1;
										if ( cur === tN ) { break; }
										cur = ( cur % 7 ) + 1;
									}
									var map2 = { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 0 };
									__forViewDays( out, function( dow ) {
										var dn = 0;
										for ( var k in map2 ) { if ( map2.hasOwnProperty( k ) && map2[ k ] === dow ) { dn = parseInt( k, 10 ); break; } }
										return !!seq[ dn ];
									}, '00:00', '23:59' );
								}
								return out;
							};
							var unavRulesGlobal = Array.isArray( res[ 0 ] ) ? res[ 0 ] : [];
							var unavRulesProduct = Array.isArray( res[ 1 ] ) ? res[ 1 ] : [];
							var unavRulesStaff = Array.isArray( res[ 2 ] ) ? res[ 2 ] : [];
							var availRulesGlobal = Array.isArray( res[ 3 ] ) ? res[ 3 ] : [];
							var availRulesProduct = Array.isArray( res[ 4 ] ) ? res[ 4 ] : [];
							var availRulesStaff = Array.isArray( res[ 5 ] ) ? res[ 5 ] : [];
							var unavIntervals = [];
							unavRulesGlobal.forEach( function( r ) { try { unavIntervals = unavIntervals.concat( expandRuleToIntervalsLocal( r || {} ) ); } catch ( __ ) {} } );
							unavRulesProduct.forEach( function( r ) { try { unavIntervals = unavIntervals.concat( expandRuleToIntervalsLocal( r || {} ) ); } catch ( __ ) {} } );
							unavRulesStaff.forEach( function( r ) { try { unavIntervals = unavIntervals.concat( expandRuleToIntervalsLocal( r || {} ) ); } catch ( __ ) {} } );
							unavIntervals = mergeIntervals( unavIntervals );
							var availByScope = {
								global: [],
								product: [],
								staff: []
							};
							availRulesGlobal.forEach( function( r ) {
								var list = [];
								try {
									list = expandRuleToIntervalsLocal( r || {} ) || [];
								} catch ( __ ) {}
								if ( list && list.length ) {
									availByScope.global = availByScope.global.concat( list );
								}
							} );
							availRulesProduct.forEach( function( r ) {
								var list = [];
								try {
									list = expandRuleToIntervalsLocal( r || {} ) || [];
								} catch ( __ ) {}
								if ( list && list.length ) {
									availByScope.product = availByScope.product.concat( list );
								}
							} );
							availRulesStaff.forEach( function( r ) {
								var list = [];
								try {
									list = expandRuleToIntervalsLocal( r || {} ) || [];
								} catch ( __ ) {}
								if ( list && list.length ) {
									availByScope.staff = availByScope.staff.concat( list );
								}
							} );
							// Calculate effective availability with proper priority hierarchy:
							// Global -> Product (overrides global) -> Staff (overrides product)
							var effectiveAvail = null;
							var globalMerged = availByScope.global.length ? mergeIntervals( availByScope.global ) : null;
							var productMerged = availByScope.product.length ? mergeIntervals( availByScope.product ) : null;
							var productOverGlobal = overrideIntervals( globalMerged, productMerged, useStartTs, useEndTs );
							var staffMerged = availByScope.staff.length ? mergeIntervals( availByScope.staff ) : null;
							effectiveAvail = overrideIntervals( productOverGlobal, staffMerged, useStartTs, useEndTs );
							// Only mark days as unavailable if they are explicitly in unavailability rules
							// Availability rules should NOT create gaps that mark other days as unavailable
							// Days should only be marked unavailable when explicitly set by unavailability rules
							var finalUnav = unavIntervals.slice();
							finalUnav = mergeIntervals( finalUnav );
							try {
								var cal = calRef.current;
								if ( cal && 'function' === typeof cal.getEvents ) {
									cal.getEvents().forEach( function( e ) {
										try {
											var p = e.extendedProps || {};
											if ( p && p.block ) {
												e.remove();
											}
										} catch ( _ ) {}
									} );
								}
								var vtype = ( cal && cal.view && cal.view.type ) ? cal.view.type : '';
								finalUnav.forEach( function( iv, idx ) {
									var sTs = iv[ 0 ];
									var eTs = iv[ 1 ];
									if ( 0 === String( vtype || '' ).indexOf( 'dayGrid' ) ) {
										var sD = new Date( sTs * 1000 );
										var dayStart = new Date( sD.getFullYear(), sD.getMonth(), sD.getDate() );
										var curTs = Math.floor( dayStart.getTime() / 1000 );
										while ( curTs < eTs ) {
											var nextTs = curTs + 86400;
											var startDay = new Date( curTs * 1000 );
											var endDay = new Date( nextTs * 1000 );
											var padSec = 60;
											var fullDayStart = curTs;
											var fullDayEnd = nextTs;
											var coversFullDay = ( sTs <= ( fullDayStart + padSec ) ) && ( eTs >= ( fullDayEnd - padSec ) );
											if ( !coversFullDay ) {
												curTs = nextTs;
												continue;
											}
											if ( cal && 'function' === typeof cal.addEvent ) {
												cal.addEvent( {
													id: 'block-bg-auto-' + idx + '-' + curTs,
													title: '',
													start: startDay,
													end: endDay,
													display: 'background',
													allDay: true,
													className: 'fc-bg-event-striped',
													extendedProps: {
														block: true,
														rule_id: null,
														scope: null,
														product_id: useProductId || null,
														staff_id: useStaffParam || null,
														start_ts: sTs,
														end_ts: eTs
													}
												} );
											}
											curTs = nextTs;
										}
									} else {
										var start = new Date( sTs * 1000 );
										var end = new Date( eTs * 1000 );
										if ( cal && 'function' === typeof cal.addEvent ) {
											cal.addEvent( {
												id: 'block-bg-auto-' + idx,
												title: '',
												start: start,
												end: end,
												display: 'background',
												className: 'fc-bg-event-striped',
												extendedProps: {
													block: true,
													rule_id: null,
													scope: null,
													product_id: useProductId || null,
													staff_id: useStaffParam || null,
													start_ts: sTs,
													end_ts: eTs
												}
											} );
										}
									}
								} );
								availabilitiesDirtyRef.current = false;
							} catch ( _ ) {}
						} catch ( _ ) {}
					};

					// Store globally for use from preloaded appointments path
					window.__wcaProcessAvailability = processAvailabilityResults;

					// Check cache first (includes preloaded data)
					var cachedAppts = getCachedAppointments( startTs, endTs, currentProductId, staffParam );
					var appointmentsPromise = null;
					if ( null !== cachedAppts ) {
						// Use cached data immediately - no API call needed
						var apptsFiltered = cachedAppts.filter( function( row ) {
							return row && !isExcludedStatus( row.status );
						} );

						// Build indexes
						var idx = {};
						var usersIdx = {};
						apptsFiltered.forEach( function( row ) {
							if ( row.product_id && row.cal_color ) {
								idx[ String( row.product_id ) ] = String( row.cal_color );
							}
							if ( row.customer_id ) {
								var cid = parseInt( row.customer_id, 10 );
								if ( 0 < cid && !usersIdx[ cid ] ) {
									usersIdx[ cid ] = {
										name: row.customer_name || row.customer_full_name || ( '#' + cid ),
										email: '',
										first_name: row.customer_first_name || '',
										last_name: row.customer_last_name || ''
									};
								}
							}
						} );

						try {
							window.__wcaUsersIndex = usersIdx || {};
							window.__wcaProductColorsIndex = idx || {};
						} catch ( _ ) {}

						var evs = [];
						apptsFiltered.forEach( function( row ) {
							evs.push( buildAppointmentEvent( row, usersIdx, idx ) );
						} );

						success( evs );
						setLoadingEvents( false );
						setEmptyTip( 0 === evs.length );
						appointmentsDirtyRef.current = false;

						// Store full preloaded range in cache on first use to enable instant month switch
						if ( preloadedDataRef.current ) {
							var pd = preloadedDataRef.current;
							setCachedAppointments( pd.startTs, pd.endTs, pd.productId, pd.staffId, pd.appointments || [] );
							preloadedDataRef.current = null;
						}

						// Availability rules will load after appointments are rendered
						// Create a resolved promise so availability processing runs for preloaded appointments
						availabilitiesDirtyRef.current = true;
						skipAvailRef.current = false;
						appointmentsPromise = Promise.resolve();
					} else {
						// Only make API call if we don't have cached appointments
						if ( null === cachedAppts ) {
							setLoadingEvents( true );
							setEmptyTip( false );
							var apptParams = {
								date_from: '@' + startTs,
								date_to: '@' + endTs,
								_fields: 'id,start,end,product_id,staff_id,staff_ids,status,customer_id,order_id,order_item_id,cost,all_day,customer_status,customer_name,customer_first_name,customer_last_name,customer_full_name,cal_color'
							};
							if ( currentProductId ) {
								apptParams.product_id = currentProductId;
							}
							if ( null !== staffParam ) {
								apptParams.staff_id = staffParam;
							}

							var apptsP = apiGetAllAppointments( apptParams );
							appointmentsPromise = apptsP.then( function( appts ) {
								appts = ensureArray( appts );
								var evs = [];
								var apptsFiltered = appts.filter( function( row ) {
									return row && !isExcludedStatus( row.status );
								} );

								// Build indexes directly from appointments data (cal_color and customer info now included in API response)
								var idx = {};
								var usersIdx = {};
								apptsFiltered.forEach( function( row ) {
									// Build product colors index from cal_color in appointment response
									if ( row.product_id && row.cal_color ) {
										var pid = String( row.product_id );
										if ( !idx[ pid ] ) {
											idx[ pid ] = String( row.cal_color );
										}
									}
									// Build users index from customer name fields in appointment response
									if ( row.customer_id ) {
										var cid = parseInt( row.customer_id, 10 );
										if ( 0 < cid && !usersIdx[ cid ] ) {
											usersIdx[ cid ] = {
												name: row.customer_name || row.customer_full_name || ( '#' + cid ),
												email: '',
												first_name: row.customer_first_name || '',
												last_name: row.customer_last_name || ''
											};
										}
									}
								} );

								try {
									window.__wcaUsersIndex = usersIdx || {};
								} catch ( _ ) {}
								try {
									window.__wcaProductColorsIndex = idx || {};
								} catch ( _ ) {}
								cleanupSseInjectedEvents();
								evs = [];
								apptsFiltered.forEach( function( row ) {
									evs.push( buildAppointmentEvent( row, usersIdx, idx ) );
								} );

								// Store in cache for future use
								setCachedAppointments( startTs, endTs, currentProductId, staffParam, apptsFiltered );

								success( evs );
								setLoadingEvents( false );
								setEmptyTip( 0 === evs.length );
								appointmentsDirtyRef.current = false;
								skipAvailRef.current = false;
								// After appointments are rendered, fetch availability rules and add background blocks.

								if ( !skipAvailRef.current && availabilitiesDirtyRef.current && false !== __wcaRestApiAvailable ) {
									// Use current filter values from refs for availability fetching
									var availProductId = productIdRef.current;
									var availStaffParam = null;
									if ( 'unassigned' === staffIdRef.current ) {
										availStaffParam = 0;
									} else if ( staffIdRef.current ) {
										availStaffParam = staffIdRef.current;
									}

									// OPTIMIZED: Use v2 availabilities endpoint with batch scopes to reduce API calls
									// This reduces from 12 calls (6 scopes × 2 calls each) to 1-2 calls total
									// by fetching all scopes in a single optimized query using indexed cache
									var scopesToFetch = [ { kind: 'availability#global', kindId: null } ];
									if ( availProductId ) {
										scopesToFetch.push( { kind: 'availability#product', kindId: availProductId } );
									}
									if ( null !== availStaffParam && 0 < parseInt( availStaffParam, 10 ) ) {
										scopesToFetch.push( { kind: 'availability#staff', kindId: availStaffParam } );
									}

									// Try v2 optimized endpoint first (uses indexed cache when available)
									// Serialize scopes array to JSON since it contains objects
									var v2Params = {
										start_ts: startTs,
										end_ts: endTs,
										scopes: JSON.stringify( scopesToFetch ),
										per_page: 100
									};
									if ( availProductId ) {
										v2Params.product_id = availProductId;
									}
									if ( null !== availStaffParam && 0 < parseInt( availStaffParam, 10 ) ) {
										v2Params.staff_id = availStaffParam;
									}

									apiGet( 'wc-appointments/v2/availabilities', v2Params ).then( function( v2Results ) {
										// v2 endpoint returns results organized by scope kind
										// Convert to expected format: [unavGlobal, unavProduct, unavStaff, availGlobal, availProduct, availStaff]
										var unavGlobal = [];
										var unavProduct = [];
										var unavStaff = [];
										var availGlobal = [];
										var availProduct = [];
										var availStaff = [];

										if ( v2Results && 'object' === typeof v2Results ) {
											// Check if this is the batch format (organized by scope)
											if ( v2Results[ 'availability#global' ] ) {
												// Batch format from optimized endpoint
												if ( v2Results[ 'availability#global' ].unavailable ) {
													unavGlobal = v2Results[ 'availability#global' ].unavailable || [];
												}
												if ( v2Results[ 'availability#global' ].available ) {
													availGlobal = v2Results[ 'availability#global' ].available || [];
												}
												if ( availProductId && v2Results[ 'availability#product' ] ) {
													unavProduct = v2Results[ 'availability#product' ].unavailable || [];
													availProduct = v2Results[ 'availability#product' ].available || [];
												}
												if ( null !== availStaffParam && 0 < parseInt( availStaffParam, 10 ) && v2Results[ 'availability#staff' ] ) {
													unavStaff = v2Results[ 'availability#staff' ].unavailable || [];
													availStaff = v2Results[ 'availability#staff' ].available || [];
												}
											} else {
												// Fallback: treat as array of rules (single scope query)
												// This shouldn't happen with batch scopes, but handle gracefully
												var allRules = ensureArray( v2Results );
												allRules.forEach( function( rule ) {
													var kind = rule.kind || '';
													var isAvailable = false !== rule.appointable;
													if ( -1 !== kind.indexOf( '#global' ) ) {
														if ( isAvailable ) {
															availGlobal.push( rule );
														} else {
															unavGlobal.push( rule );
														}
													} else if ( -1 !== kind.indexOf( '#product' ) ) {
														if ( isAvailable ) {
															availProduct.push( rule );
														} else {
															unavProduct.push( rule );
														}
													} else if ( -1 !== kind.indexOf( '#staff' ) ) {
														if ( isAvailable ) {
															availStaff.push( rule );
														} else {
															unavStaff.push( rule );
														}
													}
												} );
											}
										}

										// Reconstruct the expected result array format
										var res = [ unavGlobal, unavProduct, unavStaff, availGlobal, availProduct, availStaff ];
										processAvailabilityResults( res, startTs, endTs, availProductId, availStaffParam );
									} ).catch( function( v2Error ) {
										// Log error but don't show error message for availability fetch failures
										// Availability rules are optional for calendar display
										console.error( 'Failed to fetch availability rules:', v2Error );
									} );
								}
							} ).catch( function( err ) {
								setLoadingEvents( false );
								var blocked = false;
								try {
									blocked = isRequestsBlocked() || ( err && ( 'AbortError' === err.name || /Blocked during drag\/resize/i.test( String( err.message || '' ) ) ) );
								} catch ( _ ) {}
								if ( !blocked ) {
									try {
										var msg = ( err && err.message ) ? err.message : String( err || t( 'unknownError', 'Unknown error' ) );
										setError( t( 'restFetchFailed', 'Failed to load data from REST API: ' ) + msg + '.' );
									} catch ( e ) {}
								}
								if ( 'function' === typeof failure ) {
									failure( err );
								}
							} );
						} else {
							// We have cached appointments, create a resolved promise so availability processing runs
							appointmentsPromise = Promise.resolve();
						}
					}

					// Availability processing - runs for preloaded, cached, and API-fetched appointments
					// Skip if REST API is unavailable
					if ( appointmentsPromise && !skipAvailRef.current && availabilitiesDirtyRef.current && false !== __wcaRestApiAvailable ) {
						appointmentsPromise.then( function() {
							// Use current filter values from refs for availability fetching
							var availProductId = productIdRef.current;
							var availStaffParam = null;
							if ( 'unassigned' === staffIdRef.current ) {
								availStaffParam = 0;
							} else if ( staffIdRef.current ) {
								availStaffParam = staffIdRef.current;
							}

							// OPTIMIZED: Use v2 availabilities endpoint with batch scopes to reduce API calls
							var scopesToFetch = [ { kind: 'availability#global', kindId: null } ];
							if ( availProductId ) {
								scopesToFetch.push( { kind: 'availability#product', kindId: availProductId } );
							}
							if ( availStaffParam ) {
								scopesToFetch.push( { kind: 'availability#staff', kindId: availStaffParam } );
							}

							var v2Params = {
								start_ts: startTs,
								end_ts: endTs,
								scopes: JSON.stringify( scopesToFetch ),
								per_page: 100
							};
							if ( availProductId ) {
								v2Params.product_id = availProductId;
							}
							if ( availStaffParam ) {
								v2Params.staff_id = availStaffParam;
							}

							// Fetch availability rules using the optimized v2 endpoint
							apiGet( '/wc-appointments/v2/availabilities', v2Params ).then( function( v2Results ) {
								// Convert v2Results to the format expected by processAvailabilityResults
								var unavGlobal = [];
								var unavProduct = [];
								var unavStaff = [];
								var availGlobal = [];
								var availProduct = [];
								var availStaff = [];

								if ( v2Results && 'object' === typeof v2Results ) {
									if ( v2Results[ 'availability#global' ] ) {
										unavGlobal = v2Results[ 'availability#global' ].unavailable || [];
										availGlobal = v2Results[ 'availability#global' ].available || [];
									}
									if ( availProductId && v2Results[ 'availability#product' ] ) {
										unavProduct = v2Results[ 'availability#product' ].unavailable || [];
										availProduct = v2Results[ 'availability#product' ].available || [];
									}
									if ( availStaffParam && v2Results[ 'availability#staff' ] ) {
										unavStaff = v2Results[ 'availability#staff' ].unavailable || [];
										availStaff = v2Results[ 'availability#staff' ].available || [];
									}
								}

								// Call the existing processAvailabilityResults function via global reference
								var res = [ unavGlobal, unavProduct, unavStaff, availGlobal, availProduct, availStaff ];
								if ( window.__wcaProcessAvailability ) {
									window.__wcaProcessAvailability( res, startTs, endTs, availProductId, availStaffParam );
								}
							} ).catch( function( v2Error ) {
								console.error( 'Failed to fetch availability rules:', v2Error );
								availabilitiesDirtyRef.current = false;
							} );
						} );
					}
				}
			} );

			// Render calendar and mount listeners
			calendar.render();
			try {
				var vtype0 = ( calendar && calendar.view && calendar.view.type ) ? calendar.view.type : '';
				if ( 'timeGridWeek' === vtype0 || 'timeGridDay' === vtype0 || 'listWeek' === vtype0 ) {
					calendar.setOption( 'contentHeight', 'auto' );
				} else {
					calendar.setOption( 'contentHeight', undefined );
				}
			} catch ( e ) {}
			try {
				setViewTitle( calendar && calendar.view ? calendar.view.title : '' );
			} catch ( e ) {}
			calRef.current = calendar;
			var hoverTimeout = null;
			var hoverCellEl = null;
			var hoverColEl = null;
			var hoverTimeEl = null;
			var hoverSuppressed = false;
			var calendarEl = calElRef.current;
			var timeGridBodyEl = null;

			function clearHoverCell() {
				try {
					if ( hoverTimeout ) {
						clearTimeout( hoverTimeout );
						hoverTimeout = null;
					}
					if ( hoverCellEl && hoverCellEl.parentNode ) {
						hoverCellEl.parentNode.removeChild( hoverCellEl );
					}
				} catch ( _ ) {}
				hoverCellEl = null;
				hoverColEl = null;
				hoverTimeEl = null;
			}

			function getTimeGridBody() {
				try {
					if ( timeGridBodyEl && calendarEl && calendarEl.contains( timeGridBodyEl ) ) {
						return timeGridBodyEl;
					}
				} catch ( _ ) {}
				timeGridBodyEl = calendarEl ? calendarEl.querySelector( '.fc-timegrid-body' ) : null;
				return timeGridBodyEl;
			}

			function formatTimeString( timeStr ) {
				if ( !timeStr ) {
					return '';
				}
				var parts = String( timeStr ).split( ':' );
				var hh = parseInt( parts[ 0 ] || '0', 10 );
				var mm = parseInt( parts[ 1 ] || '0', 10 );
				var ss = parseInt( parts[ 2 ] || '0', 10 );
				if ( isNaN( hh ) || isNaN( mm ) || isNaN( ss ) ) {
					return '';
				}
				var d = new Date();
				d.setHours( hh, mm, ss, 0 );
				return formatTimeByPatternLocal( d, timePattern );
			}

			function getSlotTimeLabel( slotLane ) {
				try {
					if ( !slotLane ) {
						return '';
					}
					var row = slotLane.closest( 'tr' );
					if ( row ) {
						var labelEl = row.querySelector( '.fc-timegrid-slot-label-cushion' );
						if ( labelEl && labelEl.textContent ) {
							return labelEl.textContent.trim();
						}
						var rowTime = row.getAttribute( 'data-time' );
						if ( rowTime ) {
							return formatTimeString( rowTime );
						}
					}
					var laneTime = slotLane.getAttribute( 'data-time' ) || ( slotLane.dataset ? slotLane.dataset.time : '' );
					if ( laneTime ) {
						return formatTimeString( laneTime );
					}
				} catch ( _ ) {}
				return '';
			}

			function onTimeGridPointerDown() {
				hoverSuppressed = true;
				clearHoverCell();
			}

			function onTimeGridPointerUp() {
				hoverSuppressed = false;
			}

			function onTimeGridMouseMove( e ) {
				if ( hoverTimeout ) {
					clearTimeout( hoverTimeout );
				}
				hoverTimeout = setTimeout( function() {
					try {
						var view = ( calendar && calendar.view ) ? calendar.view : null;
						if ( hoverSuppressed ) {
							clearHoverCell();
							return;
						}
						if ( dragInProgressRef.current ) {
							clearHoverCell();
							return;
						}
						if ( !view || ( 'timeGridWeek' !== view.type && 'timeGridDay' !== view.type ) ) {
							clearHoverCell();
							return;
						}
						var timeGridBody = getTimeGridBody();
						if ( !timeGridBody ) {
							clearHoverCell();
							return;
						}
						var target = e && e.target ? e.target : null;
						if ( !target || !target.closest ) {
							clearHoverCell();
							return;
						}
						if ( target.closest( '.fc-timegrid-slot-label' ) || target.closest( '.fc-timegrid-axis' ) ) {
							clearHoverCell();
							return;
						}
						var dayCol = target.closest( '.fc-timegrid-col' );
						if ( !dayCol && calendarEl ) {
							var cols = calendarEl.querySelectorAll( '.fc-timegrid-col' );
							for ( var i = 0; i < cols.length; i++ ) {
								var rect = cols[ i ].getBoundingClientRect();
								if ( rect && e.clientX >= rect.left && e.clientX <= rect.right ) {
									dayCol = cols[ i ];
									break;
								}
							}
						}
						if ( !dayCol ) {
							clearHoverCell();
							return;
						}
						var slotLane = target.closest( '.fc-timegrid-slot-lane' );
						if ( !slotLane && calendarEl ) {
							var lanes = calendarEl.querySelectorAll( '.fc-timegrid-slot-lane' );
							for ( var j = 0; j < lanes.length; j++ ) {
								var laneRect = lanes[ j ].getBoundingClientRect();
								if ( laneRect && e.clientY >= laneRect.top && e.clientY <= laneRect.bottom ) {
									slotLane = lanes[ j ];
									break;
								}
							}
						}
						if ( !slotLane ) {
							clearHoverCell();
							return;
						}
						var slotRect = slotLane.getBoundingClientRect();
						var colRect = dayCol.getBoundingClientRect();
						var bodyRect = timeGridBody.getBoundingClientRect();
						if ( !slotRect || !colRect || !bodyRect ) {
							clearHoverCell();
							return;
						}
						var relTop = slotRect.top - bodyRect.top;
						var relLeft = colRect.left - bodyRect.left;
						if ( !hoverCellEl ) {
							hoverCellEl = document.createElement( 'div' );
							hoverCellEl.className = 'fc-timegrid-cell-hover';
							hoverTimeEl = document.createElement( 'span' );
							hoverTimeEl.className = 'fc-timegrid-cell-hover-time';
							hoverCellEl.appendChild( hoverTimeEl );
							timeGridBody.appendChild( hoverCellEl );
						}
						hoverColEl = dayCol;
						var timeLabel = getSlotTimeLabel( slotLane );
						if ( hoverTimeEl ) {
							hoverTimeEl.textContent = timeLabel;
						}
						if ( hoverCellEl && hoverCellEl.classList ) {
							hoverCellEl.classList.toggle( 'is-empty', !timeLabel );
						}
						hoverCellEl.style.top = relTop + 'px';
						hoverCellEl.style.left = relLeft + 'px';
						hoverCellEl.style.width = colRect.width + 'px';
						hoverCellEl.style.height = slotRect.height + 'px';
					} catch ( _ ) {}
				}, 12 );
			}

			if ( calendarEl ) {
				calendarEl.addEventListener( 'mousemove', onTimeGridMouseMove );
				calendarEl.addEventListener( 'mouseleave', clearHoverCell );
				calendarEl.addEventListener( 'mousedown', onTimeGridPointerDown, true );
				calendarEl.addEventListener( 'mouseup', onTimeGridPointerUp, true );
				calendarEl.addEventListener( 'touchstart', onTimeGridPointerDown, true );
				calendarEl.addEventListener( 'touchend', onTimeGridPointerUp, true );
			}
			// Expose a global refresh hook for modal to trigger immediate update
			try {
				// Immediate refresh triggered by Backbone modal after create
				// Gate non-SSE refetch to avoid duplicate event flashes
				window.wcAppointmentsRefreshCalendar = function() {
					try {
						pendingCreateRef.current = Date.now();
						skipAvailRef.current = true;
						appointmentsDirtyRef.current = true;
						availabilitiesDirtyRef.current = false;
						if ( !dragInProgressRef.current && !sseReadyRef.current ) {
							calendar.refetchEvents();
						}
					} catch ( e ) {}
				};
			} catch ( e ) {}
			// Also listen for custom creation/update events to refetch
			try {
				// Fallback custom event from modal; gate on SSE readiness
				window.addEventListener( 'wc_appointments_created', function() {
					try {
						pendingCreateRef.current = Date.now();
						skipAvailRef.current = true;
						appointmentsDirtyRef.current = true;
						availabilitiesDirtyRef.current = false;
						if ( !dragInProgressRef.current && !sseReadyRef.current ) {
							calendar.refetchEvents();
						}
					} catch ( e ) {}
				} );
				window.addEventListener( 'wc_appointments_updated', function() {
					try {
						skipAvailRef.current = true;
						appointmentsDirtyRef.current = true;
						availabilitiesDirtyRef.current = false;
						if ( !dragInProgressRef.current ) {
							calendar.refetchEvents();
						}
					} catch ( e ) {}
				} );
				window.addEventListener( 'wc_appointments_deleted', function() {
					try {
						skipAvailRef.current = true;
						appointmentsDirtyRef.current = true;
						availabilitiesDirtyRef.current = false;
						if ( !dragInProgressRef.current ) {
							calendar.refetchEvents();
						}
					} catch ( e ) {}
				} );
				// Optional: listen for availability changes and only then recompute blocks
				window.addEventListener( 'wca_availability_created', function() {
					try {
						availabilitiesDirtyRef.current = true;
						calendar.refetchEvents();
					} catch ( e ) {}
				} );
				window.addEventListener( 'wca_availability_updated', function() {
					try {
						availabilitiesDirtyRef.current = true;
						calendar.refetchEvents();
					} catch ( e ) {}
				} );
				window.addEventListener( 'wca_availability_deleted', function() {
					try {
						availabilitiesDirtyRef.current = true;
						calendar.refetchEvents();
					} catch ( e ) {}
				} );
			} catch ( e ) {}
			return function() {
				try {
					if ( calendarEl ) {
						calendarEl.removeEventListener( 'mousemove', onTimeGridMouseMove );
						calendarEl.removeEventListener( 'mouseleave', clearHoverCell );
						calendarEl.removeEventListener( 'mousedown', onTimeGridPointerDown, true );
						calendarEl.removeEventListener( 'mouseup', onTimeGridPointerUp, true );
						calendarEl.removeEventListener( 'touchstart', onTimeGridPointerDown, true );
						calendarEl.removeEventListener( 'touchend', onTimeGridPointerUp, true );
					}
					clearHoverCell();
					calendar.destroy();
				} catch ( e ) {}
			};
		}, [] ); // Only initialize calendar once on mount; filter changes handled via refetchEvents

		// Re-evaluate past/active state periodically so opacity stays current without navigation
		useEffect( function() {
			var t = setInterval( function() {
				try {
					if ( dragInProgressRef.current ) {
						return;
					}
					if ( calRef.current && 'function' === typeof calRef.current.rerenderEvents ) {
						calRef.current.rerenderEvents();
					}
				} catch ( e ) {}
			}, 60000 );
			return function() {
				try {
					clearInterval( t );
				} catch ( e ) {}
			};
		}, [] );

		// Live updates via SSE: replace aggressive polling
		useEffect( function() {
			var src = null;
			var watchdog = null;
			var reconnectTimer = null;
			var reconnectAttempts = 0;
			var destroyed = false;
			var restBase = '';
			var sseDurationMs = 60000; // 1 minute default

			// Add filter support for SSE duration
			try {
				sseDurationMs = parseInt( wcAppointmentsReactCalendar.sseDurationMs || '60000', 10 );
			} catch ( _e ) {}

			try {
				restBase = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.restUrl ) ? wcAppointmentsReactCalendar.restUrl : '';
			} catch ( _r0 ) {
				restBase = '';
			}
			if ( restBase && '/' !== restBase.slice( -1 ) ) {
				restBase += '/';
			}
			var ES = ( window && window.EventSource ) ? window.EventSource : null;
			if ( !restBase || !ES ) {
				return function() {};
			}

			function clearWatchdog() {
				try {
					if ( watchdog ) {
						clearTimeout( watchdog );
						watchdog = null;
					}
				} catch ( _ ) {}
			}

			function resetWatchdog() {
				try {
					clearWatchdog();
					watchdog = setTimeout( function() {
						try {
							if ( destroyed ) {
								return;
							}
							if ( document && document.hidden ) {
								return;
							}
							if ( dragInProgressRef.current ) {
								return;
							}
							if ( calRef.current && 'function' === typeof calRef.current.refetchEvents ) {
								calRef.current.refetchEvents();
							}
						} catch ( _w ) {}
					}, 60000 );
				} catch ( _ ) {}
			}

			function closeSource() {
				try {
					// Clear stream timeout if exists
					if ( src && src._timeout ) {
						clearTimeout( src._timeout );
						src._timeout = null;
					}
					if ( src && 'function' === typeof src.close ) {
						src.close();
					}
				} catch ( _ ) {}
				src = null;
			}

			function buildSseUrl() {
				try {
					var u = new URL( restBase + 'wc-appointments/v2/sse' );
					var lastId = 0;
					try {
						// Use site-specific localStorage key for multisite compatibility
						var blogId = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.blogId ) ? wcAppointmentsReactCalendar.blogId : '';
						var storageKey = blogId ? 'wca:lastEventId:' + blogId : 'wca:lastEventId';
						lastId = window.localStorage ? parseInt( window.localStorage.getItem( storageKey ) || '0', 10 ) : 0;
					} catch ( _s ) {
						lastId = 0;
					}
					if ( 0 < lastId ) {
						u.searchParams.set( 'since_id', String( lastId ) );
					}
					var nonceVal = '';
					try {
						nonceVal = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.nonce ) ? wcAppointmentsReactCalendar.nonce : '';
					} catch ( _n ) {
						nonceVal = '';
					}
					if ( nonceVal ) {
						u.searchParams.set( 'nonce', nonceVal );
					}
					return u.toString();
				} catch ( _ ) {
					return '';
				}
			}

			function scheduleReconnect( immediate ) {
				if ( destroyed ) {
					return;
				}
				try {
					if ( reconnectTimer ) {
						clearTimeout( reconnectTimer );
						reconnectTimer = null;
					}
					var attempt = reconnectAttempts;
					var delay = immediate ? 0 : Math.min( Math.pow( 2, attempt ) * 1000, 30000 );
					var jitter = immediate ? 0 : Math.floor( Math.random() * 500 );
					reconnectTimer = setTimeout( function() {
						reconnectTimer = null;
						startStream();
					}, delay + jitter );
					if ( 6 > reconnectAttempts ) {
						reconnectAttempts++;
					}
				} catch ( _ ) {}
			}

			function startStream() {
				if ( destroyed ) {
					return;
				}
				closeSource();
				clearWatchdog();
				var url = buildSseUrl();
				if ( !url ) {
					scheduleReconnect( false );
					return;
				}
				try {
					src = new ES( url, { withCredentials: true } );
				} catch ( _err ) {
					scheduleReconnect( false );
					return;
				}
				// Deduplicate SSE events for a short TTL to avoid double-handling
				var __sseSeen = {};
				var __sseTTL = 3000;

				// Track SSE connection readiness to control refetch behavior
				src.addEventListener( 'open', function() {
					try {
						sseReadyRef.current = true;
					} catch ( _r ) {}
					reconnectAttempts = 0;
					resetWatchdog();

					// Set duration limit - close and reconnect after sseDurationMs
					var streamTimeout = setTimeout(function() {
						if ( src && src.readyState === 1 ) { // OPEN
							src.close();
							sseReadyRef.current = false;
							// Immediate reconnect (not an error, just duration limit)
							startStream();
						}
					}, sseDurationMs);

					// Store timeout reference for cleanup
					src._timeout = streamTimeout;
				} );
				src.addEventListener( 'error', function() {
					try {
						sseReadyRef.current = false;
					} catch ( _r2 ) {}
					resetWatchdog();
					closeSource();
					scheduleReconnect( false );
				} );
				// Handle appointment and availability SSE events with gating
				var onEvent = function( e ) {
					try {
						if ( dragInProgressRef.current ) {
							return;
						}
						var d = e && e.data ? JSON.parse( e.data ) : null;
						if ( !d || !d.topic ) {
							return;
						}
						var __eid = e && e.lastEventId ? parseInt( e.lastEventId, 10 ) : 0;
						var __rid = d && ( d.resource_id || ( d.payload && d.payload.id ) ) ? ( d.resource_id || d.payload.id ) : 0;
						var __key = __eid ? ( 'e:' + __eid ) : ( 'r:' + String( __rid ) + ':' + String( d.topic || '' ) );
						var __now = Date.now();
						if ( __sseSeen[ __key ] && ( __now - __sseSeen[ __key ] ) < __sseTTL ) {
							return;
						}
						__sseSeen[ __key ] = __now;

						// Show toast notification for SSE events (neutral color for all)
						try {
							var toastMsg = getToastMessageForEvent( d.topic, d.payload || null, i18n, d.resource_id );
							if ( toastMsg ) {
								showToast( toastMsg, 'info' );
							}
						} catch ( __toast ) {}

						var inCurrentRange = function( st, en ) {
							try {
								var cal = calRef.current;
								if ( !cal || !cal.view ) {
									return true;
								}
								var aS = cal.view.activeStart || cal.view.currentStart || cal.view.start;
								var aE = cal.view.activeEnd || cal.view.currentEnd || cal.view.end;
								var sTs = Math.floor( aS.getTime() / 1000 );
								var eTs = Math.floor( aE.getTime() / 1000 );
								return ( en > sTs ) && ( st < eTs );
							} catch ( _ ) {
								return true;
							}
						};
						// Decide if an appointment row should be displayed in the current view
						var shouldDisplay = function( row ) {
							try {
								var st = parseInt( row.start, 10 );
								var en = parseInt( row.end, 10 );
								if ( isExcludedStatus( row.status ) ) {
									return false;
								}
								if ( productId && String( row.product_id ) !== String( productId ) ) {
									return false;
								}
								var staffParam = null;
								if ( 'unassigned' === staffId ) {
									staffParam = 0;
								} else if ( staffId ) {
									staffParam = staffId;
								}
								if ( null !== staffParam ) {
									var arr = Array.isArray( row.staff_ids ) ? row.staff_ids.slice() : ( undefined !== row.staff_id ? [ row.staff_id ] : [] );
									if ( '0' === String( staffParam ) ) {
										var isUnassigned = 0 === arr.length || -1 !== arr.map( function( x ) {
											return String( x );
										} ).indexOf( '0' );
										if ( !isUnassigned ) {
											return false;
										}
									} else {
										var match = -1 !== arr.map( function( x ) {
											return String( x );
										} ).indexOf( String( staffParam ) );
										if ( !match ) {
											return false;
										}
									}
								}
								if ( !inCurrentRange( st, en ) ) {
									return false;
								}
								return true;
							} catch ( _ ) {
								return true;
							}
						};
						// Remove existing appointment events from FullCalendar by id
						var removeAppt = function( id ) {
							try {
								var cal = calRef.current;
								if ( !cal ) {
									return;
								}
								var arr = ( 'function' === typeof cal.getEvents ) ? ( function() {
									try {
										var apptIdStr = String( id );
										var eid = 'appt-' + apptIdStr;
										var all = cal.getEvents();
										var out = [];
										for ( var i = 0; i < all.length; i++ ) {
											var evx = all[ i ];
											var p = evx.extendedProps || {};
											var m1 = String( evx.id || '' ) === eid;
											var m2 = String( p.appointmentId || p.appointment_id || '' ) === apptIdStr;
											if ( m1 || m2 ) {
												out.push( evx );
											}
										}
										return out;
									} catch ( __ ) {
										return [];
									}
								}() ) : [];
								for ( var j = 0; j < arr.length; j++ ) {
									try {
										arr[ j ].remove();
									} catch ( __r ) {}
								}
								if ( 'function' === typeof cal.getEventById ) {
									var ev = cal.getEventById( 'appt-' + String( id ) );
									if ( ev ) {
										try {
											ev.remove();
										} catch ( __r2 ) {}
									}
								}
								clearSseInjectionFlag( id );
							} catch ( _ ) {}
						};
						// Fetch and inject a single appointment into the current calendar
						var injectAppt = function( id ) {
							try {
								var cal = calRef.current;
								if ( !cal ) {
									return;
								}

								// Fetch single appointment with all modal data fields using the appointments endpoint
								// (calendar endpoint requires date range, so we use appointments for single fetch)
								// Include all fields needed for modal display to avoid subsequent API calls
								apiGet( 'wc-appointments/v2/appointments/' + String( id ), {
									_fields: 'id,start,end,product_id,product_title,staff_id,staff_ids,staff_name,staff_avatar,status,customer_id,order_id,order_item_id,order_info,cost,all_day,qty,customer_status,customer_name,customer_first_name,customer_last_name,customer_full_name,customer_avatar,cal_color'
								} ).then( function( row ) {
									try {
										if ( !row || !row.id ) {
											return;
										}
										var st = parseInt( row.start, 10 );
										var en = parseInt( row.end, 10 );

										if ( isExcludedStatus( row.status ) ) {
											return;
										}
										if ( productId && String( row.product_id ) !== String( productId ) ) {
											return;
										}
										var staffParam = null;
										if ( 'unassigned' === staffId ) {
											staffParam = 0;
										} else if ( staffId ) {
											staffParam = staffId;
										}
										if ( null !== staffParam ) {
											var arr = Array.isArray( row.staff_ids ) ? row.staff_ids.slice() : ( undefined !== row.staff_id ? [ row.staff_id ] : [] );
											if ( '0' === String( staffParam ) ) {
												var isUnassigned = 0 === arr.length || -1 !== arr.map( function( x ) {
													return String( x );
												} ).indexOf( '0' );
												if ( !isUnassigned ) {
													return;
												}
											} else {
												var match = -1 !== arr.map( function( x ) {
													return String( x );
												} ).indexOf( String( staffParam ) );
												if ( !match ) {
													return;
												}
											}
										}
										if ( !inCurrentRange( st, en ) ) {
											return;
										}
										var usersIdx = window.__wcaUsersIndex || {};
										var colorsIdx = window.__wcaProductColorsIndex || {};
										removeAppt( row.id );
										var mk = ( window && 'function' === typeof window.__wcaBuildAppointmentEvent ? window.__wcaBuildAppointmentEvent : ( window && 'function' === typeof window.buildAppointmentEvent ? window.buildAppointmentEvent : null ) );
										if ( 'function' !== typeof mk ) {
											return;
										}
										var evt = mk ? mk( row, usersIdx, colorsIdx ) : null;
										if ( 'function' === typeof cal.addEvent ) {
											if ( evt ) {
												var added = cal.addEvent( evt );
												recordSseInjection( row.id, added );
											}
										}
									} catch ( _inj ) {}
								} ).catch( function() {} );
							} catch ( _ ) {}
						};

						var upsertAppointmentEvent = function( row ) {
							try {
								var cal = calRef.current;
								if ( !cal ) {
									return false;
								}
								removeAppt( row.id );
								var mk = ( window && 'function' === typeof window.__wcaBuildAppointmentEvent ? window.__wcaBuildAppointmentEvent : ( window && 'function' === typeof window.buildAppointmentEvent ? window.buildAppointmentEvent : null ) );
								if ( 'function' !== typeof mk ) {
									return false;
								}
								var usersIdx = window.__wcaUsersIndex || {};
								var colorsIdx = window.__wcaProductColorsIndex || {};
								var evt = mk ? mk( row, usersIdx, colorsIdx ) : null;
								if ( 'function' === typeof cal.addEvent ) {
									if ( evt ) {
										var added = cal.addEvent( evt );
										recordSseInjection( row.id, added );
									}
								}
								if ( 'function' === typeof cal.rerenderEvents ) {
									cal.rerenderEvents();
								}
								return true;
							} catch ( _ ) {
								return false;
							}
						};
						try {
							var evId = e && e.lastEventId ? parseInt( e.lastEventId, 10 ) : 0;
							if ( 0 < evId && window.localStorage ) {
								// Use site-specific localStorage key for multisite compatibility
								var blogId = ( wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.blogId ) ? wcAppointmentsReactCalendar.blogId : '';
								var storageKey = blogId ? 'wca:lastEventId:' + blogId : 'wca:lastEventId';
								window.localStorage.setItem( storageKey, String( evId ) );
							}
						} catch ( _ ) {}
						var t = String( d.topic || '' );
						if ( 0 === t.indexOf( 'appointment.' ) ) {
							// If this is a delete/trash event, remove and exit early
							if ( /\.(deleted|trashed)$/.test( t ) ) {
								try {
									var delId = d && d.resource_id ? d.resource_id : ( d && d.payload && d.payload.id ? d.payload.id : null );
									if ( delId ) {
										removeAppt( delId );
									} else if ( calRef.current && 'function' === typeof calRef.current.refetchEvents ) {
										calRef.current.refetchEvents();
									}
								} catch ( __del ) {}
								return;
							}
							appointmentsDirtyRef.current = true;
							skipAvailRef.current = true;
							if ( d && d.resource_id ) {
								if ( d.payload && d.payload.id ) {
									try {
										var row = d.payload;
										if ( !shouldDisplay( row ) ) {
											return;
										}
										var cal = calRef.current;
										if ( !cal ) {
											try {
												injectAppt( row.id );
											} catch ( __ ) {}
											return;
										}
										// Upsert or inject appointment into the calendar from SSE payload
										try {
											if ( !upsertAppointmentEvent( row ) ) {
												injectAppt( row.id );
											}

											// Update modal if it's currently open for this appointment
											if ( selectedAppt && selectedAppt.id === row.id ) {
												setSelectedAppt( {
													...selectedAppt,
													start_ts: row.start,
													end_ts: row.end,
													all_day: row.all_day || selectedAppt.all_day,
													status: row.status || selectedAppt.status,
													customer_status: row.customer_status || selectedAppt.customer_status,
													cost: row.cost || selectedAppt.cost
												} );
											}

											// Clear pending create marker after SSE delivers the event
											try {
												pendingCreateRef.current = 0;
											} catch ( ___pc ) {}
										} catch ( __up ) {
											try {
												injectAppt( row.id );
											} catch ( ___inj ) {}
										}
									} catch ( __ ) {}
								} else {
									// No payload, fetch and update cache
									injectAppt( d.resource_id );
								}
							}
							// handled via direct injection above
						} else if ( /\.(deleted|trashed)$/.test( t ) ) {
							if ( d && d.resource_id ) {
								removeAppt( d.resource_id );
							} else if ( calRef.current && 'function' === typeof calRef.current.refetchEvents ) {
								calRef.current.refetchEvents();
							}
						} else if ( 0 === t.indexOf( 'availability.' ) ) {
							availabilitiesDirtyRef.current = true;
							// Force availability refetch even if a previous appointment flow skipped it.
							skipAvailRef.current = false;
							if ( calRef.current && 'function' === typeof calRef.current.refetchEvents ) {
								calRef.current.refetchEvents();
							}
							// no global dispatch
						}
						resetWatchdog();
					} catch ( _ ) {}
				};
				src.addEventListener( 'appointment.created', onEvent );
				src.addEventListener( 'appointment.updated', onEvent );
				src.addEventListener( 'appointment.deleted', onEvent );
				src.addEventListener( 'appointment.trashed', onEvent );
				src.addEventListener( 'appointment.rescheduled', onEvent );
				src.addEventListener( 'appointment.cancelled', onEvent );
				src.addEventListener( 'availability.created', onEvent );
				src.addEventListener( 'availability.updated', onEvent );
				src.addEventListener( 'availability.deleted', onEvent );
				// Also handle default messages in case server does not set event names
				src.addEventListener( 'message', onEvent );
			}

			startStream();

			return function() {
				destroyed = true;
				clearWatchdog();
				closeSource();
				try {
					if ( reconnectTimer ) {
						clearTimeout( reconnectTimer );
					}
				} catch ( _ ) {}
			};
		}, [] );

		function setQSParam( name, value ) {
			try {
				var currentHref = window.location.href;
				var url = new URL( currentHref );
				var existing = url.searchParams.get( name );
				var isEmpty = ( '' === value || null === value || value === undefined );
				if ( isEmpty ) {
					if ( null === existing ) {
						return;
					}
					url.searchParams.delete( name );
				} else {
					if ( String( existing ) === String( value ) ) {
						return;
					}
					url.searchParams.set( name, value );
				}
				var nextHref = url.toString();
				if ( nextHref !== currentHref ) {
					window.history.replaceState( {}, '', nextHref );
				}
			} catch ( e ) {}
		}

		useEffect( function() {
			setQSParam( 'product_id', productId || '' );
		}, [ productId ] );
		useEffect( function() {
			setQSParam( 'staff_id', staffId || '' );
		}, [ staffId ] );

		function mapViewToParam( v ) {
			switch ( v ) {
				case 'dayGridMonth':
					return 'month';
				case 'timeGridDay':
					return 'day';
				case 'listWeek':
					return 'list';
				case 'timeGridWeek':
				default:
					return 'week';
			}
		}

		function changeView( v, date ) {
			if ( calRef.current ) {
				calRef.current.changeView( v, date );
			}
			setCurrentView( v );
			var viewParam = mapViewToParam( v );
			setQSParam( 'view', viewParam );

			// Save view preference to user meta (fire-and-forget)
			var ajaxUrl = wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.ajaxUrl;
			var viewNonce = wcAppointmentsReactCalendar && wcAppointmentsReactCalendar.viewNonce;
			if ( ajaxUrl && viewNonce ) {
				var formData = new FormData();
				formData.append( 'action', 'wc_appointments_save_calendar_view' );
				formData.append( 'security', viewNonce );
				formData.append( 'view', viewParam );
				fetch( ajaxUrl, { method: 'POST', body: formData, credentials: 'same-origin' } ).catch( function() {} );
			}
		}

		function prev() {
			if ( calRef.current ) {
				calRef.current.prev();
			}
		}

		function next() {
			if ( calRef.current ) {
				calRef.current.next();
			}
		}

		function today() {
			if ( calRef.current ) {
				calRef.current.today();
			}
		}

		return h(
			'div', {
				className: 'wca-react-calendar'
			},
			error ? h( 'div', {
				className: 'notice notice-error'
			}, h( 'p', null, error, ' ', h( 'a', {
				href: '#',
				onClick: function( e ) {
					e.preventDefault();
					window.location.reload();
				},
				style: { textDecoration: 'underline', cursor: 'pointer' }
			}, t( 'reloadPage', 'Reload page' ) ) ) ) : null,
			h( Toolbar, {
				title: viewTitle,
				products: products,
				staff: staff,
				canManageOthers: canManageOthers,
				productId: productId,
				setProductId: setProductId,
				staffId: staffId,
				setStaffId: setStaffId,
				changeView: changeView,
				onPrev: prev,
				onNext: next,
				onToday: today,
				currentView: currentView
			} ),
			h( 'div',
				{
					className: 'wca-calendar-container'
				},
				h( 'div',
					{
						className: 'wca-loading-bar' + ( loadingEvents ? ' is-active' : '' )
					}
				),
				h( 'div',
					{
						id: 'wca-calendar-view',
						ref: calElRef
					}
				)
			),
			showModal ? h( AppointmentModal, {
				appt: selectedAppt,
				products: products,
				staff: staff,
				onClose: function() {
					setShowModal( false );
				},
				onEdit: function( id ) {
					window.location = wcAppointmentsReactCalendar.adminPost + '?post=' + id + '&action=edit';
				}
			} ) : null,
			showRuleModal ? h( RuleModal, {
				rule: selectedRule,
				onClose: function() {
					setShowRuleModal( false );
				}
			} ) : null,
			h( ToastContainer )
		);
	}

	document.addEventListener( 'DOMContentLoaded', function() {
		var rootEl = document.getElementById( 'wca-react-calendar-root' );
		if ( !rootEl ) {
			return;
		}
		if ( wp.element && 'function' === typeof wp.element.createRoot ) {
			var root = wp.element.createRoot( rootEl );
			root.render( h( App ) );
		} else if ( wp.element && 'function' === typeof wp.element.render ) {
			wp.element.render( h( App ), rootEl );
		}
	} );
} )();
