186 lines
5.8 KiB
TypeScript
186 lines
5.8 KiB
TypeScript
import type { MedusaVariant } from "./medusa";
|
|
|
|
// ── Variant taxonomy ───────────────────────────────────────────────
|
|
|
|
export type FormatVariant = {
|
|
title: string;
|
|
slug: string;
|
|
suffix: string;
|
|
};
|
|
|
|
export type Subgroup = {
|
|
label: string;
|
|
slug: string;
|
|
variants: FormatVariant[];
|
|
};
|
|
|
|
export type FormatGroup = {
|
|
label: string;
|
|
slug: string;
|
|
subgroups?: Subgroup[];
|
|
variants?: FormatVariant[];
|
|
};
|
|
|
|
const PHYSICAL_VARIANTS: FormatVariant[] = [
|
|
{ title: "CD", slug: "cd", suffix: "CD" },
|
|
{ title: "SACD", slug: "sacd", suffix: "SACD" },
|
|
{ title: "LP", slug: "lp", suffix: "LP" },
|
|
];
|
|
|
|
const DIGITAL_SUBGROUPS: Subgroup[] = [
|
|
{
|
|
label: "Stereo",
|
|
slug: "stereo",
|
|
variants: [
|
|
{ title: "Stereo FLAC 88/24", slug: "88k24b2ch", suffix: "88K24B2CH" },
|
|
{ title: "Stereo FLAC 176/24", slug: "176k24b2ch", suffix: "176K24B2CH" },
|
|
{ title: "Stereo FLAC 352/24", slug: "352k24b2ch", suffix: "352K24B2CH" },
|
|
{ title: "Stereo FLAC 352/32", slug: "352k32b2ch", suffix: "352K32B2CH" },
|
|
],
|
|
},
|
|
{
|
|
label: "Surround",
|
|
slug: "surround",
|
|
variants: [
|
|
{ title: "Surround FLAC 88/24", slug: "88k24b5ch", suffix: "88K24B5CH" },
|
|
{ title: "Surround FLAC 176/24", slug: "176k24b5ch", suffix: "176K24B5CH" },
|
|
{ title: "Surround FLAC 352/24", slug: "352k24b5ch", suffix: "352K24B5CH" },
|
|
{ title: "Surround FLAC 352/32", slug: "352k32b5ch", suffix: "352K32B5CH" },
|
|
],
|
|
},
|
|
{
|
|
label: "Immersive",
|
|
slug: "immersive",
|
|
variants: [
|
|
{ title: "Dolby Atmos, DTS:X & Auro-3D in MKV", slug: "mkv", suffix: "MKV" },
|
|
{ title: "Auro-3D FLAC", slug: "a3d", suffix: "A3D" },
|
|
{ title: "Dolby Atmos ADM 48kHz", slug: "adm48", suffix: "ADM48" },
|
|
{ title: "Dolby Atmos ADM 96kHz", slug: "adm96", suffix: "ADM96" },
|
|
],
|
|
},
|
|
{
|
|
label: "Video",
|
|
slug: "video",
|
|
variants: [
|
|
{ title: "HD Video", slug: "hd", suffix: "HD" },
|
|
{ title: "4K Video", slug: "4k", suffix: "4K" },
|
|
],
|
|
},
|
|
];
|
|
|
|
export const FORMAT_GROUPS: FormatGroup[] = [
|
|
{ label: "Physical", slug: "physical", variants: PHYSICAL_VARIANTS },
|
|
{ label: "Digital", slug: "digital", subgroups: DIGITAL_SUBGROUPS },
|
|
];
|
|
|
|
// ── Matched variant (with Medusa data) ─────────────────────────────
|
|
|
|
export type MatchedVariant = FormatVariant & {
|
|
medusaVariantId: string;
|
|
price: number;
|
|
currencyCode: string;
|
|
};
|
|
|
|
export type MatchedSubgroup = {
|
|
label: string;
|
|
slug: string;
|
|
variants: MatchedVariant[];
|
|
};
|
|
|
|
export type MatchedGroup = {
|
|
label: string;
|
|
slug: string;
|
|
subgroups?: MatchedSubgroup[];
|
|
variants?: MatchedVariant[];
|
|
};
|
|
|
|
// ── Matching logic ─────────────────────────────────────────────────
|
|
|
|
function findMedusaVariant(
|
|
catalogNo: string,
|
|
suffix: string,
|
|
medusaVariants: MedusaVariant[],
|
|
): MedusaVariant | undefined {
|
|
const expectedSku = `${catalogNo}_${suffix}`.toUpperCase();
|
|
return medusaVariants.find((v) => v.sku?.toUpperCase() === expectedSku);
|
|
}
|
|
|
|
function extractPrice(mv: MedusaVariant): { price: number; currencyCode: string } {
|
|
if (mv.calculated_price) {
|
|
return {
|
|
price: mv.calculated_price.calculated_amount,
|
|
currencyCode: mv.calculated_price.currency_code,
|
|
};
|
|
}
|
|
if (mv.prices?.[0]) {
|
|
return {
|
|
price: mv.prices[0].amount,
|
|
currencyCode: mv.prices[0].currency_code,
|
|
};
|
|
}
|
|
return { price: 0, currencyCode: "eur" };
|
|
}
|
|
|
|
export function matchVariants(
|
|
catalogNo: string,
|
|
medusaVariants: MedusaVariant[],
|
|
): MatchedGroup[] {
|
|
const result: MatchedGroup[] = [];
|
|
|
|
for (const group of FORMAT_GROUPS) {
|
|
if (group.variants) {
|
|
// Physical group — flat variants
|
|
const matched: MatchedVariant[] = [];
|
|
for (const fv of group.variants) {
|
|
const mv = findMedusaVariant(catalogNo, fv.suffix, medusaVariants);
|
|
if (mv) {
|
|
const { price, currencyCode } = extractPrice(mv);
|
|
matched.push({ ...fv, medusaVariantId: mv.id, price, currencyCode });
|
|
}
|
|
}
|
|
if (matched.length > 0) {
|
|
result.push({ label: group.label, slug: group.slug, variants: matched });
|
|
}
|
|
}
|
|
|
|
if (group.subgroups) {
|
|
// Digital group — nested subgroups
|
|
const matchedSubgroups: MatchedSubgroup[] = [];
|
|
for (const sg of group.subgroups) {
|
|
const matched: MatchedVariant[] = [];
|
|
for (const fv of sg.variants) {
|
|
const mv = findMedusaVariant(catalogNo, fv.suffix, medusaVariants);
|
|
if (mv) {
|
|
const { price, currencyCode } = extractPrice(mv);
|
|
matched.push({ ...fv, medusaVariantId: mv.id, price, currencyCode });
|
|
}
|
|
}
|
|
if (matched.length > 0) {
|
|
matchedSubgroups.push({ label: sg.label, slug: sg.slug, variants: matched });
|
|
}
|
|
}
|
|
if (matchedSubgroups.length > 0) {
|
|
result.push({ label: group.label, slug: group.slug, subgroups: matchedSubgroups });
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ── SKU → display-name lookup ───────────────────────────────────────
|
|
|
|
const suffixToTitle = new Map<string, string>();
|
|
for (const group of FORMAT_GROUPS) {
|
|
for (const v of group.variants ?? []) suffixToTitle.set(v.suffix, v.title);
|
|
for (const sg of group.subgroups ?? []) {
|
|
for (const v of sg.variants) suffixToTitle.set(v.suffix, v.title);
|
|
}
|
|
}
|
|
|
|
/** Resolve a Medusa SKU (e.g. "TRP001_352K24B2CH") to a friendly name. */
|
|
export function formatDisplayName(sku: string): string | null {
|
|
const suffix = sku.split("_").pop()?.toUpperCase();
|
|
if (!suffix) return null;
|
|
return suffixToTitle.get(suffix) ?? null;
|
|
}
|