/**
 * Frontend React component for Algolia Autocomplete block.
 * Uses @algolia/autocomplete-js with React wrapper.
 */

import React, { useEffect, useRef, useLayoutEffect } from 'react';
import { createRoot } from 'react-dom/client';
import { autocomplete } from '@algolia/autocomplete-js';
import algoliasearch from 'algoliasearch/lite';
// Bundled locally so the insights client is never loaded from a CDN at runtime.
import insightsClient from 'search-insights';
import '@algolia/autocomplete-theme-classic';

const BLOCK_SELECTOR = '.wp-block-wlc-algolia-autocomplete';

function normalizeResultLayout( layout ) {
	if ( layout === 'grid' ) {
		return 'grid';
	}
	// Legacy fallback: old "horizontal" is treated as vertical.
	return 'vertical';
}

/** Tags Algolia may emit in highlight/snippet HTML; everything else is stripped/unwrapped. */
const ALLOWED_ALGOLIA_HIGHLIGHT_TAGS = new Set( [
	'EM',
	'MARK',
	'STRONG',
	'B',
	'I',
	'SPAN',
	'BR',
] );

/**
 * Store raw highlight/snippet HTML in a data attribute without quote-breakouts (base64).
 *
 * @param {string} html Raw string from Algolia.
 * @return {string}
 */
function encodeAlgoliaHtmlForDataAttr( html ) {
	if ( html == null || typeof html !== 'string' ) {
		return '';
	}
	try {
		return btoa( encodeURIComponent( html ) );
	} catch {
		return '';
	}
}

/**
 * @param {string} raw Attribute value (base64+URI or legacy plain string).
 * @return {string}
 */
function decodeAlgoliaHtmlFromDataAttr( raw ) {
	if ( ! raw || typeof raw !== 'string' ) {
		return '';
	}
	try {
		return decodeURIComponent( atob( raw ) );
	} catch {
		return raw;
	}
}

/**
 * @param {Node|null} node Parsed node.
 * @return {Node|DocumentFragment|null}
 */
function sanitizeHighlightNode( node ) {
	if ( ! node ) {
		return null;
	}
	if ( node.nodeType === Node.TEXT_NODE ) {
		return document.createTextNode( node.textContent || '' );
	}
	if ( node.nodeType !== Node.ELEMENT_NODE ) {
		return null;
	}
	const tag = node.tagName;
	if ( ! ALLOWED_ALGOLIA_HIGHLIGHT_TAGS.has( tag ) ) {
		const frag = document.createDocumentFragment();
		for ( const child of node.childNodes ) {
			const cleaned = sanitizeHighlightNode( child );
			if ( cleaned ) {
				if ( cleaned.nodeType === Node.DOCUMENT_FRAGMENT_NODE ) {
					while ( cleaned.firstChild ) {
						frag.appendChild( cleaned.firstChild );
					}
				} else {
					frag.appendChild( cleaned );
				}
			}
		}
		if ( frag.childNodes.length === 0 ) {
			return null;
		}
		if ( frag.childNodes.length === 1 ) {
			return frag.firstChild;
		}
		return frag;
	}
	const el = document.createElement( tag.toLowerCase() );
	if ( tag === 'SPAN' ) {
		const cls = node.getAttribute( 'class' );
		if ( cls && /^[a-zA-Z0-9_\s-]+$/.test( cls ) ) {
			el.setAttribute( 'class', cls );
		}
	}
	for ( const child of node.childNodes ) {
		const cleaned = sanitizeHighlightNode( child );
		if ( cleaned ) {
			if ( cleaned.nodeType === Node.DOCUMENT_FRAGMENT_NODE ) {
				while ( cleaned.firstChild ) {
					el.appendChild( cleaned.firstChild );
				}
			} else {
				el.appendChild( cleaned );
			}
		}
	}
	return el;
}

/**
 * Allow only Algolia-style highlight markup; drop scripts/events/unknown tags.
 *
 * @param {string} raw HTML fragment from Algolia.
 * @return {string}
 */
function sanitizeAlgoliaHtmlFragment( raw ) {
	if ( raw == null || typeof raw !== 'string' ) {
		return '';
	}
	const trimmed = raw.trim();
	if ( ! trimmed ) {
		return '';
	}
	try {
		const tpl = document.createElement( 'template' );
		tpl.innerHTML = trimmed;
		const out = document.createDocumentFragment();
		for ( const child of tpl.content.childNodes ) {
			const cleaned = sanitizeHighlightNode( child );
			if ( cleaned ) {
				if ( cleaned.nodeType === Node.DOCUMENT_FRAGMENT_NODE ) {
					while ( cleaned.firstChild ) {
						out.appendChild( cleaned.firstChild );
					}
				} else {
					out.appendChild( cleaned );
				}
			}
		}
		const wrap = document.createElement( 'div' );
		wrap.appendChild( out );
		return wrap.innerHTML;
	} catch {
		return '';
	}
}

