"""Operation dataclass and validation for the form patch engine."""

from __future__ import annotations

from dataclasses import dataclass
from enum import StrEnum
from typing import Any


class OperationType(StrEnum):
    """Supported patch operation types."""

    SET = "set"
    ADD = "add"
    REMOVE = "remove"
    MOVE = "move"
    REPLACE_ALL = "replace_all"


@dataclass(frozen=True)
class Operation:
    """A single form patch operation.

    Immutable dataclass describing one atomic change to a Form.io schema.
    Use ``from_dict`` to construct from a plain dictionary (e.g. parsed JSON).
    """

    op: OperationType
    key: str | None = None
    parent_key: str | None = None
    properties: dict[str, Any] | None = None
    component: dict[str, Any] | None = None
    position: int | None = None
    prop: str | None = None
    match: str | None = None
    value: str | None = None

    def __post_init__(self) -> None:
        # Coerce a plain string to OperationType.
        if isinstance(self.op, str) and not isinstance(self.op, OperationType):
            object.__setattr__(self, "op", OperationType(self.op))

    @classmethod
    def from_dict(cls, d: dict[str, Any]) -> Operation:
        """Create an Operation from a plain dictionary.

        Unknown keys are silently ignored.
        """
        known_fields = {
            "op",
            "key",
            "parent_key",
            "properties",
            "component",
            "position",
            "prop",
            "match",
            "value",
        }
        filtered = {k: v for k, v in d.items() if k in known_fields}
        return cls(**filtered)


# ── Validation rules per operation type ──────────────────────────────

_REQUIRED_FIELDS: dict[OperationType, list[str]] = {
    OperationType.SET: ["key", "properties"],
    OperationType.ADD: ["component"],
    OperationType.REMOVE: ["key"],
    OperationType.MOVE: ["key"],
    OperationType.REPLACE_ALL: ["prop", "match", "value"],
}


def validate_operation(op: Operation) -> list[str]:
    """Validate an Operation and return a list of error messages.

    Returns an empty list when the operation is valid.
    """
    errors: list[str] = []
    required = _REQUIRED_FIELDS.get(op.op, [])
    for field_name in required:
        if getattr(op, field_name) is None:
            errors.append(f"{op.op.value} operation requires '{field_name}' to be set")

    # Empty properties is a no-op — reject early
    if (
        op.op == OperationType.SET
        and op.properties is not None
        and len(op.properties) == 0
    ):
        errors.append("set operation 'properties' must not be empty")

    return errors
