from collections.abc import Callable, Iterable
from typing import (
    Any,
    NewType,
    TypeVar,
    overload,
)

from graphql import (
    GraphQLScalarLiteralParser,
    GraphQLScalarSerializer,
    GraphQLScalarValueParser,
)

from strawberry.types.scalar import ScalarWrapper, _process_scalar

_T = TypeVar("_T", bound=type | NewType)


def identity(x: _T) -> _T:  # pragma: no cover
    return x


@overload
def scalar(
    *,
    name: str | None = None,
    description: str | None = None,
    specified_by_url: str | None = None,
    serialize: GraphQLScalarSerializer = identity,
    parse_value: GraphQLScalarValueParser | None = None,
    parse_literal: GraphQLScalarLiteralParser | None = None,
    directives: Iterable[object] = (),
    authenticated: bool = False,
    inaccessible: bool = False,
    policy: list[list[str]] | None = None,
    requires_scopes: list[list[str]] | None = None,
    tags: Iterable[str] | None = (),
) -> Callable[[_T], _T]: ...


@overload
def scalar(
    cls: _T,
    *,
    name: str | None = None,
    description: str | None = None,
    specified_by_url: str | None = None,
    serialize: GraphQLScalarSerializer = identity,
    parse_value: GraphQLScalarValueParser | None = None,
    parse_literal: GraphQLScalarLiteralParser | None = None,
    directives: Iterable[object] = (),
    authenticated: bool = False,
    inaccessible: bool = False,
    policy: list[list[str]] | None = None,
    requires_scopes: list[list[str]] | None = None,
    tags: Iterable[str] | None = (),
) -> _T: ...


def scalar(
    cls: _T | None = None,
    *,
    name: str | None = None,
    description: str | None = None,
    specified_by_url: str | None = None,
    serialize: GraphQLScalarSerializer = identity,
    parse_value: GraphQLScalarValueParser | None = None,
    parse_literal: GraphQLScalarLiteralParser | None = None,
    directives: Iterable[object] = (),
    authenticated: bool = False,
    inaccessible: bool = False,
    policy: list[list[str]] | None = None,
    requires_scopes: list[list[str]] | None = None,
    tags: Iterable[str] | None = (),
) -> Any:
    """Annotates a class or type as a GraphQL custom scalar.

    Args:
        cls: The class or type to annotate
        name: The GraphQL name of the scalar
        description: The description of the scalar
        specified_by_url: The URL of the specification
        serialize: The function to serialize the scalar
        parse_value: The function to parse the value
        parse_literal: The function to parse the literal
        directives: The directives to apply to the scalar
        authenticated: Whether to add the @authenticated directive
        inaccessible: Whether to add the @inaccessible directive
        policy: The list of policy names to add to the @policy directive
        requires_scopes: The list of scopes to add to the @requires directive
        tags: The list of tags to add to the @tag directive

    Returns:
        The decorated class or type

    Example usages:

    ```python
    strawberry.federation.scalar(
        datetime.date,
        serialize=lambda value: value.isoformat(),
        parse_value=datetime.parse_date,
    )

    Base64Encoded = strawberry.federation.scalar(
        NewType("Base64Encoded", bytes),
        serialize=base64.b64encode,
        parse_value=base64.b64decode,
    )


    @strawberry.federation.scalar(
        serialize=lambda value: ",".join(value.items),
        parse_value=lambda value: CustomList(value.split(",")),
    )
    class CustomList:
        def __init__(self, items):
            self.items = items
    ```
    """
    from strawberry.federation.schema_directives import (
        Authenticated,
        Inaccessible,
        Policy,
        RequiresScopes,
        Tag,
    )

    if parse_value is None:
        parse_value = cls

    directives = list(directives)

    if authenticated:
        directives.append(Authenticated())

    if inaccessible:
        directives.append(Inaccessible())

    if policy:
        directives.append(Policy(policies=policy))

    if requires_scopes:
        directives.append(RequiresScopes(scopes=requires_scopes))

    if tags:
        directives.extend(Tag(name=tag) for tag in tags)

    def wrap(cls: _T) -> ScalarWrapper:
        return _process_scalar(
            cls,
            name=name,
            description=description,
            specified_by_url=specified_by_url,
            serialize=serialize,
            parse_value=parse_value,
            parse_literal=parse_literal,
            directives=directives,
        )

    if cls is None:
        return wrap

    return wrap(cls)


__all__ = ["scalar"]
