/**
 * Frontend React component for Algolia Search Page V3 block.
 * Uses React InstantSearch with all widgets.
 */

import React from 'react';
import { createRoot } from 'react-dom/client';
import algoliasearch from 'algoliasearch/lite';
import { __ } from '@wordpress/i18n';
import {
	InstantSearch,
	SearchBox,
	RefinementList,
	Hits,
	Pagination,
	SortBy,
	Stats,
	Configure,
	useHits,
	useRefinementList,
	usePagination,
	useSortBy,
	useStats,
	useSearchBox,
	useCurrentRefinements,
} from 'react-instantsearch';
import { history } from 'instantsearch.js/es/lib/routers';
// Bundled locally so the insights client is never loaded from a CDN at runtime.
import insightsClient from 'search-insights';

const BLOCK_SELECTOR = '.wp-block-wlc-algolia-search-page';
const permalinkCache = new Map();

function parsePriceParam( value ) {
	if ( value == null ) {
		return '';
	}
	const raw = String( value ).trim();
	if ( raw === '' ) {
		return '';
	}
	const parsed = parseInt( raw, 10 );
	return Number.isFinite( parsed ) && parsed >= 0 ? String( parsed ) : '';
}

function parseDateParam( value ) {
	if ( value == null ) {
		return '';
	}
	const raw = String( value ).trim();
	if ( ! raw ) {
		return '';
	}
	return /^\d{4}-\d{2}-\d{2}$/.test( raw ) ? raw : '';
}

function dateInputToUnix( value, edge = 'start' ) {
	const normalized = parseDateParam( value );
	if ( ! normalized ) {
		return null;
	}
	const suffix = edge === 'end' ? 'T23:59:59' : 'T00:00:00';
	const timestamp = Date.parse( `${ normalized }${ suffix }` );
	if ( Number.isNaN( timestamp ) ) {
		return null;
	}
	return Math.floor( timestamp / 1000 );
}

/**
 * Convert possibly HTML-bearing Algolia fields to plain text (no HTML execution).
 *
 * @param {unknown} value Raw field value.
 * @return {string}
 */
function htmlToPlainText( value ) {
	if ( value == null || value === '' ) {
		return '';
	}
	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();
	}
}

function getHitPostId( hit ) {
	const candidates = [
		hit?.post_id,
		hit?.ID,
		hit?.id,
		hit?.objectID,
	];

	for ( const value of candidates ) {
		const parsed = parseInt( value, 10 );
		if ( ! isNaN( parsed ) && parsed > 0 ) {
			return parsed;
		}
	}

	return null;
}

// Custom Hits component with template rendering
function CustomHits( { templateBlocks, viewMode, gridColumns, gap, currency, imagePlaceholder } ) {
	const { hits } = useHits();
	const normalizedGridColumns = Math.max( 2, Math.min( 4, Number( gridColumns ) || 3 ) );

	if ( hits.length === 0 ) {
		return (
			<div className="wp-block-wlc-algolia-search-page__empty">
				<p>No results found.</p>
			</div>
		);
	}

	const containerClass = `wp-block-wlc-algolia-search-page__results-inner is-${ viewMode }`;
	const containerStyle = viewMode === 'grid' 
		? {
			gridTemplateColumns: `repeat(${ normalizedGridColumns }, 1fr)`,
			gap: `${ gap || 16 }px`,
		}
		: {
			gap: `${ gap || 16 }px`,
		};

	return (
		<div className={ containerClass } style={ containerStyle }>
			{ hits.map( ( hit ) => {
				const rendered = renderHitWithTemplate( hit, templateBlocks, currency, imagePlaceholder );
				if ( ! Array.isArray( rendered ) ) {
					return (
						<div key={ hit.objectID } className="wlc-algolia-builder__hit is-no-image">
							<div className="wlc-algolia-builder__content">
								{ rendered }
							</div>
						</div>
					);
				}

				const imageBlock = rendered.find( ( r ) => r && r.props && r.props.className === 'wlc-algolia-builder__image' );
				const otherBlocks = rendered.filter( ( r ) => ! r || ! r.props || r.props.className !== 'wlc-algolia-builder__image' );
				const hitClasses = `wlc-algolia-builder__hit ${ imageBlock ? 'has-image' : 'is-no-image' }`;

				return (
					<div key={ hit.objectID } className={ hitClasses }>
						{ imageBlock }
						<div className="wlc-algolia-builder__content">
							{ otherBlocks }
						</div>
					</div>
				);
			} ) }
		</div>
	);
}