function AutocompleteComponent( { config } ) {
	const containerRef = useRef( null );
	const autocompleteInstanceRef = useRef( null );
	const suppressPanelAfterClearRef = useRef( false );
	const updateResultsLayoutRef = useRef( () => {} );
	const isRearrangingResultsRef = useRef( false );

	useLayoutEffect( () => {
		const blockRoot = containerRef.current?.closest( '.wp-block-wlc-algolia-autocomplete' );
		if ( blockRoot ) {
			blockRoot.classList.remove(
				'is-result-layout-vertical',
				'is-result-layout-grid'
			);
			const layout = normalizeResultLayout( config.resultLayout );
			blockRoot.classList.add( `is-result-layout-${ layout }` );
			if ( layout === 'grid' ) {
				const columns = Math.max( 2, Math.min( 4, Number( config.resultLayoutColumns ) || 2 ) );
				blockRoot.style.setProperty( '--wlc-ac-grid-columns', String( columns ) );
			} else {
				blockRoot.style.removeProperty( '--wlc-ac-grid-columns' );
			}
		}

		// Wait a bit to ensure DOM is ready
		const timeoutId = setTimeout( () => {
			if ( ! containerRef.current ) {
				return;
			}
			
			// Clear container to ensure it's empty for autocomplete
			containerRef.current.innerHTML = '';

			// Get API keys from config or fetch from REST API
			const appId = config.appId;
			const apiKey = config.apiKey;

			if ( ! appId || ! apiKey ) {
				return;
			}

			const searchClient = algoliasearch( appId, apiKey );

			insightsClient( 'init', { appId, apiKey, partial: true } );

			// Create autocomplete instance
			const autocompleteInstance = autocomplete( {
				container: containerRef.current,
				panelContainer: containerRef.current,
				placeholder: config.placeholder || 'Search...',
				openOnFocus: true,
				insights: { insightsClient },
				// Disable detached mode completely - show panel inline
				detachedMediaQuery: 'none', // This should disable detached mode
				// Close panel when clicking outside
				onStateChange( { state, prevState } ) {
					// After clear-button clicks, prevent openOnFocus from re-opening default results.
					if ( suppressPanelAfterClearRef.current ) {
						const hasQuery = Boolean( state.query && state.query.trim().length > 0 );
						if ( hasQuery ) {
							suppressPanelAfterClearRef.current = false;
							const blockRoot = containerRef.current?.closest( '.wp-block-wlc-algolia-autocomplete' );
							if ( blockRoot ) {
								blockRoot.classList.remove( 'is-panel-hidden' );
							}
						} else {
							if ( state.isOpen && typeof autocompleteInstance.setIsOpen === 'function' ) {
								autocompleteInstance.setIsOpen( false );
							}
							const blockRoot = containerRef.current?.closest( '.wp-block-wlc-algolia-autocomplete' );
							if ( blockRoot ) {
								blockRoot.classList.add( 'is-panel-hidden' );
							}
							const panel = containerRef.current?.querySelector( '.aa-Panel' );
							if ( panel ) {
								panel.style.display = 'none';
								panel.style.visibility = 'hidden';
								panel.style.opacity = '0';
							}
						}
					}

					// Close panel on ESC key
					if ( state.isOpen && ! prevState.isOpen ) {
						const handleKeyDown = ( e ) => {
							if ( e.key === 'Escape' ) {
								autocompleteInstance.setIsOpen( false );
								document.removeEventListener( 'keydown', handleKeyDown );
							}
						};
						document.addEventListener( 'keydown', handleKeyDown );
					}
				},
				getSources( { query } ) {
				const sources = [];

				// 1. Query / popular suggestions source
				if ( config.showSuggestions && config.querySuggestionsIndex ) {
					sources.push( {
						sourceId: 'querySuggestions',
						getItems() {
							// For empty query, try to get popular queries using empty search
							// Note: browseObjects is not available in algoliasearch/lite, so we use search instead
							const searchQuery = ( ! query || query.trim().length === 0 ) ? '' : query;
							return searchClient
								.initIndex( config.querySuggestionsIndex )
								.search( searchQuery, { hitsPerPage: config.maxSuggestions || 5 } )
								.then( ( { hits } ) => {
									if ( searchQuery === '' ) {
										console.log( '[Algolia Autocomplete] Query suggestions (empty search):', hits.length, hits );
									} else {
										console.log( '[Algolia Autocomplete] Query suggestions (search):', hits.length, 'for query:', searchQuery );
									}
									return hits;
								} )
								.catch( ( err ) => {
									console.warn( '[Algolia Autocomplete] Query suggestions search failed:', err );
									return [];
								} );
						},
						templates: {
							header( { html } ) {
								return html`<div class="aa-SourceHeaderTitle">Popular Searches</div>`;
							},
							item( { item, html } ) {
								// Query Suggestions index uses `query`; content indices use title-like fields.
								const queryText =
									item.query ||
									item.query_suggestion ||
									item.query_text ||
									item.suggestion ||
									item.text ||
									item.post_title ||
									item.title ||
									'';
								// Prefer search page URL with query so clicking goes to search page with that term
								let url =
									item.url ||
									item.permalink ||
									item.post_permalink ||
									item.link ||
									'';
								if ( config.searchPageUrl && queryText ) {
									const base = config.searchPageUrl;
									const sep = base.includes( '?' ) ? '&' : '?';
									url = `${ base }${ sep }s=${ encodeURIComponent( queryText ) }`;
								} else if ( ! url ) {
									url = '#';
								}
								url = sanitizeHref( url );
								if ( ! queryText ) {
									return null; // Don't render empty items
								}
								return html`
									<a href="${ url }" class="aa-ItemLink" data-aa-query="${ queryText }">
										${ queryText }
									</a>
								`;
							},
							noResults( { html } ) {
								return html`<div class="aa-NoResults">No suggestions</div>`;
							},
						},
					} );
				}

				// 2. Search results for each post type
				if ( config.postTypeIndexes && Array.isArray( config.postTypeIndexes ) ) {
					config.postTypeIndexes.forEach( ( { post_type, index_name } ) => {
						sources.push( {
							sourceId: `searchResults_${ post_type }`,
							getItems() {
								return searchClient
									.initIndex( index_name )
									.search( query, { hitsPerPage: config.maxResultsPerType || 3 } )
									.then( ( { hits } ) => hits );
							},
						templates: {
							header( { html } ) {
								return html`<div class="aa-SourceHeaderTitle">${ post_type.charAt( 0 ).toUpperCase() + post_type.slice( 1 ) } Results</div>`;
							},
							item( { item, components, html } ) {
								const templatesByPostType =
									config.templatesByPostType &&
									typeof config.templatesByPostType === 'object'
										? config.templatesByPostType
										: {};
								const activeTemplateBlocks =
									Array.isArray( templatesByPostType[ post_type ] ) &&
									templatesByPostType[ post_type ].length > 0
										? templatesByPostType[ post_type ]
										: config.templateBlocks;
								// Use per-post-type template if available, otherwise fallback to shared template.
								if ( activeTemplateBlocks && Array.isArray( activeTemplateBlocks ) && activeTemplateBlocks.length > 0 ) {
									return renderHitWithTemplate( item, activeTemplateBlocks, components, config.resultLayout, config.resultLayoutColumns, html, config.currency || 'USD' );
								}
								// Fallback to default template
								const imageUrl = item.images?.thumbnail?.url || item.thumbnail_url || '';
								const title = item.post_title || item.title || '';
								const excerpt = item.post_excerpt || item.content_snippet || '';
								const permalink = sanitizeHref( item.post_permalink || item.permalink || item.url || '#' );

								return html`
									<a href="${ permalink }" class="aa-ItemLink">
										<div class="aa-ItemContent">
											${ imageUrl ? html`<div class="aa-ItemImage"><img src="${ imageUrl }" alt="${ title }" /></div>` : '' }
											<div class="aa-ItemContentBody">
												<div class="aa-ItemTitle">${ components.Highlight( { hit: item, attribute: 'post_title' } ) }</div>
												${ excerpt ? html`<div class="aa-ItemExcerpt">${ components.Snippet( { hit: item, attribute: 'post_excerpt' } ) }</div>` : '' }
											</div>
										</div>
									</a>
								`;
							},
							footer( { html, state } ) {
								// Show "Show all" button if searchPageUrl is configured
								if ( ! config.searchPageUrl || ! state.query ) {
									return null;
								}
								const hasCustomLabel = typeof config.showAllButtonLabel === 'string';
								const trimmedLabel = hasCustomLabel ? config.showAllButtonLabel.trim() : '';
								const buttonLabel = hasCustomLabel ? trimmedLabel : 'Show all';
								if ( ! buttonLabel ) {
									return null;
								}
								const searchUrl = new URL( config.searchPageUrl, window.location.origin );
								searchUrl.searchParams.set( 's', state.query );
								const safeSearchUrl = sanitizeHref( searchUrl.toString() );
								return html`
									<a href="${ safeSearchUrl }" class="aa-ShowAllButton">
										${ buttonLabel }
									</a>
								`;
							},
							noResults( { html } ) {
								return html`<div class="aa-NoResults">No ${ post_type } results</div>`;
							},
						},
						} );
					} );
				}

				return sources;
			},
			classNames: {
				root: 'wp-block-wlc-algolia-autocomplete__autocomplete',
				form: 'wp-block-wlc-algolia-autocomplete__form',
				input: 'wp-block-wlc-algolia-autocomplete__input',
				panel: 'wp-block-wlc-algolia-autocomplete__panel',
				panelLayout: `wp-block-wlc-algolia-autocomplete__panel-layout ${ ! config.showSuggestions || ! config.querySuggestionsIndex ? 'is-full-width' : '' }`,
			},
		} );

			autocompleteInstanceRef.current = autocompleteInstance;
			
			const hidePanel = () => {
				const blockRoot = containerRef.current?.closest( '.wp-block-wlc-algolia-autocomplete' );
				if ( blockRoot ) {
					blockRoot.classList.add( 'is-panel-hidden' );
				}
				const panel = containerRef.current?.querySelector( '.aa-Panel' );
				if ( panel ) {
					panel.style.display = 'none';
					panel.style.visibility = 'hidden';
					panel.style.opacity = '0';
				}
			};
			
			const closeAndHidePanel = () => {
				suppressPanelAfterClearRef.current = true;

				try {
					if ( typeof autocompleteInstance.setQuery === 'function' ) {
						autocompleteInstance.setQuery( '' );
					}
					if ( typeof autocompleteInstance.setIsOpen === 'function' ) {
						autocompleteInstance.setIsOpen( false );
					}
				} catch {
					// Silent
				}
				
				const input = containerRef.current?.querySelector( '.aa-Input' );
				if ( input ) {
					input.blur();
				}
				
				// Run once now and once on next frame to beat openOnFocus re-open.
				hidePanel();
				requestAnimationFrame( hidePanel );
				setTimeout( hidePanel, 40 );
			};
			
			// Fix panel positioning - override inline styles set by Algolia
			const fixPanelPosition = () => {
				const panel = containerRef.current?.querySelector( '.aa-Panel' );
				if ( panel && containerRef.current ) {
					// Reset inline styles and use CSS positioning
					panel.style.top = 'calc(100% + 8px)';
					panel.style.left = '0';
					panel.style.right = '0';
					panel.style.width = '100%';
					panel.style.maxWidth = '100%';
					panel.style.position = 'absolute';
					const isOpen = typeof autocompleteInstance.getState === 'function' ? autocompleteInstance.getState().isOpen : true;
					if ( ! isOpen ) {
						hidePanel();
						return;
					}
					const blockRoot = containerRef.current?.closest( '.wp-block-wlc-algolia-autocomplete' );
					if ( blockRoot ) {
						blockRoot.classList.remove( 'is-panel-hidden' );
					}
					panel.style.display = 'flex';
					panel.style.visibility = 'visible';
					panel.style.opacity = '1';
				}
			};
			
			// Fix position after autocomplete renders - multiple attempts
			setTimeout( fixPanelPosition, 100 );
			setTimeout( fixPanelPosition, 300 );
			setTimeout( fixPanelPosition, 500 );
			setTimeout( fixPanelPosition, 1000 );
			
			const updateResultsLayout = () => {
				if ( ! containerRef.current ) {
					return;
				}

				const panelLayouts = containerRef.current.querySelectorAll( '.aa-PanelLayout' );
				panelLayouts.forEach( ( panelLayout ) => {
					const directChildren = Array.from( panelLayout.children );
					const suggestionsSource = directChildren.find(
						( el ) =>
							el.classList &&
							el.classList.contains( 'aa-Source' ) &&
							el.classList.contains( 'aa-Source-query-suggestions' )
					);
					let resultsColumn = directChildren.find(
						( el ) => el.classList && el.classList.contains( 'aa-ResultsColumn' )
					);
					const topLevelSearchSources = directChildren.filter(
						( el ) =>
							el.classList &&
							el.classList.contains( 'aa-Source' ) &&
							el.classList.contains( 'aa-Source-search-results' )
					);

					if ( topLevelSearchSources.length === 0 && ! resultsColumn ) {
						return;
					}

					if ( topLevelSearchSources.length === 0 && resultsColumn ) {
						return;
					}

					if ( topLevelSearchSources.length === 0 ) {
						if ( resultsColumn ) {
							resultsColumn.remove();
						}
						return;
					}

					if ( ! resultsColumn ) {
						resultsColumn = document.createElement( 'div' );
						resultsColumn.className = 'aa-ResultsColumn';
					}

					isRearrangingResultsRef.current = true;
					try {
						topLevelSearchSources.forEach( ( source ) => {
							resultsColumn.appendChild( source );
						} );

						const shouldInsertAfterSuggestions =
							suggestionsSource && suggestionsSource.parentElement === panelLayout;
						if ( shouldInsertAfterSuggestions ) {
							const expectedSibling = suggestionsSource.nextSibling;
							if ( expectedSibling !== resultsColumn ) {
								panelLayout.insertBefore( resultsColumn, expectedSibling );
							}
						} else if ( panelLayout.firstChild !== resultsColumn ) {
							panelLayout.insertBefore( resultsColumn, panelLayout.firstChild );
						}
					} finally {
						isRearrangingResultsRef.current = false;
					}
				} );

				const layout = normalizeResultLayout( config.resultLayout );
				const columns = Math.max( 2, Math.min( 4, Number( config.resultLayoutColumns ) || 2 ) );
				const sources = containerRef.current.querySelectorAll( '.aa-Source' );
				sources.forEach( ( source ) => {
					const sourceId = source.getAttribute( 'data-autocomplete-source-id' ) || '';
					const isQuerySuggestions = sourceId === 'querySuggestions';
					source.classList.toggle( 'aa-Source-query-suggestions', isQuerySuggestions );
					source.classList.toggle( 'aa-Source-search-results', ! isQuerySuggestions );
				} );
				const lists = containerRef.current.querySelectorAll( '.aa-List' );
				lists.forEach( ( list ) => {
					const source = list.closest( '.aa-Source' );
					const sourceId = source?.getAttribute( 'data-autocomplete-source-id' ) || '';
					const isQuerySuggestions = sourceId === 'querySuggestions';
					const listLayout = isQuerySuggestions ? 'vertical' : layout;
					list.classList.remove(
						'aa-ListLayout-vertical',
						'aa-ListLayout-grid'
					);
					list.classList.add( `aa-ListLayout-${ listLayout }` );
					if ( listLayout === 'grid' ) {
						list.style.setProperty( '--wlc-ac-grid-columns', String( columns ) );
					} else {
						list.style.removeProperty( '--wlc-ac-grid-columns' );
					}
				} );
			};
			updateResultsLayoutRef.current = updateResultsLayout;

			// Watch for panel updates and fix position, also process highlights
			const observer = new MutationObserver( () => {
				if ( isRearrangingResultsRef.current ) {
					return;
				}
				fixPanelPosition();
				updateResultsLayout();
				
				// Process highlighted HTML from data attributes
				if ( containerRef.current ) {
					// Find all elements with data-highlight-html
					const highlightLinks = containerRef.current.querySelectorAll( 'a[data-highlight-html]' );
					highlightLinks.forEach( ( link ) => {
						if ( link.dataset.highlightHtml ) {
							const decoded = decodeAlgoliaHtmlFromDataAttr( link.dataset.highlightHtml );
							link.innerHTML = sanitizeAlgoliaHtmlFragment( decoded );
							link.removeAttribute( 'data-highlight-html' );
						}
					} );
					
					// Find all elements with data-snippet-html
					const snippetDivs = containerRef.current.querySelectorAll( '.aa-ItemExcerpt[data-snippet-html]' );
					snippetDivs.forEach( ( div ) => {
						if ( div.dataset.snippetHtml ) {
							const decoded = decodeAlgoliaHtmlFromDataAttr( div.dataset.snippetHtml );
							div.innerHTML = sanitizeAlgoliaHtmlFragment( decoded );
							div.removeAttribute( 'data-snippet-html' );
						}
					} );
				}
			} );
			if ( containerRef.current ) {
				observer.observe( containerRef.current, { childList: true, subtree: true, attributes: true, attributeFilter: [ 'style', 'data-highlight-html', 'data-snippet-html' ] } );
			}
			setTimeout( updateResultsLayout, 0 );
			setTimeout( updateResultsLayout, 150 );
			
			// Close panel when clicking outside (or close and let link navigate when clicking an item link)
			const handleClickOutside = ( e ) => {
				if ( ! containerRef.current ) return;
				
				// Clicking clear should also hide the panel wrapper immediately.
				const clearButton = e.target?.closest?.( '.aa-ClearButton' );
				if ( clearButton ) {
					// Let Algolia clear logic run first, then force-close wrapper.
					setTimeout( closeAndHidePanel, 0 );
					return;
				}

				// If user clicked an item link, close panel so navigation feels immediate (link will still navigate)
				const link = e.target?.closest?.( 'a.aa-ItemLink' );
				if ( link && link.getAttribute( 'href' ) && link.getAttribute( 'href' ) !== '#' ) {
					try {
						if ( typeof autocompleteInstance.setIsOpen === 'function' ) {
							autocompleteInstance.setIsOpen( false );
						}
					} catch {
						// Silent
					}
					return;
				}

				const inContainer = containerRef.current.contains( e.target );
				if ( inContainer ) {
					return;
				}

				// Truly outside – close panel
				const panelEl = containerRef.current?.querySelector( '.aa-Panel' );
				try {
					if ( typeof autocompleteInstance.setIsOpen === 'function' ) {
						autocompleteInstance.setIsOpen( false );
					} else if ( typeof autocompleteInstance.setQuery === 'function' ) {
						autocompleteInstance.setQuery( '' );
					} else if ( panelEl ) {
						const input = containerRef.current?.querySelector( '.aa-Input' );
						if ( input ) input.blur();
						hidePanel();
					}
				} catch ( err ) {
					// Silent fail
				}
			};
			
			const handleClearButtonMouseDown = ( e ) => {
				const clearButton = e.target?.closest?.( '.aa-ClearButton' );
				if ( clearButton ) {
					suppressPanelAfterClearRef.current = true;
				}
			};
			// Capture phase, short delay so autocomplete DOM is ready
			let clickTimeoutId = setTimeout( () => {
				document.addEventListener( 'mousedown', handleClearButtonMouseDown, true );
				document.addEventListener( 'click', handleClickOutside, true );
			}, 50 );

			// Store cleanup function
			const clickHandler = handleClickOutside;
			autocompleteInstanceRef.current = {
				instance: autocompleteInstance,
				cleanup: () => {
					if ( clickTimeoutId ) clearTimeout( clickTimeoutId );
					observer.disconnect();
					document.removeEventListener( 'mousedown', handleClearButtonMouseDown, true );
					document.removeEventListener( 'click', clickHandler, true );
					autocompleteInstance.destroy();
				}
			};
		}, 100 );

		return () => {
			const root = containerRef.current?.closest( '.wp-block-wlc-algolia-autocomplete' );
			if ( root ) {
				root.classList.remove(
					'is-result-layout-vertical',
					'is-result-layout-grid'
				);
				root.style.removeProperty( '--wlc-ac-grid-columns' );
			}
			clearTimeout( timeoutId );
			if ( autocompleteInstanceRef.current && autocompleteInstanceRef.current.cleanup ) {
				autocompleteInstanceRef.current.cleanup();
				autocompleteInstanceRef.current = null;
			}
		};
	}, [ config ] );

	return (
		<div
			className="wp-block-wlc-algolia-autocomplete__wrapper"
			ref={ containerRef }
			style={ {
				'--wlc-accent': config.accentColor || '#DB002F',
				width: '100%',
				display: 'block',
				visibility: 'visible',
			} }
		/>
	);
}

