import re
from typing import Tuple


def parse_quantity(text: str) -> Tuple[float, str]:
    # supports "2,5 т" and "2.5 t"
    m = re.search(r"([0-9]+(?:[\.,][0-9]+)?)\s*([a-zA-Zа-яА-Я/]+)", text)
    if not m:
        raise ValueError("quantity_not_found")
    value = float(m.group(1).replace(',', '.'))
    unit = m.group(2).lower()
    return value, unit


def parse_rate(text: str) -> Tuple[float, str]:
    # "150 кг/га"
    m = re.search(r"([0-9]+(?:[\.,][0-9]+)?)\s*([a-zA-Zа-яА-Я/]+)", text)
    if not m:
        raise ValueError("rate_not_found")
    value = float(m.group(1).replace(',', '.'))
    unit = m.group(2).lower()
    return value, unit


def normalize_unit(unit: str, unit_map: dict):
    unit_cf = unit.casefold().strip()
    for u in unit_map:
        if u['id'] == unit_cf:
            return u['id']
        if unit_cf == u.get('name','').casefold():
            return u['id']
        for s in u.get('synonyms', []):
            if unit_cf == s.casefold():
                return u['id']
    return None


def convert(value: float, from_unit: str, to_unit: str, unit_map: dict) -> float:
    if from_unit == to_unit:
        return value
    units = {u['id']: u for u in unit_map}
    f = units.get(from_unit)
    t = units.get(to_unit)
    if not f or not t:
        raise ValueError("unit_not_found")
    # only support to_base conversion for now
    if f.get('to_base') and f['to_base']['base'] == to_unit:
        return value * float(f['to_base']['factor'])
    if t.get('to_base') and t['to_base']['base'] == from_unit:
        return value / float(t['to_base']['factor'])
    raise ValueError("conversion_not_supported")
