from typing import Dict, List, Optional
from selectolax.parser import HTMLParser
from bs4 import BeautifulSoup

# Simple, selector-driven parser used by multiple brokers.
# Supports selectors with:
#  - ::text to extract text content
#  - ::attr(name) to extract an attribute
#  - comma-separated fallbacks: ".title::text, h2::text"


def _pick_first_non_empty(values: List[Optional[str]]) -> Optional[str]:
    for v in values:
        if v is None:
            continue
        v2 = v.strip()
        if v2:
            return v2
    return None


def _extract_selectolax(node, sel: str) -> Optional[str]:
    sel = sel.strip()
    if not sel:
        return None
    text_mode = sel.endswith("::text")
    attr_mode = sel.endswith(")") and "::attr(" in sel
    if text_mode:
        base = sel[:-7]
        target_attr = None
    elif attr_mode:
        base, rest = sel.split("::attr(", 1)
        target_attr = rest[:-1]
    else:
        base = sel
        target_attr = None
    if not base:
        return None
    n = node.css_first(base)
    if not n:
        return None
    if target_attr:
        return n.attributes.get(target_attr)
    # default to text content
    return n.text(strip=True)

def _extract_bs4(node, sel: str) -> Optional[str]:
    sel = sel.strip()
    if not sel:
        return None
    text_mode = sel.endswith("::text")
    attr_mode = sel.endswith(")") and "::attr(" in sel
    if text_mode:
        base = sel[:-7]
        target_attr = None
    elif attr_mode:
        base, rest = sel.split("::attr(", 1)
        target_attr = rest[:-1]
    else:
        base = sel
        target_attr = None
    if not base:
        return None
    n = node.select_one(base)
    if not n:
        return None
    if target_attr:
        return n.get(target_attr)
    return n.get_text(strip=True)


def parse_listings(html: str, selectors: Dict[str, Optional[str]]) -> List[Dict]:
    doc = HTMLParser(html)
    soup = None  # lazy until needed
    item_sel = selectors.get("item")
    if not item_sel:
        return []
    try:
        items = doc.css(item_sel)
        node_mode = "selectolax"
    except NotImplementedError:
        soup = BeautifulSoup(html, "html.parser")
        items = soup.select(item_sel)
        node_mode = "bs4"
    out: List[Dict] = []
    for it in items:
        def field(s: Optional[str]) -> Optional[str]:
            if not s:
                return None
            # allow comma-separated fallbacks
            parts = [p.strip() for p in s.split(",")]
            vals: List[Optional[str]] = []
            for p in parts:
                try:
                    if node_mode == "selectolax":
                        vals.append(_extract_selectolax(it, p))
                    else:
                        vals.append(_extract_bs4(it, p))
                except NotImplementedError:
                    # fall back to bs4 for unsupported selectors
                    nonlocal_soup = soup or BeautifulSoup(html, "html.parser")
                    vals.append(_extract_bs4(it if node_mode == "bs4" else nonlocal_soup, p))
            return _pick_first_non_empty(vals)

        title = field(selectors.get("title"))
        price = field(selectors.get("price"))
        points = field(selectors.get("points"))
        resort = field(selectors.get("resort"))
        beds = field(selectors.get("beds"))
        baths = field(selectors.get("baths"))
        status = field(selectors.get("status")) or "active"
        link = field(selectors.get("link"))

        # images: collect potentially multiple
        imgs: List[str] = []
        img_sel = selectors.get("images")
        if img_sel:
            for s in [p.strip() for p in img_sel.split(",")]:
                base = s
                attr = None
                if "::attr(" in s:
                    base, rest = s.split("::attr(", 1)
                    attr = rest[:-1]
                try:
                    nodes = it.css(base) if node_mode == "selectolax" else it.select(base)
                except NotImplementedError:
                    if soup is None:
                        soup = BeautifulSoup(html, "html.parser")
                    # when selectolax can't, try bs4 selecting within full soup then filter to this item
                    nodes = (soup.select(base) if node_mode == "selectolax" else it.select(base))
                for img in nodes:
                    if attr:
                        v = (img.attributes.get(attr) if node_mode == "selectolax" else img.get(attr))
                    else:
                        v = (img.attributes.get("src") if node_mode == "selectolax" else img.get("src"))
                    if v:
                        imgs.append(v)

        # listing_id heuristic: prefer link, else title
        listing_id = link or title or None
        row = {
            "broker": "dvc-resale-market",  # caller may override
            "listing_id": listing_id,
            "title": title,
            "resort": resort,
            "unit": None,
            "beds": beds,
            "baths": baths,
            "points": points,
            "price": price,
            "status": status,
            "link": link,
            "images": imgs,
        }
        out.append(row)
    return out
