"""Filter panel data extraction (mobile + desktop)."""
from __future__ import annotations

from shared.sx.parser import SxExpr
from shared.sx.helpers import (
    sx_call,
    search_mobile_sx, search_desktop_sx,
)


# ---------------------------------------------------------------------------
# Sentinel hyperscript constants
# ---------------------------------------------------------------------------

_MOBILE_SENTINEL_HS = (
    "init\n"
    "  if not me.dataset.retryMs then set me.dataset.retryMs to 1000 end\n"
    "  if window.matchMedia('(min-width: 768px)').matches then set @hx-disabled to '' end\n"
    "on resize from window\n"
    "  if window.matchMedia('(min-width: 768px)').matches then set @hx-disabled to '' else remove @hx-disabled end\n"
    "on htmx:beforeRequest\n"
    "  if window.matchMedia('(min-width: 768px)').matches then halt end\n"
    "  add .hidden to .js-neterr in me\n"
    "  remove .hidden from .js-loading in me\n"
    "  remove .opacity-100 from me\n"
    "  add .opacity-0 to me\n"
    "def backoff()\n"
    "  set ms to me.dataset.retryMs\n"
    "  if ms > 30000 then set ms to 30000 end\n"
    "  add .hidden to .js-loading in me\n"
    "  remove .hidden from .js-neterr in me\n"
    "  remove .opacity-0 from me\n"
    "  add .opacity-100 to me\n"
    "  wait ms ms\n"
    "  trigger sentinelmobile:retry\n"
    "  set ms to ms * 2\n"
    "  if ms > 30000 then set ms to 30000 end\n"
    "  set me.dataset.retryMs to ms\n"
    "end\n"
    "on htmx:sendError call backoff()\n"
    "on htmx:responseError call backoff()\n"
    "on htmx:timeout call backoff()"
)

_DESKTOP_SENTINEL_HS = (
    "init\n"
    "  if not me.dataset.retryMs then set me.dataset.retryMs to 1000 end\n"
    "on htmx:beforeRequest(event)\n"
    "  add .hidden to .js-neterr in me\n"
    "  remove .hidden from .js-loading in me\n"
    "  remove .opacity-100 from me\n"
    "  add .opacity-0 to me\n"
    "  set trig to null\n"
    "  if event.detail and event.detail.triggeringEvent then set trig to event.detail.triggeringEvent end\n"
    "  if trig and trig.type is 'intersect'\n"
    "    set scroller to the closest .js-grid-viewport\n"
    "    if scroller is null then halt end\n"
    "    if scroller.scrollTop < 20 then halt end\n"
    "  end\n"
    "def backoff()\n"
    "  set ms to me.dataset.retryMs\n"
    "  if ms > 30000 then set ms to 30000 end\n"
    "  add .hidden to .js-loading in me\n"
    "  remove .hidden from .js-neterr in me\n"
    "  remove .opacity-0 from me\n"
    "  add .opacity-100 to me\n"
    "  wait ms ms\n"
    "  trigger sentinel:retry\n"
    "  set ms to ms * 2\n"
    "  if ms > 30000 then set ms to 30000 end\n"
    "  set me.dataset.retryMs to ms\n"
    "end\n"
    "on htmx:sendError call backoff()\n"
    "on htmx:responseError call backoff()\n"
    "on htmx:timeout call backoff()"
)


# ---------------------------------------------------------------------------
# Filter data extraction helpers
# ---------------------------------------------------------------------------

def _filter_common(ctx: dict) -> tuple:
    """Extract common filter params from context."""
    from shared.utils import route_prefix
    prefix = route_prefix()
    current_local_href = ctx.get("current_local_href", "/")
    hx_select = ctx.get("hx_select_search", "#main-panel")
    qs_fn = ctx.get("qs_filter")
    asset_url_fn = ctx.get("asset_url")
    return prefix, current_local_href, hx_select, qs_fn, asset_url_fn