function initAutocomplete() {
	let blocks = document.querySelectorAll( BLOCK_SELECTOR );
	
	if ( ! blocks.length ) {
		// Try alternative selector
		const altBlocks = document.querySelectorAll( '[class*="autocomplete"]' );
		if ( ! altBlocks.length ) {
			return;
		}
		blocks = altBlocks;
	}

	blocks.forEach( ( block ) => {
		if ( block.dataset.initialized ) {
			return;
		}
		block.dataset.initialized = '1';

		const config = JSON.parse( block.dataset.config || '{}' );
		
		const container = block.querySelector( '.wp-block-wlc-algolia-autocomplete__container' );

		if ( ! container ) {
			return;
		}

		// Get API keys from REST API if not in config
		async function loadConfig() {
			if ( ! config.appId || ! config.apiKey ) {
				try {
					const root = document.querySelector( 'link[rel="https://api.w.org/"]' )?.getAttribute( 'href' ) || '/wp-json/';
					const res = await fetch( `${ root }wlc-algolia/v1/api-keys/` );
					if ( res.ok ) {
						const data = await res.json();
						config.appId = data.api_keys?.application_id || '';
						config.apiKey = data.api_keys?.search_api_key || '';
					}
				} catch ( e ) {
					// Silent fail
				}
			}

			// Create React root and render
			const root = createRoot( container );
			root.render( <AutocompleteComponent config={ config } /> );
		}

		loadConfig();
	} );
}

