"""``SchemaSource`` ABC + ``SchemaValidationIssue`` shape.

See: ``DESIGN.md`` §5 (Schema source priority).

Every schema source produces a uniform ``list[SchemaValidationIssue]``
for a given payload. The check that consumes a SchemaSource turns
each issue into a :class:`~mgf.apiprobe.core.finding.Finding` —
keeping the schema-side and finding-side concerns separate.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any


@dataclass(frozen=True, slots=True)
class SchemaValidationIssue:
    """One mismatch between a payload and a schema.

    Schema-source-agnostic — both the Pydantic and (future) OpenAPI
    backends emit this shape so checks consume one type.
    """

    path: str
    """JSONPath-style location (``$.users[0].email``) or empty for whole-doc errors."""

    message: str
    """Human-readable description of the mismatch."""

    expected: str
    """Short expected-shape description (``"int"``, ``"string with email format"``)."""

    actual: str
    """Short actual-shape description (``"null"``, ``"int 42"``)."""


class SchemaSource(ABC):
    """Abstract base for schema validators.

    Implementations:

    - :class:`mgf.apiprobe.schema.pydantic.PydanticSchemaSource` — shipping today
    - ``OpenApiSchemaSource`` — future add (OpenAPI ingestion)
    - ``JsonSchemaSource`` — future add (raw JSON Schema)
    """

    @abstractmethod
    def validate(self, payload: Any) -> list[SchemaValidationIssue]:
        """Validate ``payload`` against the schema.

        Returns an empty list on full conformance, or a list of
        :class:`SchemaValidationIssue` objects describing every
        mismatch. Implementations SHOULD report every issue, not just
        the first — fail-fast belongs to the consuming check.
        """

    @abstractmethod
    def describe(self) -> str:
        """Return a short human-readable description of the schema.

        Surfaces in Finding evidence when the schema fails. Pydantic
        backends return the model's qualified name; OpenAPI backends
        return the path of the spec file + the operation id.
        """


__all__ = [
    "SchemaSource",
    "SchemaValidationIssue",
]
