"""Class model of the Arazzo specification."""
# generated by datamodel-codegen:
#   filename:  schema.yaml
#   timestamp: 2024-12-07T14:24:13+00:00

from __future__ import annotations

from abc import ABC, abstractmethod
from enum import StrEnum
from typing import Annotated, Any, Literal, SupportsIndex

from pydantic import BaseModel, Field, RootModel

# from pyarazzo.utils import load_spec
from pyarazzo import utils


class ArazzoVisitor(ABC):
    """Abstract Arazzo Visitor class implementing the visitor pattern.

    This interface defines methods for visiting different types of Arazzo specification elements.
    Implementations of this interface can perform various operations (e.g., document generation,
    validation, transformation) on Arazzo specifications by traversing their structure.
    """

    @abstractmethod
    def visit_specification(self, instance: ArazzoSpecification) -> None:
        """Visit an Arazzo specification.

        Args:
            instance (ArazzoSpecification): The Arazzo specification to visit.
        """

    @abstractmethod
    def visit_workflow(self, instance: Workflow) -> None:
        """Visit a workflow definition.

        Args:
            instance (Workflow): The workflow to visit.
        """

    @abstractmethod
    def visit_step(self, instance: Step) -> None:
        """Visit a step within a workflow.

        Args:
            instance (Step): The step to visit.
        """

    @abstractmethod
    def visit_components(self, instance: ComponentsObject) -> None:
        """Visit a components object containing reusable definitions.

        Args:
            instance (ComponentsObject): The components object to visit.
        """

    @abstractmethod
    def visit_info(self, instance: Info) -> None:
        """Visit metadata information about the Arazzo description.

        Args:
            instance (Info): The info object containing metadata to visit.
        """

    @abstractmethod
    def visit_source_description(self, instance: SourceDescriptionObject) -> None:
        """Visit a source description (e.g., OpenAPI specification reference).

        Args:
            instance (SourceDescriptionObject): The source description to visit.
        """

    @abstractmethod
    def visit_criterion_expression_type(self, instance: CriterionExpressionTypeObject) -> None:
        """Visit a criterion expression used for conditional logic.

        Args:
            instance (CriterionExpressionTypeObject): The criterion expression to visit.
        """

    @abstractmethod
    def visit_reusable(self, instance: ReusableObject) -> None:
        """Visit a reusable object component.

        Args:
            instance (ReusableObject): The reusable object to visit.
        """

    @abstractmethod
    def visit_parameter(self, instance: ParameterObject) -> None:
        """Visit a parameter definition.

        Args:
            instance (ParameterObject): The parameter object to visit.
        """

    @abstractmethod
    def visit_payload_replacement(self, instance: PayloadReplacementObject) -> None:
        """Visit a payload replacement operation.

        Args:
            instance (PayloadReplacementObject): The payload replacement to visit.
        """


