"""Rust extension compatibility shim.

Centralises all ``nexus_runtime`` imports so that the slim ``nexus-fs``
package (which does not depend on the Rust extension) can still import
``nexus.core.*`` modules without crashing at import time.

Every symbol is re-exported as its original name.  When ``nexus_runtime``
is unavailable the symbol is set to ``None`` and call-sites must
guard with ``if _Symbol is not None:`` before use.

Usage in other modules::

    from nexus._rust_compat import RustDCache  # None when Rust unavailable

This avoids scattering try/except blocks across dozens of files.
"""

from __future__ import annotations

from typing import Any

# -- Attempt bulk import of the Rust extension --------------------------------

try:
    import nexus_runtime as _nf

    RUST_AVAILABLE: bool = True
except ModuleNotFoundError:
    # Module genuinely not installed — slim/nexus-fs mode.
    _nf = None  # type: ignore[assignment]
    RUST_AVAILABLE = False
except Exception:
    # Module is installed but broken (ImportError, NameError, ABI skew,
    # missing transitive dep, init-time crash). Catch broad Exception to
    # prevent ANY import-time failure from escaping the shim.
    _nf = None  # type: ignore[assignment]
    RUST_AVAILABLE = False

# Whether the import found a module at all (even if broken).
# ModuleNotFoundError = not installed. Other ImportError = installed but broken.
# hash_fast uses this to decide: "no extension" (SHA-256 ok for pure-Python
# environments) vs "broken extension" (SHA-256 never ok — fail closed).
RUST_EXTENSION_INSTALLED: bool = True  # assume installed unless ModuleNotFoundError
try:
    import importlib.util

    RUST_EXTENSION_INSTALLED = importlib.util.find_spec("nexus_runtime") is not None
except Exception:
    pass


# -- Capability-group validation -----------------------------------------------
# Symbol groups and required Kernel methods come from _kernel_api_groups.py,
# which is auto-generated by scripts/codegen_kernel_abi.py.
# Symbols are validated per capability group. A missing symbol disables only
# its group, not all of Rust. This prevents e.g. a missing VolumeEngine from
# disabling BLAKE3 hashing.
#
# The "core" group is special: if any core symbol is missing, ALL Rust is
# disabled (these are required for basic kernel operation).

try:
    from nexus._kernel_api_groups import (
        KERNEL_REQUIRED_METHODS as _KERNEL_REQUIRED_METHODS,
    )
    from nexus._kernel_api_groups import (
        MODULE_CAPABILITY_GROUPS as _CAPABILITY_GROUPS,
    )
except ImportError:
    # _kernel_api_groups.py is missing — codegen hasn't been run.
    # This is a developer-environment problem, not a production state.
    # Emit a loud warning so it's not silently ignored.
    import warnings as _warnings

    _warnings.warn(
        "nexus._kernel_api_groups not found — Kernel method validation is disabled. "
        "Run: python scripts/codegen_kernel_abi.py",
        stacklevel=2,
    )
    _CAPABILITY_GROUPS = {
        "core": ("PyKernel",),
    }
    _KERNEL_REQUIRED_METHODS = frozenset()

# Per-group availability flags (set after validation below)
RUST_HASH_AVAILABLE: bool = False

_REBUILD_CMD = "cd rust/kernel && maturin develop --release"

# Initialised to empty/False — filled in by the validation block below when
# RUST_AVAILABLE is True.  Declaring them here makes them unconditionally
# defined so the code after the block does not get possibly-undefined warnings.
_group_ok: dict[str, bool] = {}
_kernel_methods_stale: bool = False

if RUST_AVAILABLE:
    import logging as _logging

    _log = _logging.getLogger(__name__)

    # Compute per-group availability BEFORE the core kill-switch clears _nf.
    # This ensures hash flag reflects actual symbol presence, not whether
    # an unrelated core symbol (e.g. SyscallEngine) is missing.
    _group_ok = {}
    for _group, _symbols in _CAPABILITY_GROUPS.items():
        _missing = [s for s in _symbols if not hasattr(_nf, s)]
        _group_ok[_group] = len(_missing) == 0
        if _missing:
            _log.warning(
                "nexus_runtime binary is stale — %s group missing symbols %s. "
                "%s features disabled. Rebuild: %s",
                _group,
                _missing,
                _group,
                _REBUILD_CMD,
            )

    _core_disabled = not _group_ok.get("core", False)
    if _core_disabled:
        RUST_AVAILABLE = False
    else:
        # -- Kernel class method validation ------------------------------------
        # Check that the installed Kernel class exposes all expected methods.
        # A stale binary may import fine but be missing recently-added methods.
        # This catches the exact bug from Issue #3712 (close_all_pipes, etc.).
        _kernel_cls = getattr(_nf, "PyKernel", None)
        if _kernel_cls is not None and _KERNEL_REQUIRED_METHODS:
            _missing_methods = sorted(
                m for m in _KERNEL_REQUIRED_METHODS if not hasattr(_kernel_cls, m)
            )
            if _missing_methods:
                _log.warning(
                    "nexus_runtime.PyKernel is stale — binary missing %d method(s): %s. Rebuild: %s",
                    len(_missing_methods),
                    _missing_methods,
                    _REBUILD_CMD,
                )
                # Disable all Rust features — stale Kernel is unsafe to use.
                RUST_AVAILABLE = False
                _kernel_methods_stale = True

