trptk/lib/variants.ts
2026-02-24 17:14:07 +01:00

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;
}