"""Per-tube tag/category scraper z page HTML.

yt-dlp generic nie zwraca tagów dla większości tubes (formaty się różnią).
Każda funkcja `extract_<tube>(html) -> list[str]` parsuje HTML konkretnego tube'a
żeby wyciągnąć kategoryczne tagi sceny (nie auto-generated n-gramy z tytułu).

Mapowanie sitetag → extractor jest w `EXTRACTORS` na końcu. `extract_tags(sitetag, html)`
to public entrypoint.

UWAGA: nie zwracamy auto-generated word-ngramów (KVS bywa generuje "Tania",
"Tania Amazon", "Amazon Shaft"... — bez wartości semantycznej). Dlatego pornditt
NIE jest tu obsłużone — KVS tagi to garbage.
"""
from __future__ import annotations

import json
import re
from collections.abc import Callable

# Common helpers
def _clean_name(name: str) -> str:
    return re.sub(r"\s+", " ", name).strip()


def _dedupe(items: list[str], cap: int = 20) -> list[str]:
    seen: set[str] = set()
    out: list[str] = []
    for it in items:
        key = it.lower().strip()
        if not key or key in seen:
            continue
        seen.add(key)
        out.append(it)
        if len(out) >= cap:
            break
    return out


# ---- per-tube extractors --------------------------------------------------

def extract_porntrex(html: str) -> list[str]:
    """porntrex: <span>Categories:</span><div class="items-holder js-categories"><a class="js-cat" href="/categories/<slug>/">NAME</a>...
    `Tags:` block na porntrex zawiera auto-generated n-gramy ("Tania", "L H") — pomijamy.
    """
    m = re.search(r'Categories:</span>\s*<div[^>]+>(.*?)</div>', html, re.DOTALL | re.IGNORECASE)
    if not m:
        return []
    block = m.group(1)
    names = re.findall(r'<a[^>]+href="[^"]*?/categories/[^"]+"[^>]*>([^<]+)</a>', block, re.IGNORECASE)
    return _dedupe([_clean_name(n) for n in names])


def extract_youporn(html: str) -> list[str]:
    """youporn: <a href="/category/<slug>/">NAME</a>"""
    matches = re.findall(
        r'<a[^>]+href="/category/[a-z0-9-]+/?"[^>]*>\s*([^<]+?)\s*</a>',
        html, re.IGNORECASE,
    )
    return _dedupe([_clean_name(n) for n in matches])


def extract_xvideos(html: str) -> list[str]:
    """xvideos: <a href="/tags/<slug>/">name</a> (slug-style names — przerobimy na Title Case)."""
    matches = re.findall(
        r'<a[^>]+href="[^"]*?/tags/([a-z0-9-]+)/?"[^>]*>',
        html, re.IGNORECASE,
    )
    # slug "ass-to-mouth" → "Ass To Mouth"
    return _dedupe([s.replace("-", " ").title() for s in matches])


def extract_xnxx(html: str) -> list[str]:
    """xnxx: tagi w JSON `"video_tags":["ass","deep-throat","rimming",...]` wewnątrz
    html5player config (NIE w `/tags/` URL-ach jak xvideos — to są różne systemy
    pomimo tego że oba należą do WGCZ Holding).
    """
    m = re.search(r'"video_tags"\s*:\s*\[([^\]]+)\]', html)
    if not m:
        return []
    slugs = re.findall(r'"([^"]+)"', m.group(1))
    return _dedupe([s.replace("-", " ").title() for s in slugs if s])


def extract_redtube(html: str) -> list[str]:
    """redtube: <a class="...video_carousel_category..." href="/redtube/<slug>">NAME</a>"""
    matches = re.findall(
        r'<a[^>]+href="/redtube/[a-z0-9-]+"[^>]*>\s*([^<]+?)\s*</a>',
        html, re.IGNORECASE,
    )
    cleaned = [_clean_name(n) for n in matches]
    cleaned = [n for n in cleaned if n.lower() not in {"categories", "tags", "category", "tag"}]
    return _dedupe(cleaned)


def _find_balanced_array(html: str, start_after: str) -> str | None:
    """Po napotkaniu `"tags":[` znajdź matching `]` honorując nested {}/[]/strings.
    Zwraca zawartość arraya (bez nawiasów) albo None.
    """
    idx = html.find(start_after)
    if idx == -1:
        return None
    pos = idx + len(start_after)  # right after `[`
    depth = 1
    in_str = False
    escape = False
    n = len(html)
    while pos < n:
        ch = html[pos]
        if escape:
            escape = False
        elif ch == "\\":
            escape = True
        elif ch == '"' and not escape:
            in_str = not in_str
        elif not in_str:
            if ch in "[{":
                depth += 1
            elif ch in "]}":
                depth -= 1
                if depth == 0:
                    return html[idx + len(start_after):pos]
        pos += 1
    return None


