"""
Private helper for lazy-importing chromadb in the rag package.

Shared by retriever.py and vector_store.py to avoid duplicating
the probe + ensure + error-builder code.  See
docs/DEVELOPER_GUIDE.md "Optional dependency handling" for the
broader pattern.

Why this module exists
----------------------
chromadb (and its transitive opentelemetry / protobuf dependencies)
can fail to import for reasons beyond "not installed":

  - Version conflicts in transitive deps surface as TypeError,
    RuntimeError, etc. — NOT always ImportError.  (Observed:
    `TypeError: Descriptors cannot be created directly` when
    protobuf is too new for chromadb's bundled _pb2.py files.)

If retriever.py and vector_store.py did eager top-level imports of
chromadb, those modules would fail to load entirely in any env
where the chromadb stack is unusable — breaking unrelated callers
(prompt-template functions, etc.).

This helper provides:

  - `ensure_chroma()`  — lazy import + cached probe.  Returns
                          (chromadb_module, Chroma_class) on
                          success, (None, None) on failure.
  - `is_chroma_available()` — boolean probe for callers.
  - `chroma_import_error()` — captured error string for diagnostics.
  - `chroma_unavailable_error()` — pre-built RuntimeError with
                                    install hint.

Consumers call `ensure_chroma()` inside functions that need
chromadb and raise `chroma_unavailable_error()` if it returns
(None, None).  Functions that don't need chromadb work normally
in any env.

CRITICAL: this module catches Exception (not just ImportError)
because the chromadb failure mode is a TypeError raised from
inside opentelemetry's import-time code.  ImportError-only
catches would miss it.
"""
from __future__ import absolute_import, division, print_function


# Tri-state cache: None = not yet probed; True = available; False = unavailable
_CHROMA_PROBE_STATE = None
_CHROMA_IMPORT_ERROR = None
_CHROMA_MODULE = None
_CHROMA_CLASS = None


def ensure_chroma():
    """Lazy-import chromadb module and langchain_chroma.Chroma.

    Returns:
        (chromadb_module, Chroma_class) tuple on success.
        (None, None) on failure.  The captured error message is
        available via chroma_import_error().

    Caches the result after the first call.  Repeated calls do
    not re-attempt the import.
    """
    global _CHROMA_PROBE_STATE, _CHROMA_IMPORT_ERROR
    global _CHROMA_MODULE, _CHROMA_CLASS
    if _CHROMA_PROBE_STATE is not None:
        return _CHROMA_MODULE, _CHROMA_CLASS
    try:
        import chromadb as _chromadb_mod
        from langchain_chroma import Chroma as _Chroma
        _CHROMA_MODULE = _chromadb_mod
        _CHROMA_CLASS = _Chroma
        _CHROMA_PROBE_STATE = True
    except Exception as e:
        # Broad catch: chromadb's protobuf conflict surfaces as
        # TypeError, not ImportError.  See module docstring.
        _CHROMA_IMPORT_ERROR = "%s: %s" % (type(e).__name__, e)
        _CHROMA_PROBE_STATE = False
    return _CHROMA_MODULE, _CHROMA_CLASS


def is_chroma_available():
    """Return True if chromadb / langchain_chroma is usable in this env."""
    chromadb_mod, _ = ensure_chroma()
    return chromadb_mod is not None


def chroma_import_error():
    """Return the captured import error message, or None if not yet probed
    or available."""
    return _CHROMA_IMPORT_ERROR


def chroma_unavailable_error():
    """Build a RuntimeError describing the chromadb unavailability with
    install hint suitable for raising at function-call time.

    The hint covers both the "not installed" case and the more subtle
    "installed but transitive opentelemetry-proto out of date" case
    that was observed in v118 on a linux conda env (protobuf >= 4.x
    rejecting chromadb's bundled _pb2.py files from opentelemetry-
    proto 1.11.1, which were generated by an old protoc).
    """
    err = _CHROMA_IMPORT_ERROR or "unknown"
    return RuntimeError(
        "chromadb / langchain_chroma is required for this feature "
        "but is not usable in the current environment.\n"
        "Error: %s\n"
        "\n"
        "If the packages are not installed:\n"
        "  pip install langchain_chroma chromadb\n"
        "\n"
        "If they ARE installed and you see a protobuf TypeError "
        "above, opentelemetry-proto is likely out of date and ships\n"
        "_pb2.py files incompatible with newer protobuf.  Try:\n"
        "  pip install --upgrade opentelemetry-proto "
        "opentelemetry-exporter-otlp-proto-grpc\n"
        "\n"
        "Quickest workaround (no package changes, slower runtime):\n"
        "  export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python"
        % err)


def _reset_probe_for_tests():
    """Test-only: clear the probe cache so the next call re-runs.

    Used by tst_optional_dep_resilience.py to simulate different
    states across tests.  NOT part of the public API.
    """
    global _CHROMA_PROBE_STATE, _CHROMA_IMPORT_ERROR
    global _CHROMA_MODULE, _CHROMA_CLASS
    _CHROMA_PROBE_STATE = None
    _CHROMA_IMPORT_ERROR = None
    _CHROMA_MODULE = None
    _CHROMA_CLASS = None
