/**
 * External dependencies.
 */
import {
	useCallback,
	useEffect,
	useMemo,
	useState,
	useRef,
	useContext,
} from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';

import {
	Eye,
	EyeOff,
	ChevronDown,
	ListPlus,
	ListX,
	Save,
} from 'lucide-react';

// Initialize global storage for temporary unsaved changes
if ( ! window.spectraGSTemporaryStorage ) {
	window.spectraGSTemporaryStorage = {};
}
import apiFetch from '@wordpress/api-fetch';
import {
	Button,
	Dialog,
	DropdownMenu,
	Input,
	Label,
	Loader,
	Toaster,
	toast,
} from '@bsf/force-ui';

/**
 * Internal dependencies.
 */
import { spectraClassNames } from '@spectra-helpers';
import SettingsHeader from '@spectra-pro-admin/global-styles/components/SettingsHeader';
import { GlobalStylesContext } from '@spectra-pro-admin/global-styles/helpers';

/**
 * Converts a stylesheet string to an object.
 *
 * @since x.x.x
 *
 * @param {string} cssVarSheet - A string containing the entire stylesheet content. 
 * @return {Object}            - The style object with the CSS properties as keys, and their CSS values as values.
 */
const convertToCssObject = ( cssVarSheet ) => {

	// Split into lines and filter empty ones and lines containing { or }.
	const lines = cssVarSheet.split( '\n' ).filter( line => {
		const trimmedLine = line.trim();
		return trimmedLine && !trimmedLine.includes( '{' ) && !trimmedLine.includes( '}' );
	} );

	// Create object to store key-value pairs.
	const cssClassObject = {};
	
	// Process each line
	lines.forEach( line => {
		line = line.trim();
		// Extract key and value using regex.
		const match = line.match( /^([a-zA-Z][a-zA-Z0-9_-]*):(.+);/ );

		if ( match ) {
			// Get key and value, trimming whitespace.
			const key = match[ 1 ].trim();
			const value = match[ 2 ].trim();
			
			if ( window.CSS.supports( key, value ) ) {
				// Add to cssClassObject.
				cssClassObject[ key ] = value;
			}
		}
	} );

	// Return the CSS class object.
	return cssClassObject;
};

/**
 * Convert the given stylesheet object to a string.
 *
 * @since x.x.x
 *
 * @param {Object} cssClassObject - The CSS class object. 
 * @return {string}               - The stringified stylesheet based on the object.
 */
const convertToCssSheet = ( cssClassObject ) => {
	// If no object was passed, abandon ship.
	if ( ! cssClassObject ) {
		return;
	}

    // Map over object entries and format each key-value pair.
    const lines = Object.entries( cssClassObject ).map( ( [ key, value ] ) => (
		window.CSS.supports( key, value ) ? `${ key }: ${ value };` : ''
	) );
    
    // Join with newlines.
    return lines.filter( Boolean ).join( '\n' );
};

/**
 * Renders a divider.
 *
 * @since x.x.x
 *
 * @return {Element} A simple divider.
 */
const Divider = () => (
	<hr className="w-full border-b-0 border-x-0 border-t border-solid border-t-border-subtle" />
);

/**
 * Renders the preview element for the current styles.
 *
 * @since x.x.x
 *
 * @param {Object} props - The element props. 
 * @return {Element}     - The rendered preview.
 */
