"""
Primitive Libraries System

Provides modular loading of primitives. Core primitives are always available,
additional primitive libraries can be loaded on-demand with scoped availability.

Usage in sexp:
    ;; Load at recipe level - available throughout
    (primitives math :path "primitive_libs/math.py")

    ;; Or use with-primitives for scoped access
    (with-primitives "image"
      (blur frame 3))  ;; blur only available inside

    ;; Nested scopes work
    (with-primitives "math"
      (with-primitives "color"
        (hue-shift frame (* (sin t) 30))))

Library file format (primitive_libs/math.py):
    import math

    def prim_sin(x): return math.sin(x)
    def prim_cos(x): return math.cos(x)

    PRIMITIVES = {
        'sin': prim_sin,
        'cos': prim_cos,
    }
"""

import importlib.util
from pathlib import Path
from typing import Dict, Callable, Any, Optional

# Cache of loaded primitive libraries
_library_cache: Dict[str, Dict[str, Any]] = {}

# Core primitives - always available, cannot be overridden
CORE_PRIMITIVES: Dict[str, Any] = {}


def register_core_primitive(name: str, fn: Callable):
    """Register a core primitive that's always available."""
    CORE_PRIMITIVES[name] = fn


def load_primitive_library(name: str, path: Optional[str] = None) -> Dict[str, Any]:
    """
    Load a primitive library by name or path.

    Args:
        name: Library name (e.g., "math", "image", "color")
        path: Optional explicit path to library file

    Returns:
        Dict of primitive name -> function
    """
    # Check cache first
    cache_key = path or name
    if cache_key in _library_cache:
        return _library_cache[cache_key]

    # Find library file
    if path:
        lib_path = Path(path)
    else:
        # Look in standard locations
        lib_dir = Path(__file__).parent
        lib_path = lib_dir / f"{name}.py"

        if not lib_path.exists():
            raise ValueError(f"Primitive library '{name}' not found at {lib_path}")

    if not lib_path.exists():
        raise ValueError(f"Primitive library file not found: {lib_path}")

    # Load the module
    spec = importlib.util.spec_from_file_location(f"prim_lib_{name}", lib_path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)

    # Get PRIMITIVES dict from module
    if not hasattr(module, 'PRIMITIVES'):
        raise ValueError(f"Primitive library '{name}' missing PRIMITIVES dict")

    primitives = module.PRIMITIVES

    # Cache and return
    _library_cache[cache_key] = primitives
    return primitives


def get_library_names() -> list:
    """Get names of available primitive libraries."""
    lib_dir = Path(__file__).parent
    return [p.stem for p in lib_dir.glob("*.py") if p.stem != "__init__"]


def clear_cache():
    """Clear the library cache (useful for testing)."""
    _library_cache.clear()
