import { UNIT_INFO, type Unit, type UnitKind } from "@app/shared-types"; export interface IngredientUnitInfo { /** g per ml – krävs för volym → massa. */ densityGPerMl?: number | null; /** g per styck – krävs för antal → massa. */ gramsPerPiece?: number | null; } export function unitKind(unit: Unit): UnitKind { return UNIT_INFO[unit].kind; } /** Konvertera till basenhet inom samma slag (g, ml eller st). */ export function toBase(quantity: number, unit: Unit): { kind: UnitKind; amount: number } { const info = UNIT_INFO[unit]; return { kind: info.kind, amount: quantity * info.toBase }; } /** * Konvertera valfri mängd till gram. Returnerar null när konvertering inte är * möjlig utan mer information – hellre ärlig osäkerhet än gissning (spec §61.4). */ export function toGrams( quantity: number, unit: Unit, info: IngredientUnitInfo = {}, ): number | null { const base = toBase(quantity, unit); switch (base.kind) { case "mass": return base.amount; case "volume": { const density = info.densityGPerMl; if (density == null || density <= 0) return null; return base.amount * density; } case "count": { const perPiece = info.gramsPerPiece; if (perPiece == null || perPiece <= 0) return null; return base.amount * perPiece; } } } /** Konvertera en mängd mellan enheter (samma slag, eller via densitet/styckvikt). */ export function convert( quantity: number, from: Unit, to: Unit, info: IngredientUnitInfo = {}, ): number | null { const fromInfo = UNIT_INFO[from]; const toInfo = UNIT_INFO[to]; if (fromInfo.kind === toInfo.kind) { return (quantity * fromInfo.toBase) / toInfo.toBase; } // Olika slag: gå via gram. const grams = toGrams(quantity, from, info); if (grams == null) return null; if (toInfo.kind === "mass") return grams / toInfo.toBase; if (toInfo.kind === "volume") { const density = info.densityGPerMl; if (density == null || density <= 0) return null; return grams / density / toInfo.toBase; } const perPiece = info.gramsPerPiece; if (perPiece == null || perPiece <= 0) return null; return grams / perPiece / toInfo.toBase; }