// Helper function to extract highlighted HTML from Algolia hit
function getHighlightedValue( hit, attribute ) {
	if ( ! hit._highlightResult || ! hit._highlightResult[ attribute ] ) {
		return null;
	}
	const highlightResult = hit._highlightResult[ attribute ];
	if ( highlightResult && highlightResult.value ) {
		return highlightResult.value; // This is already HTML string with <em> tags
	}
	return null;
}

// Helper function to extract snippet HTML from Algolia hit
function getSnippetValue( hit, attribute ) {
	if ( ! hit._snippetResult || ! hit._snippetResult[ attribute ] ) {
		return null;
	}
	const snippetResult = hit._snippetResult[ attribute ];
	if ( snippetResult && snippetResult.value ) {
		return snippetResult.value; // This is already HTML string with <em> tags
	}
	return null;
}

function toDisplayText( value ) {
	if ( value == null ) return '';
	if ( typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ) {
		const str = String( value );
		try {
			const tpl = document.createElement( 'template' );
			tpl.innerHTML = str;
			const rawText = tpl.content?.textContent || tpl.textContent || '';
			return rawText.replace( /\s+/g, ' ' ).trim();
		} catch {
			return str.replace( /\s+/g, ' ' ).trim();
		}
	}
	if ( Array.isArray( value ) ) {
		for ( const item of value ) {
			const text = toDisplayText( item );
			if ( text ) return text;
		}
		return '';
	}
	if ( typeof value === 'object' && value ) {
		const preferredObjectKeys = [ 'value', 'rendered', 'raw', 'text', 'snippet' ];
		for ( const key of preferredObjectKeys ) {
			if ( key in value ) {
				const text = toDisplayText( value[ key ] );
				if ( text ) return text;
			}
		}
		for ( const nestedValue of Object.values( value ) ) {
			const text = toDisplayText( nestedValue );
			if ( text ) return text;
		}
	}
	return '';
}

