# manual_agregator/parser/handlers/from_main.py
from .base import FieldHandler
from ..utils import resolve_missing_text

class FromMainHandler(FieldHandler):
    def parse(self, field_name, config, soup, main_values, selectors, extracted):
        base_field = config.get("fromMain")
        base_value = main_values.get(base_field)

        # Weź konfigurację pola-bazy (np. address), bo to tam masz reverseSplit/splitBy
        base_cfg = selectors.get(base_field, {}) if isinstance(selectors, dict) else {}

        # splitBy może być nadpisane lokalnie, w przeciwnym razie z pola-bazy, fallback: ","
        split_by = config.get("splitBy") or base_cfg.get("splitBy") or ","

        # reverseSplit – lokalne nadpisuje, inaczej bierz z pola-bazy.
        # Ważne: jeśli dziecko nadpisuje splitBy, nie dziedzicz reverseSplit z bazy domyślnie
        # (domyśl do False), aby uniknąć nieoczekiwanego odwracania.
        reverse_split = config.get("reverseSplit")
        if reverse_split is None:
            if "splitBy" in config:
                reverse_split = False
            else:
                reverse_split = base_cfg.get("reverseSplit", False)

        split_index = config.get("splitIndex")

        if base_value is None:
            # debug info for troubleshooting missing base
            try:
                print(f"[fromMain] base missing for {field_name}: fromMain={base_field}")
            except Exception:
                pass
            return resolve_missing_text(config)

        if split_index is None:
            # jeśli nie podano splitIndex – zwróć bazową wartość (z ewentualnym rzutowaniem)
            result = base_value
        else:
            # Rozbij i przytnij spacje
            try:
                parts = [p.strip() for p in str(base_value).split(split_by) if p is not None]
            except Exception as e:
                try:
                    print(f"[fromMain] split error field={field_name}, split_by={split_by!r}, base_value={str(base_value)[:120]!r}: {e}")
                except Exception:
                    pass
                parts = []

            # Opcjonalny warunek minimalnej liczby części po split
            try:
                min_parts = config.get("minParts")
                if isinstance(min_parts, int) and len(parts) < min_parts:
                    try:
                        print(f"[fromMain] not enough parts field={field_name}, required={min_parts}, got={len(parts)}")
                    except Exception:
                        pass
                    result = None
                else:
                    result = "__CONTINUE_SPLIT__"
            except Exception:
                result = "__CONTINUE_SPLIT__"

            if result == "__CONTINUE_SPLIT__":
            # Odwróć kolejność, jeśli reverseSplit = True
                if reverse_split:
                    parts = list(reversed(parts))

                # Obsłuż także ujemne indeksy (jak w Pythonie)
                try:
                    result = parts[split_index]
                except (IndexError, TypeError) as e:
                    result = None
                    try:
                        print(f"[fromMain] index error field={field_name}, idx={split_index}, parts_len={len(parts)}: {e}")
                    except Exception:
                        pass
        # Drugi etap dzielenia (opcjonalny):
        #  - postSplit { by: ",", index: 0 }
        #  - lub z fallbackami: postSplit { by: ",", indices: [2,1], mustContainAny: ["ul.", "ulica"] }
        try:
            post = config.get("postSplit")
            if result is not None and isinstance(post, dict):
                by = post.get("by", ",")
                idx = post.get("index", 0)
                indices = post.get("indices")  # opcjonalna lista indeksów do spróbowania po kolei
                # opcjonalne reverse dla drugiego etapu
                post_reverse = bool(post.get("reverseSplit", False))
                # opcjonalny warunek akceptacji kandydata: musi zawierać którykolwiek z tokenów
                must_tokens = post.get("mustContainAny") or post.get("requireContains")
                if isinstance(must_tokens, (list, tuple)):
                    must_tokens = [str(t).lower() for t in must_tokens if t is not None]
                else:
                    must_tokens = None

                parts2 = [p.strip() for p in str(result).split(by)]
                if post_reverse:
                    parts2 = list(reversed(parts2))

                def _accept_candidate(val):
                    if val is None:
                        return False
                    if must_tokens is None:
                        return True
                    low = str(val).lower()
                    return any(tok in low for tok in must_tokens)

                selected = None
                if isinstance(indices, (list, tuple)) and len(indices) > 0:
                    # Spróbuj po kolei indeksy, wybierz pierwszy spełniający warunki
                    for i in indices:
                        try:
                            cand = parts2[i]
                        except Exception:
                            cand = None
                        if _accept_candidate(cand):
                            selected = cand
                            break
                    result = selected
                    if selected is None:
                        try:
                            print(f"[fromMain] postSplit no candidate accepted field={field_name}, indices={list(indices)}, parts_len={len(parts2)}")
                        except Exception:
                            pass
                else:
                    # klasyczny pojedynczy index
                    try:
                        cand = parts2[idx]
                    except Exception as e:
                        cand = None
                        try:
                            print(f"[fromMain] postSplit index error field={field_name}, idx={idx}, parts_len={len(parts2)}: {e}")
                        except Exception:
                            pass
                    result = cand if _accept_candidate(cand) else None
        except Exception as e:
            try:
                print(f"[fromMain] postSplit failed field={field_name}: {e}")
            except Exception:
                pass

        # Opcjonalne rzutowanie typu ("int" | "float"), jeśli podane w config
        cast = (config.get("cast") or "").strip().lower()
        if result is None:
            try:
                print(f"[fromMain] result None for {field_name} after split. base_field={base_field}, split_by={split_by!r}, idx={split_index}")
            except Exception:
                pass
            return resolve_missing_text(config)

        # Opcjonalny filtr: odrzuć wynik jeśli zawiera którykolwiek z podanych fragmentów (case-insensitive)
        try:
            rejects = config.get("rejectIfContains")
            if isinstance(rejects, (list, tuple)) and result is not None:
                res_low = str(result).lower()
                for token in rejects:
                    if token is None:
                        continue
                    if str(token).lower() in res_low:
                        try:
                            print(f"[fromMain] '{field_name}' rejected by token {token!r} in result {result!r}")
                        except Exception:
                            pass
                        return resolve_missing_text(config)
        except Exception as e:
            try:
                print(f"[fromMain] rejectIfContains failed field={field_name}: {e}")
            except Exception:
                pass

        if cast:
            s = str(result).strip().replace(" ", "").replace(",", ".")
            try:
                if cast == "int":
                    return int(float(s))
                if cast == "float":
                    return float(s)
            except Exception as e:
                try:
                    print(f"[fromMain] cast failed field={field_name}, value={s!r}, cast={cast}: {e}")
                except Exception:
                    pass
                return resolve_missing_text(config)

        return result