class ArazzoElement(BaseModel):
    """Base class for all Arazzo elements."""

    @abstractmethod
    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept a visitor to process this element."""


class Info(ArazzoElement):
    """The object provides metadata about API workflows defined in this Arazzo document. The metadata MAY be used by the clients if needed."""

    title: Annotated[
        str,
        Field(description="A human readable title of the Arazzo Description"),
    ]
    summary: Annotated[
        str,
        Field(None, description="A short summary of the Arazzo Description"),
    ]
    description: Annotated[
        str,
        Field(
            None,
            description="A description of the purpose of the workflows defined. CommonMark syntax MAY be used for rich text representation",
        ),
    ]
    version: Annotated[
        str,
        Field(
            ...,
            description="The version identifier of the Arazzo document (which is distinct from the Arazzo Specification version)",
        ),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_info(self)


class SourceType(StrEnum):
    """The type of source description."""

    arazzo = "arazzo"
    openapi = "openapi"


class SourceDescriptionObject(ArazzoElement):
    """Describes a source description (such as an OpenAPI description) that will be referenced by one or more workflows described within an Arazzo Description."""

    name: Annotated[
        str,
        Field(
            ...,
            description="A unique name for the source description",
            pattern=r"^[A-Za-z0-9_\\-]+$",
        ),
    ]
    url: Annotated[
        str,
        Field(..., description="A URL to a source description to be used by a workflow"),
    ]
    type: Annotated[
        SourceType,
        Field(None, description="The type of source description"),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_source_description(self)


class CriterionExpressionTypeObjectType(StrEnum):
    """The type of condition to be applied."""

    jsonpath = "jsonpath"
    xpath = "xpath"
    jsonpointer = "jsonpointer"


class CriterionExpressionTypeObjectVersion(StrEnum):
    """A short hand string representing the version of the expression type."""

    JSONPATH_DRAFT = "draft-goessner-dispatch-jsonpath-00"
    JSONPATH_RFC = "rfc9535"
    XPATH10 = "xpath-10"
    XPATH20 = "xpath-20"
    XPATH30 = "xpath-30"
    XPATH31 = "xpath-31"
    JSONPOINTER = "rfc6901"


class CriterionExpressionTypeObject(ArazzoElement):
    """An object used to describe the type and version of an expression used within a Criterion Object."""

    type: Annotated[CriterionExpressionTypeObjectType, Field(description="The type of condition to be applied")]
    version: Annotated[
        CriterionExpressionTypeObjectVersion,
        Field(
            description="A short hand string representing the version of the expression type",
        ),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_criterion_expression_type(self)


class SuccessActionObjectType(StrEnum):
    """The type of action to take."""

    end = "end"
    goto = "goto"


class FailureActionObjectType(StrEnum):
    """The type of action to take."""

    end = "end"
    goto = "goto"
    retry = "retry"


class ReusableObject(ArazzoElement):
    """A simple object to allow referencing of objects contained within the Components Object."""

    reference: Annotated[
        str,
        Field(description="A runtime expression used to reference the desired object"),
    ]
    value: Annotated[
        str | int | float,
        Field(None, description="lists a value of the referenced parameter"),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_reusable(self)


class In(StrEnum):
    """The location of the parameter."""

    path = "path"
    query = "query"
    header = "header"
    cookie = "cookie"


class ParameterObject(ArazzoElement):
    """Describes a single step parameter. A unique parameter is defined by the combination of a name and in fields."""

    name: Annotated[str, Field(..., description="The name of the parameter")]
    in_: Annotated[
        In | None,
        Field(None, alias="in", description="The named location of the parameter"),
    ]
    value: Annotated[
        Any,
        Field(..., description="The value to pass in the parameter"),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_parameter(self)


class PayloadReplacementObject(ArazzoElement):
    """Describes a location within a payload (e.g., a request body) and a value to set within the location."""

    target: Annotated[
        str,
        Field(
            ...,
            description="A JSONPath, JSON Pointer, or XPath Expression which MUST be resolved against the request body",
        ),
    ]
    target_selector_type: Annotated[
        Any,
        Field(None, description="The selector expression type to use (e.g., `jsonpath`, `xpath`, or `jsonpointer`)", alias="targetSelectorType"),
    ]
    value: Annotated[
        Any,
        Field(..., description="The value to set at the location defined by the target. May be a literal, a runtime expression string, or a selector object."),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_payload_replacement(self)


class ExpressionTypeObject(BaseModel):
    """An object used to describe the type and version of an expression used within a Criterion Object or Selector Object."""

    type: Annotated[
        Literal["jsonpath", "xpath", "jsonpointer"],
        Field(..., description="The type of selector to use"),
    ]
    version: Annotated[
        str,
        Field(..., description="A short hand string representing the version of the expression type"),
    ]


class SelectorType(RootModel):
    """A selector expression type that may be a simple string enum or an Expression Type Object for version-specific support."""

    root: Annotated[Literal["jsonpointer", "jsonpath", "xpath"] | ExpressionTypeObject, Field(...)]


class SelectorObject(BaseModel):
    """An object which enables fine-grained traversal and precise data selection from structured data."""

    context: Annotated[
        str,
        Field(..., description="A valid Runtime Expression which MUST evaluate to structured data (e.g., `$response.body`), and sets the context for the selector to be applied on"),
    ]
    selector: Annotated[
        str,
        Field(..., description="A selector expression (e.g., `$.items[0].id`, `/Envelope/Item`) in the form of JSONPath expression, XPath expression, or JSON Pointer expression"),
    ]
    type: Annotated[
        Any,
        Field(..., description="The selector expression type to use (e.g., `jsonpath`, `xpath`, or `jsonpointer`) or an Expression Type Object for older version support"),
    ]


class StepId(RootModel):
    """Unique string to represent the step."""

    root: Annotated[
        str,
        Field(..., description="Unique string to represent the step", pattern="[A-Za-z0-9_\\-]+"),
    ]

    @classmethod
    def validate(cls, value: Any) -> Any:
        """Validate the workflow ID."""
        if not isinstance(value, str):
            raise TypeError("StepId must be a string")
        if not value.isalnum() and not all(c in "_-" for c in value):
            raise ValueError("StepId must match pattern [A-Za-z0-9_-]+")
        return value

    def __str__(self) -> str:
        """Return the workflow ID as a string."""
        return self.root

    def __repr__(self) -> str:
        """Return a string representation of the WorkflowId."""
        return f"WorkflowId('{self.root}')"

    def __hash__(self) -> int:
        """Return the hash of the workflow ID."""
        return hash(self.root)

    # String-like behavior
    def replace(self, old: str, new: str, count: SupportsIndex = -1) -> str:
        """Replace occurrences of a substring with another substring."""
        return self.root.replace(old, new, count)

    def upper(self) -> str:
        """Return the workflow ID in uppercase."""
        return self.root.upper()

    def lower(self) -> str:
        """Return the workflow ID in lowercase."""
        return self.root.lower()

    def __add__(self, other: str) -> str:
        """Allow concatenation with a string."""
        return self.root + other

    def __radd__(self, other: str) -> str:
        """Allow concatenation with a string."""
        return other + self.root

    def __eq__(self, other: object) -> bool:
        """Check equality with another WorkflowId or string."""
        if isinstance(other, StepId):
            return self.root == other.root
        if isinstance(other, str):
            return self.root == other
        return False


# class WorkflowId(RootModel):
#     """The workflowId referencing an existing workflow within the Arazzo Description to transfer to upon success of the step."""

#     root: Annotated[
#         str,
#         Field(..., description="Unique string to represent the workflow", pattern="[A-Za-z0-9_\\-]+"),
#     ]

#     def __str__(self) -> str:
#         return self.root

#     def __repr__(self) -> str:
#         return f"StepId('{self.root}')"

#     def __eq__(self, other: Any) -> bool:
#         if isinstance(other, StepId):
#             return self.root == other.root
#         elif isinstance(other, str):
#             return self.root == other
#         return False


class WorkflowId(RootModel):
    """The workflowId referencing an existing workflow within the Arazzo Description to transfer to upon success of the step."""

    root: Annotated[
        str,
        Field(..., description="Unique string to represent the workflow", pattern="^[A-Za-z0-9_-]+$"),
    ]

    @classmethod
    def validate(cls, value: Any) -> Any:
        """Validate the workflow ID."""
        if not isinstance(value, str):
            raise TypeError("StepId must be a string")
        if not value.isalnum() and not all(c in "_-" for c in value):
            raise ValueError("StepId must match pattern [A-Za-z0-9_-]+")
        return value

    def __str__(self) -> str:
        """Return the workflow ID as a string."""
        return self.root

    def __repr__(self) -> str:
        """Return a string representation of the WorkflowId."""
        return f"WorkflowId('{self.root}')"

    def __hash__(self) -> int:
        """Return the hash of the workflow ID."""
        return hash(self.root)

    # String-like behavior
    def replace(self, old: str, new: str, count: SupportsIndex = -1) -> str:
        """Replace occurrences of a substring with another substring."""
        return self.root.replace(old, new, count)

    def upper(self) -> str:
        """Return the workflow ID in uppercase."""
        return self.root.upper()

    def lower(self) -> str:
        """Return the workflow ID in lowercase."""
        return self.root.lower()

    def __add__(self, other: str) -> str:
        """Allow concatenation with a string."""
        return self.root + other

    def __radd__(self, other: str) -> str:
        """Allow concatenation with a string."""
        return other + self.root

    def __eq__(self, other: object) -> bool:
        """Check equality with another WorkflowId or string."""
        if isinstance(other, StepId):
            return self.root == other.root
        if isinstance(other, str):
            return self.root == other
        return False


class RequestBodyObject(BaseModel):
    """A single request body describing the Content-Type and request body content to be passed by a step to an operation."""

    content_type: Annotated[
        str,
        Field(None, description="The Content-Type for the request content", alias="contentType"),
    ]
    payload: Annotated[
        Any,
        Field(None, description="A value representing the request body payload. "),
    ]
    replacements: Annotated[
        list[PayloadReplacementObject],
        Field(
            [],
            description="A list of locations and values to list within a payload",
        ),
    ]


class CriterionObjectConditiontype(StrEnum):
    """Type of the condition."""

    SIMPLE = "simple"
    REGEX = "regex"
    JSONPATH = "jsonpath"
    XPATH = "xpath"


class CriterionObject(BaseModel):
    """An object used to specify the context, conditions, and condition types that can be used to prove or satisfy assertions specified in Step Object successCriteria, Success Action Object criteria, and Failure Action Object criteria."""

    context: Annotated[
        RuntimeExpression,
        Field(
            None,
            description="A Runtime Expression used to set the context for the condition to be applied on.",
        ),
    ]
    condition: Annotated[
        str,
        Field(
            ...,
            description="A Runtime Expression used to set the context for the condition to be applied on.",
        ),
    ]
    type: Annotated[
        CriterionObjectConditiontype,
        Field(
            CriterionObjectConditiontype.SIMPLE,
            description="The type of condition to be applied",
        ),
    ]


class SuccessActionObject(BaseModel):
    """A single success action which describes an action to take upon success of a workflow step."""

    name: Annotated[str, Field(description="The name of the success action")]
    type: Annotated[SuccessActionObjectType, Field(description="The type of action to take")]
    workflow_id: Annotated[
        WorkflowId,
        Field(
            None,
            description="The workflowId referencing an existing workflow within the Arazzo description to transfer to upon success of the step",
            alias="workflowId",
        ),
    ]
    step_id: Annotated[
        StepId,
        Field(None, description="The stepId to transfer to upon success of the step", alias="stepId"),
    ]
    criteria: Annotated[
        list[CriterionObject],
        Field(
            [],
            description="A list of assertions to determine if this action SHALL be executed",
            min_length=1,
        ),
    ]


class FailureActionObject(BaseModel):
    """A single failure action which describes an action to take upon failure of a workflow step."""

    name: Annotated[str, Field(description="The name of the failure action")]
    type: Annotated[FailureActionObjectType, Field(description="The type of action to take")]
    workflow_id: Annotated[
        WorkflowId,
        Field(
            None,
            description="The workflowId referencing an existing workflow within the Arazzo description to transfer to upon failure of the step",
            alias="workflowId",
        ),
    ]
    step_id: Annotated[StepId, Field(None, description="The stepId to transfer to upon failure of the step", alias="stepId")]
    retry_after: Annotated[
        float | None,
        Field(
            None,
            description="A non-negative decimal indicating the seconds to delay after the step failure before another attempt SHALL be made",
            ge=0.0,
            alias="retryAfter",
        ),
    ]
    retry_limit: Annotated[
        int,
        Field(
            None,
            description="A non-negative integer indicating how many attempts to retry the step MAY be attempted before failing the overall step",
            ge=0,
            alias="retryLimit",
        ),
    ]
    criteria: Annotated[
        list[CriterionObject],
        Field(
            description="A list of assertions to determine if this action SHALL be executed",
        ),
    ] = []


class Step(ArazzoElement):
    """Describes a single workflow step which MAY be a call to an API operation (OpenAPI Operation Object) or another Workflow Object."""

    step_id: Annotated[StepId, Field(..., description="Unique string to represent the step", alias="stepId")]
    description: Annotated[
        str,
        Field(
            None,
            description="A description of the step. CommonMark syntax MAY be used for rich text representation",
        ),
    ]
    operation_id: Annotated[
        str,
        Field(
            None,
            description="The name of an existing, resolvable operation, as defined with a unique operationId and existing within one of the sourceDescriptions",
            alias="operationId",
        ),
    ]
    operation_path: Annotated[
        str,
        Field(
            None,
            description="A reference to a Source combined with a JSON Pointer to reference an operation",
            alias="operationPath",
        ),
    ]
    workflow_id: Annotated[
        WorkflowId,
        Field(
            None,
            description="The workflowId referencing an existing workflow within the Arazzo description",
            alias="workflowId",
        ),
    ]
    parameters: Annotated[
        list[ParameterObject | ReusableObject],
        Field(
            [],
            description="A list of parameters that MUST be passed to an operation or workflow as referenced by operationId, operationPath, or workflowId",
        ),
    ]
    request_body: Annotated[
        RequestBodyObject | None,
        Field(
            None,
            description="The request body to pass to an operation as referenced by operationId or operationPath",
            alias="requestBody",
        ),
    ]
    success_criteria: Annotated[
        list[CriterionObject],
        Field(
            [],
            description="A list of assertions to determine the success of the step",
            min_length=1,
            alias="successCriteria",
        ),
    ]
    on_success: Annotated[
        list[SuccessActionObject | ReusableObject],
        Field(
            [],
            description="An array of success action objects that specify what to do upon step success",
            alias="onSuccess",
        ),
    ]
    on_failure: Annotated[
        list[FailureActionObject | ReusableObject],
        Field(
            [],
            description="An array of failure action objects that specify what to do upon step failure",
            alias="onFailure",
        ),
    ]
    outputs: Annotated[
        dict[str, Any],
        Field(
            dict,
            description="A map between a friendly name and a dynamic output value defined using a runtime expression",
        ),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_step(self)


class RuntimeExpression(RootModel):
    """A  runtime expression allows values to be defined based on information that will be available within the HTTP message in an actual API call, or within objects serialized from the Arazzo document such as workflows or steps."""

    root: Annotated[
        str,
        Field(..., description="", pattern="^$\\$sourceDescriptions\\.([A-Za-z0-9_\\-]+)\\.([A-Za-z0-9_\\-]+)$"),
    ]

    @classmethod
    def validate(cls, value: Any) -> Any:
        """Validate the workflow ID."""
        if not isinstance(value, str):
            raise TypeError("StepId must be a string")
        if not value.isalnum() and not all(c in "_-" for c in value):
            raise ValueError("StepId must match pattern [A-Za-z0-9_-]+")
        return value

    def __str__(self) -> str:
        return self.root

    def __hash__(self) -> int:
        """Return the hash of the workflow ID."""
        return hash(self.root)

    def __repr__(self) -> str:
        """Return a string representation of the WorkflowId."""
        return f"WorkflowId('{self.root}')"

    # String-like behavior
    def replace(self, old: str, new: str, count: SupportsIndex = -1) -> str:
        """Replace occurrences of a substring with another substring."""
        return self.root.replace(old, new, count)

    def upper(self) -> str:
        """Return the workflow ID in uppercase."""
        return self.root.upper()

    def lower(self) -> str:
        """Return the workflow ID in lowercase."""
        return self.root.lower()

    def __add__(self, other: str) -> str:
        """Allow concatenation with a string."""
        return self.root + other

    def __radd__(self, other: str) -> str:
        """Allow concatenation with a string."""
        return other + self.root

    def __eq__(self, other: object) -> bool:
        """Check equality with another WorkflowId or string."""
        if isinstance(other, StepId):
            return self.root == other.root
        if isinstance(other, str):
            return self.root == other
        return False


class Workflow(ArazzoElement):
    """Describes the steps to be taken across one or more APIs to achieve an objective."""

    workflow_id: Annotated[
        WorkflowId,
        Field(..., description="Unique string to represent the workflow", alias="workflowId"),
    ]
    summary: Annotated[
        str,
        Field(None, description="A summary of the purpose or objective of the workflow"),
    ]
    description: Annotated[
        str,
        Field(
            None,
            description="A description of the workflow. CommonMark syntax MAY be used for rich text representation",
        ),
    ]
    inputs: Annotated[
        dict,
        Field(
            None,
            description="A JSON Schema 2020-12 object representing the input parameters used by this workflow",
        ),
    ]
    depends_on: Annotated[
        list[str],
        Field(
            [],
            description="A list of workflows that MUST be completed before this workflow can be processed",
            alias="dependsOn",
        ),
    ]
    steps: Annotated[
        list[Step],
        Field(
            ...,
            description="An ordered list of steps where each step represents a call to an API operation or to another workflow",
            min_length=1,
        ),
    ]
    success_actions: Annotated[
        list[SuccessActionObject | ReusableObject],
        Field(
            [],
            description="A list of success actions that are applicable for all steps described under this workflow",
            alias="successActions",
        ),
    ]
    failure_actions: Annotated[
        list[FailureActionObject | ReusableObject],
        Field(
            [],
            description="A list of failure actions that are applicable for all steps described under this workflow",
            alias="failureActions",
        ),
    ]
    outputs: Annotated[
        dict[str, Any],
        Field(None, description="A map between a friendly name and a dynamic output value"),
    ]
    parameters: Annotated[
        list[ParameterObject | ReusableObject],
        Field(
            None,
            description="A list of parameters that are applicable for all steps described under this workflow",
        ),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_workflow(self)


class ComponentsObject(ArazzoElement):
    """Holds a set of reusable objects for different aspects of the Arazzo Specification."""

    inputs: Annotated[
        dict,
        Field(
            None,
            description="An object to hold reusable JSON Schema 2020-12 schemas to be referenced from workflow inputs",
        ),
    ]
    parameters: Annotated[
        dict[str, ParameterObject],
        Field(
            None,
            description="An object to hold reusable Parameter Objects",
        ),
    ]
    success_actions: Annotated[
        dict[str, SuccessActionObject],
        Field(None, description="An object to hold reusable Success Actions Objects", alias="successActions"),
    ]
    failure_actions: Annotated[
        dict[str, FailureActionObject],
        Field(None, description="An object to hold reusable Failure Actions Objects", alias="failureActions"),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Accept instance of Arazzo Visitor."""
        return visitor.visit_components(self)


