import type { ProductVariantInput, TargetingRule, WidgetSizeConfig } from './types'; const ONE_SIZE_LABELS = new Set( [ 'onesize', 'os', 'tagliaunica', 'tallaunica', 'tailleunique', 'einheitsgrosse', 'tamanhounico', 'maatuniek', 'uniwersalny', 'unisize', 'freesize', 'unica', 'unico', ] ); export function normalizeSizeLabel( label: unknown ): string { if ( typeof label !== 'string' ) return ''; return label .normalize( 'NFKD' ) .replace( /[\u0300-\u036f]/g, '' ) // Product data must normalize identically regardless of the shopper's // browser locale (notably Turkish casing rules). .toLowerCase() .replace( /[^a-z0-9]+/g, '' ); } function eligibleSizeLabels( guideLabels: unknown, variants: ProductVariantInput[] ): Set { if ( ! Array.isArray( guideLabels ) || ! Array.isArray( variants ) ) return new Set(); const guide = new Set( guideLabels.map( normalizeSizeLabel ).filter( Boolean ) ); const matched = new Set(); for ( const variant of variants ) { if ( ! variant || variant.available !== true ) continue; const label = normalizeSizeLabel( variant.size_label ); if ( label && ! ONE_SIZE_LABELS.has( label ) && guide.has( label ) ) matched.add( label ); } return matched; } export function sizeFeatureEnabled( rule: TargetingRule | undefined, categories: string[], productId: string ): boolean { if ( ! rule || ( rule.mode !== 'include' && rule.mode !== 'exclude' ) ) return true; const matched = categories.some( ( category ) => ( rule.categories || [] ).includes( category ) ) || ( rule.productIds || [] ).map( String ).includes( String( productId ) ); return rule.mode === 'include' ? matched : ! matched; } export function sizeGuideForProduct( size: WidgetSizeConfig, categories: string[], variants: ProductVariantInput[], productId = '' ) { if ( ! Array.isArray( size.guides ) ) return null; const guides = size.guides.filter((guide) => guide && (guide.categories == null || (typeof guide.categories === 'object' && !Array.isArray(guide.categories) && [guide.categories.categories, guide.categories.productIds].every((list) => list == null || (Array.isArray(list) && list.every((value) => typeof value === 'string')))))); const explicit = guides.find( ( guide ) => ( guide.categories?.productIds || [] ).includes( productId ) ); const assigned = guides.find( ( guide ) => ( guide.categories?.categories || [] ).some( ( category ) => categories.includes( category ) ) ); const fallback = guides.find( ( guide ) => ( guide.categories?.categories || [] ).length === 0 && ( guide.categories?.productIds || [] ).length === 0 ); const guide = explicit || assigned || fallback || null; if ( ! guide ) return null; return eligibleSizeLabels( guide.labels, variants ).size >= 2 ? guide : null; } export function isSizeEligible( size: WidgetSizeConfig | undefined, categories: string[], productId: string, variants: ProductVariantInput[] ): boolean { if ( ! size?.hasSizeGuide || ! Array.isArray( variants ) ) return false; return sizeFeatureEnabled( size.targeting, categories, productId ) && sizeGuideForProduct( size, categories, variants, productId ) !== null; } export function sizeGuideResolution( size: WidgetSizeConfig, categories: string[], variants: ProductVariantInput[], productId = '' ): 'product_id' | 'collection' | 'shop_default' | 'none' { const guide = sizeGuideForProduct( size, categories, variants, productId ); if ( ! guide ) return 'none'; if ( guide.categories?.productIds?.includes( productId ) ) return 'product_id'; if ( ! ( 'categories' in guide ) ) return 'shop_default'; return ( guide.categories?.categories || [] ).length > 0 ? 'collection' : 'shop_default'; }