def _sort_data(sort_options: list, current_sort: str, ctx: dict) -> list:
    """Extract sort option data for .sx composition."""
    prefix, current_local_href, hx_select, qs_fn, asset_url_fn = _filter_common(ctx)
    items = []
    for k, label, icon in sort_options:
        href = prefix + current_local_href + qs_fn({"sort": k}) if callable(qs_fn) else "#"
        active = k == current_sort
        ring = " ring-2 ring-emerald-500 rounded" if active else ""
        src = asset_url_fn(icon) if callable(asset_url_fn) else icon
        items.append({"href": href, "hx-select": hx_select,
                      "ring-cls": ring, "src": src, "label": label})
    return items


def _like_data(liked: bool, liked_count: int, ctx: dict) -> dict:
    """Extract like filter data for .sx composition."""
    prefix, current_local_href, hx_select, qs_fn, _ = _filter_common(ctx)
    href = prefix + current_local_href + qs_fn({"liked": not liked}) if callable(qs_fn) else "#"
    return {"href": href, "hx-select": hx_select, "liked": liked}


def _label_data(labels: list, selected: list, ctx: dict, *, img_prefix: str = "nav-labels") -> list:
    """Extract label filter data for .sx composition."""
    prefix, current_local_href, hx_select, qs_fn, asset_url_fn = _filter_common(ctx)
    items = []
    for lb in labels:
        name = lb.get("name", "")
        is_sel = name in selected
        if callable(qs_fn):
            new_sel = [s for s in selected if s != name] if is_sel else selected + [name]
            href = prefix + current_local_href + qs_fn({"labels": new_sel})
        else:
            href = "#"
        ring = " ring-2 ring-emerald-500 rounded" if is_sel else ""
        src = asset_url_fn(f"{img_prefix}/{name}.svg") if callable(asset_url_fn) else ""
        items.append({"href": href, "hx-select": hx_select,
                      "ring-cls": ring, "src": src, "name": name})
    return items


def _sticker_data(stickers: list, selected: list, ctx: dict) -> list:
    """Extract sticker filter data for .sx composition."""
    prefix, current_local_href, hx_select, qs_fn, asset_url_fn = _filter_common(ctx)
    items = []
    for st in stickers:
        name = st.get("name", "")
        count = st.get("count", 0)
        is_sel = name in selected
        if callable(qs_fn):
            new_sel = [s for s in selected if s != name] if is_sel else selected + [name]
            href = prefix + current_local_href + qs_fn({"stickers": new_sel})
        else:
            href = "#"
        ring = " ring-2 ring-emerald-500 rounded" if is_sel else ""
        src = asset_url_fn(f"stickers/{name}.svg") if callable(asset_url_fn) else ""
        cls = "text-[10px] text-stone-500" if count != 0 else "text-md text-red-500 font-bold"
        items.append({"href": href, "hx-select": hx_select, "ring-cls": ring,
                      "src": src, "name": name, "count-cls": cls, "count": str(count)})
    return items


def _brand_data(brands: list, selected: list, ctx: dict) -> list:
    """Extract brand filter data for .sx composition."""
    prefix, current_local_href, hx_select, qs_fn, _ = _filter_common(ctx)
    items = []
    for br in brands:
        name = br.get("name", "")
        count = br.get("count", 0)
        is_sel = name in selected
        if callable(qs_fn):
            new_sel = [s for s in selected if s != name] if is_sel else selected + [name]
            href = prefix + current_local_href + qs_fn({"brands": new_sel})
        else:
            href = "#"
        bg = " bg-yellow-200" if is_sel else ""
        cls = "text-md" if count else "text-md text-red-500"
        items.append({"href": href, "hx-select": hx_select, "bg-cls": bg,
                      "name-cls": cls, "name": name, "count": str(count)})
    return items


