# This file was auto-generated by Fern from our API Definition.

"""
jsonable_encoder converts a Python object to a JSON-friendly dict
(e.g. datetimes to strings, Pydantic models to dicts).

Taken from FastAPI, and made a bit simpler
https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py
"""

import base64
import dataclasses
import datetime as dt
from enum import Enum
from pathlib import PurePath
from types import GeneratorType
from typing import Any, Callable, Dict, List, Optional, Set, Union

import pydantic
from .datetime_utils import serialize_datetime
from .pydantic_utilities import (
    IS_PYDANTIC_V2,
    encode_by_type,
    to_jsonable_with_fallback,
)

SetIntStr = Set[Union[int, str]]
DictIntStrAny = Dict[Union[int, str], Any]


def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any:
    custom_encoder = custom_encoder or {}
    # Generated SDKs use Ellipsis (`...`) as the sentinel value for "OMIT".
    # OMIT values should be excluded from serialized payloads.
    if obj is Ellipsis:
        return None
    if custom_encoder:
        if type(obj) in custom_encoder:
            return custom_encoder[type(obj)](obj)
        else:
            for encoder_type, encoder_instance in custom_encoder.items():
                if isinstance(obj, encoder_type):
                    return encoder_instance(obj)
    if isinstance(obj, pydantic.BaseModel):
        if IS_PYDANTIC_V2:
            encoder = getattr(obj.model_config, "json_encoders", {})  # type: ignore # Pydantic v2
        else:
            encoder = getattr(obj.__config__, "json_encoders", {})  # type: ignore # Pydantic v1
        if custom_encoder:
            encoder.update(custom_encoder)
        obj_dict = obj.dict(by_alias=True)
        if "__root__" in obj_dict:
            obj_dict = obj_dict["__root__"]
        if "root" in obj_dict:
            obj_dict = obj_dict["root"]
        return jsonable_encoder(obj_dict, custom_encoder=encoder)
    if dataclasses.is_dataclass(obj):
        obj_dict = dataclasses.asdict(obj)  # type: ignore
        return jsonable_encoder(obj_dict, custom_encoder=custom_encoder)
    if isinstance(obj, bytes):
        return base64.b64encode(obj).decode("utf-8")
    if isinstance(obj, Enum):
        return obj.value
    if isinstance(obj, PurePath):
        return str(obj)
    if isinstance(obj, (str, int, float, type(None))):
        return obj
    if isinstance(obj, dt.datetime):
        return serialize_datetime(obj)
    if isinstance(obj, dt.date):
        return str(obj)
    if isinstance(obj, dict):
        encoded_dict = {}
        allowed_keys = set(obj.keys())
        for key, value in obj.items():
            if key in allowed_keys:
                if value is Ellipsis:
                    continue
                encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder)
                encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder)
                encoded_dict[encoded_key] = encoded_value
        return encoded_dict
    if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)):
        encoded_list = []
        for item in obj:
            if item is Ellipsis:
                continue
            encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder))
        return encoded_list

    def fallback_serializer(o: Any) -> Any:
        attempt_encode = encode_by_type(o)
        if attempt_encode is not None:
            return attempt_encode

        try:
            data = dict(o)
        except Exception as e:
            errors: List[Exception] = []
            errors.append(e)
            try:
                data = vars(o)
            except Exception as e:
                errors.append(e)
                raise ValueError(errors) from e
        return jsonable_encoder(data, custom_encoder=custom_encoder)

    return to_jsonable_with_fallback(obj, fallback_serializer)