// Template rendering function (similar to v2)
function renderHitWithTemplate( hit, templateBlocks, currency = 'USD', imagePlaceholder = '' ) {
	if ( ! templateBlocks || templateBlocks.length === 0 ) {
		// Fallback: render basic title
		return (
			<div>
				<h3>{ hit.post_title || hit.title || 'Untitled' }</h3>
			</div>
		);
	}

	return templateBlocks.map( ( block, idx ) => {
		let rawFieldValue = getFieldValue( hit, block.fieldPath );
		if ( block.type === 'excerpt' ) {
			rawFieldValue = resolveExcerptText( hit, block );
		}
		if ( block.hideIfEmpty && block.type !== 'image' && block.type !== 'meta' && block.type !== 'excerpt' && ! rawFieldValue ) {
			return null;
		}
		if ( block.hideIfEmpty && block.type === 'image' && ! rawFieldValue ) {
			return null;
		}

		switch ( block.type ) {
			case 'title':
				return (
					<h3 key={ idx } className="wlc-algolia-builder__title">
						{ renderLink( getFieldValue( hit, block.fieldPath ) || hit.post_title || 'Untitled', block.linkType, hit, block.linkFieldPath ) }
					</h3>
				);
			case 'image':
				const imageUrl = rawFieldValue || ( block.hideIfEmpty ? '' : imagePlaceholder ) || '';
				if ( ! imageUrl ) return null;
				return (
					<div key={ idx } className="wlc-algolia-builder__image">
						{ renderLink(
							<img src={ imageUrl } alt={ hit.post_title || '' } />,
							block.linkType,
							hit,
							block.linkFieldPath
						) }
					</div>
				);
			case 'excerpt': {
				const plain = toDisplayText( rawFieldValue );
				if ( ! plain ) return null;
				const maxLength = block.maxLength || 220;
				const truncated =
					plain.length > maxLength ? plain.substring( 0, maxLength ) + '...' : plain;
				return (
					<div key={ idx } className="wlc-algolia-builder__excerpt">
						{ truncated }
					</div>
				);
			}
			case 'meta':
				const metaDateValue = hit.post_date || hit.post_date_gmt || '';
				const metaAuthorValue =
					hit.post_author_name ||
					hit.post_author_display_name ||
					hit.post_author ||
					'';
				const showMetaDate = !! block.metaDate && !! metaDateValue;
				const showMetaAuthor = !! block.metaAuthor && !! metaAuthorValue;
				if ( ! showMetaDate && ! showMetaAuthor ) {
					return null;
				}
				return (
					<div key={ idx } className="wlc-algolia-builder__meta">
						{ showMetaDate ? <span>{ new Date( metaDateValue ).toLocaleDateString() }</span> : null }
						{ showMetaAuthor ? <span>{ metaAuthorValue }</span> : null }
					</div>
				);
			case 'field':
				const value = getFieldValue( hit, block.fieldPath );
				if ( ! value ) return null;
				const fieldClasses = [ 'wlc-algolia-builder__field' ];
				const isPrice = block.fieldPath === '_price' || block.fieldPath === '_sale_price' || block.fieldPath === '_regular_price';
				const isStockStatus = block.fieldPath === '_stock_status' || block.fieldPath === 'stock_status';
				const isRating = block.fieldPath === 'rating_bucket';
				if ( isPrice ) {
					fieldClasses.push( 'is-price' );
				} else if ( block.fieldPath === '_sku' ) {
					fieldClasses.push( 'is-sku' );
				} else if ( isStockStatus ) {
					fieldClasses.push( 'is-badge' );
				} else if ( block.fieldPath === '_stock' ) {
					fieldClasses.push( 'is-stock' );
				} else if ( isRating ) {
					fieldClasses.push( 'is-rating' );
				}
				return (
					<div key={ idx } className={ fieldClasses.join( ' ' ) }>
						{ ! isPrice && block.label ? <span className="wlc-algolia-builder__label">{ block.label }</span> : null }
						<span className="wlc-algolia-builder__value">{ formatFieldValue( value, block.fieldPath, currency, hit ) }</span>
					</div>
				);
			case 'button':
				if ( block.linkType === 'add_to_cart' ) {
					const availability = getProductAvailabilityState( hit );
					const addToCartLabel = getAddToCartLabel( hit, block.label || 'Add to cart' );
					if ( availability.showMessageInsteadOfButton ) {
						return (
							<div key={ idx } className="wlc-algolia-builder__cta">
								<span className={ `wlc-algolia-builder__availability ${ availability.className }` }>
									{ availability.label }
								</span>
							</div>
						);
					}
					return (
						<div key={ idx } className="wlc-algolia-builder__cta">
							{ renderButton( addToCartLabel, block.linkType, hit, block.linkFieldPath ) }
						</div>
					);
				}
				return (
					<div key={ idx } className="wlc-algolia-builder__cta">
						{ renderButton( block.label || 'View', block.linkType, hit, block.linkFieldPath ) }
					</div>
				);
			default:
				return null;
		}
	} );
}

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 toDisplayText( value ) {
	if ( value == null ) return '';
	if ( typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ) {
		return htmlToPlainText( String( value ) );
	}
	if ( Array.isArray( value ) ) {
		for ( const item of value ) {
			const text = toDisplayText( item );
			if ( text ) return text;
		}
		return '';
	}
	if ( typeof value === 'object' && value && typeof value.value === 'string' ) {
		return htmlToPlainText( value.value );
	}
	if ( typeof value === 'object' && value && typeof value.raw === 'string' ) {
		return htmlToPlainText( value.raw );
	}
	if ( typeof value === 'object' && value && typeof value.rendered === 'string' ) {
		return htmlToPlainText( value.rendered );
	}
	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 resolveExcerptText( hit, block ) {
	const preferredPath = block?.fieldPath || 'post_excerpt';
	const candidates = [
		getFieldValue( hit, preferredPath ),
		hit?.post_excerpt,
		hit?.excerpt,
		hit?.content_snippet,
		hit?.post_content,
		hit?.content,
		getFieldValue( hit, '_snippetResult.post_excerpt' ),
		getFieldValue( hit, '_snippetResult.excerpt' ),
		getFieldValue( hit, '_snippetResult.content' ),
		getFieldValue( hit, '_snippetResult.post_excerpt.value' ),
		getFieldValue( hit, '_highlightResult.excerpt' ),
		getFieldValue( hit, '_highlightResult.content' ),
		getFieldValue( hit, '_highlightResult.post_excerpt.value' ),
		hit?._snippetResult,
		hit?._highlightResult,
	];
	for ( const candidate of candidates ) {
		const text = toDisplayText( candidate );
		if ( text ) return text;
	}
	return '';
}

function formatFieldValue( value, fieldPath, currency = 'USD', hit = null ) {
	if ( fieldPath === '_price' || fieldPath === '_sale_price' || fieldPath === '_regular_price' ) {
		return formatCurrency( value, currency );
	}
	if ( fieldPath === '_stock_status' || fieldPath === 'stock_status' ) {
		return formatStockStatus( value );
	}
	if ( fieldPath === '_stock' ) {
		return formatStockQuantity( value );
	}
	if ( fieldPath === 'rating_bucket' ) {
		return formatRating( value );
	}
	return String( value );
}

function formatCurrency( value, currency = 'USD' ) {
	const normalized = String( value )
		.replace( /\s/g, '' )
		.replace( /,/g, '.' )
		.replace( /[^0-9.-]/g, '' );
	const num = parseFloat( normalized );
	if ( isNaN( num ) ) return value;
	return new Intl.NumberFormat( 'en-US', { style: 'currency', currency: currency } )
		.format( num )
		.replace( /\u00A0/g, ' ' );
}

function formatStockStatus( value ) {
	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 <span className={ `wlc-algolia-builder__badge ${ badge.class }` }>{ badge.label }</span>;
}

function formatStockQuantity( value ) {
	const num = parseInt( value, 10 );
	if ( isNaN( num ) ) return value;
	return <span className="wlc-algolia-builder__stock">Stock: { num }</span>;
}

function formatRating( value ) {
	const parsed = parseInt( value, 10 );
	const rating = Number.isFinite( parsed ) ? Math.max( 0, Math.min( 5, parsed ) ) : 0;

	return (
		<span className="wlc-algolia-builder__rating">
			<span className="wlc-algolia-builder__rating-stars" aria-label={ `${ rating } out of 5` }>
				{ [ 1, 2, 3, 4, 5 ].map( ( star ) => (
					<span
						key={ star }
						className={ `wlc-algolia-builder__rating-star ${ star <= rating ? 'is-filled' : 'is-empty' }` }
					>
						★
					</span>
				) ) }
			</span>
		</span>
	);
}

function getPrettyPathFromHit( hit ) {
	const rawSlug = hit.post_name || hit.slug || hit.post_slug || '';
	if ( typeof rawSlug !== 'string' || ! rawSlug.trim() ) {
		return '';
	}
	const slug = rawSlug.trim().replace( /^\/+|\/+$/g, '' );
	return slug ? `/${ slug }/` : '';
}

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 normalizePermalinkCandidate( candidate, hit ) {
	if ( typeof candidate !== 'string' || ! candidate.trim() ) {
		return '';
	}

	const value = candidate.trim();
	if ( value === '#' ) {
		return '#';
	}

	try {
		if ( typeof window === 'undefined' ) {
			return sanitizeHref( value );
		}
		const parsed = new URL( value, window.location.origin );
		const hasQueryId = parsed.searchParams.has( 'p' ) || parsed.searchParams.has( 'page_id' );
		if ( hasQueryId ) {
			const prettyPath = getPrettyPathFromHit( hit );
			if ( prettyPath ) {
				return sanitizeHref( `${ parsed.origin }${ prettyPath }` );
			}
		}
		return sanitizeHref( parsed.toString() );
	} catch {
		return sanitizeHref( value );
	}
}

function getPermalinkUrl( hit ) {
	const candidates = [
		hit.post_permalink,
		hit.permalink,
		hit.link,
		hit.url,
	];

	for ( const candidate of candidates ) {
		const normalized = normalizePermalinkCandidate( candidate, hit );
		if ( normalized ) {
			return normalized;
		}
	}

	const prettyPath = getPrettyPathFromHit( hit );
	if ( prettyPath && typeof window !== 'undefined' ) {
		return sanitizeHref( `${ window.location.origin || '' }${ prettyPath }` );
	}

	const postId = parseInt( hit.post_id, 10 );
	if ( ! isNaN( postId ) && typeof window !== 'undefined' ) {
		const baseUrl = window.location.origin || '';
		if ( hit.post_type === 'page' ) {
			return sanitizeHref( `${ baseUrl }/?page_id=${ postId }` );
		}
		return sanitizeHref( `${ baseUrl }/?p=${ postId }` );
	}

	return '#';
}

function getAddToCartUrl( hit ) {
	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 postId = getHitPostId( hit );
	if ( postId && typeof window !== 'undefined' ) {
		try {
			const currentUrl = new URL( window.location.href );
			currentUrl.searchParams.set( 'add-to-cart', String( postId ) );
			return sanitizeHref( currentUrl.toString() );
		} catch {
			return sanitizeHref( `${ window.location.origin || '' }/?add-to-cart=${ postId }` );
		}
	}

	return getPermalinkUrl( hit );
}

function getNormalizedStockStatus( hit ) {
	const candidates = [
		hit?._stock_status,
		hit?.stock_status,
		hit?.woocommerce?.stock_status,
		hit?.product?.stock_status,
	];
	for ( const candidate of candidates ) {
		const normalized = String( candidate || '' ).trim().toLowerCase();
		if ( normalized ) {
			return normalized;
		}
	}

	const visibilitySlugCandidates = [];
	const slugValues = hit?.taxonomies?.taxonomies_slugs?.product_visibility;
	if ( Array.isArray( slugValues ) ) {
		visibilitySlugCandidates.push( ...slugValues );
	}
	const nestedVisibilityTerms = hit?.taxonomies?.taxonomies?.product_visibility;
	if ( Array.isArray( nestedVisibilityTerms ) ) {
		nestedVisibilityTerms.forEach( ( term ) => {
			if ( term && typeof term === 'object' ) {
				visibilitySlugCandidates.push( term.slug || term.name || '' );
			} else {
				visibilitySlugCandidates.push( term );
			}
		} );
	}

	for ( const candidate of visibilitySlugCandidates ) {
		const normalized = String( candidate || '' ).trim().toLowerCase();
		if ( normalized === 'outofstock' ) {
			return 'outofstock';
		}
		if ( normalized === 'instock' ) {
			return 'instock';
		}
		if ( normalized === 'onbackorder' ) {
			return 'onbackorder';
		}
	}

	return '';
}

function getProductAvailabilityState( hit ) {
	const status = getNormalizedStockStatus( hit );

	if ( status === 'outofstock' ) {
		return {
			showMessageInsteadOfButton: true,
			label: __( 'Out of stock', 'wlc-search-integration-with-algolia' ),
			className: 'is-out-of-stock',
		};
	}

	if ( status === 'onbackorder' ) {
		return {
			showMessageInsteadOfButton: true,
			label: __( 'Available on backorder', 'wlc-search-integration-with-algolia' ),
			className: 'is-on-backorder',
		};
	}

	return {
		showMessageInsteadOfButton: false,
		label: '',
		className: '',
	};
}

function getAddToCartLabel( hit, fallbackLabel ) {
	const candidates = [
		hit?.add_to_cart_text,
		hit?.woocommerce?.add_to_cart_text,
		hit?.product?.add_to_cart_text,
	];

	for ( const candidate of candidates ) {
		const label = String( candidate || '' ).trim();
		if ( label ) {
			return label;
		}
	}

	return fallbackLabel;
}

function ResolvedPermalinkLink( { content, hit } ) {
	const fallbackUrl = getPermalinkUrl( hit );
	const [ resolvedUrl, setResolvedUrl ] = React.useState( fallbackUrl );

	React.useEffect( () => {
		const postId = getHitPostId( hit );
		if ( ! postId ) {
			setResolvedUrl( fallbackUrl );
			return;
		}

		const needsResolve = fallbackUrl === '#' || /[?&](p|page_id)=\d+/.test( fallbackUrl );
		if ( ! needsResolve ) {
			setResolvedUrl( fallbackUrl );
			return;
		}

		if ( permalinkCache.has( postId ) ) {
			setResolvedUrl( permalinkCache.get( postId ) );
			return;
		}

		let isCancelled = false;
		const root = window?.wpApiSettings?.root || '/wp-json/';
		fetch( `${ root.replace( /\/+$/, '/' ) }wlc-algolia/v1/permalink/?post_id=${ postId }` )
			.then( ( res ) => ( res.ok ? res.json() : null ) )
			.then( ( data ) => {
				if ( isCancelled || ! data?.permalink ) {
					return;
				}
				const safePermalink = sanitizeHref( data.permalink );
				permalinkCache.set( postId, safePermalink );
				setResolvedUrl( safePermalink );
			} )
			.catch( () => {
				// Keep fallback URL silently.
			} );

		return () => {
			isCancelled = true;
		};
	}, [ hit, fallbackUrl ] );

	return <a href={ sanitizeHref( resolvedUrl ) }>{ content }</a>;
}

function renderLink( content, linkType, hit, linkFieldPath ) {
	if ( linkType === 'none' || ! linkType ) {
		return content;
	}

	if ( linkType === 'permalink' ) {
		return <ResolvedPermalinkLink content={ content } hit={ hit } />;
	}

	let url = '#';
	if ( linkType === 'add_to_cart' ) {
		url = getAddToCartUrl( hit );
	} else if ( linkFieldPath ) {
		url = getFieldValue( hit, linkFieldPath ) || '#';
	}
	url = sanitizeHref( String( url ) );

	return (
		<a href={ url }>
			{ content }
		</a>
	);
}

function renderButton( label, linkType, hit, linkFieldPath ) {
	return renderLink( <button className="wlc-algolia-builder__btn">{ label }</button>, linkType, hit, linkFieldPath );
}

function humanizeVisibilityLabel( rawValue ) {
	const value = String( rawValue || '' ).trim().toLowerCase();
	if ( value === 'outofstock' ) return 'Out of stock';
	if ( value === 'instock' ) return 'In stock';
	if ( value === 'onbackorder' ) return 'On backorder';
	const ratedMatch = value.match( /^rated-([1-5])$/ );
	if ( ratedMatch ) {
		const stars = parseInt( ratedMatch[ 1 ], 10 );
		return `${ stars } star${ stars === 1 ? '' : 's' }`;
	}
	return String( rawValue || '' );
}

function formatFacetValueLabel( attribute, rawLabel, authorLabels = {} ) {
	const cleanAttribute = String( attribute || '' ).replace( /^searchable\((.+)\)$/, '$1' );
	const normalizedRaw = String( rawLabel || '' ).trim();
	if ( cleanAttribute === 'post_author' ) {
		return authorLabels?.[ normalizedRaw ] || normalizedRaw;
	}
	if ( cleanAttribute === 'post_year' ) {
		return normalizedRaw;
	}
	if ( cleanAttribute === 'post_date' || cleanAttribute === 'post_month' ) {
		const date = new Date( normalizedRaw );
		if ( ! Number.isNaN( date.getTime() ) ) {
			return date.toLocaleString();
		}
	}
	if ( cleanAttribute === 'rating_bucket' ) {
		const stars = parseInt( normalizedRaw, 10 );
		if ( ! isNaN( stars ) && stars >= 1 && stars <= 5 ) {
			return `${ stars } star${ stars === 1 ? '' : 's' }`;
		}
	}
	if (
		cleanAttribute === 'taxonomies.taxonomies_slugs.product_visibility' ||
		cleanAttribute === 'taxonomies.taxonomies_names.product_visibility'
	) {
		return humanizeVisibilityLabel( normalizedRaw );
	}
	return normalizedRaw;
}

function getHierarchicalTaxonomyMeta( attribute ) {
	const cleanAttribute = String( attribute || '' ).replace( /^searchable\((.+)\)$/, '$1' );
	const match = cleanAttribute.match( /^taxonomies\.taxonomies_(names|slugs)\.(.+)$/ );
	if ( ! match ) {
		return null;
	}
	return {
		valueType: match[ 1 ],
		taxonomy: match[ 2 ],
		cleanAttribute,
	};
}

function normalizeFacetLookupValue( value ) {
	return String( value || '' ).trim().toLowerCase();
}

// Custom RefinementList with search
function CustomRefinementList( { attribute, label, showCounts, countStyle, enableSearch, taxonomyHierarchy, authorLabels } ) {
	const cleanAttribute = attribute.replace( /^searchable\((.+)\)$/, '$1' );
	const isSearchable = attribute.startsWith( 'searchable(' );
	const shouldShowSearchInput = !! enableSearch;
	const hierarchyMeta = getHierarchicalTaxonomyMeta( attribute );
	const hierarchyTerms = hierarchyMeta?.taxonomy
		? taxonomyHierarchy?.[ hierarchyMeta.taxonomy ] || []
		: [];
	const isHierarchicalFacet = Array.isArray( hierarchyTerms ) && hierarchyTerms.length > 0;

	const refinementListConfig = {
		attribute: cleanAttribute,
		limit: 100,
		sortBy: [ 'name:asc' ],
	};

	if ( isSearchable ) {
		refinementListConfig.searchable = true;
	}

	const { items, refine, searchForItems } = useRefinementList( refinementListConfig );
	const [ searchQuery, setSearchQuery ] = React.useState( '' );

	React.useEffect( () => {
		if ( refinementListConfig.searchable ) {
			const timer1 = setTimeout( () => {
				searchForItems( '' );
			}, 100 );
			const timer2 = setTimeout( () => {
				searchForItems( '' );
			}, 500 );
			return () => {
				clearTimeout( timer1 );
				clearTimeout( timer2 );
			};
		}
	}, [ refinementListConfig.searchable, searchForItems ] );

	React.useEffect( () => {
		if ( ! refinementListConfig.searchable || ! shouldShowSearchInput ) {
			return;
		}
		if ( searchQuery && searchQuery.trim().length > 0 ) {
			searchForItems( searchQuery.trim() );
		} else {
			searchForItems( '' );
		}
	}, [ searchQuery, searchForItems, refinementListConfig.searchable, shouldShowSearchInput ] );

	const displayedItems = React.useMemo( () => {
		let nextItems = items;
		if ( ! refinementListConfig.searchable ) {
			const query = String( searchQuery || '' ).trim().toLowerCase();
			if ( shouldShowSearchInput && query ) {
				nextItems = items.filter( ( item ) =>
					String( item.label || '' ).toLowerCase().includes( query )
				);
			}
		}

		if ( isHierarchicalFacet ) {
			return nextItems;
		}

		// Enforce alphabetical ordering for non-hierarchical facets.
		return [ ...nextItems ].sort( ( a, b ) =>
			String( a?.label || '' ).localeCompare( String( b?.label || '' ), undefined, {
				sensitivity: 'base',
				numeric: true,
			} )
		);
	}, [ items, searchQuery, shouldShowSearchInput, refinementListConfig.searchable, isHierarchicalFacet ] );

	const hierarchyRoots = React.useMemo( () => {
		if ( ! isHierarchicalFacet ) {
			return [];
		}

		const valueType = hierarchyMeta?.valueType || 'names';
		const itemLookup = new Map();
		displayedItems.forEach( ( item ) => {
			const keys = [ item.value, item.label ].map( normalizeFacetLookupValue );
			keys.forEach( ( key ) => {
				if ( key ) {
					itemLookup.set( key, item );
				}
			} );
		} );

		const query = String( searchQuery || '' ).trim().toLowerCase();
		const termNodes = new Map();
		hierarchyTerms.forEach( ( term ) => {
			if ( ! term || typeof term !== 'object' || ! term.slug ) {
				return;
			}
			const lookupValue = valueType === 'slugs' ? term.slug : term.name;
			const item = itemLookup.get( normalizeFacetLookupValue( lookupValue ) );
			termNodes.set( term.slug, {
				...term,
				item,
				children: [],
			} );
		} );

		const roots = [];
		termNodes.forEach( ( node ) => {
			if ( node.parentSlug && termNodes.has( node.parentSlug ) ) {
				termNodes.get( node.parentSlug ).children.push( node );
			} else {
				roots.push( node );
			}
		} );

		const sortNodeChildren = ( node ) => {
			node.children.sort( ( a, b ) =>
				String( a.name || '' ).localeCompare( String( b.name || '' ), undefined, {
					sensitivity: 'base',
					numeric: true,
				} )
			);
			node.children.forEach( sortNodeChildren );
		};
		roots.forEach( sortNodeChildren );

		const filterTree = ( node ) => {
			const filteredChildren = node.children
				.map( filterTree )
				.filter( Boolean );
			const matchesQuery =
				! query || String( node.name || '' ).toLowerCase().includes( query );
			const hasRefinableDescendant = filteredChildren.length > 0;
			const hasOwnFacetValue = !! node.item;

			if ( matchesQuery || hasOwnFacetValue || hasRefinableDescendant ) {
				return {
					...node,
					children: filteredChildren,
				};
			}
			return null;
		};

		return roots.map( filterTree ).filter( Boolean );
	}, [ isHierarchicalFacet, hierarchyMeta?.valueType, displayedItems, hierarchyTerms, searchQuery ] );

	const HierarchyItem = ( { node, level = 0 } ) => {
		const displayLabel = formatFacetValueLabel( cleanAttribute, node.name, authorLabels );
		const hasFacetValue = !! node.item;
		return (
			<li
				key={ `${ node.slug }-${ level }` }
				className="wp-block-wlc-algolia-search-page__filter-item wp-block-wlc-algolia-search-page__filter-item--hierarchical"
			>
				<label>
					{ hasFacetValue ? (
						<input
							type="checkbox"
							checked={ node.item.isRefined }
							onChange={ () => refine( node.item.value ) }
						/>
					) : (
						<span
							className="wp-block-wlc-algolia-search-page__filter-item-placeholder"
							aria-hidden="true"
						/>
					) }
					<span>{ displayLabel }</span>
					{ showCounts && hasFacetValue && (
						<small>
							{ countStyle === 'paren' ? `(${ node.item.count })` : node.item.count }
						</small>
					) }
				</label>
				{ node.children.length > 0 && (
					<ul className="wp-block-wlc-algolia-search-page__filter-items wp-block-wlc-algolia-search-page__filter-items--nested">
						{ node.children.map( ( child ) => (
							<HierarchyItem key={ child.slug } node={ child } level={ level + 1 } />
						) ) }
					</ul>
				) }
			</li>
		);
	};

	return (
		<div className="wp-block-wlc-algolia-search-page__filter">
			<h4 className="wp-block-wlc-algolia-search-page__filter-title">{ label }</h4>
			{ shouldShowSearchInput && (
				<div className="wp-block-wlc-algolia-search-page__facet-search-wrap">
					<input
						type="search"
						className="wp-block-wlc-algolia-search-page__facet-search"
						placeholder={ `Search ${ label.toLowerCase() }...` }
						value={ searchQuery }
						onChange={ ( e ) => setSearchQuery( e.target.value ) }
					/>
					{ searchQuery.trim() !== '' && (
						<button
							type="button"
							className="wp-block-wlc-algolia-search-page__facet-search-clear"
							onClick={ () => setSearchQuery( '' ) }
							aria-label={ `Clear ${ label.toLowerCase() } filter search` }
						>
							&times;
						</button>
					) }
				</div>
			) }
			{ displayedItems.length === 0 ? (
				<div className="wp-block-wlc-algolia-search-page__filter-empty">No values</div>
			) : (
				<ul className="wp-block-wlc-algolia-search-page__filter-items">
					{ isHierarchicalFacet
						? hierarchyRoots.map( ( node ) => (
							<HierarchyItem key={ node.slug } node={ node } />
						) )
						: displayedItems.map( ( item ) => {
							const displayLabel = formatFacetValueLabel( cleanAttribute, item.label, authorLabels );
							return (
								<li key={ item.value } className="wp-block-wlc-algolia-search-page__filter-item">
									<label>
										<input
											type="checkbox"
											checked={ item.isRefined }
											onChange={ () => refine( item.value ) }
										/>
										<span>{ displayLabel }</span>
										{ showCounts && (
											<small>
												{ countStyle === 'paren' ? `(${ item.count })` : item.count }
											</small>
										) }
									</label>
								</li>
							);
						} ) }
				</ul>
			) }
		</div>
	);
}

function PriceRangeFilter( { label, priceRange, onPriceRangeChange } ) {
	const onInputChange = ( key ) => ( event ) => {
		const value = event.target.value.replace( /[^0-9]/g, '' );
		onPriceRangeChange( ( prev ) => ( { ...prev, [ key ]: value } ) );
	};

	return (
		<div className="wp-block-wlc-algolia-search-page__filter wp-block-wlc-algolia-search-page__filter--price">
			<h4 className="wp-block-wlc-algolia-search-page__filter-title">{ label }</h4>
			<div className="wp-block-wlc-algolia-search-page__price-range">
				<label className="wp-block-wlc-algolia-search-page__price-range-field">
					<span>Min</span>
					<input
						type="number"
						inputMode="numeric"
						step="1"
						min="0"
						placeholder="0"
						value={ priceRange.min }
						onChange={ onInputChange( 'min' ) }
					/>
				</label>
				<label className="wp-block-wlc-algolia-search-page__price-range-field">
					<span>Max</span>
					<input
						type="number"
						inputMode="numeric"
						step="1"
						min="0"
						placeholder="9999"
						value={ priceRange.max }
						onChange={ onInputChange( 'max' ) }
					/>
				</label>
			</div>
		</div>
	);
}

function DateRangeFilter( { label, dateRange, onDateRangeChange } ) {
	const onInputChange = ( key ) => ( event ) => {
		onDateRangeChange( ( prev ) => ( { ...prev, [ key ]: parseDateParam( event.target.value ) } ) );
	};

	return (
		<div className="wp-block-wlc-algolia-search-page__filter wp-block-wlc-algolia-search-page__filter--date">
			<h4 className="wp-block-wlc-algolia-search-page__filter-title">{ label }</h4>
			<div className="wp-block-wlc-algolia-search-page__price-range">
				<label className="wp-block-wlc-algolia-search-page__price-range-field">
					<span>From</span>
					<input
						type="date"
						value={ dateRange.from }
						onChange={ onInputChange( 'from' ) }
					/>
				</label>
				<label className="wp-block-wlc-algolia-search-page__price-range-field">
					<span>To</span>
					<input
						type="date"
						value={ dateRange.to }
						onChange={ onInputChange( 'to' ) }
					/>
				</label>
			</div>
		</div>
	);
}

function CustomPagination( { config } ) {
	const { nbPages } = usePagination();
	if ( ! nbPages || nbPages <= 1 ) {
		return null;
	}

	return (
		<nav className="wp-block-wlc-algolia-search-page__pagination" aria-label="Pagination">
			{ /* `padding` is "siblings per side", not "max pages total". */ }
			<Pagination
				showFirst={ config.paginationMode === 'numbers' }
				showLast={ config.paginationMode === 'numbers' }
				showPrevious={ true }
				showNext={ true }
				padding={
					config.paginationMode === 'numbers'
						? Math.max( 0, Math.floor( ( ( config.paginationMaxPages || 10 ) - 1 ) / 2 ) )
						: 0
				}
				classNames={ {
					root: 'wp-block-wlc-algolia-search-page__pagination-list',
					list: 'wp-block-wlc-algolia-search-page__pagination-items',
					item: 'wp-block-wlc-algolia-search-page__pagination-item',
					link: 'wp-block-wlc-algolia-search-page__pagination-link',
					selectedItem: 'wp-block-wlc-algolia-search-page__pagination-item--selected',
					disabledItem: 'wp-block-wlc-algolia-search-page__pagination-item--disabled',
				} }
			/>
		</nav>
	);
}

// Active Filters Component
function ActiveFilters( { facetLabels, showClearAll = true, priceRange, onClearPriceRange, dateRange, onClearDateRange, authorLabels } ) {
	const { items, refine, canRefine } = useCurrentRefinements();
	const hasPriceFilter = priceRange && ( priceRange.min !== '' || priceRange.max !== '' );
	const hasDateFilter = dateRange && ( dateRange.from !== '' || dateRange.to !== '' );
	const hasFacetFilters = canRefine && items.length > 0;

	if ( ! hasFacetFilters && ! hasPriceFilter && ! hasDateFilter ) {
		return null;
	}

	// Helper to get label for attribute
	const getAttributeLabel = ( attribute ) => {
		// Remove searchable() wrapper if present
		const cleanAttr = attribute.replace( /^searchable\((.+)\)$/, '$1' );
		
		// Try to find label in facetLabels
		if ( facetLabels && facetLabels[ cleanAttr ] ) {
			return facetLabels[ cleanAttr ];
		}
		// Try WordPress format (taxonomies_names.product_cat)
		if ( cleanAttr.startsWith( 'taxonomies.taxonomies_' ) ) {
			const wpFormat = cleanAttr.replace( /^taxonomies\.taxonomies_(names|slugs)\./, 'taxonomies_$1.' );
			if ( facetLabels && facetLabels[ wpFormat ] ) {
				return facetLabels[ wpFormat ];
			}
		}
		// Fallback: make it readable
		const parts = cleanAttr.split( '.' );
		const lastPart = parts[ parts.length - 1 ];
		return lastPart.replace( /_/g, ' ' ).replace( /\b\w/g, l => l.toUpperCase() );
	};

	return (
		<div className="wp-block-wlc-algolia-search-page__active-filters">
			<div className="wp-block-wlc-algolia-search-page__active-filters-header">
				<h4 className="wp-block-wlc-algolia-search-page__active-filters-title">
					Active Filters
				</h4>
				{ showClearAll && ( items.length > 1 || hasPriceFilter || hasDateFilter ) && (
					<button
						type="button"
						className="wp-block-wlc-algolia-search-page__active-filters-clear-all"
						onClick={ () => {
							items.forEach( ( item ) => {
								item.refinements.forEach( ( refinement ) => {
									refine( refinement );
								} );
							} );
							if ( hasPriceFilter ) {
								onClearPriceRange();
							}
							if ( hasDateFilter ) {
								onClearDateRange();
							}
						} }
					>
						Clear All
					</button>
				) }
			</div>
			<div className="wp-block-wlc-algolia-search-page__active-filters-list">
				{ items.map( ( item ) => {
					const attributeLabel = getAttributeLabel( item.attribute );
					return item.refinements.map( ( refinement, index ) => (
						<div
							key={ `${ item.attribute }-${ index }` }
							className="wp-block-wlc-algolia-search-page__active-filter-tag"
						>
							<span className="wp-block-wlc-algolia-search-page__active-filter-label">
								{ attributeLabel }:
							</span>
							<span className="wp-block-wlc-algolia-search-page__active-filter-value">
								{ formatFacetValueLabel( item.attribute, refinement.label, authorLabels ) }
							</span>
							<button
								type="button"
								className="wp-block-wlc-algolia-search-page__active-filter-remove"
								onClick={ () => refine( refinement ) }
								aria-label={ `Remove filter ${ refinement.label }` }
							>
								<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
									<path d="M1 1L13 13M13 1L1 13" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
								</svg>
							</button>
						</div>
					) );
				} ) }
				{ hasPriceFilter && (
					<div className="wp-block-wlc-algolia-search-page__active-filter-tag">
						<span className="wp-block-wlc-algolia-search-page__active-filter-label">Price:</span>
						<span className="wp-block-wlc-algolia-search-page__active-filter-value">
							{ `${ priceRange.min || '0' } - ${ priceRange.max || 'Any' }` }
						</span>
						<button
							type="button"
							className="wp-block-wlc-algolia-search-page__active-filter-remove"
							onClick={ onClearPriceRange }
							aria-label="Remove price filter"
						>
							<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
								<path d="M1 1L13 13M13 1L1 13" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
							</svg>
						</button>
					</div>
				) }
				{ hasDateFilter && (
					<div className="wp-block-wlc-algolia-search-page__active-filter-tag">
						<span className="wp-block-wlc-algolia-search-page__active-filter-label">Date:</span>
						<span className="wp-block-wlc-algolia-search-page__active-filter-value">
							{ `${ dateRange.from || 'Any' } - ${ dateRange.to || 'Any' }` }
						</span>
						<button
							type="button"
							className="wp-block-wlc-algolia-search-page__active-filter-remove"
							onClick={ onClearDateRange }
							aria-label="Remove date filter"
						>
							<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
								<path d="M1 1L13 13M13 1L1 13" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
							</svg>
						</button>
					</div>
				) }
			</div>
		</div>
	);
}

// Main Search Page Component
function SearchPageV3( { config } ) {
	const indexName = config.indexNameOverride || `${ config.baseIndex }_${ config.selectedPostType }`;
	const [ priceRange, setPriceRange ] = React.useState( () => {
		if ( typeof window === 'undefined' ) {
			return { min: '', max: '' };
		}
		try {
			const params = new URLSearchParams( window.location.search || '' );
			return {
				min: parsePriceParam( params.get( 'priceMin' ) ),
				max: parsePriceParam( params.get( 'priceMax' ) ),
			};
		} catch {
			return { min: '', max: '' };
		}
	} );
	const [ dateRange, setDateRange ] = React.useState( () => {
		if ( typeof window === 'undefined' ) {
			return { from: '', to: '' };
		}
		try {
			const params = new URLSearchParams( window.location.search || '' );
			return {
				from: parseDateParam( params.get( 'dateFrom' ) ),
				to: parseDateParam( params.get( 'dateTo' ) ),
			};
		} catch {
			return { from: '', to: '' };
		}
	} );
	const baseHitsPerPage = config.hitsPerPage || 10;
	const sortItems = React.useMemo( () => {
		const items = [ { label: 'Relevance', value: indexName } ];
		const suffixes = config.sortIndexSuffixes || {};
		const selectedPostType = String( config?.selectedPostType || '' );

		if ( selectedPostType === 'product' ) {
			items.push( {
				label: 'Price: High to Low',
				value: `${ indexName }${ suffixes.price_desc || '_price_desc' }`,
			} );
			items.push( {
				label: 'Price: Low to High',
				value: `${ indexName }${ suffixes.price_asc || '_price_asc' }`,
			} );
			items.push( {
				label: 'Newest',
				value: `${ indexName }${ suffixes.newest || '_newest' }`,
			} );
			return items;
		}

		items.push( {
			label: 'Alphabetical: A to Z',
			value: `${ indexName }${ suffixes.title_asc || '_title_asc' }`,
		} );
		items.push( {
			label: 'Alphabetical: Z to A',
			value: `${ indexName }${ suffixes.title_desc || '_title_desc' }`,
		} );
		items.push( {
			label: 'Newest',
			value: `${ indexName }${ suffixes.newest || '_newest' }`,
		} );
		items.push( {
			label: 'Oldest',
			value: `${ indexName }${ suffixes.oldest || '_oldest' }`,
		} );

		return items;
	}, [ indexName, config.sortIndexSuffixes, config?.selectedPostType ] );
	const defaultSortValue = React.useMemo( () => {
		const map = {
			relevance: indexName,
			price_desc: `${ indexName }${ config?.sortIndexSuffixes?.price_desc || '_price_desc' }`,
			price_asc: `${ indexName }${ config?.sortIndexSuffixes?.price_asc || '_price_asc' }`,
			title_asc: `${ indexName }${ config?.sortIndexSuffixes?.title_asc || '_title_asc' }`,
			title_desc: `${ indexName }${ config?.sortIndexSuffixes?.title_desc || '_title_desc' }`,
			newest: `${ indexName }${ config?.sortIndexSuffixes?.newest || '_newest' }`,
			oldest: `${ indexName }${ config?.sortIndexSuffixes?.oldest || '_oldest' }`,
		};
		const requested = String( config?.sortDefault || 'relevance' );
		return map[ requested ] || indexName;
	}, [ indexName, config?.sortDefault, config?.sortIndexSuffixes ] );
	const priceFilterAttribute = React.useMemo( () => {
		if ( ! Array.isArray( config.facets ) ) {
			return '_price_num';
		}
		const priceFacet = config.facets.find( ( facet ) => {
			const clean = ( facet?.attribute || '' ).replace( /^searchable\((.+)\)$/, '$1' );
			return clean === '_price' || clean === '_regular_price' || clean === '_sale_price';
		} );
		const cleanPriceFacet = ( priceFacet?.attribute || '_price' ).replace( /^searchable\((.+)\)$/, '$1' );
		return `${ cleanPriceFacet }_num`;
	}, [ config.facets ] );
	const numericFilters = React.useMemo( () => {
		const filters = [];
		if ( priceRange.min !== '' ) {
			filters.push( `${ priceFilterAttribute }>=${ parseInt( priceRange.min, 10 ) }` );
		}
		if ( priceRange.max !== '' ) {
			filters.push( `${ priceFilterAttribute }<=${ parseInt( priceRange.max, 10 ) }` );
		}
		const fromTimestamp = dateInputToUnix( dateRange.from, 'start' );
		const toTimestamp = dateInputToUnix( dateRange.to, 'end' );
		if ( fromTimestamp !== null ) {
			filters.push( `post_date_ts>=${ fromTimestamp }` );
		}
		if ( toTimestamp !== null ) {
			filters.push( `post_date_ts<=${ toTimestamp }` );
		}
		return filters;
	}, [ priceRange, dateRange, priceFilterAttribute ] );

	React.useEffect( () => {
		if ( ! config.enableUrlSync || typeof window === 'undefined' ) {
			return;
		}
		try {
			const url = new URL( window.location.href );
			const minPrice = parsePriceParam( priceRange.min );
			const maxPrice = parsePriceParam( priceRange.max );
			if ( minPrice ) {
				url.searchParams.set( 'priceMin', minPrice );
			} else {
				url.searchParams.delete( 'priceMin' );
			}
			if ( maxPrice ) {
				url.searchParams.set( 'priceMax', maxPrice );
			} else {
				url.searchParams.delete( 'priceMax' );
			}
			const fromDate = parseDateParam( dateRange.from );
			const toDate = parseDateParam( dateRange.to );
			if ( fromDate ) {
				url.searchParams.set( 'dateFrom', fromDate );
			} else {
				url.searchParams.delete( 'dateFrom' );
			}
			if ( toDate ) {
				url.searchParams.set( 'dateTo', toDate );
			} else {
				url.searchParams.delete( 'dateTo' );
			}
			window.history.replaceState( window.history.state, '', url.toString() );
		} catch {
			// Ignore URL sync failures silently.
		}
	}, [ config.enableUrlSync, priceRange.min, priceRange.max, dateRange.from, dateRange.to ] );

	// Build routing configuration
	const routing = config.enableUrlSync
		? {
				router: history( {
					windowTitle: ( { query } ) => ( query ? `${ query } - Search` : 'Search' ),
				} ),
				stateMapping: {
					stateToRoute( uiState ) {
						const indexUiState = uiState[ indexName ] || {};
						const minPrice = parsePriceParam( priceRange.min );
						const maxPrice = parsePriceParam( priceRange.max );
						return {
							query: indexUiState.query,
							page: indexUiState.page,
							refinementList: indexUiState.refinementList,
							sortBy: indexUiState.sortBy,
							priceMin: minPrice || undefined,
							priceMax: maxPrice || undefined,
							dateFrom: parseDateParam( dateRange.from ) || undefined,
							dateTo: parseDateParam( dateRange.to ) || undefined,
						};
					},
					routeToState( routeState ) {
						// Autocomplete / WordPress use ?s=…; InstantSearch URL sync uses ?query=…
						const fromQuery =
							routeState.query != null && String( routeState.query ).trim() !== ''
								? String( routeState.query )
								: '';
						const fromS =
							routeState.s != null && String( routeState.s ).trim() !== ''
								? String( routeState.s )
								: '';
						const query = fromQuery || fromS;
						const nextPriceRange = {
							min: parsePriceParam( routeState.priceMin ),
							max: parsePriceParam( routeState.priceMax ),
						};
						if (
							nextPriceRange.min !== priceRange.min ||
							nextPriceRange.max !== priceRange.max
						) {
							setPriceRange( nextPriceRange );
						}
						const nextDateRange = {
							from: parseDateParam( routeState.dateFrom ),
							to: parseDateParam( routeState.dateTo ),
						};
						if (
							nextDateRange.from !== dateRange.from ||
							nextDateRange.to !== dateRange.to
						) {
							setDateRange( nextDateRange );
						}
						return {
							[ indexName ]: {
								query,
								page: routeState.page,
								refinementList: routeState.refinementList,
								// Keep configured default sort when URL does not specify one.
								sortBy: routeState.sortBy || defaultSortValue,
							},
						};
					},
				},
		  }
		: undefined;

	return (
		<InstantSearch
			searchClient={ algoliasearch( config.appId, config.apiKey ) }
			indexName={ indexName }
			routing={ routing }
			insights={ { insightsClient } }
		>
			<Configure 
				hitsPerPage={ baseHitsPerPage }
				numericFilters={ numericFilters }
				// Don't specify facets here - React InstantSearch automatically requests facets
				// for RefinementList widgets. Specifying facets manually can cause issues
				// with searchable facets, as React InstantSearch needs to use the searchable facet API.
			/>
			<div className="wp-block-wlc-algolia-search-page__input-row">
				<SearchBox
					placeholder={ config.placeholder || 'Search...' }
					classNames={ {
						root: 'wp-block-wlc-algolia-search-page__searchbox',
						form: 'wp-block-wlc-algolia-search-page__searchbox-form',
						input: 'wp-block-wlc-algolia-search-page__input',
						submit: 'wp-block-wlc-algolia-search-page__searchbox-submit',
						reset: 'wp-block-wlc-algolia-search-page__searchbox-reset',
					} }
				/>
			</div>

			{ config.showActiveFilters !== false && (
				<ActiveFilters
					facetLabels={ config.facetLabels || {} }
					showClearAll={ true }
					priceRange={ priceRange }
					onClearPriceRange={ () => setPriceRange( { min: '', max: '' } ) }
					dateRange={ dateRange }
					onClearDateRange={ () => setDateRange( { from: '', to: '' } ) }
					authorLabels={ config.authorLabels || {} }
				/>
			) }

			<div
				className={
					`wp-block-wlc-algolia-search-page__layout${
						config.showFilters && config.facets && config.facets.length > 0 ? '' : ' is-no-filters'
					}`
				}
			>
				{ config.showFilters && config.facets && config.facets.length > 0 && (
					<aside className="wp-block-wlc-algolia-search-page__filters" aria-label="Filters">
						<div className="wp-block-wlc-algolia-search-page__filters-inner">
							{ config.facets.map( ( facet ) => {
								const cleanAttribute = facet.attribute.replace( /^searchable\((.+)\)$/, '$1' );
								const isPriceAttribute =
									cleanAttribute === '_price' ||
									cleanAttribute === '_regular_price' ||
									cleanAttribute === '_sale_price';
								const isDateAttribute =
									cleanAttribute === 'post_date' ||
									cleanAttribute === 'post_date_ts';
								if ( isPriceAttribute ) {
									return (
										<PriceRangeFilter
											key={ facet.attribute }
											label={ facet.label || 'Price' }
											priceRange={ priceRange }
											onPriceRangeChange={ setPriceRange }
										/>
									);
								}
								if ( isDateAttribute ) {
									return (
										<DateRangeFilter
											key={ facet.attribute }
											label={ facet.label || 'Date' }
											dateRange={ dateRange }
											onDateRangeChange={ setDateRange }
										/>
									);
								}
								return (
									<CustomRefinementList
										key={ facet.attribute }
										attribute={ facet.attribute }
										label={ facet.label }
										showCounts={ config.showFacetCounts !== false }
										countStyle={ config.facetCountStyle || 'paren' }
										enableSearch={ config.enableFacetSearch !== false }
										taxonomyHierarchy={ config.taxonomyHierarchy || {} }
										authorLabels={ config.authorLabels || {} }
									/>
								);
							} ) }
						</div>
					</aside>
				) }

				<main className="wp-block-wlc-algolia-search-page__results">
					<div className="wp-block-wlc-algolia-search-page__results-header">
						{ config.showResultsCount && (
							<div className="wp-block-wlc-algolia-search-page__stats">
								<Stats
									translations={ {
										stats: ( nbHits, processingTimeMS ) =>
											`${ nbHits.toLocaleString() } results${ processingTimeMS ? ` in ${ processingTimeMS }ms` : '' }`,
									} }
								/>
							</div>
						) }
						{ config.showSort && (
							<div className="wp-block-wlc-algolia-search-page__sort">
								<SortBy
									items={ sortItems }
									defaultRefinement={ defaultSortValue }
								/>
							</div>
						) }
					</div>

					<CustomHits
						templateBlocks={ config.templateBlocks || [] }
						viewMode={ config.viewMode || 'list' }
						gridColumns={ config.gridColumns || 3 }
						gap={ config.gap || 16 }
						currency={ config.currency || 'USD' }
						imagePlaceholder={ config.imagePlaceholder || '' }
					/>

					{ config.showPagination && <CustomPagination config={ config } /> }
				</main>
			</div>
		</InstantSearch>
	);
}

// Initialize blocks
function initSearchPageV3() {
	const blocks = document.querySelectorAll( BLOCK_SELECTOR );
	if ( ! blocks.length ) return;

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

		const config = JSON.parse( block.dataset.config || '{}' );
		const container = block.querySelector( '[data-role="react-root"]' ) || block;

		// Check if API keys are in config (from render.php)
		if ( ! config.appId || ! config.apiKey ) {
			console.error( '[Algolia Search Page V3] Missing API keys in config. Please check plugin settings.' );
			container.innerHTML = '<div class="wp-block-wlc-algolia-search-page__error">Error: Missing Algolia configuration. Please check plugin settings.</div>';
			return;
		}

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

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

