/**
 * Central enhetsdatabas för Recipe App.
 *
 * Alla stödda enheter definieras här med:
 *  - value       : kanonisk förkortning (det som lagras i DB)
 *  - labelSv     : svensk visningstext
 *  - type        : enhetstyp (weight | volume | cooking | piece | other)
 *  - toBaseFactor: multiplicera för att nå SI-basenheten för typen
 *                  (g för vikt, ml för volym och cooking)
 *  - aliases     : alternativa stavningar/namn som normaliseras hit
 *
 * Konvertering sker alltid via basenheten:
 *   result = quantity * from.toBaseFactor / to.toBaseFactor
 *
 * Obs: "cooking"-enheter (tsk, msk, krm) är tekniskt sett volym men
 * behandlas som en separat typ för att matcha befintlig datamodell.
 * De kan inte konverteras med ml/dl utan vidare konfiguration.
 */

export type UnitType = 'weight' | 'volume' | 'cooking' | 'piece' | 'other';

export interface UnitDefinition {
  /** Kanonisk förkortning — det som lagras i databasen */
  value: string;
  /** Svensk visningstext */
  labelSv: string;
  /** Enhetstyp */
  type: UnitType;
  /**
   * Faktor för att konvertera till SI-basenheten för typen.
   *  - weight  → gram  (g = 1)
   *  - volume  → milliliter  (ml = 1)
   *  - cooking → milliliter  (krm = 1 ml, tsk = 5 ml, msk = 15 ml)
   *  - piece/other → 1 (ej konverterbara)
   */
  toBaseFactor: number;
  /** Alternativa stavningar/namn som normaliseras till value */
  aliases: string[];
}

export const UNIT_DEFINITIONS: UnitDefinition[] = [
  // ── Vikt ──────────────────────────────────────────────────
  { value: 'g',   labelSv: 'g (gram)',         type: 'weight',  toBaseFactor: 1,       aliases: ['gram'] },
  { value: 'hg',  labelSv: 'hg (hektogram)',   type: 'weight',  toBaseFactor: 100,     aliases: ['hektogram'] },
  { value: 'kg',  labelSv: 'kg (kilogram)',     type: 'weight',  toBaseFactor: 1000,    aliases: ['kilo', 'kilogram'] },
  { value: 'mg',  labelSv: 'mg (milligram)',    type: 'weight',  toBaseFactor: 0.001,   aliases: ['milligram'] },

  // ── Volym ─────────────────────────────────────────────────
  { value: 'ml',  labelSv: 'ml (milliliter)',   type: 'volume',  toBaseFactor: 1,       aliases: ['milliliter'] },
  { value: 'cl',  labelSv: 'cl (centiliter)',   type: 'volume',  toBaseFactor: 10,      aliases: ['centiliter'] },
  { value: 'dl',  labelSv: 'dl (deciliter)',    type: 'volume',  toBaseFactor: 100,     aliases: ['deciliter'] },
  { value: 'l',   labelSv: 'l (liter)',         type: 'volume',  toBaseFactor: 1000,    aliases: ['liter'] },

  // ── Matlagning (svenska köksenheter) ──────────────────────
  { value: 'krm', labelSv: 'krm (kryddmått)',   type: 'cooking', toBaseFactor: 1,       aliases: ['kryddmatt', 'kryddmått'] },
  { value: 'tsk', labelSv: 'tsk (tesked)',       type: 'cooking', toBaseFactor: 5,       aliases: ['tesked', 'test'] },
  { value: 'msk', labelSv: 'msk (matsked)',      type: 'cooking', toBaseFactor: 15,      aliases: ['matsked', 'matsled'] },

  // ── Styckenheter (ej konverterbara) ───────────────────────
  { value: 'st',    labelSv: 'st (styck)',       type: 'piece',   toBaseFactor: 1,       aliases: ['stycke', 'styck', 'stk'] },
  { value: 'port',  labelSv: 'port (portioner)', type: 'piece',   toBaseFactor: 1,       aliases: ['portion', 'portioner'] },
  { value: 'förp',  labelSv: 'förp (förpackning)',type: 'piece',  toBaseFactor: 1,       aliases: ['forp', 'förpackning', 'forpackning'] },
  { value: 'klyfta',labelSv: 'klyfta',           type: 'piece',   toBaseFactor: 1,       aliases: [] },

  // ── Övrigt ────────────────────────────────────────────────
  { value: 'efter smak', labelSv: 'efter smak',  type: 'other',   toBaseFactor: 1,       aliases: ['eftr smak', 'efter smak'] },
];