function resolveExcerptData( hit, block ) {
	const preferredPath = block?.fieldPath || 'post_excerpt';
	const snippetCandidateAttributes = [ preferredPath, 'post_excerpt', 'excerpt', 'content' ];
	let snippetHtml = null;
	for ( const attr of snippetCandidateAttributes ) {
		if ( ! attr || typeof attr !== 'string' ) continue;
		const snippet = getSnippetValue( hit, attr );
		if ( snippet ) {
			snippetHtml = snippet;
			break;
		}
	}

	const candidates = [
		getFieldValue( hit, preferredPath ),
		hit?.post_excerpt,
		hit?.excerpt,
		hit?.content_snippet,
		hit?.post_content,
		hit?.content,
		getFieldValue( hit, '_snippetResult.post_excerpt.value' ),
		getFieldValue( hit, '_snippetResult.excerpt.value' ),
		getFieldValue( hit, '_snippetResult.content.value' ),
		getFieldValue( hit, '_highlightResult.post_excerpt.value' ),
		getFieldValue( hit, '_highlightResult.excerpt.value' ),
		getFieldValue( hit, '_highlightResult.content.value' ),
	];
	for ( const candidate of candidates ) {
		const text = toDisplayText( candidate );
		if ( text ) {
			return { text, snippetHtml };
		}
	}
	return { text: '', snippetHtml };
}

