from quart import request

from typing import Iterable, Optional, Union

from shared.browser.app.filters.qs_base import (
    KEEP, _norm, make_filter_set, build_qs,
)
from shared.browser.app.filters.query_types import BlogQuery


def decode() -> BlogQuery:
    page = int(request.args.get("page", 1))
    search = request.args.get("search")
    sort = request.args.get("sort")
    liked = request.args.get("liked")
    drafts = request.args.get("drafts")

    selected_tags = tuple(s.strip() for s in request.args.getlist("tag") if s.strip())[:1]
    selected_authors = tuple(s.strip().lower() for s in request.args.getlist("author") if s.strip())[:1]
    selected_groups = tuple(s.strip() for s in request.args.getlist("group") if s.strip())[:1]
    view = request.args.get("view") or None

    return BlogQuery(page, search, sort, selected_tags, selected_authors, liked, view, drafts, selected_groups)


def makeqs_factory():
    """
    Build a makeqs(...) that starts from the current filters + page.
    Auto-resets page to 1 when filters change unless you pass page explicitly.
    """
    q = decode()
    base_tags = [s for s in q.selected_tags if (s or "").strip()]
    base_authors = [s for s in q.selected_authors if (s or "").strip()]
    base_groups = [s for s in q.selected_groups if (s or "").strip()]
    base_search = q.search or None
    base_liked = q.liked or None
    base_sort = q.sort or None
    base_page = int(q.page or 1)
    base_view = q.view or None
    base_drafts = q.drafts or None

    def makeqs(
        *,
        clear_filters: bool = False,
        add_tag: Union[str, Iterable[str], None] = None,
        remove_tag: Union[str, Iterable[str], None] = None,
        add_author: Union[str, Iterable[str], None] = None,
        remove_author: Union[str, Iterable[str], None] = None,
        add_group: Union[str, Iterable[str], None] = None,
        remove_group: Union[str, Iterable[str], None] = None,
        search: Union[str, None, object] = KEEP,
        sort: Union[str, None, object] = KEEP,
        page: Union[int, None, object] = None,
        extra: Optional[Iterable[tuple]] = None,
        leading_q: bool = True,
        liked: Union[bool, None, object] = KEEP,
        view: Union[str, None, object] = KEEP,
        drafts: Union[str, None, object] = KEEP,
    ) -> str:
        groups = make_filter_set(base_groups, add_group, remove_group, clear_filters, single_select=True)
        tags = make_filter_set(base_tags, add_tag, remove_tag, clear_filters, single_select=True)
        authors = make_filter_set(base_authors, add_author, remove_author, clear_filters, single_select=True)

        # Mutual exclusion: selecting a group clears tags, selecting a tag clears groups
        if add_group is not None:
            tags = []
        if add_tag is not None:
            groups = []

        final_search = None if clear_filters else base_search if search is KEEP else ((search or "").strip() or None)
        final_sort = base_sort if sort is KEEP else (sort or None)
        final_liked = None if clear_filters else base_liked if liked is KEEP else liked
        final_view = base_view if view is KEEP else (view or None)
        final_drafts = None if clear_filters else base_drafts if drafts is KEEP else (drafts or None)

        # Did filters change?
        filters_changed = (
            set(map(_norm, tags)) != set(map(_norm, base_tags))
            or set(map(_norm, authors)) != set(map(_norm, base_authors))
            or set(map(_norm, groups)) != set(map(_norm, base_groups))
            or final_search != base_search
            or final_sort != base_sort
            or final_liked != base_liked
            or final_drafts != base_drafts
        )

        # Page logic
        if page is KEEP:
            final_page = 1 if filters_changed else base_page
        else:
            final_page = page

        # Build params
        params = []
        for s in groups:
            params.append(("group", s))
        for s in tags:
            params.append(("tag", s))
        for s in authors:
            params.append(("author", s))
        if final_search:
            params.append(("search", final_search))
        if final_liked is not None:
            params.append(("liked", final_liked))
        if final_sort:
            params.append(("sort", final_sort))
        if final_view:
            params.append(("view", final_view))
        if final_drafts:
            params.append(("drafts", final_drafts))
        if final_page is not None:
            params.append(("page", str(final_page)))
        if extra:
            for k, v in extra:
                if v is not None:
                    params.append((k, str(v)))

        return build_qs(params, leading_q=leading_q)

    return makeqs
