"""
Mapeo fila CSV → datos para Class (clases de materiales).
"""
from typing import Dict, Any, Optional, Set


def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]:
    if val is None:
        return None
    s = str(val).strip()
    if not s:
        return None
    if max_len and len(s) > max_len:
        return s[:max_len]
    return s


def _int_or_none(val: Any) -> Optional[int]:
    if val is None or val == "":
        return None
    try:
        return int(val)
    except (ValueError, TypeError):
        return None


def row_to_class_data(
    row_norm: Dict[str, Any],
    valid_material_keys: Set[str],
    valid_uom_codes: Set[str],
) -> Dict[str, Any]:
    """
    Mapea una fila normalizada del CSV a un diccionario de datos para Class.
    class_code se normaliza a mayúsculas (Clip(Upper) Clarion).
    """
    raw_clase = _str_or_none(row_norm.get("CLASE"), 8)
    class_code = raw_clase.upper() if raw_clase else None
    desc_es = _str_or_none(row_norm.get("DESCRIPCIONE"), 500)
    desc_en = _str_or_none(row_norm.get("DESCRIPCIONI"), 500)
    material_key = _str_or_none(row_norm.get("CLAVEMAT"), 10)
    if material_key and material_key not in valid_material_keys:
        material_key = None
    unit_of_measure = _str_or_none(row_norm.get("UNIMED"), 5)
    if unit_of_measure and unit_of_measure not in valid_uom_codes:
        unit_of_measure = None
    fraction = _str_or_none(row_norm.get("FRACCION"), 20)
    us_fraction = _str_or_none(row_norm.get("FRACCIONAME"), 16)
    sub_key = _str_or_none(row_norm.get("CLAVESUB"), 5)
    physical_review = _int_or_none(row_norm.get("REVFISICA"))
    iva_exempt_fraction = _str_or_none(row_norm.get("FRACCIONEXENTAIVA"), 4)

    return {
        "class_code": class_code,
        "description_es": desc_es,
        "description_en": desc_en,
        "material_key": material_key,
        "unit_of_measure": unit_of_measure,
        "fraction": fraction,
        "us_fraction": us_fraction,
        "sub_key": sub_key,
        "physical_review": physical_review,
        "iva_exempt_fraction": iva_exempt_fraction,
    }


def row_to_class_data_merge_existing(
    row_norm: Dict[str, Any],
    existing_data: Dict[str, Any],
    valid_material_keys: Set[str],
    valid_uom_codes: Set[str],
) -> Dict[str, Any]:
    """
    Para modo actualizar (parcial): valores del CSV si no vacíos, sino los de la clase existente (Clarion VALIDA_PARCIAL_CLASE).
    """
    data = row_to_class_data(row_norm, valid_material_keys, valid_uom_codes)
    if not data["class_code"]:
        return data
    for key in ("description_es", "description_en", "material_key", "unit_of_measure",
                "fraction", "us_fraction", "sub_key", "physical_review", "iva_exempt_fraction"):
        if data.get(key) is None or (isinstance(data[key], str) and not data[key].strip()):
            data[key] = existing_data.get(key)
    return data