// Template rendering function for autocomplete (returns HTML using html template literal)
function renderHitWithTemplate( hit, templateBlocks, components, layout = 'vertical', _columns = 2, html, currency = 'USD' ) {
	layout = normalizeResultLayout( layout );

	if ( ! templateBlocks || templateBlocks.length === 0 ) {
		// Fallback: render basic title
		const title = hit.post_title || hit.title || 'Untitled';
		const permalink = hit.post_permalink || hit.permalink || hit.url || '#';
		return html`
			<a href="${ permalink }" class="aa-ItemLink aa-ItemLinkLayout-${ layout }">
				<div class="aa-ItemContent aa-ItemLayout-${ layout }">
					<div class="aa-ItemContentBody">
						<div class="aa-ItemTitle">${ title }</div>
					</div>
				</div>
			</a>
		`;
	}

	const permalink = sanitizeHref( hit.post_permalink || hit.permalink || hit.url || '#' );
	const layoutClass = `aa-ItemLayout-${ layout }`;
	
	// Find image block first
	const imageBlock = templateBlocks.find( ( b ) => b.type === 'image' );
	const imageUrl = imageBlock ? ( getFieldValue( hit, imageBlock.fieldPath ) || '' ) : '';
	const imageLinkUrl = imageBlock && imageUrl ? getLinkUrl( hit, imageBlock.linkType, imageBlock.linkFieldPath ) : '';

	// Build content body elements as array of HTML fragments
	const contentElements = [];
	
	templateBlocks.forEach( ( block ) => {
		if ( block.type === 'image' ) {
			// Already handled above
			return;
		}

		const rawFieldValue =
			block.type === 'excerpt'
				? resolveExcerptData( hit, block ).text
				: getFieldValue( hit, block.fieldPath );

		if ( block.hideIfEmpty && ! rawFieldValue ) {
			return;
		}

		switch ( block.type ) {
			case 'title':
				const title = getFieldValue( hit, block.fieldPath ) || hit.post_title || 'Untitled';
				const titleLinkUrl = getLinkUrl( hit, block.linkType, block.linkFieldPath );
				const attributePath = block.fieldPath || 'post_title';
				// Try to get highlighted HTML from _highlightResult first
				const highlightedTitle = getHighlightedValue( hit, attributePath );
				if ( highlightedTitle ) {
					// Use html template literal - it should handle HTML strings
					// We need to create a wrapper that will contain the HTML
					contentElements.push( html`
						<div class="aa-ItemTitle">
							<a href="${ titleLinkUrl }" data-highlight-html="${ encodeAlgoliaHtmlForDataAttr( highlightedTitle ) }">${ title }</a>
						</div>
					` );
				} else if ( components && components.Highlight ) {
					// Use Highlight component directly - it should work with html template literal
					contentElements.push( html`
						<div class="aa-ItemTitle">
							<a href="${ titleLinkUrl }">${ components.Highlight( { hit: hit, attribute: attributePath } ) }</a>
						</div>
					` );
				} else {
					// Final fallback - plain text
					contentElements.push( html`
						<div class="aa-ItemTitle">
							<a href="${ titleLinkUrl }">${ title }</a>
						</div>
					` );
				}
				break;
			case 'excerpt':
				const excerptData = resolveExcerptData( hit, block );
				const excerpt = excerptData.text;
				if ( ! excerpt ) return;
				const maxLength = block.maxLength || 220;
				const snippetExcerpt = excerptData.snippetHtml;
				if ( snippetExcerpt ) {
					contentElements.push( html`
						<div class="aa-ItemExcerpt" data-snippet-html="${ encodeAlgoliaHtmlForDataAttr( snippetExcerpt ) }">${ excerpt }</div>
					` );
				} else if ( components && components.Snippet && block.fieldPath ) {
					// Use Snippet component directly
					contentElements.push( html`
						<div class="aa-ItemExcerpt">${ components.Snippet( { hit: hit, attribute: block.fieldPath } ) }</div>
					` );
				} else {
					// Final fallback - plain text
					const truncated = excerpt.length > maxLength ? excerpt.substring( 0, maxLength ) + '...' : excerpt;
					contentElements.push( html`
						<div class="aa-ItemExcerpt">${ truncated }</div>
					` );
				}
				break;
			case 'meta':
				const metaParts = [];
				if ( block.metaDate && hit.post_date ) {
					metaParts.push( html`<span class="aa-ItemMetaDate">${ new Date( hit.post_date ).toLocaleDateString() }</span>` );
				}
				if ( block.metaAuthor && hit.post_author_name ) {
					metaParts.push( html`<span class="aa-ItemMetaAuthor">${ hit.post_author_name }</span>` );
				}
				if ( metaParts.length > 0 ) {
					contentElements.push( html`<div class="aa-ItemMeta">${ metaParts }</div>` );
				}
				break;
			case 'field':
				if ( block.fieldPath === '_stock_status' || block.fieldPath === '_stock' ) {
					return;
				}
				const value = getFieldValue( hit, block.fieldPath );
				if ( ! value ) return;
				const formattedValue = formatFieldValueForHtml( value, block.fieldPath, html, currency );
				const fieldLinkUrl = block.linkType !== 'none' ? getLinkUrl( hit, block.linkType, block.linkFieldPath ) : '';
				const fieldClasses = [ 'aa-ItemField' ];
				const isPrice = block.fieldPath === '_price' || block.fieldPath === '_sale_price' || block.fieldPath === '_regular_price';
				if ( isPrice ) {
					fieldClasses.push( 'is-price' );
				} else if ( block.fieldPath === '_sku' ) {
					fieldClasses.push( 'is-sku' );
				} else if ( block.fieldPath === '_stock_status' ) {
					fieldClasses.push( 'is-badge' );
				} else if ( block.fieldPath === '_stock' ) {
					fieldClasses.push( 'is-stock' );
				}
				const fieldContent = fieldLinkUrl 
					? html`<a href="${ fieldLinkUrl }" class="aa-ItemFieldValue">${ formattedValue }</a>`
					: html`<span class="aa-ItemFieldValue">${ formattedValue }</span>`;
				const fieldLabel = ! isPrice && block.label ? html`<span class="aa-ItemFieldLabel">${ block.label }</span>` : '';
				contentElements.push( html`
					<div class="${ fieldClasses.join( ' ' ) }">
						${ fieldLabel }
						${ fieldContent }
					</div>
				` );
				break;
			case 'button':
				const buttonLabel = block.label || 'View';
				const buttonLinkUrl = getLinkUrl( hit, block.linkType, block.linkFieldPath );
				contentElements.push( html`
					<div class="aa-ItemButton">
						<a href="${ buttonLinkUrl }" class="aa-ItemButtonLink">${ buttonLabel }</a>
					</div>
				` );
				break;
		}
	} );

	// Build the complete HTML structure using html template literal
	// Note: html template literal can contain arrays of html fragments
	const imageElement = imageUrl 
		? html`<div class="aa-ItemImage"><a href="${ imageLinkUrl }"><img src="${ imageUrl }" alt="${ hit.post_title || '' }" /></a></div>`
		: '';

	return html`
		<a href="${ permalink }" class="aa-ItemLink aa-ItemLinkLayout-${ layout }">
			<div class="aa-ItemContent ${ layoutClass }">
				${ imageElement }
				<div class="aa-ItemContentBody">
					${ contentElements }
				</div>
			</div>
		</a>
	`;
}

