// Unit conversion + display for garden dimensions. The API and DB are metric // only (centimeters); imperial is purely a display/entry concern here. A minimal // helper set for #8 — #9's geometry lib consolidates/extends it. export type UnitPref = 'metric' | 'imperial' const CM_PER_INCH = 2.54 const CM_PER_METER = 100 const INCHES_PER_FOOT = 12 // Mirrors the server's garden dimension bounds (service/gardens.go): [1cm, 100m]. export const MIN_DIMENSION_CM = 1 export const MAX_DIMENSION_CM = 10_000 /** Whether a centimeter dimension is within the server's accepted range. */ export function isValidDimensionCm(cm: number): boolean { return Number.isFinite(cm) && cm >= MIN_DIMENSION_CM && cm <= MAX_DIMENSION_CM } /** Feet (+ optional inches) → whole centimeters. */ export function cmFromFtIn(feet: number, inches = 0): number { return Math.round((feet * INCHES_PER_FOOT + inches) * CM_PER_INCH) } /** Meters → whole centimeters. */ export function cmFromMeters(meters: number): number { return Math.round(meters * CM_PER_METER) } /** A value typed in the given unit (meters, or feet) → centimeters. */ export function cmFromDisplay(value: number, unit: UnitPref): number { return unit === 'imperial' ? cmFromFtIn(value) : cmFromMeters(value) } /** Centimeters → a number in the given unit, for prefilling an input field. * Rounded so cm-quantization doesn't show through (e.g. 244cm shows as 8 ft, * not 8.01): meters to the cm (2 dp), feet to 0.1 ft. */ export function displayFromCm(cm: number, unit: UnitPref): number { if (unit === 'imperial') { const feet = cm / CM_PER_INCH / INCHES_PER_FOOT return Math.round(feet * 10) / 10 } return Math.round((cm / CM_PER_METER) * 100) / 100 } /** Centimeters → a human string in the given unit (e.g. "1.22 m" or "4′ 0″"). */ export function formatCm(cm: number, unit: UnitPref): string { if (unit === 'imperial') { const totalInches = cm / CM_PER_INCH let feet = Math.floor(totalInches / INCHES_PER_FOOT) let inches = Math.round(totalInches - feet * INCHES_PER_FOOT) if (inches === INCHES_PER_FOOT) { feet += 1 inches = 0 } return `${feet}′ ${inches}″` } const meters = Math.round((cm / CM_PER_METER) * 100) / 100 return `${meters} m` } /** Centimeters width × height → a human string in the given unit. */ export function formatDimensions(widthCm: number, heightCm: number, unit: UnitPref): string { return `${formatCm(widthCm, unit)} × ${formatCm(heightCm, unit)}` } /** The input-field label for a single dimension in the given unit. */ export function dimensionUnitLabel(unit: UnitPref): string { return unit === 'imperial' ? 'ft' : 'm' } // --- Plant spacing (small scale) ------------------------------------------- // Plant spacing is a handful of centimeters, so meters/feet read poorly here; // metric shows cm and imperial shows whole inches. /** Centimeters → a spacing string in the given unit (e.g. "15 cm" or "6″"). */ export function formatSpacing(cm: number, unit: UnitPref): string { if (unit === 'imperial') return `${Math.round(cm / CM_PER_INCH)}″` return `${Math.round(cm)} cm` } /** A spacing value typed in the given unit (cm, or inches) → whole centimeters. */ export function cmFromSpacing(value: number, unit: UnitPref): number { return unit === 'imperial' ? Math.round(value * CM_PER_INCH) : Math.round(value) } /** Centimeters → a spacing number in the given unit, for prefilling an input. * Inches to 0.1 so cm-quantization doesn't show through. */ export function spacingFromCm(cm: number, unit: UnitPref): number { return unit === 'imperial' ? Math.round((cm / CM_PER_INCH) * 10) / 10 : Math.round(cm) } /** The spacing input-field unit label. */ export function spacingUnitLabel(unit: UnitPref): string { return unit === 'imperial' ? 'in' : 'cm' }