class ArazzoSpecification(ArazzoElement):
    """This is the root object of the Arazzo Description."""

    arazzo: Annotated[
        str,
        Field(
            ...,
            description="The version number of the Arazzo Specification",
            pattern=r"^1\.1\.\d+(-.+)?$",
        ),
    ]
    info: Annotated[
        Info,
        Field(
            ...,
            description="Provides metadata about the workflows contain within the Arazzo Description.",
        ),
    ]
    source_descriptions: Annotated[
        list[SourceDescriptionObject],
        Field(
            ...,
            description="A list of source descriptions such as Arazzo or OpenAPI",
            min_length=1,
            alias="sourceDescriptions",
        ),
    ]
    workflows: Annotated[
        list[Workflow],
        Field(..., description="A list of workflows", min_length=1),
    ]
    components: Annotated[
        ComponentsObject,
        Field(None, description="A list of workflows"),
    ]

    def accept(self, visitor: ArazzoVisitor) -> None:
        """Allows an Arazzo visitor to traverse the instance.

        Args:
            visitor (ArazzoVisitor): instance of an Arazzo visitor

        """
        return visitor.visit_specification(self)


class ArazzoSpecificationLoader:
    """Helper class to load an Arazzo specific ation from a file or URL."""

    @staticmethod
    def load(path_or_url: str) -> ArazzoSpecification:
        """Load and arazzo specification.

        Args:
            path_or_url (str): url or path

        Returns:
            ArazzoSpecification: _description_
        """
        spec_dict = utils.load_spec(path_or_url)
        return ArazzoSpecification(**spec_dict)