function getFieldValue( hit, path ) {
	if ( ! path ) return null;
	const parts = path.split( '.' );
	let value = hit;
	for ( const part of parts ) {
		if ( value && typeof value === 'object' && part in value ) {
			value = value[ part ];
		} else {
			return null;
		}
	}
	return value;
}

function formatFieldValueForHtml( value, fieldPath, html, currency = 'USD' ) {
	if ( fieldPath === '_price' || fieldPath === '_sale_price' || fieldPath === '_regular_price' ) {
		const normalized = String( value )
			.replace( /\s/g, '' )
			.replace( /,/g, '.' )
			.replace( /[^0-9.-]/g, '' );
		const num = parseFloat( normalized );
		if ( isNaN( num ) ) return String( value );
		return new Intl.NumberFormat( 'en-US', { style: 'currency', currency: currency } ).format( num );
	}
	if ( fieldPath === '_stock_status' ) {
		const status = String( value ).toLowerCase();
		const badges = {
			instock: { label: 'In Stock', class: 'is-in-stock' },
			outofstock: { label: 'Out of Stock', class: 'is-out-of-stock' },
			onbackorder: { label: 'On Backorder', class: 'is-on-backorder' },
		};
		const badge = badges[ status ] || { label: status, class: '' };
		return html`<span class="aa-ItemBadge ${ badge.class }">${ badge.label }</span>`;
	}
	if ( fieldPath === '_stock' ) {
		const num = parseInt( value, 10 );
		if ( isNaN( num ) ) return String( value );
		return html`<span class="aa-ItemStock">Stock: ${ num }</span>`;
	}
	return String( value );
}

