from typing import Any
from typing import Literal
from typing import Tuple
from typing import TypeVar
from typing import Union

from inline_snapshot._customize._custom import Custom

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

options = {
    "new": ["new", "old"],
    "old": ["old"],
}


def unwrap(value, which) -> Tuple[Any, bool]:
    if isinstance(value, (External, ExternalFile, GenericValue)):
        for w in options[which]:
            try:
                v = value._load_value(w)
            except ValueNotFound:
                continue
            return unwrap(v, which)[0], True
        return (..., True)

    if isinstance(value, Custom):

        def unwrap_child(value):
            return unwrap(value, which)[0]

        return value._map(unwrap_child), True

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

    if isinstance(value, Is):  # type: ignore
        return unwrap(value.value, which)[0], True

    return value, False


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

EllipsisType = type(...)


def get_snapshot_value(
    snapshot_value: Snapshot[T], which: Literal["new", "old"] = "new"
) -> Union[T, EllipsisType]:  # type: ignore[valid-type]
    """
    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.
        which: can be:

            - `"old"`: the value which is "visible" in the source is returned
            - `"new"`: the current value is returned
    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 which not in ("old", "new"):
        raise UsageError("which must be 'old' or 'new'")

    if state().active:
        return unwrap(snapshot_value, which)[0]
    else:
        return snapshot_value  # type: ignore
