/**
* Resolve which quoted rate a row should default to, given the
* "Apply to all" service strategy and any per-order manual override.
*
* The two sentinels resolve dynamically against each order's own rates;
* a named strategy (e.g. "USPS Priority Mail") matches by title /
* service id and falls back to the cheapest rate when the order doesn't
* offer that service.
*/
import { __ } from '@wordpress/i18n';
import { SERVICE_CHEAPEST, SERVICE_FASTEST } from './types';
import type { ApplyOption } from 'components/bulk-labels/bulk-purchase-modal/toolbar';
import type { OrderRate } from './types';
/** Named strategies offered by the Service "Apply to all" control. */
export const NAMED_SERVICE_STRATEGIES = [
'USPS Priority Mail',
'USPS Ground Advantage',
] as const;
const cheapest = ( rates: OrderRate[] ): OrderRate | null =>
rates.reduce< OrderRate | null >(
( best, r ) => ( ! best || r.rate < best.rate ? r : best ),
null
);
const fastest = ( rates: OrderRate[] ): OrderRate | null => {
const withEta = rates.filter( ( r ) => typeof r.deliveryDays === 'number' );
if ( withEta.length === 0 ) {
// No transit estimates to compare — cheapest is the safest default.
return cheapest( rates );
}
return withEta.reduce< OrderRate | null >(
( best, r ) =>
! best || r.deliveryDays! < best.deliveryDays! ? r : best,
null
);
};
/** Lowercase + collapse every non-alphanumeric run to a single space. */
const normalize = ( value: string ): string =>
value
.toLowerCase()
.replace( /[^a-z0-9]+/g, ' ' )
.trim();
/**
* A rate matches a named strategy when every word of the strategy
* appears as a word in the rate's title or service id. This tolerates
* real-world shapes like title "USPS - Priority Mail" / service id
* "Priority" against the "USPS Priority Mail" option, instead of the
* old exact include/equals check that fell back to cheapest.
*/
const matchesNamed = ( rate: OrderRate, strategy: string ): boolean => {
const haystack = new Set(
`${ normalize( rate.title ) } ${ normalize( rate.serviceId ) }`
.split( ' ' )
.filter( Boolean )
);
const words = normalize( strategy ).split( ' ' ).filter( Boolean );
return words.length > 0 && words.every( ( word ) => haystack.has( word ) );
};
/**
* The rate a given order should select. `manualRateId` (a row-level
* override) wins when it still exists in the order's rates.
*/
export const resolveSelectedRate = (
rates: OrderRate[],
mode: string,
manualRateId?: string
): OrderRate | null => {
if ( rates.length === 0 ) {
return null;
}
if ( manualRateId ) {
const manual = rates.find( ( r ) => r.rateId === manualRateId );
if ( manual ) {
return manual;
}
}
if ( mode === SERVICE_FASTEST ) {
return fastest( rates );
}
if ( mode === SERVICE_CHEAPEST ) {
return cheapest( rates );
}
const named = rates.find( ( r ) => matchesNamed( r, mode ) );
return named ?? cheapest( rates );
};
/** Build the Service "Apply to all" option list (order matters). */
export const buildServiceApplyOptions = (): ApplyOption[] => [
{
label: __( 'Cheapest available', 'woocommerce-shipping' ),
value: SERVICE_CHEAPEST,
},
{
label: __( 'Fastest available', 'woocommerce-shipping' ),
value: SERVICE_FASTEST,
},
...NAMED_SERVICE_STRATEGIES.map( ( strategy ) => ( {
label: strategy,
value: strategy,
} ) ),
];