/** Snabbuppslagning: alias/value → UnitDefinition */
const _unitLookup = new Map<string, UnitDefinition>();
for (const def of UNIT_DEFINITIONS) {
  _unitLookup.set(def.value.toLowerCase(), def);
  for (const alias of def.aliases) {
    _unitLookup.set(alias.toLowerCase(), def);
  }
}

/**
 * Normalisera en enhetssträng till kanonisk förkortning.
 * T.ex. "Matsked" → "msk", "Kilogram" → "kg".
 * Om enheten är okänd returneras den trimmad och lowercasad.
 */
export function normalizeUnit(unit: string): string {
  const key = unit.trim().toLowerCase();
  return _unitLookup.get(key)?.value ?? key;
}

/**
 * Hämta UnitDefinition för en enhet (normaliseras automatiskt).
 * Returnerar undefined om enheten inte finns i UNIT_DEFINITIONS.
 */
export function getUnitDefinition(unit: string): UnitDefinition | undefined {
  const key = unit.trim().toLowerCase();
  return _unitLookup.get(key);
}

/**
 * Hämta enhetstypen för en enhet.
 * Returnerar null om enheten är okänd.
 */
export function getUnitType(unit: string): UnitType | null {
  return getUnitDefinition(unit)?.type ?? null;
}

/**
 * Kontrollera om konvertering är möjlig mellan två enheter.
 * Konvertering kräver att båda tillhör samma typ och inte är 'piece' eller 'other'.
 */
export function canConvert(fromUnit: string, toUnit: string): boolean {
  const from = getUnitDefinition(fromUnit);
  const to = getUnitDefinition(toUnit);
  if (!from || !to) return false;
  if (from.type !== to.type) return false;
  if (from.type === 'piece' || from.type === 'other') return false;
  return true;
}

/**
 * Konverterar en mängd från en enhet till en annan.
 *
 * @throws Error om quantity ≤ 0
 * @throws Error om en enhet är okänd ("Unknown unit: ...")
 * @throws Error om enheterna inte kan konverteras ("Cannot convert between incompatible unit types: ...")
 */
export function convertUnit(quantity: number, fromUnit: string, toUnit: string): number {
  if (quantity <= 0) {
    throw new Error(`Invalid quantity: ${quantity}. Quantity must be positive.`);
  }

  const normalizedFrom = normalizeUnit(fromUnit);
  const normalizedTo = normalizeUnit(toUnit);

  if (normalizedFrom === normalizedTo) return quantity;

  const fromDef = getUnitDefinition(normalizedFrom);
  const toDef = getUnitDefinition(normalizedTo);

  if (!fromDef) throw new Error(`Unknown unit: "${fromUnit}"`);
  if (!toDef) throw new Error(`Unknown unit: "${toUnit}"`);

  if (fromDef.type !== toDef.type) {
    throw new Error(
      `Cannot convert between incompatible unit types: "${fromUnit}" (${fromDef.type}) and "${toUnit}" (${toDef.type})`,
    );
  }

  if (fromDef.type === 'piece' || fromDef.type === 'other') {
    return quantity; // Styckenheter konverteras inte
  }

  return (quantity * fromDef.toBaseFactor) / toDef.toBaseFactor;
}
