"""
General utilities for store and other components.

This module provides dependency-free utilities that can be used in any context
(webapp, recipe, params, tests, CLI tools) without requiring Flask app config
or workload folder access.
"""

from functools import wraps
from flask import g, has_request_context

__all__ = ["request_cached", "invalidate_request_cache", "_has_unpublished_changes"]


def _freeze(obj):
    if isinstance(obj, (str, int, float, bool, type(None))):
        return obj
    if isinstance(obj, dict):
        return frozenset((k, _freeze(v)) for k, v in obj.items())
    if isinstance(obj, (list, tuple)):
        return tuple(_freeze(x) for x in obj)
    try:
        hash(obj)
        return obj
    except TypeError:
        return str(obj)


def request_cached(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        if not has_request_context():
            return fn(*args, **kwargs)
        cache = getattr(g, "_store_cache", None)
        if cache is None:
            cache = {}
            g._store_cache = cache
        key = (fn.__name__, _freeze(args), _freeze(kwargs))
        if key in cache:
            return cache[key]
        result = fn(*args, **kwargs)
        cache[key] = result
        return result

    return wrapper


def invalidate_request_cache(*method_names: str):
    def decorator(fn):
        @wraps(fn)
        def wrapper(self, *args, **kwargs):
            if has_request_context() and hasattr(g, "_store_cache"):
                cache = g._store_cache
                for k in list(cache):
                    if k[0] in method_names:
                        del cache[k]

            result = fn(self, *args, **kwargs)

            return result

        return wrapper

    return decorator


def _has_unpublished_changes(agent: dict) -> bool:
    pv = agent.get("published_version")
    if not pv:
        return True

    keys = (
        "name",
        "description",
        "system_prompt",
        "kb_description",
        "sample_questions",
        "llmid",
        "tools",
        "documents",
    )
    return any(agent.get(k) != pv.get(k) for k in keys)
