"""BooruGacha Search — normalise user tags to Danbooru equivalents."""

from ..data import flatten, format_weighted_tag, parse_weighted_tag


class BooruGachaSearch:
    """Takes comma-separated tags and resolves each to the closest
    matching Danbooru tag via string + semantic search.

    Weight syntax ``(tag:1.2)`` is preserved through normalisation.

    When a ``BANLIST`` is connected:
    - Tags the user clearly typed (exact/close string match) pass
      through with a warning.
    - Tags the engine *guessed* via semantic search are suppressed
      and an alternative non-banned match is attempted.
    """

    DESCRIPTION = (
        "Normalises user-input tags to matching Danbooru tags. "
        "Handles typos, spacing differences, and natural-language variations. "
        "Separate tags with commas. Weight syntax (tag:1.2) is preserved. "
        "Connect a ban_list to suppress unwanted semantic matches."
    )

    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "tags": (
                    "STRING",
                    {
                        "multiline": True,
                        "default": "",
                        "tooltip": (
                            "Comma-separated tags to normalise, e.g. "
                            "'cat girl, (purple hair:1.2), demon horns'"
                        ),
                    },
                ),
                "threshold": (
                    "FLOAT",
                    {
                        "default": 0.3,
                        "min": 0.0,
                        "max": 1.0,
                        "step": 0.05,
                        "tooltip": (
                            "Minimum match score. Tags scoring below this "
                            "are passed through unchanged."
                        ),
                    },
                ),
                "content_filter": (
                    ["all", "sfw", "sfw+nudity", "sfw+nudity+sexual"],
                    {
                        "default": "all",
                        "tooltip": (
                            "Filter for semantic/fuzzy matches. "
                            "User-typed exact matches pass through with "
                            "a warning regardless of filter."
                        ),
                    },
                ),
            },
            "optional": {
                "ban_list": (
                    "BANLIST",
                    {"tooltip": "Ban list from Booru Gacha Ban List node."},
                ),
            },
        }

    RETURN_TYPES = ("STRING", "STRING")
    RETURN_NAMES = ("tags", "debug_info")
    OUTPUT_TOOLTIPS = (
        "Comma-separated Danbooru tags",
        "Per-tag match details",
    )
    FUNCTION = "search"
    CATEGORY = "BooruGacha"

    def search(
        self,
        tags: str,
        threshold: float,
        content_filter: str = "all",
        ban_list: set | None = None,
    ):
        from ..search import CONTENT_FILTER_SETS, get_engine

        engine = get_engine()
        ban = ban_list or set()
        allowed = CONTENT_FILTER_SETS.get(content_filter)

        sub_queries = [q.strip() for q in tags.split(",") if q.strip()]
        out_tags: list[str] = []
        debug_lines: list[str] = []

        for sq in sub_queries:
            raw, weight = parse_weighted_tag(sq)

            match = engine.find_one(raw, threshold=threshold)

            if not match:
                passed = raw.replace(" ", "_")
                out_tags.append(format_weighted_tag(passed, weight))
                debug_lines.append(
                    f"{sq} -> {format_weighted_tag(passed, weight)}"
                    f" [no match]"
                )
                continue

            db_name, label, score = match
            user_intended = flatten(raw) == flatten(db_name)

            # Check ban list and content filter violations
            is_banned = db_name in ban
            reg = engine.registry
            idx = reg.danbooru_to_idx.get(db_name)
            tag_filtering = reg.tags[idx]["filtering"] if idx is not None else "sfw"
            is_filtered = allowed is not None and tag_filtering not in allowed

            if not is_banned and not is_filtered:
                # Clean match
                out_tags.append(format_weighted_tag(db_name, weight))
                debug_lines.append(
                    f"{sq} -> {format_weighted_tag(db_name, weight)}"
                    f" [{label}] ({score:.2f})"
                )
            elif user_intended:
                # User clearly typed this — warn but keep
                reasons = []
                if is_banned:
                    reasons.append("banned")
                if is_filtered:
                    reasons.append(f"filtered:{tag_filtering}")
                out_tags.append(format_weighted_tag(db_name, weight))
                debug_lines.append(
                    f"{sq} -> {format_weighted_tag(db_name, weight)}"
                    f" [{label}] ({score:.2f})"
                    f" [{', '.join(reasons)} - kept: user input]"
                )
            else:
                # Semantic/fuzzy guess violated a rule — try alternative
                alt = engine.find_one(
                    raw,
                    threshold=threshold,
                    exclude=ban or None,
                    allowed_filtering=allowed,
                )
                if alt:
                    adb, alabel, ascore = alt
                    out_tags.append(format_weighted_tag(adb, weight))
                    debug_lines.append(
                        f"{sq} -> {format_weighted_tag(adb, weight)}"
                        f" [{alabel}] ({ascore:.2f})"
                        f" [alt: {db_name} was blocked]"
                    )
                else:
                    debug_lines.append(
                        f"{sq} -> [suppressed: {db_name} blocked,"
                        f" no alternative]"
                    )

        return (", ".join(out_tags), "\n".join(debug_lines))