const RenderPreview = ( props ) => {
	// Destructure the required props.
	const {
		previewClass,
		pseudoSelector,
		content = __( 'This has your custom class', 'spectra-pro' ),
	} = props;

	// Get all the variables from the context.
	const { allVariables } = useContext( GlobalStylesContext );
	// Loop through each variable, and format them.
	const styleVariables = useMemo( () => (
		Object.keys( allVariables ).reduce( ( updatedVariables, varKey ) => {
			updatedVariables[ `--${ varKey }`] = allVariables[ varKey ];
			return updatedVariables;
		}, {} )
	), [ allVariables ] );

	/**
	 * Render the preview based on the pseudo element.
	 *
	 * @since x.x.x
	 *
	 * @return {Element} - The rendered preview based on the pseudo-element.
	 */
	const RenderBasedOnPseudoSelector = () => {

		switch ( pseudoSelector ) {
			case 'active':
			case 'focus-visible':
			case 'disabled':
				// This case is for default and hover.
				return(
					<button
						className={ spectraClassNames( [
							'reset-styles',
							previewClass,
						] ) }
						disabled={ 'disabled' === pseudoSelector }
					>
						{ content }
					</button>
				);
			case 'first-child':
			case 'last-child':
				// This case is for first and last child CSS.
				return(
					<>
						<div className={ previewClass }>
							{ __( 'First child with your class.', 'spectra-pro' ) }
						</div>
						<div className={ previewClass }>
							{ __( 'Second child with your class.', 'spectra-pro' ) }
						</div>
						<div className={ previewClass }>
							{ __( 'Last child with your class.', 'spectra-pro' ) }
						</div>
					</>
				);
			default:
				// This case is for default, hover and only-child. first-child and last-child CSS will also apply.
				return(
					<div className={ previewClass }>
						{ content }
					</div>
				);
				
		}
	};

	// Render the preview in the preview wrapper.
	return (
		<div style={ {
			display: 'flex',
			width: 'fit-content',
			fontSize: 'initial',
			flexWrap: 'wrap',
			gap: '1rem',
			...styleVariables,
		} }>
			<RenderBasedOnPseudoSelector/>
		</div>
	);
};

/**
 * Render a section for users to create new classes.
 *
 * @since x.x.x
 *
 * @param {Object} props - The element props.
 * @return {Element}     - The rendered class creator.
 */
const RenderClassCreator = ( props ) => {
	// Destructure the required props.
	const {
		createdClasses,
		setCreatedClasses,
		setEditingClass,
		setEditingPseudoSelector,
		setEditingClassValue,
		finalizedUserClasses,
		setFinalizedUserClasses,
		updateUserClasses,
	} = props;

	// Set a state for the new class.
	const [ newClass, setNewClass ] = useState( '' );

	// Test the new class with a regex to ensure it starts with an alphabet, and only contains valid CSS naming characters.
	const isValid = /^[a-zA-Z][\w-]*$/.test( newClass );
	const isInvalid = ( newClass && ! isValid );

	// Function to add a new class.
	const addNewClass = () => {
		if ( ! newClass ) {
			return;
		}
		setCreatedClasses( [
			...createdClasses,
			newClass,
		] );
		setEditingClass( newClass );
		setFinalizedUserClasses( {
			...finalizedUserClasses,
			[ newClass ]: {},
		} );
		setEditingPseudoSelector( 'default' );
		setEditingClassValue( {} );
		
		// No need to store in temp memory - immediately saved via AJAX
		
		// Immediately save the new class via AJAX
		updateUserClasses( newClass, {}, 'create' );
		
		setNewClass( '' );
	};

	// Render the class creator.
	return (
		<>
			<div className="flex gap-4 items-end">
				<Input
					label={ __( 'Create a new class', 'spectra-pro' ) }
					value={ newClass }
					placeholder={ __( 'your-new-class-name', 'spectra-pro' ) }
					size="sm"
					type="text"
					error={ isInvalid }
					onChange={ ( value ) => {
						setNewClass( value )
					} }
				/>
				<Button
					icon={ <ListPlus aria-label="icon" role="img" size={ 16 }/> }
					iconPosition="left"
					size="sm"
					tag="button"
					type="button"
					variant="primary"
					disabled={ isInvalid }
					onClick={ addNewClass }
				>
					{ __( 'Add Class', 'spectra-pro' ) }
				</Button>
			</div>
			{ isInvalid && (
				<Label size="sm" variant="error">
					{ __( 'Class name should start with a letter, and can only contain letters( a-z ), numbers( 0-9 ), hyphens( - ) and underscores( _ )', 'spectra-pro' ) }
				</Label>
			) }
		</>
	);
};

/**
 * Render a section for users to switch between and delete created classes.
 *
 * @since x.x.x
 *
 * @param {Object} props - The element props.
 * @return {Element}     - The rendered class selector.
 */
