from typing import TypeVar

from ._exceptions import UsageError
from ._external._external import External
from ._external._external_file import ExternalFile
from ._external._outsource import Outsourced
from ._external._storage._protocol import StorageLookupError
from ._global_state import state
from ._is import Is
from ._snapshot.generic_value import GenericValue
from ._types import Snapshot


def unwrap(value):
    if isinstance(value, GenericValue):
        return value._visible_value()._map(lambda v: unwrap(v)[0]), True

    if isinstance(value, Outsourced):
        return (value.data, True)

    if isinstance(value, (External, Outsourced, ExternalFile)):
        try:
            return unwrap(value._load_value())[0], True
        except (UsageError, StorageLookupError):
            return (None, False)

    if isinstance(value, Is):
        return value.value, True

    return value, False


T = TypeVar("T", covariant=True)


def get_snapshot_value(snapshot_value: Snapshot[T]) -> T:
    """
    Extracts and returns the raw value stored inside a snapshot, removing all inline-snapshot-specific
    wrappers such as those generated by `external()`, `Is()`, or `snapshot()`.

    This function is primarily intended for extension authors who need direct access to the value
    of a previously stored snapshot. For standard test assertions, prefer using the snapshot directly.

    Args:
        snapshot_value: The snapshot object from which to extract the value.

    Returns:
        The unwrapped value contained in the snapshot.

    Example:
        ``` python
        from inline_snapshot import external, snapshot, get_snapshot_value

        s = snapshot([0, external("uuid:e3e70682-c209-4cac-a29f-6fbed82c07cd.json")])

        if record:
            # Store value
            assert s == [0, 5]
        else:
            # Use value from snapshot
            value = get_snapshot_value(s)
            # ... do something with value
        ```

    <!-- TODO: Find a way to test this code in the docs -->
    """
    if state().active:
        return unwrap(snapshot_value)[0]
    else:
        return snapshot_value  # type: ignore
