# number.py
import re
from .base import FieldHandler
from ..utils import resolve_missing_text, extract_number_and_label

DEFAULT_SPECIAL_MAP = {
    "parter": 0, "wysoki parter": 0, "niski parter": 0,
    "suterena": -1, "suterenie": -1, "piwnica": -1, "przyziemie": -1,
}

RANGE_RE = re.compile(
    r'(-?\d+(?:[.,]\d+)?)\s*(?:-|–|—|/|\bdo\b)\s*(-?\d+(?:[.,]\d+)?)',
    flags=re.IGNORECASE
)

def _to_number(token: str, cast: str = "auto"):
    if token is None:
        return None
    s = token.strip().replace(' ', '').replace(',', '.')
    if not s:
        return None
    if cast == "int":
        try:
            return int(float(s))
        except ValueError:
            return None
    if cast == "float":
        try:
            return float(s)
        except ValueError:
            return None
    try:
        f = float(s)
        i = int(f)
        return i if abs(f - i) < 1e-9 else f
    except ValueError:
        return None

def parse_number_from_text(field_name, raw, config, extracted):
    """
    Rdzeń parsowania liczby z tekstu:
    - obsługa waluty/etykiety (extract_number_and_label)
    - zakresy (1/4, 1970–1980, 1-4, 1 do 4)
    - specialMap (parter/suterena/…)
    - onNotNumeric: null|zero|keep|raise
    - minField/maxField, rangeMode, cast
    Zwraca liczbę (int/float) lub None. Uzupełnia extracted[minField/maxField/currencyField/labelField] jeśli trzeba.
    """
    cast = (config.get("cast") or "auto").lower()
    text = (raw or "").strip().lower()

    # wstępny parse (wyciąga też currency i label)
    number, label_val, currency = extract_number_and_label(raw)

    # currency/label side-effects
    if config.get("currencyField") and currency:
        extracted[config["currencyField"]] = currency
    if config.get("labelField") and label_val:
        extracted[config["labelField"]] = label_val

    allow_range = config.get("allowRange", True)
    min_field = config.get("minField")
    max_field = config.get("maxField")
    range_mode = (config.get("rangeMode") or "min").lower()

    # Special map do zastąpienia słów (np. „parter/4” → „0/4”) NA POTRZEBY wykrycia zakresu
    special_map = dict(DEFAULT_SPECIAL_MAP)
    special_map.update(config.get("specialMap", {}))

    if allow_range and text:
        text_for_range = text
        for key, mapped_val in special_map.items():
            text_for_range = re.sub(rf"\b{re.escape(key)}\b", str(mapped_val), text_for_range)

        m = RANGE_RE.search(text_for_range)
        if m:
            a_raw, b_raw = m.group(1), m.group(2)
            a = _to_number(a_raw, cast=cast)
            b = _to_number(b_raw, cast=cast)

            if min_field:
                extracted[min_field] = (min(a, b) if (a is not None and b is not None) else (a or b))
            if max_field:
                extracted[max_field] = (max(a, b) if (a is not None and b is not None) else (a or b))

            if a is not None and b is not None:
                if range_mode == "max":
                    number = max(a, b)
                elif range_mode == "avg":
                    number = (a + b) / 2
                elif range_mode == "first":
                    number = a
                else:  # min
                    number = min(a, b)
            else:
                number = a if a is not None else b

    # Jeśli dalej nie ma liczby – sprawdź słowne mapowanie (parter, suterena…)
    if number is None and text:
        for key, mapped_val in special_map.items():
            if key in text:
                number = _to_number(str(mapped_val), cast=cast)
                if config.get("labelField") and not label_val:
                    extracted[config["labelField"]] = key
                break

    # Fallback gdy brak liczby
    if number is None:
        behavior = (config.get("onNotNumeric") or "null").lower()
        if behavior == "zero":
            number = _to_number("0", cast=cast)
        elif behavior == "keep":
            number = raw  # UWAGA: string w polu liczbowym tylko jeśli tak chcesz
        elif behavior == "raise":
            raise ValueError(f"{field_name}: non-numeric value: {raw!r}")
        else:
            number = None

    return number


class NumberHandler(FieldHandler):
    def parse(self, field_name, config, soup, main_values, selectors, extracted):
        sel = config.get("selector")
        el = soup.select_one(sel) if sel else None
        raw = el.get_text(strip=True) if el else resolve_missing_text(config)
        return parse_number_from_text(field_name, raw, config, extracted)