def _subcategory_data(subs: list, top_href: str, current_sub: str, ctx: dict) -> dict:
    """Extract subcategory filter data for .sx composition."""
    from shared.utils import route_prefix
    rp = route_prefix()
    hx_select = ctx.get("hx_select_search", "#main-panel")

    items = []
    for sub in subs:
        slug = sub.get("slug", "")
        active = slug == current_sub
        active_cls = " bg-stone-200 font-medium" if active else ""
        items.append({"href": rp + sub.get("href", ""), "hx-select": hx_select,
                      "active-cls": active_cls, "name": sub.get("name", "")})
    return {"items": items, "all-href": rp + top_href, "current-sub": current_sub}


# ---------------------------------------------------------------------------
# Desktop filter panel
# ---------------------------------------------------------------------------

async def _desktop_filter_sx(ctx: dict) -> str:
    """Build the desktop aside filter panel — delegates to .sx defcomp."""
    hx_select = ctx.get("hx_select_search", "#main-panel")

    # Search (still uses render_to_sx for shared component)
    search_sx = await search_desktop_sx(ctx)

    # Sort data
    sort_options = ctx.get("sort_options", [])
    sort = ctx.get("sort", "")
    sd = _sort_data(sort_options, sort, ctx) if sort_options else None

    # Like data
    ld = _like_data(ctx.get("liked", False), ctx.get("liked_count", 0), ctx)

    # Labels data
    labels = ctx.get("labels", [])
    selected_labels = ctx.get("selected_labels", [])
    lab_d = _label_data(labels, selected_labels, ctx) if labels else None

    # Stickers data
    stickers = ctx.get("stickers", [])
    selected_stickers = ctx.get("selected_stickers", [])
    st_d = _sticker_data(stickers, selected_stickers, ctx) if stickers else None

    # Brands data
    brands = ctx.get("brands", [])
    selected_brands = ctx.get("selected_brands", [])
    br_d = _brand_data(brands, selected_brands, ctx) if brands else None

    # Subcategory data
    subs_local = ctx.get("subs_local", [])
    top_local_href = ctx.get("top_local_href", "")
    sub_slug = ctx.get("sub_slug", "")
    sub_d = (_subcategory_data(subs_local, top_local_href, sub_slug, ctx)
             if subs_local and top_local_href else None)

    return sx_call("market-desktop-filter-from-data",
                   search_sx=SxExpr(search_sx),
                   category_label=ctx.get("category_label", ""),
                   sort_data=sd,
                   like_data=ld,
                   label_data=lab_d,
                   sticker_data=st_d,
                   brand_data=br_d,
                   sub_data=sub_d,
                   hx_select=hx_select)


# ---------------------------------------------------------------------------
# Mobile filter summary
# ---------------------------------------------------------------------------

