"""Module-level registry for file-based data/bind definitions.

Used by data.py files in directory-based apps. Decorators register
into a thread-local registry which is collected after each file import.
"""

from __future__ import annotations

from typing import Callable

from nextpy_server.registry import action as action  # noqa: F401 — re-export
from nextpy_server.registry import defaults as defaults  # noqa: F401 — re-export


class _Registry:
    def __init__(self):
        self.sources: dict[str, Callable] = {}
        self.derivations: list[tuple[str, str, Callable]] = []
        self.views: dict[str, tuple[str, list[str]]] = {}
        self.rpcs: dict[str, tuple[Callable, str | None]] = {}

    def clear(self):
        self.sources.clear()
        self.derivations.clear()
        self.views.clear()
        self.rpcs.clear()


_current = _Registry()


class DeferredDataSource:
    """Returned by @data() in registry mode. Supports .derive() and .view() chaining."""

    def __init__(self, name: str):
        self._name = name

    def derive(self, name: str | None = None):
        def decorator(fn: Callable) -> DeferredDataSource:
            resolved = name if name is not None else fn.__name__
            _current.derivations.append((self._name, resolved, fn))
            return DeferredDataSource(resolved)

        return decorator

    def view(self, name: str | None = None):
        def decorator(fn: Callable) -> None:
            resolved = name if name is not None else fn.__name__
            sql = fn()
            if not isinstance(sql, str):
                raise TypeError(f"@<data>.view('{resolved}') must return a SQL string, got {type(sql).__name__}")
            _current.views[resolved] = (sql.strip(), [self._name])
            return None

        return decorator

    def rpc(self, name: str | None = None):
        def decorator(fn: Callable) -> Callable:
            resolved = name if name is not None else fn.__name__
            _current.rpcs[resolved] = (fn, self._name)
            return fn

        return decorator


def data(name: str | None = None):
    """Register a data source. Name defaults to function name."""

    def decorator(fn: Callable) -> DeferredDataSource:
        resolved = name if name is not None else fn.__name__
        _current.sources[resolved] = fn
        return DeferredDataSource(resolved)

    return decorator


def derive(parent_name: str, name: str | None = None):
    """Cross-file derivation: derive from a table defined in another data.py."""

    def decorator(fn: Callable) -> DeferredDataSource:
        resolved = name if name is not None else fn.__name__
        _current.derivations.append((parent_name, resolved, fn))
        return DeferredDataSource(resolved)

    return decorator


def view(name: str | None = None, *, depends_on: list[str]):
    """Register a named SQL view. The decorated function must return a SQL string."""

    def decorator(fn: Callable) -> None:
        resolved = name if name is not None else fn.__name__
        sql = fn()
        if not isinstance(sql, str):
            raise TypeError(f"@view('{resolved}') must return a SQL string, got {type(sql).__name__}")
        _current.views[resolved] = (sql.strip(), depends_on)
        return None

    return decorator


def rpc(name: str | None = None):
    """Register a standalone RPC function."""

    def decorator(fn: Callable) -> Callable:
        resolved = name if name is not None else fn.__name__
        _current.rpcs[resolved] = (fn, None)
        return fn

    return decorator


def collect() -> dict:
    """Collect all registered data items and reset the registry.

    Also collects bindings from nextpy_server.registry.
    """
    from nextpy_server.registry import collect as collect_bindings

    bindings_result = collect_bindings()

    result = {
        "sources": dict(_current.sources),
        "derivations": list(_current.derivations),
        "bindings": bindings_result["bindings"],
        "actions": bindings_result["actions"],
        "views": dict(_current.views),
        "rpcs": dict(_current.rpcs),
    }
    _current.clear()
    return result