def extract_xhamster(html: str) -> list[str]:
    """xhamster: rich JSON w `<script>` z `"tags":[{...isCategory:true...}, ...]`.
    Bierzemy `isCategory:true` (prawdziwe kategorie sceny) + `isTag:true` (drobne tagi
    typu "Hard", "Horny"). Pomijamy `isPornstar:true` (performerów mamy z innego źródła).
    Używamy bracket-balanced parser bo array jest długi z nested objects (`features:null`).
    """
    blob = _find_balanced_array(html, '"tags":[')
    if blob is None:
        return []
    try:
        items = json.loads("[" + blob + "]")
    except json.JSONDecodeError:
        return []
    out: list[str] = []
    for it in items:
        if not isinstance(it, dict):
            continue
        if it.get("isCategory") or it.get("isTag"):
            name = it.get("nameEn") or it.get("name")
            if name:
                out.append(_clean_name(name))
    return _dedupe(out)


def extract_pornhat(html: str) -> list[str]:
    """pornhat: `class="info-video js-ajax-tag btn" data-setup='{"title": ...}'`."""
    matches = re.findall(
        r"class=\"info-video js-ajax-tag[^\"]*\"[^>]*data-setup='([^']+)'",
        html, re.IGNORECASE,
    )
    names: list[str] = []
    for raw in matches:
        try:
            data = json.loads(raw)
        except json.JSONDecodeError:
            continue
        title = (data.get("title") or "").strip()
        if title:
            names.append(title)
    return _dedupe(names)


def extract_eporner(html: str) -> list[str]:
    """eporner: `<li class="vit-category">` + `<li class="vit-tag">` zawierają linki do
    kategorii/tagów sceny. Pomijamy `vit-uploader` / `vit-suggester` itp."""
    matches = re.findall(
        r'<li class="vit-(?:category|tag)"[^>]*>\s*<a[^>]+>([^<]+)</a>',
        html, re.IGNORECASE,
    )
    cleaned = [_clean_name(n) for n in matches]
    # "Uncategorized" pomijamy
    cleaned = [n for n in cleaned if n.lower() != "uncategorized"]
    return _dedupe(cleaned)


def extract_hqporner(html: str) -> list[str]:
    """hqporner: tagi siedzą w meta description po "Tags related to this video:".

    Detail page jest "thin" (~26KB) bez tag links w body — wszystko jest tylko
    w `<meta name="description">` w formie zdania:
      `Watch porn video <Title>. Video duration is Xmin Ysec. Tags related to
       this video: handjob, babe, for cash, blowjob, black hair.`

    Plus HTML comment na początku page:
      `<!--+handjob +mofos network +public pickup 1080p_handjob+mofosnetwork+publicpickup-->`
    """
    out: list[str] = []
    # Primary: meta description
    if (m := re.search(
        r'<meta\s+name="description"\s+content="[^"]*Tags related to this video:\s*([^."]+)',
        html, re.IGNORECASE,
    )):
        for tok in m.group(1).split(","):
            t = _clean_name(tok)
            if t:
                out.append(t)

    # Fallback: HTML opening comment `<!--+tag1 +tag2 ...-->`
    if not out:
        if (m := re.search(r"<!--((?:\+[\w \-]+)+)", html)):
            for tok in m.group(1).split("+"):
                t = _clean_name(tok)
                if t:
                    out.append(t)
    return _dedupe(out)


# ---- registry --------------------------------------------------------------

EXTRACTORS: dict[str, Callable[[str], list[str]]] = {
    "porntrexcom": extract_porntrex,
    "youporncom": extract_youporn,
    "xvideoscom": extract_xvideos,
    "xnxxcom": extract_xnxx,
    "redtubecom": extract_redtube,
    "xhamstercom": extract_xhamster,
    "epornercom": extract_eporner,
    "pornhatcom": extract_pornhat,
    "hqpornercom": extract_hqporner,
}


def extract_tags(sitetag: str, html: str) -> list[str]:
    """Zwraca clean listę tagów dla danego tube'a, lub [] gdy brak extractora/tagów."""
    fn = EXTRACTORS.get(sitetag)
    if fn is None:
        return []
    try:
        return fn(html)
    except Exception:
        return []


__all__ = ["extract_tags", "EXTRACTORS"]
