import type { RecipeUnit, ImperialUnit } from '@meshitrack/shared';

type AnyUnit = RecipeUnit | ImperialUnit;

interface ConversionSuccess {
  ok: true;
  quantity: number;
  unit: RecipeUnit;
}

interface ConversionError {
  ok: false;
  code: 'MISSING_DENSITY' | 'INCOMPATIBLE_UNITS';
  message: string;
}

export type ConversionResult = ConversionSuccess | ConversionError;

// US customary volume → ml (exact per NIST)
const VOLUME_TO_ML: Partial<Record<AnyUnit, number>> = {
  tsp: 4.92892,
  tbsp: 14.7868,
  fl_oz: 29.5735,
  cup: 236.588,
};

// Mass → grams (exact)
const MASS_TO_G: Partial<Record<AnyUnit, number>> = {
  oz: 28.3495,
  lb: 453.592,
};

const METRIC_UNITS = new Set<AnyUnit>(['g', 'ml', 'piece', 'slice']);

/**
 * Converts a recipe ingredient quantity/unit to its metric RecipeUnit equivalent.
 *
 * - Already metric/discrete → returned unchanged.
 * - Mass imperial (oz, lb) → grams.
 * - Volume imperial (cup, tbsp, tsp, fl_oz) → ml.
 * - Mass↔volume cross-conversion requires densityGPerMl on the product;
 *   returns MISSING_DENSITY error if absent.
 * - Discrete (piece, slice) cannot be converted from imperial;
 *   returns INCOMPATIBLE_UNITS error.
 */
export function toMetric(
  quantity: number,
  unit: AnyUnit,
  productServingUnit: RecipeUnit,
  densityGPerMl?: number,
): ConversionResult {
  // Already a valid storage unit — pass through.
  if (METRIC_UNITS.has(unit)) {
    return { ok: true, quantity, unit: unit as RecipeUnit };
  }

  const massMultiplier = MASS_TO_G[unit];
  const volumeMultiplier = VOLUME_TO_ML[unit];

  if (massMultiplier !== undefined) {
    const inGrams = quantity * massMultiplier;

    if (productServingUnit === 'g') {
      return { ok: true, quantity: round(inGrams), unit: 'g' };
    }

    if (productServingUnit === 'ml') {
      if (!densityGPerMl) {
        return {
          ok: false,
          code: 'MISSING_DENSITY',
          message: `Cannot convert mass unit "${unit}" to ml without product density (densityGPerMl).`,
        };
      }
      return { ok: true, quantity: round(inGrams / densityGPerMl), unit: 'ml' };
    }

    return {
      ok: false,
      code: 'INCOMPATIBLE_UNITS',
      message: `Cannot convert mass unit "${unit}" to discrete unit "${productServingUnit}".`,
    };
  }

  if (volumeMultiplier !== undefined) {
    const inMl = quantity * volumeMultiplier;

    if (productServingUnit === 'ml') {
      return { ok: true, quantity: round(inMl), unit: 'ml' };
    }

    if (productServingUnit === 'g') {
      if (!densityGPerMl) {
        return {
          ok: false,
          code: 'MISSING_DENSITY',
          message: `Cannot convert volume unit "${unit}" to g without product density (densityGPerMl).`,
        };
      }
      return { ok: true, quantity: round(inMl * densityGPerMl), unit: 'g' };
    }

    return {
      ok: false,
      code: 'INCOMPATIBLE_UNITS',
      message: `Cannot convert volume unit "${unit}" to discrete unit "${productServingUnit}".`,
    };
  }

  return {
    ok: false,
    code: 'INCOMPATIBLE_UNITS',
    message: `Unknown unit "${unit}".`,
  };
}

function round(value: number): number {
  return Math.round(value * 1000) / 1000;
}