const RenderClassSelector = ( props ) => {
	// Desturcture the required props.
	const {
		createdClasses = [],
		editingClass,
		setEditingClass,
		editingPseudoSelector,
		setEditingPseudoSelector,
		editingClassValue,
		isDeleting,
		setIsDeleting,
		updateUserClasses,
	} = props;

	// Create a state to trigger the deletion popup.
	const [ openDeleteDialog, setOpenDeleteDialog ] = useState( false );

	// Object of all the supported pseudo-selectors.
	const pseudoSelectors = {
		none: [
			'default',
		],
		action: [
			'hover',
			'active',
			'focus-visible',
			'focus-within',
		],
		input: [
			'disabled',
			'checked',
		],
		location: [
			'visited',
		],
		structural: [
			'first-child',
			'last-child',
			'only-child',
		],
	}

	// Render the class selector.
	return (
		<>
			<SettingsHeader
				heading={ __( 'Class Editor', 'spectra-pro' ) }
				description={ __( 'Select a class to edit, and start editing!', 'spectra-pro' ) }
				help={ editingClass && sprintf(
					/* translators: %s: The selected class name. */
					__( 'Currently editing: %s', 'spectra-pro' ),
					editingClass,
				) }
			>
				{ isDeleting && <Loader size="sm" /> }
			</SettingsHeader>
			<div className="flex justify-between">
				<div className='flex gap-2'>
					<DropdownMenu>
						<DropdownMenu.Trigger>
							<Button size="sm" variant="outline" icon={ <ChevronDown size={ 16 }/> } iconPosition="right">
								<span>{ editingClass ? `.${ editingClass }` : __( 'No Classes', 'spectra-pro' ) }</span>
							</Button>
						</DropdownMenu.Trigger>
						<DropdownMenu.ContentWrapper>
							<DropdownMenu.Content>
								<DropdownMenu.List heading={ __( 'Created Classes', 'spectra-pro' ) }>
									{ createdClasses.map( ( classOption, index ) => (
										<DropdownMenu.Item key={ index } className='text-sm' onClick={ () => {
											setEditingClass( classOption );
											setEditingPseudoSelector( 'default' );
										} }>
											{ classOption }
										</DropdownMenu.Item>
									) ) }
								</DropdownMenu.List>
							</DropdownMenu.Content>
						</DropdownMenu.ContentWrapper>
					</DropdownMenu>
					<DropdownMenu>
						<DropdownMenu.Trigger>
							<Button size="sm" variant="outline" icon={ <ChevronDown size={ 16 }/> } iconPosition="right">
								<span>{ 'default' !== editingPseudoSelector ? `:${ editingPseudoSelector }` : 'Default' }</span>
							</Button>
						</DropdownMenu.Trigger>
						<DropdownMenu.ContentWrapper>
							<DropdownMenu.Content>
								{ Object.entries( pseudoSelectors ).map( ( [ selector, selectorValues ], index ) => (
									<DropdownMenu.List
										key={ index }
										className='capitalize'
										heading={ selector }
										open={ [ 'none', 'action' ].includes( selector ) }
										arrow={ true }
									>
										{ selectorValues.map( ( selectorValue, selectorIndex ) => (
											<DropdownMenu.Item key={ selectorIndex } className='text-sm' onClick={ () => {
												setEditingPseudoSelector( selectorValue );
											} }>
												{ selectorValue }
											</DropdownMenu.Item>
										) ) }
									</DropdownMenu.List>
								) ) }
							</DropdownMenu.Content>
						</DropdownMenu.ContentWrapper>
					</DropdownMenu>
				</div>
				{ editingClass && (
					<Dialog
						design="simple"
						exitOnEsc
						exitOnClickOutside
						scrollLock
						open={ openDeleteDialog }
						setOpen={ setOpenDeleteDialog }
						trigger={ 
							<Button
								icon={ <ListX aria-label="icon" role="img" size={ 16 }/> }
								iconPosition="left"
								size="sm"
								tag="button"
								type="button"
								variant="primary"
								destructive
							>
								{ __( 'Delete Class', 'spectra-pro' ) }
							</Button>
						}
					>
						<Dialog.Backdrop />
						<Dialog.Panel>
							<Dialog.Header>
								<div className="flex items-center justify-between">
									<Dialog.Title>
										{ __( 'Delete Class', 'spectra-pro' ) }
									</Dialog.Title>
									<Dialog.CloseButton />
								</div>
								<Dialog.Description>
									{ __( 'Are you sure you want to delete the following class and all of its associated styles?', 'spectra-pro' ) }
								</Dialog.Description>
							</Dialog.Header>
							<Dialog.Body>
								<RenderPreview previewClass={ editingClass } pseudoSelector={ editingPseudoSelector } content={ `.${ editingClass }` }/>
							</Dialog.Body>
							<Dialog.Footer>
								<Button
									tag="button"
									type="button"
									variant="primary"
									destructive
									onClick={ () => {
										setIsDeleting( true );
										updateUserClasses( editingClass, editingClassValue, 'destructive' );
										setOpenDeleteDialog( false );
									} }
								>
									{ __( 'Delete', 'spectra-pro' ) }
								</Button>
								<Button
									tag="button"
									type="button"
									variant="outline"
									onClick={ () => { setOpenDeleteDialog( false ); } }
								>
									{ __( 'Cancel', 'spectra-pro' ) }
								</Button>
							</Dialog.Footer>
						</Dialog.Panel>
					</Dialog>
				 )}
			</div>
		</>
	);
};