def _mobile_chips_data(ctx: dict) -> dict:
    """Extract mobile filter chip data for .sx composition."""
    asset_url_fn = ctx.get("asset_url")
    sort = ctx.get("sort", "")
    sort_options = ctx.get("sort_options", [])
    liked = ctx.get("liked", False)
    liked_count = ctx.get("liked_count", 0)
    selected_labels = ctx.get("selected_labels", [])
    selected_stickers = ctx.get("selected_stickers", [])
    selected_brands = ctx.get("selected_brands", [])
    labels = ctx.get("labels", [])
    stickers = ctx.get("stickers", [])
    brands = ctx.get("brands", [])

    # Sort chip
    sort_chip = None
    if sort and sort_options:
        for k, l, i in sort_options:
            if k == sort and callable(asset_url_fn):
                sort_chip = {"src": asset_url_fn(i), "label": l}

    # Liked chip
    liked_chip = None
    if liked:
        count_cls = ("text-[10px] text-stone-500" if liked_count != 0
                     else "text-md text-red-500 font-bold")
        liked_chip = {"count": str(liked_count) if liked_count is not None else None,
                      "count-cls": count_cls}

    # Label chips
    label_chips = []
    if selected_labels:
        for sl in selected_labels:
            for lb in labels:
                if lb.get("name") == sl and callable(asset_url_fn):
                    chip: dict = {"src": asset_url_fn("nav-labels/" + sl + ".svg"), "name": sl}
                    if lb.get("count") is not None:
                        cls = ("text-[10px] text-stone-500" if lb["count"] != 0
                               else "text-md text-red-500 font-bold")
                        chip["count"] = str(lb["count"])
                        chip["count-cls"] = cls
                    label_chips.append(chip)

    # Sticker chips
    sticker_chips = []
    if selected_stickers:
        for ss in selected_stickers:
            for st in stickers:
                if st.get("name") == ss and callable(asset_url_fn):
                    chip = {"src": asset_url_fn("stickers/" + ss + ".svg"), "name": ss}
                    if st.get("count") is not None:
                        cls = ("text-[10px] text-stone-500" if st["count"] != 0
                               else "text-md text-red-500 font-bold")
                        chip["count"] = str(st["count"])
                        chip["count-cls"] = cls
                    sticker_chips.append(chip)

    # Brand chips
    brand_chips = []
    if selected_brands:
        for b in selected_brands:
            count = 0
            for br in brands:
                if br.get("name") == b:
                    count = br.get("count", 0)
            brand_chips.append({"name": b, "count": str(count), "has-count": bool(count)})

    return {
        "sort-chip": sort_chip,
        "liked-chip": liked_chip,
        "label-chips": label_chips or None,
        "sticker-chips": sticker_chips or None,
        "brand-chips": brand_chips or None,
    }


def _mobile_filter_content_data(ctx: dict) -> dict:
    """Extract mobile filter expanded panel data."""
    from shared.utils import route_prefix
    prefix = route_prefix()
    hx_select = ctx.get("hx_select_search", "#main-panel")

    sort_options = ctx.get("sort_options", [])
    sort = ctx.get("sort", "")
    sd = _sort_data(sort_options, sort, ctx) if sort_options else None

    ld = _like_data(ctx.get("liked", False), ctx.get("liked_count", 0), ctx)

    labels = ctx.get("labels", [])
    selected_labels = ctx.get("selected_labels", [])
    lab_d = _label_data(labels, selected_labels, ctx) if labels else None

    stickers = ctx.get("stickers", [])
    selected_stickers = ctx.get("selected_stickers", [])
    st_d = _sticker_data(stickers, selected_stickers, ctx) if stickers else None

    brands = ctx.get("brands", [])
    selected_brands = ctx.get("selected_brands", [])
    br_d = _brand_data(brands, selected_brands, ctx) if brands else None

    # Clear filters URL
    clear_href = None
    search = ctx.get("search", "")
    has_filters = search or selected_labels or selected_stickers or selected_brands
    qs_fn = ctx.get("qs_filter")
    if has_filters and callable(qs_fn):
        current_local_href = ctx.get("current_local_href", "/")
        clear_href = prefix + current_local_href + qs_fn({"clear_filters": True})

    return {
        "sort-data": sd,
        "like-data": ld,
        "label-data": lab_d,
        "sticker-data": st_d,
        "brand-data": br_d,
        "clear-href": clear_href,
        "hx-select": hx_select,
    }


async def _mobile_filter_summary_sx(ctx: dict) -> str:
    """Build mobile filter summary — delegates to .sx defcomps."""
    # Search bar (still uses render_to_sx for shared component)
    search_bar = await search_mobile_sx(ctx)

    # Chips data
    chips_data = _mobile_chips_data(ctx)
    chips = sx_call("market-mobile-chips-from-data", **chips_data)

    # Expanded filter content data
    filter_data = _mobile_filter_content_data(ctx)
    filter_content = sx_call("market-mobile-filter-content-from-data", **filter_data)

    return sx_call("market-mobile-filter-summary",
                   search_bar=SxExpr(search_bar),
                   chips=SxExpr(chips),
                   filter=SxExpr(filter_content))
