from __future__ import annotations

import logging
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Callable, Mapping

    from kele.grounder import GroundedRule
    from kele.grounder.grounded_rule_ds._nodes import _AssertionNode
    from kele.syntax import CompoundTerm, Constant, Rule, Variable

logger = logging.getLogger(__name__)


def register_assertion_check_hook(
    grounded_rule: GroundedRule,
    *,
    rule_name: str | None = None,
    vars_filter: Mapping[str, str] | None = None,
    on_match: Callable[[Rule, _AssertionNode, dict[Variable, Constant | CompoundTerm], bool], None] | None = None,
    break_on_match: bool = False,
) -> None:
    """
    Register a built-in hook that observes assertion check results.

    This is a user-facing inspection hook: it lets you capture whether a specific
    assertion evaluation (for a given variable binding) is True/False, and optionally
    stop on matches for deep debugging.

    Example:
        def log_match(rule, assertion, combination, result):
            print(rule.name, assertion.content, combination, result)

        register_assertion_check_hook(
            grounded_rule,
            rule_name="rule_3",
            vars_filter={"p1": "f", "p2": "a", "p13": "b", "p14": "c"},
            on_match=log_match,
            break_on_match=True,
        )
    """
    normalized_vars_filter = dict(vars_filter) if vars_filter is not None else None

    def _hook(
        *,
        rule: Rule,
        assertion: _AssertionNode,
        combination: dict[Variable, Constant | CompoundTerm],
        result: bool,
    ) -> None:
        if rule_name and rule.name != rule_name:
            return

        combination_str = {str(k): str(v) for k, v in combination.items()}
        if normalized_vars_filter:
            for key, value in normalized_vars_filter.items():
                if combination_str.get(key) != value:
                    return

        if on_match is None:
            logger.debug(
                "Assertion check hook: rule=%s content=%s combination=%s result=%s",
                rule,
                assertion.content,
                combination_str,
                result,
            )
        else:
            on_match(rule, assertion, combination, result)

        if break_on_match:
            breakpoint()  # noqa: T100

    grounded_rule.register_hook("assertion_check", _hook)


class BuiltinHookEnabler:
    """
    Enabler for built-in hooks by name.

    This class does not register hooks by itself. Create an instance and call
    ``enable`` to attach a built-in hook to a grounded rule.

    Example:
        hooks = BuiltinHookEnabler()
        hooks.enable(
            grounded_rule,
            "assertion_check",
            rule_name="rule_3",
            vars_filter={"p1": "f"},
        )
    """

    def __init__(self) -> None:
        self._hooks: dict[str, Callable[..., None]] = {
            "assertion_check": register_assertion_check_hook,
        }

    def available_hooks(self) -> list[str]:
        """
        Return the names of available built-in hooks.
        """
        return sorted(self._hooks)

    def enable(self, grounded_rule: GroundedRule, name: str, **kwargs: object) -> None:
        """
        Enable a built-in hook by name.
        """
        if name not in self._hooks:
            raise KeyError(f"Unknown built-in hook: {name}")
        self._hooks[name](grounded_rule, **kwargs)

    def enable_many(self, grounded_rule: GroundedRule, names: list[str], **kwargs: object) -> None:
        """
        Enable multiple built-in hooks by name.
        """
        for hook_name in names:
            self.enable(grounded_rule, hook_name, **kwargs)
