"""
typing.Protocol classes for jsonschema interfaces.
"""

# for reference material on Protocols, see
#   https://www.python.org/dev/peps/pep-0544/

from __future__ import annotations

from typing import (
    TYPE_CHECKING,
    Any,
    ClassVar,
    Iterable,
    Protocol,
    runtime_checkable,
)

# in order for Sphinx to resolve references accurately from type annotations,
# it needs to see names like `jsonschema.TypeChecker`
# therefore, only import at type-checking time (to avoid circular references),
# but use `jsonschema` for any types which will otherwise not be resolvable
if TYPE_CHECKING:
    from collections.abc import Mapping

    import referencing.jsonschema

    from jsonschema import _typing
    from jsonschema.exceptions import ValidationError
    import jsonschema
    import jsonschema.validators

# For code authors working on the validator protocol, these are the three
# use-cases which should be kept in mind:
#
# 1. As a protocol class, it can be used in type annotations to describe the
#    available methods and attributes of a validator
# 2. It is the source of autodoc for the validator documentation
# 3. It is runtime_checkable, meaning that it can be used in isinstance()
#    checks.
#
# Since protocols are not base classes, isinstance() checking is limited in
# its capabilities. See docs on runtime_checkable for detail


@runtime_checkable
class Validator(Protocol):
    """
    The protocol to which all validator classes adhere.

    Arguments:

        schema:

            The schema that the validator object will validate with.
            It is assumed to be valid, and providing
            an invalid schema can lead to undefined behavior. See
            `Validator.check_schema` to validate a schema first.

        registry:

            a schema registry that will be used for looking up JSON references

        resolver:

            a resolver that will be used to resolve :kw:`$ref`
            properties (JSON references). If unprovided, one will be created.

            .. deprecated:: v4.18.0

                `RefResolver <_RefResolver>` has been deprecated in favor of
                `referencing`, and with it, this argument.

        format_checker:

            if provided, a checker which will be used to assert about
            :kw:`format` properties present in the schema. If unprovided,
            *no* format validation is done, and the presence of format
            within schemas is strictly informational. Certain formats
            require additional packages to be installed in order to assert
            against instances. Ensure you've installed `jsonschema` with
            its `extra (optional) dependencies <index:extras>` when
            invoking ``pip``.

    .. deprecated:: v4.12.0

        Subclassing validator classes now explicitly warns this is not part of
        their public API.

    """

    #: An object representing the validator's meta schema (the schema that
    #: describes valid schemas in the given version).
    META_SCHEMA: ClassVar[Mapping]

    #: A mapping of validation keywords (`str`\s) to functions that
    #: validate the keyword with that name. For more information see
    #: `creating-validators`.
    VALIDATORS: ClassVar[Mapping]

    #: A `jsonschema.TypeChecker` that will be used when validating
    #: :kw:`type` keywords in JSON schemas.
    TYPE_CHECKER: ClassVar[jsonschema.TypeChecker]

    #: A `jsonschema.FormatChecker` that will be used when validating
    #: :kw:`format` keywords in JSON schemas.
    FORMAT_CHECKER: ClassVar[jsonschema.FormatChecker]

    #: A function which given a schema returns its ID.
    ID_OF: _typing.id_of

    #: The schema that will be used to validate instances
    schema: Mapping | bool

    def __init__(
        self,
        schema: Mapping | bool,
        registry: referencing.jsonschema.SchemaRegistry,
        format_checker: jsonschema.FormatChecker | None = None,
    ) -> None:
        ...

    @classmethod
    def check_schema(cls, schema: Mapping | bool) -> None:
        """
        Validate the given schema against the validator's `META_SCHEMA`.

        Raises:

            `jsonschema.exceptions.SchemaError`:

                if the schema is invalid

        """

    def is_type(self, instance: Any, type: str) -> bool:
        """
        Check if the instance is of the given (JSON Schema) type.

        Arguments:

            instance:

                the value to check

            type:

                the name of a known (JSON Schema) type

        Returns:

            whether the instance is of the given type

        Raises:

            `jsonschema.exceptions.UnknownType`:

                if ``type`` is not a known type

        """

    def is_valid(self, instance: Any) -> bool:
        """
        Check if the instance is valid under the current `schema`.

        Returns:

            whether the instance is valid or not

        >>> schema = {"maxItems" : 2}
        >>> Draft202012Validator(schema).is_valid([2, 3, 4])
        False

        """

    def iter_errors(self, instance: Any) -> Iterable[ValidationError]:
        r"""
        Lazily yield each of the validation errors in the given instance.

        >>> schema = {
        ...     "type" : "array",
        ...     "items" : {"enum" : [1, 2, 3]},
        ...     "maxItems" : 2,
        ... }
        >>> v = Draft202012Validator(schema)
        >>> for error in sorted(v.iter_errors([2, 3, 4]), key=str):
        ...     print(error.message)
        4 is not one of [1, 2, 3]
        [2, 3, 4] is too long

        .. deprecated:: v4.0.0

            Calling this function with a second schema argument is deprecated.
            Use `Validator.evolve` instead.
        """

    def validate(self, instance: Any) -> None:
        """
        Check if the instance is valid under the current `schema`.

        Raises:

            `jsonschema.exceptions.ValidationError`:

                if the instance is invalid

        >>> schema = {"maxItems" : 2}
        >>> Draft202012Validator(schema).validate([2, 3, 4])
        Traceback (most recent call last):
            ...
        ValidationError: [2, 3, 4] is too long

        """

    def evolve(self, **kwargs) -> Validator:
        """
        Create a new validator like this one, but with given changes.

        Preserves all other attributes, so can be used to e.g. create a
        validator with a different schema but with the same :kw:`$ref`
        resolution behavior.

        >>> validator = Draft202012Validator({})
        >>> validator.evolve(schema={"type": "number"})
        Draft202012Validator(schema={'type': 'number'}, format_checker=None)

        The returned object satisfies the validator protocol, but may not
        be of the same concrete class! In particular this occurs
        when a :kw:`$ref` occurs to a schema with a different
        :kw:`$schema` than this one (i.e. for a different draft).

        >>> validator.evolve(
        ...     schema={"$schema": Draft7Validator.META_SCHEMA["$id"]}
        ... )
        Draft7Validator(schema=..., format_checker=None)
        """
