"""Categorical vocabularies and label normalization."""

from __future__ import annotations

from typing import List, Sequence

CEMENT_TYPES: Sequence[str] = ("opc", "type_i_ii", "type_iii", "hs", "other")
FIBRE_TYPES: Sequence[str] = ("none", "steel", "pe", "pva", "other")
CURING_REGIMES: Sequence[str] = ("standard", "water", "steam", "heat", "autoclave", "other")
# Fly-ash class. "none" (default, no fly ash) -> id 0; ASTM C618 Class F / C; an
# "other" bucket for fly ash whose class is present but unspecified.
FLY_ASH_TYPES: Sequence[str] = ("none", "class_f", "class_c", "other")
# Coarse-aggregate lithology. Index 0 ("natural") is the default for sources that
# only report a generic natural aggregate; "recycled" pairs with rca_replacement.
COARSE_AGGREGATE_TYPES: Sequence[str] = (
    "natural", "granite", "basalt", "limestone", "quartzite", "gravel",
    "dolomite", "marble", "slag", "recycled", "other",
)


def _clean(raw) -> str:
    s = str(raw).strip().lower()
    return "" if s in ("", "nan", "none") else s


def canonical_cement_type(raw) -> str:
    t = _clean(raw)
    if not t:
        return "other"
    if "sulfate" in t or "type hs" in t or "hs cement" in t:
        return "hs"
    if "type iii" in t or "type-iii" in t or "type 3" in t:
        return "type_iii"
    if "i/ii" in t or "i / ii" in t:
        return "type_i_ii"
    if any(k in t for k in ("portland", "opc", "cem i", "type i", "type 1", "p.o", "p.i")):
        return "opc"
    return "other"


def canonical_fibre_type(raw) -> str:
    t = _clean(raw)
    if not t:
        return "none"
    if "steel" in t or "seel" in t:  # "seel" = common typo in the sheet
        return "steel"
    if "pva" in t:
        return "pva"
    # Polyolefin synthetics grouped: PE (polyethylene) and PP (polypropylene).
    if t.startswith("pe") or "polyeth" in t or t == "pp" or "polypro" in t:
        return "pe"
    return "other"


def canonical_fly_ash_type(raw) -> str:
    t = _clean(raw)
    if not t:
        return "none"
    if "class f" in t or "class-f" in t or t == "f" or "type f" in t:
        return "class_f"
    if "class c" in t or "class-c" in t or t == "c" or "type c" in t:
        return "class_c"
    return "other"


def canonical_curing_regime(raw) -> str:
    t = _clean(raw)
    if not t:
        return "other"
    if "autoclave" in t:
        return "autoclave"
    if "steam" in t:
        return "steam"
    # Temperature-driven curing; "hot/warm water" counts as heat (caught here first).
    if any(k in t for k in ("heat", "hot", "warm", "thermal", "oven", "elevated", "boil")):
        return "heat"
    # Water immersion / submerged / ponding / lime-saturated water (~100% RH) -- a
    # distinct regime from standard moist curing.
    if any(k in t for k in ("water", "submerg", "immers", "pond", "soak", "lime", "saturat", "bath")):
        return "water"
    # Standard moist curing: ambient/normal chamber + moist covering (not submerged).
    if any(k in t for k in ("standard", "normal", "ambient", "room", "moist", "wet", "burlap", "rug")):
        return "standard"
    return "other"


def canonical_coarse_aggregate_type(raw) -> str:
    t = _clean(raw)
    if not t or t == "natural":
        return "natural"
    if "recycl" in t or "rca" in t:
        return "recycled"
    if "basalt" in t:
        return "basalt"
    if "granite" in t:
        return "granite"
    if "dolomit" in t:
        return "dolomite"
    if "limestone" in t or "calcareous" in t or "calcite" in t:
        return "limestone"
    if "quartz" in t or "siliceous" in t:
        return "quartzite"
    if "marble" in t:
        return "marble"
    if "slag" in t:
        return "slag"
    if "gravel" in t or "rounded" in t or "river" in t:
        return "gravel"
    # Generic natural crushed rock with no specific lithology in the vocabulary
    # ("natural coarse aggregate", "crushed stone", "angular crushed stone",
    # "<place> crushed rock", "sandstone ...", "trap rock", "crushed gabbro") ->
    # natural. Recycled/specific lithologies are already caught above.
    if "natural" in t or "crushed" in t or "stone" in t or "rock" in t:
        return "natural"
    return "other"


def one_hot(value: str, vocab: Sequence[str]) -> List[float]:
    """One-hot encode ``value`` against ``vocab`` (all-zero if not present)."""
    return [1.0 if value == v else 0.0 for v in vocab]


def type_id(value: str, vocab: Sequence[str]) -> int:
    """Integer index of ``value`` in ``vocab`` (0 if not present)."""
    return vocab.index(value) if value in vocab else 0