/**
 * Render the custom classes settings page.
 *
 * @since x.x.x
 *
 * @return {Element}     - The rendered class creator.
 */
const CustomClasses = () => {

	// Get the saved user classes from localization.
	const savedUserClasses = window.spectra_admin_gs?.user_css?.classes || {}; // Class format: { classname1: { pseudotype: { background: var-name } } }.

	// Create all the required states.
	const [ createdClasses, setCreatedClasses ] = useState( Object.keys( savedUserClasses ) ); // Created classes is an array of all the existing classes.
	const [ editingClass, setEditingClass ] = useState( Object.keys( savedUserClasses )[ 0 ] ); // Editing class is the class currently being edited.
	const [ editingPseudoSelector, setEditingPseudoSelector ] = useState( 'default' ); // The pseudo-selector for the class being currently edited.
	const [ editingClassValue, setEditingClassValue ] = useState( Object.values( savedUserClasses )[ 0 ] || {} ); // Editing class value is ALL the CSS of the class being edited, including any pseudo-selector based CSS.
	const [ finalizedUserClasses, setFinalizedUserClasses ] = useState( savedUserClasses ); // Finalized user classes is the database-saved classes object.
	const [ showPreview, setShowPreview ] = useState( false );
	const [ tempClassMemory, setTempClassMemory ] = useState( {} ); // Temporary memory for unsaved changes
	const [ hasUnsavedChanges, setHasUnsavedChanges ] = useState( false );
	const [ isSaving, setIsSaving ] = useState( false );
	const [ justSaved, setJustSaved ] = useState( false );
	const [ isDeleting, setIsDeleting ] = useState( false );

	// Create the required refs for the Code Mirror editor.
	const cssClassEditorRef = useRef( null );
	const cssClassEditorInstanceRef = useRef( null );
	const isUserTyping = useRef( false );

	// An AJAX event to update the Global Styles user classes.
	const updateUserClasses = ( editingClassToSave, editingClassValueToSave, updateType ) => {

		// Create an empty object for the user styles.
		const userStyles = {};

		// Only process styles if not deleting.
		if ( 'destructive' !== updateType ) {
			// Based on the values to save, update the user styles.
			Object.keys( editingClassValueToSave ).forEach( pseudoSelector => {
				userStyles[ pseudoSelector ] = editingClassValueToSave[ pseudoSelector ] || {};
			} )
		}

		// Create a new form and append the required data.
        const formData = new window.FormData();
        formData.append( 'action', 'spectra_pro_gs_user_classes' );
        formData.append( 'security', window.spectra_admin_gs.ajax_nonce );
		formData.append( 'user_class', editingClassToSave )
        formData.append( 'user_styles', JSON.stringify( userStyles ) );
		formData.append( 'is_destructive', 'destructive' === updateType ? 'yes' : 'no' );

		// Fetch the response from the Ajax request.
        apiFetch( {
            url: window.spectra_admin_gs.ajax_url,
            method: 'POST',
            body: formData,
        } ).then( ( fetched ) => {
			if ( fetched?.success && fetched?.data?.saved_class?.name ) {
				const data = fetched.data;

				// If it's successful and the name of the saved class exists, update the finalized classes.
				const updatedUserClasses = {
					...finalizedUserClasses,
					[ data.saved_class.name ]: {
						...data.saved_class?.styles || {},
					},
				}

				// If it was destructive, delete the item from the finalized classes.
				if ( data?.destroyed ) {
					const currentCreatedClasses = createdClasses;
					let editingClassIndex = currentCreatedClasses.indexOf( data.saved_class.name );
					delete updatedUserClasses[ data.saved_class.name ];
					currentCreatedClasses.splice( editingClassIndex, 1 );
					// If the current class is the first one, then just increment the index by 1.
					// We will shift the index lower by one post deletion.
					// If there are 3 classes and the second one is deleted, we will select the first class - not the now second class.
					if ( editingClassIndex < 1 ) {
						editingClassIndex = 1;
					}
					setEditingClass( currentCreatedClasses[ editingClassIndex - 1 ] );
					setEditingClassValue( Object.values( updatedUserClasses )[ editingClassIndex - 1 ] || {} );
					setCreatedClasses( currentCreatedClasses );
					setIsDeleting( false );
					
					// Clear both local temp memory and global storage for deleted class
					setTempClassMemory( prev => {
						const updated = { ...prev };
						delete updated[ data.saved_class.name ];
						return updated;
					} );
					
					if ( window.spectraGSTemporaryStorage ) {
						delete window.spectraGSTemporaryStorage[ `class_${ data.saved_class.name }` ];
					}
				}

				// Set the finalized classes, and the localized classes.
				setFinalizedUserClasses( { ...updatedUserClasses } );
				window.spectra_admin_gs.user_css.classes = updatedUserClasses;
				
				// Update editingClassValue to match the newly saved state if this is the current editing class
				if ( ! data?.destroyed && editingClass === data.saved_class.name ) {
					setEditingClassValue( updatedUserClasses[ data.saved_class.name ] || {} );
				}
				
				// Clear global storage for saved class
				if ( ! data?.destroyed && window.spectraGSTemporaryStorage ) {
					delete window.spectraGSTemporaryStorage[ `class_${ data.saved_class.name }` ];
				}

				toast.success( data.title, { description: data.description } );

				setJustSaved( true );
				setTimeout( () => setJustSaved( false ), 2000 );
			} else {
				toast.error( fetched?.data?.title || __( 'Variables Not Saved.', 'spectra-pro' ), { description: fetched?.data?.description  } );
				setIsDeleting( false );
			}
        } ).catch( e => {
			// If the error thrown is not formatted as an error, make it one.
			if ( ! e.message ) {
				e = new Error( e );
			}
			console.error( e ); //eslint-disable-line no-console
			toast.error( __( 'Something went wrong.', 'spectra-pro' ), { description: e.message } );
			setIsDeleting( false );
		} );
	};

	// Track unsaved changes
	useEffect( () => {
		const hasAnyUnsavedChanges = Object.keys( tempClassMemory ).some( className => {
			const savedClass = finalizedUserClasses[ className ] || {};
			const tempClass = tempClassMemory[ className ] || {};
			return JSON.stringify( savedClass ) !== JSON.stringify( tempClass );
		} );
		
		setHasUnsavedChanges( hasAnyUnsavedChanges );
	}, [ tempClassMemory, finalizedUserClasses ] );

	// Initialize temporary memory from global storage on component mount
	useEffect( () => {
		if ( ! window.spectraGSTemporaryStorage ) {
			return;
		}

		// Load all temporary storage data that starts with 'class_'
		const initialTempMemory = {};
		Object.keys( window.spectraGSTemporaryStorage ).forEach( key => {
			if ( key.startsWith( 'class_' ) ) {
				const className = key.replace( 'class_', '' );
				initialTempMemory[ className ] = window.spectraGSTemporaryStorage[ key ];
			}
		} );

		// Only update if we found some temporary storage data
		if ( Object.keys( initialTempMemory ).length > 0 ) {
			setTempClassMemory( initialTempMemory );
		}
	}, [] ); // Run only on mount

	// Check if current editing class has unsaved changes
	const currentClassHasUnsavedChanges = editingClass && tempClassMemory[ editingClass ] !== undefined && 
		JSON.stringify( finalizedUserClasses[ editingClass ] || {} ) !== JSON.stringify( tempClassMemory[ editingClass ] || {} );

	// Reset saved state when user makes new changes
	useEffect( () => {
		if ( currentClassHasUnsavedChanges ) {
			setJustSaved( false );
		}
	}, [ currentClassHasUnsavedChanges ] );

	// Get button text based on state
	const getButtonText = () => {
		switch ( true ) {
			case justSaved:
				return __( 'Saved!', 'spectra-pro' );
			case isSaving:
				return __( 'Saving…', 'spectra-pro' );
			default:
				return __( 'Save Changes', 'spectra-pro' );
		}
	};

	// Load from temporary storage when editing class changes
	useEffect( () => {
		if ( ! editingClass ) {
			setEditingClassValue( {} );
			return;
		}
		
		// Check local temporary memory first (already loaded from global storage on mount)
		if ( tempClassMemory[ editingClass ] ) {
			setEditingClassValue( tempClassMemory[ editingClass ] );
		} else {
			// Check global storage as fallback
			const globalKey = `class_${ editingClass }`;
			if ( window.spectraGSTemporaryStorage && window.spectraGSTemporaryStorage[ globalKey ] !== undefined ) {
				const globalClass = window.spectraGSTemporaryStorage[ globalKey ];
				setEditingClassValue( globalClass );
			} else if ( finalizedUserClasses[ editingClass ] ) {
				setEditingClassValue( finalizedUserClasses[ editingClass ] );
			} else {
				setEditingClassValue( {} );
			}
		}
	}, [ editingClass, finalizedUserClasses ] );

	// Sync tempClassMemory to global storage only when there are actual changes
	useEffect( () => {
		if ( ! window.spectraGSTemporaryStorage ) {
			window.spectraGSTemporaryStorage = {};
		}
		
		// Get all class names that might need cleanup (both temp and finalized)
		const allClassNames = new Set( [ 
			...Object.keys( tempClassMemory ), 
			...Object.keys( finalizedUserClasses ) 
		] );
		
		// Process each class
		allClassNames.forEach( className => {
			const tempClass = tempClassMemory[ className ];
			const savedClass = finalizedUserClasses[ className ] || {};
			
			// Only update global storage if class exists in temp memory and has changes
			if ( tempClass && JSON.stringify( tempClass ) !== JSON.stringify( savedClass ) ) {
				window.spectraGSTemporaryStorage[ `class_${ className }` ] = tempClass;
			} else {
				// Remove from global storage if no temp changes
				delete window.spectraGSTemporaryStorage[ `class_${ className }` ];
			}
		} );
	}, [ tempClassMemory, finalizedUserClasses ] );

	// Callback to update the class object of the current class, which will re-render based on the current editing class.
    const codeMirrorChange = useCallback( () => {
		if ( ! cssClassEditorInstanceRef?.current || ! editingClass ) {
			return;
		}
		
		isUserTyping.current = true;
		const newValue = cssClassEditorInstanceRef.current.codemirror.getValue();
		
		setEditingClassValue( prev => {
			const updatedClassValue = {
				...prev,
				[ editingPseudoSelector ]: convertToCssObject( newValue ),
			};
			
			// Get saved state for comparison
			const savedClassValue = finalizedUserClasses[ editingClass ] || {};
			
			// Check if the updated class value has any valid CSS properties
			const hasValidCssProperties = Object.values( updatedClassValue ).some( pseudoProperties => 
				pseudoProperties && typeof pseudoProperties === 'object' && Object.keys( pseudoProperties ).length > 0
			);
			
			// Check if saved class has any valid CSS properties
			const savedHasValidCssProperties = Object.values( savedClassValue ).some( pseudoProperties => 
				pseudoProperties && typeof pseudoProperties === 'object' && Object.keys( pseudoProperties ).length > 0
			);
			
			// Only update temp memory if there are valid CSS changes or if we're clearing valid CSS
			if ( JSON.stringify( savedClassValue ) === JSON.stringify( updatedClassValue ) ) {
				// Exact match with saved data - delete temp memory if it exists
				setTempClassMemory( prevTemp => {
					const updated = { ...prevTemp };
					delete updated[ editingClass ];
					return updated;
				} );
			} else if ( hasValidCssProperties || savedHasValidCssProperties ) {
				// Only store if current has valid CSS OR saved has valid CSS (to allow clearing)
				setTempClassMemory( prevTemp => ( { ...prevTemp, [ editingClass ]: updatedClassValue } ) );
			} else {
				// Neither has valid CSS - remove from temp memory
				setTempClassMemory( prevTemp => {
					const updated = { ...prevTemp };
					delete updated[ editingClass ];
					return updated;
				} );
			}
			
			return updatedClassValue;
		} );
		
		// Reset typing flag after a short delay
		setTimeout( () => {
			isUserTyping.current = false;
		}, 100 );
    }, [ editingClass, editingPseudoSelector, finalizedUserClasses ] );

	// An Effect to initialize the Code Mirror editor.
	useEffect( () => {
		// Initialize CodeMirror.
		const initializeEditor = () => {
			// If the code mirror editor should not be initialized, abandon ship.
			if ( ! cssClassEditorRef.current || cssClassEditorInstanceRef.current ) {
				return;
			}

			// If there are default settings, update the placeholder specifically for custom classes.
			if ( wp?.codeEditor?.defaultSettings?.codemirror ) {
				wp.codeEditor.defaultSettings.codemirror.placeholder = 'background: var(--color-wordpress);\nborder-radius: var(--radius-custom);';
			}

			// Create the settings object.
			const settings = { ...( wp?.codeEditor?.defaultSettings?.codemirror || {} ) };

			// Initialize the CodeMirror editor.
			const editor = wp?.codeEditor?.initialize( cssClassEditorRef.current, settings );

			// If the editor was not initialized, abandon ship.
			if ( ! editor ) {
				return;
			}

			// Set the reference to the editor.
			cssClassEditorInstanceRef.current = editor;

			// Set initial content.
			if ( editingClassValue?.default ) {
				cssClassEditorInstanceRef.current.codemirror.setValue( convertToCssSheet( editingClassValue.default ) );
			}
		};

		// Cleanup function.
		const cleanup = () => {
			if ( cssClassEditorInstanceRef?.current ) {
				cssClassEditorInstanceRef.current.codemirror.toTextArea();
				cssClassEditorInstanceRef.current = null;
			}
		};

		// Initialize the editor, and return the cleanup.
		initializeEditor();
		return cleanup;
	}, [ cssClassEditorRef ] );

	// Effect that re-runs whenever the CodeMirrorEditor callback changes.
	useEffect( () => {
		if ( ! cssClassEditorRef?.current || ! cssClassEditorInstanceRef?.current ) {
			return;
		}

		// Set initial content.
		if ( editingClassValue[ editingPseudoSelector ] ) {
			cssClassEditorInstanceRef.current.codemirror.setValue( convertToCssSheet( editingClassValue[ editingPseudoSelector ] ) );
		}

		const codeMirrorElement = cssClassEditorRef?.current?.nextElementSibling;

		const codeMirrorEditor = codeMirrorElement.querySelector( '.CodeMirror-code' );
		
		if ( codeMirrorEditor ) {
			// Listen for changes.
            codeMirrorEditor.addEventListener( 'keyup', codeMirrorChange );
            return () => codeMirrorEditor.removeEventListener( 'keyup', codeMirrorChange );
		}
	}, [ codeMirrorChange ]  )

	// Effect that runs whenever the editing class or pseudo selector changes.
	useEffect( () => {
		if ( cssClassEditorInstanceRef?.current && editingClass && ! isUserTyping.current ) {
			const cssToShow = editingClassValue[ editingPseudoSelector ] || {};
			const currentValue = cssClassEditorInstanceRef.current.codemirror.getValue();
			const newValue = convertToCssSheet( cssToShow );
			
			// Only update if the value has actually changed to prevent cursor jumping
			if ( currentValue !== newValue ) {
				cssClassEditorInstanceRef.current.codemirror.setValue( newValue );
			}
		}
	}, [ editingClass, editingPseudoSelector, editingClassValue ] );

	// Create stylesheet content string including temporary changes.
	const generateStylesheet = useCallback( () => {
		const styles = [];
		
		// Merge finalized classes with temporary changes
		const allClasses = { ...finalizedUserClasses };
		Object.keys( tempClassMemory ).forEach( className => {
			allClasses[ className ] = tempClassMemory[ className ];
		} );

		// Process each class in the merged object.
		Object.entries( allClasses ).forEach( ( [ className, pseudoSelectors ] ) => {
			// Process each state (default, hover, focus, etc.).
			Object.entries( pseudoSelectors ).forEach( ( [ pseudoSelector, properties ] ) => {
				// Build selector (add pseudo-class if not 'default').
				const selector = 'default' === pseudoSelector ? `.${ className }` : `.${ className }:${ pseudoSelector }`;
				
				// Convert properties to CSS declarations.
				const cssDeclarations = convertToCssSheet( properties );
								
				// Add complete rule to styles array.
				styles.push( `${ selector } { ${ cssDeclarations } }` );
			} );
		} );
		// Join all rules with spaces.
		return styles.join( ' ' );
	}, [ finalizedUserClasses, tempClassMemory ] );

	// Effect to handle stylesheet updates.
	useEffect( () => {
		// Create or update stylesheet.
		let existingSheet = document.getElementById( 'spectra-gs-preview-stylesheet' );
		
		if ( ! existingSheet ) {
			const styleSheet = document.createElement( 'style' );
			styleSheet.id = 'spectra-gs-preview-stylesheet';
			document.head.appendChild( styleSheet );
			existingSheet = styleSheet;
		}

		existingSheet.textContent = generateStylesheet();
	}, [ generateStylesheet ] );

	// Handle saving changes for current class
	const handleSaveChanges = async () => {
		if ( ! editingClass || ! hasUnsavedChanges || isSaving ) {
			return;
		}
		
		setIsSaving( true );
		
		try {
			const classToSave = tempClassMemory[ editingClass ] || editingClassValue;
			await new Promise( resolve => {
				updateUserClasses( editingClass, classToSave, 'update' );
				// Wait for the AJAX call to complete
				setTimeout( resolve, 1000 );
			} );
			
			// Clear both global storage and local temp memory for this class
			if ( window.spectraGSTemporaryStorage ) {
				delete window.spectraGSTemporaryStorage[ `class_${ editingClass }` ];
			}
			
			// Clear local temp memory for the saved class
			setTempClassMemory( prev => {
				const updated = { ...prev };
				delete updated[ editingClass ];
				return updated;
			} );
			

		} catch ( error ) {
			console.error( 'Save failed:', error ); // eslint-disable-line no-console
		} finally {
			setIsSaving( false );
			setJustSaved( false );
		}
	};

	// Render the custom class settings page.
	return (
		<>
			<Toaster position="top-right" design="stack" theme="dark" autoDismiss={ true } dismissAfter={ 2000 } />
			<SettingsHeader
				heading={ __( 'Custom CSS Classes', 'spectra-pro' ) }
				description={ __( 'Manage your own CSS classes with system and custom variables!', 'spectra-pro' ) }
				help={ __( 'You don\'t need to wrap the CSS properties in the class. Just start writing CSS!', 'spectra-pro' ) }
			/>
			<RenderClassCreator { ...{
				createdClasses,
				setCreatedClasses,
				setEditingClass,
				setEditingPseudoSelector,
				setEditingClassValue,
				finalizedUserClasses,
				setFinalizedUserClasses,
				updateUserClasses,
			} }/>
			<Divider/>
			<RenderClassSelector { ...{
				createdClasses,
				editingClass,
				setEditingClass,
				editingPseudoSelector,
				setEditingPseudoSelector,
				editingClassValue,
				isDeleting,
				setIsDeleting,
				updateUserClasses,
			} }/>
			<div style={ {
				position: 'relative',
				opacity: ! editingClass ? '0.25' : '',
				pointerEvents: ! editingClass ? 'none' : '',
			} }>
				<div
					id="spectra-pro-gs-class-editor"
					style={ {
						border: '1px solid #e5e7eb',
						borderRadius: '0.375rem',
						overflow: 'hidden',
					} }
				>
					<textarea ref={ cssClassEditorRef } defaultValue={ convertToCssSheet( editingClassValue[ editingPseudoSelector ] || {} ) }/>
				</div>
			</div>
			<div className="flex gap-4">
				<Button
					size="sm"
					variant="primary"
					icon={ <Save size={ 16 } /> }
					iconPosition="left"
					onClick={ handleSaveChanges }
					disabled={ ! editingClass || ! currentClassHasUnsavedChanges || isSaving }
				>
					{ getButtonText() }
				</Button>
				<Button
					size="sm"
					variant="outline"
					icon={ showPreview ? <EyeOff size={ 16 }/> : <Eye size={ 16 }/> }
					onClick={ () => setShowPreview( ! showPreview ) }
				>
					{ showPreview ? __( 'Hide Preview', 'spectra-pro' ) : __( 'Show Preview', 'spectra-pro' ) }
				</Button>
			</div>
			{ showPreview && <RenderPreview previewClass={ editingClass } pseudoSelector={ editingPseudoSelector } /> }
		</>
	);
};

// Export the CustomClasses by default.
export default CustomClasses;
