"""
Lightweight Python implementation of the pipeline backend described in docs/PIPELINE_SPEC.md.

This is a reference-friendly stub that:
 - exposes the required metadata and APIs (list_ops, describe_ops, register_py_op)
 - provides a simple in-memory registry for both "cpp" and "python" ops
 - ships a PyCapsule for future C++ plugin registration (stores the raw pointer for bookkeeping)

It is not a high-performance engine; C++ ops are tracked but not executed (no FFI).
"""

from __future__ import annotations

import ctypes
from dataclasses import dataclass
from typing import Callable, Dict, List, Optional

# --- Types --------------------------------------------------------------------


class Data:
    """
    Lightweight Python stand-in for the C++ Data structure.

    Keeps a few helper fields to let Python ops share state during demos/tests:
      - samples: list of numeric samples (e.g., pH readings)
      - window: last FIFO window
      - stats: last aggregation result
      - payload: generic scratch dict
    """

    def __init__(self):
        self.samples: list = []
        self.window: list = []
        self.stats: dict = {}
        self.payload: dict = {}


OpFn = Callable[[Data], None]


class ImplKind:
    CPP = "cpp"
    PYTHON = "python"


@dataclass
class OpEntry:
    impl: str
    cpp_fn_ptr: Optional[int] = None  # raw pointer address for bookkeeping
    py_fn: Optional[OpFn] = None


# --- Registry -----------------------------------------------------------------

_registry: Dict[str, OpEntry] = {}


def register_cpp_op(name: str, fn: Optional[OpFn] = None, *, cpp_fn_ptr: Optional[int] = None) -> None:
    """Register a C++ operation. Only bookkeeping occurs here; execution stays unimplemented."""
    if name in _registry:
        raise ValueError(f"Operation '{name}' already registered")
    _registry[name] = OpEntry(impl=ImplKind.CPP, cpp_fn_ptr=cpp_fn_ptr, py_fn=fn)


def register_py_op(name: str, fn: OpFn) -> None:
    """Register a pure-Python operation callable(data)."""
    if not callable(fn):
        raise TypeError("fn must be callable")
    if name in _registry:
        raise ValueError(f"Operation '{name}' already registered")
    _registry[name] = OpEntry(impl=ImplKind.PYTHON, py_fn=fn)


def list_ops() -> List[str]:
    return list(_registry.keys())


def describe_ops() -> List[dict]:
    desc = []
    for name, entry in _registry.items():
        item = {"name": name, "impl": entry.impl}
        if entry.impl == ImplKind.PYTHON and entry.py_fn is not None:
            item["callable"] = entry.py_fn
        if entry.impl == ImplKind.CPP and entry.cpp_fn_ptr is not None:
            item["cpp_fn_ptr"] = entry.cpp_fn_ptr
        desc.append(item)
    return desc


# --- Pipeline -----------------------------------------------------------------


class Pipeline:
    def __init__(self) -> None:
        self._steps: List[str] = []

    def add_step(self, name: str) -> None:
        if name not in _registry:
            raise KeyError(f"Unknown operation '{name}'")
        self._steps.append(name)

    def run(self, data: Data) -> None:
        for name in self._steps:
            entry = _registry[name]
            if entry.impl == ImplKind.PYTHON:
                # Python ops execute directly
                entry.py_fn(data)  # type: ignore[arg-type]
            else:
                # No C++ trampoline in the stub; raise to make this explicit.
                raise NotImplementedError(
                    f"C++ op '{name}' cannot run in the Python stub backend (no FFI trampoline)."
                )


# --- PyCapsule for C++ registration ------------------------------------------


def _build_register_op_capsule():
    """
    Create a PyCapsule containing a C-callable function pointer:
      void (*register_op_c_api_t)(const char* name, void* fn_ptr)

    The pointer is stored for bookkeeping; execution is not wired.
    """

    CALLBACK = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_void_p)

    def _bridge(name_ptr, fn_ptr):
        name = name_ptr.decode("utf-8")
        register_cpp_op(name, cpp_fn_ptr=ctypes.cast(fn_ptr, ctypes.c_void_p).value)

    cfunc = CALLBACK(_bridge)

    # Build capsule: PyCapsule_New(void*, name, destructor)
    PyCapsule_New = ctypes.pythonapi.PyCapsule_New
    PyCapsule_New.restype = ctypes.py_object
    PyCapsule_New.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_void_p]

    capsule_name = b"pipeline_backend.register_op_v1"
    capsule = PyCapsule_New(ctypes.cast(cfunc, ctypes.c_void_p), capsule_name, None)
    return capsule


_register_op_capsule = _build_register_op_capsule()


# --- Metadata -----------------------------------------------------------------

__backend_metadata__ = {
    "name": "pipeline_backend",
    "version": "0.0.1",
    "api_version": "1",
    "capsule_name": "pipeline_backend.register_op_v1",
    "data_abi_tag": "Data-v1",
    "supports_python_ops": True,
}


__all__ = [
    "Data",
    "Pipeline",
    "register_py_op",
    "register_cpp_op",
    "list_ops",
    "describe_ops",
    "__backend_metadata__",
    "_register_op_capsule",
]
