/** PublicController::MAX_RADIUS — the API ceiling, i.e. "do not exclude anything". */
export const IP_ORIGIN_RADIUS = 20000;

/** Keep search radius finite and positive; invalid saved/widget values fall back. */
export const normaliseRadius = ( value, fallback = 50 ) => {
	const fallbackNumber = Number( fallback );
	const safeFallback = Number.isFinite( fallbackNumber ) && fallbackNumber > 0 ? fallbackNumber : 50;
	const number = Number( value );
	return Number.isFinite( number ) && number > 0 ? number : safeFallback;
};

/** Preserve spatial state owned by geolocation or a separate distance control. */
export const mergeSearchSubmission = ( current, patch, radius ) => ( {
	...current,
	...patch,
	location: patch.location === undefined ? current.location : patch.location,
	radius: normaliseRadius( radius, current.radius ),
} );

/**
 * Clear the location-owned search state immediately.
 *
 * A typed location can fall back to `q` when geocoding fails, so clearing the
 * visible location field must clear both coordinates and that fallback term.
 * A dedicated keyword field may provide its current value to preserve it.
 */
export const clearLocationSearch = ( current, keyword = '' ) => ( {
	...current,
	location: null,
	q: String( keyword ?? '' ).trim(),
} );

/**
 * Build one search request. `fixed` contains immutable placement constraints
 * from the shortcode/block/widget; visitor-controlled form state lives only
 * in `query`, so the two scopes can never overwrite one another.
 */
export const buildSearchUrl = ( restUrl, query, lang, page = 1, fixed = {}, options = {} ) => {
	const p = new URLSearchParams();
	if ( lang ) p.set( 'lang', lang );
	if ( query.location?.lat != null && query.location?.lng != null ) {
		p.set( 'lat', String( query.location.lat ) );
		p.set( 'lng', String( query.location.lng ) );
	}
	// An IP-derived origin is a courtesy for DISTANCE LABELS ("Approximate
	// distance from IP"), not a filter the visitor chose: constraining the radius
	// around a coarse guess empties the locator for anyone whose nearest store is
	// further than the default. Send the API ceiling so results stay intact and
	// distance-sorted, unless the Map widget opts into filtering as well.
	const ipOriginIgnoresRadius = query.location?.source === 'ip' && ! options.ipLocationLimitsRadius;
	p.set( 'radius', String(
		ipOriginIgnoresRadius ? IP_ORIGIN_RADIUS : normaliseRadius( query.radius )
	) );
	if ( query.unit ) p.set( 'unit', query.unit );
	if ( query.city ) p.set( 'city', query.city );
	if ( query.country ) p.set( 'country', query.country );
	const categoryParam = Array.isArray( query.category ) ? query.category.join( ',' ) : query.category;
	if ( categoryParam ) p.set( 'category', categoryParam );
	if ( query.brand ) p.set( 'brand', query.brand );
	if ( query.q ) p.set( 'q', query.q );
	if ( query.openNow ) p.set( 'open', '1' );
	if ( query.sort ) p.set( 'sort', query.sort );
	if ( Array.isArray( query.statuses ) && query.statuses.length ) p.set( 'status', query.statuses.join( ',' ) );
	if ( query.meta ) {
		Object.entries( query.meta ).forEach( ( [ key, value ] ) => {
			if ( value === undefined || value === '' ) return;
			p.set( `meta[${ key }]`, value );
			const operator = query.metaOps && query.metaOps[ key ];
			if ( operator ) p.set( `metaop[${ key }]`, operator );
		} );
	}
	const fixedParams = {
		fixed_city: fixed.city,
		fixed_country: fixed.country,
		fixed_category: Array.isArray( fixed.category ) ? fixed.category.join( ',' ) : fixed.category,
		fixed_brand: fixed.brand,
		fixed_q: fixed.q,
		fixed_meta_key: fixed.metaKey,
		fixed_meta_value: fixed.metaValue,
	};
	Object.entries( fixedParams ).forEach( ( [ key, value ] ) => {
		if ( value !== undefined && value !== null && value !== '' ) p.set( key, String( value ) );
	} );
	p.set( 'per_page', String( query.pageSize || 50 ) );
	if ( page > 1 ) p.set( 'page', String( page ) );
	return `${ restUrl }/search?${ p.toString() }`;
};