function escapeHtml( text ) {
	if ( typeof text !== 'string' ) {
		text = String( text || '' );
	}
	const div = document.createElement( 'div' );
	div.textContent = text;
	return div.innerHTML;
}

function sanitizeHref( input ) {
	if ( typeof input !== 'string' ) {
		return '#';
	}
	const value = input.trim();
	if ( ! value || value === '#' ) {
		return '#';
	}
	if ( typeof window === 'undefined' ) {
		return value;
	}
	try {
		const parsed = new URL( value, window.location.origin );
		if ( parsed.protocol !== 'http:' && parsed.protocol !== 'https:' ) {
			return '#';
		}
		return parsed.toString();
	} catch {
		return '#';
	}
}

function getLinkUrl( hit, linkType, linkFieldPath ) {
	if ( linkType === 'none' || ! linkType ) {
		return '#';
	}
	if ( linkType === 'permalink' ) {
		// In Algolia index, permalink is stored as post_permalink
		return sanitizeHref( hit.post_permalink || hit.permalink || hit.url || '#' );
	}
	if ( linkType === 'add_to_cart' ) {
		const explicitCandidates = [
			hit?.add_to_cart_url,
			hit?.woocommerce?.add_to_cart_url,
			hit?.product?.add_to_cart_url,
		];
		for ( const candidate of explicitCandidates ) {
			const safe = sanitizeHref( String( candidate || '' ) );
			if ( safe && safe !== '#' ) {
				return safe;
			}
		}
		const postIdCandidates = [ hit?.post_id, hit?.ID, hit?.id, hit?.objectID ];
		for ( const candidate of postIdCandidates ) {
			const parsed = parseInt( candidate, 10 );
			if ( isNaN( parsed ) || parsed <= 0 ) {
				continue;
			}
			if ( typeof window !== 'undefined' ) {
				try {
					const currentUrl = new URL( window.location.href );
					currentUrl.searchParams.set( 'add-to-cart', String( parsed ) );
					return sanitizeHref( currentUrl.toString() );
				} catch {
					return sanitizeHref(
						`${ window.location.origin || '' }/?add-to-cart=${ parsed }`
					);
				}
			}
			return sanitizeHref( `/?add-to-cart=${ parsed }` );
		}
		return '#';
	}
	if ( linkFieldPath ) {
		return sanitizeHref( getFieldValue( hit, linkFieldPath ) || '#' );
	}
	return '#';
}

// Initialize on DOM ready
if ( document.readyState === 'loading' ) {
	document.addEventListener( 'DOMContentLoaded', initAutocomplete );
} else {
	initAutocomplete();
}