# Build set of symbols whose group failed validation — these return None.
_disabled_symbols: set[str] = set()
if RUST_EXTENSION_INSTALLED and not RUST_AVAILABLE and _nf is None:
    # Extension installed but import completely failed — module not usable.
    # Disable ALL symbols since we can't call any of them.
    for _syms in _CAPABILITY_GROUPS.values():
        _disabled_symbols.update(_syms)
elif RUST_EXTENSION_INSTALLED and _nf is not None:
    if not RUST_AVAILABLE:
        # Core ABI failure or kernel methods stale — disable ALL symbols to
        # prevent partial Rust usage against an unsafe binary (Issue #3712,
        # #3951). Without this, working sub-groups (e.g. prefix helpers)
        # would still execute against a binary whose core ABI is broken.
        for _syms in _CAPABILITY_GROUPS.values():
            _disabled_symbols.update(_syms)
    else:
        # Extension imported and core OK, but some non-core groups may be
        # incomplete. Only disable symbols from failing groups — working
        # groups stay live.
        for _g, _syms in _CAPABILITY_GROUPS.items():
            if not _group_ok.get(_g, False):
                _disabled_symbols.update(_syms)

# Capability flags (set after kill switch so a stale-core binary cannot
# leak a True flag while the Rust symbol itself is disabled — #3951).
RUST_HASH_AVAILABLE = RUST_AVAILABLE and bool(_group_ok) and _group_ok.get("hash", False)

# Snapshot the module for re-exports.
_nf_snapshot: Any = _nf


def _get(name: str) -> Any:
    """Get an attribute from nexus_runtime, or None if unavailable.

    Returns None if the module is absent OR if the symbol belongs to a
    capability group that failed validation (e.g. core symbols when core
    is disabled due to version skew).
    """
    if _nf_snapshot is None:
        return None
    if name in _disabled_symbols:
        return None
    return getattr(_nf_snapshot, name, None)


# -- Re-exports (alphabetical) -----------------------------------------------
# Each symbol is either the real Rust class/function or None.

# Core kernel (Issue #1868 — unified PyKernel replaces SyscallEngine+RustDCache+
# RustPathRouter+PathTrie+HookRegistry+ObserverRegistry)
PyKernel = _get("PyKernel")
PySysReadResult = _get("PySysReadResult")
PySysWriteResult = _get("PySysWriteResult")

# Legacy aliases — None on 0.9.19+ (removed in favor of PyKernel)
RustDCache = _get("RustDCache")
HookRegistry = _get("HookRegistry")
ObserverRegistry = _get("ObserverRegistry")
PathTrie = _get("PathTrie")

# Classes still exported as standalone
BloomFilter = _get("BloomFilter")
BlobPackEngine = _get("BlobPackEngine")

# Path utilities
get_ancestors = _get("get_ancestors")
get_parent = _get("get_parent")
get_parent_chain = _get("get_parent_chain")
normalize_path = _get("normalize_path")
parent_path = _get("parent_path")
path_matches_pattern = _get("path_matches_pattern")
split_path = _get("split_path")
unscope_internal_path = _get("unscope_internal_path")
validate_path = _get("validate_path")

# Hash utilities
hash_content_py = _get("hash_content_py")
hash_content_smart_py = _get("hash_content_smart_py")

# I/O utilities
read_file = _get("read_file")
read_files_bulk = _get("read_files_bulk")

# ReBAC helpers
compute_permission_single = _get("compute_permission_single")
compute_permissions_bulk = _get("compute_permissions_bulk")
expand_subjects = _get("expand_subjects")
list_objects_for_subject = _get("list_objects_for_subject")

# Mount table helpers
canonicalize_path = _get("canonicalize_path")
extract_zone_id = _get("extract_zone_id")

# Grep / search
grep_bulk = _get("grep_bulk")
grep_files_mmap = _get("grep_files_mmap")
glob_match_bulk = _get("glob_match_bulk")

# Trigram search
build_trigram_index = _get("build_trigram_index")
build_trigram_index_from_entries = _get("build_trigram_index_from_entries")
invalidate_trigram_cache = _get("invalidate_trigram_cache")
trigram_grep = _get("trigram_grep")
trigram_index_stats = _get("trigram_index_stats")
trigram_search_candidates = _get("trigram_search_candidates")

# Prefix / bitmap helpers (Issue #3951)
any_path_starts_with = _get("any_path_starts_with")
batch_prefix_check = _get("batch_prefix_check")
filter_paths_by_prefix = _get("filter_paths_by_prefix")

# Tiger bitmap helpers
any_path_accessible_tiger_cache = _get("any_path_accessible_tiger_cache")
check_permission_bitmap = _get("check_permission_bitmap")
check_permission_bitmap_batch = _get("check_permission_bitmap_batch")
filter_paths_with_tiger_cache = _get("filter_paths_with_tiger_cache")
filter_paths_with_tiger_cache_parallel = _get("filter_paths_with_tiger_cache_parallel")
intersect_paths_with_tiger_cache = _get("intersect_paths_with_tiger_cache")
tiger_cache_bitmap_stats = _get("tiger_cache_bitmap_stats")
