"""Component library validators — Phase 11.2."""
from __future__ import annotations

import re
from typing import Any

from app.models.types import (
    ComponentKind,
    ComponentStatus,
    ComponentVersionStatus,
)
from app.validators.foundation import ValidationError, validate_non_empty
from shared.exceptions import AppError

SLOT_KEY_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_\-.]{0,99}$")

COMPONENT_STATUS_TRANSITIONS: dict[ComponentStatus, frozenset[ComponentStatus]] = {
    ComponentStatus.DRAFT: frozenset(
        {ComponentStatus.ACTIVE, ComponentStatus.ARCHIVED}
    ),
    ComponentStatus.ACTIVE: frozenset(
        {ComponentStatus.DEPRECATED, ComponentStatus.ARCHIVED}
    ),
    ComponentStatus.DEPRECATED: frozenset(
        {ComponentStatus.ACTIVE, ComponentStatus.ARCHIVED}
    ),
    ComponentStatus.ARCHIVED: frozenset(),
}

VERSION_STATUS_TRANSITIONS: dict[
    ComponentVersionStatus, frozenset[ComponentVersionStatus]
] = {
    ComponentVersionStatus.DRAFT: frozenset({ComponentVersionStatus.PUBLISHED}),
    ComponentVersionStatus.PUBLISHED: frozenset({ComponentVersionStatus.RETIRED}),
    ComponentVersionStatus.RETIRED: frozenset(),
}


def validate_slot_key(slot_key: str) -> str:
    slot_key = validate_non_empty(slot_key, "slot_key")
    if not SLOT_KEY_RE.match(slot_key):
        raise ValidationError(
            "slot_key نامعتبر است",
            {"field": "slot_key", "pattern": SLOT_KEY_RE.pattern},
        )
    return slot_key


def validate_component_kind(kind: ComponentKind | str) -> ComponentKind:
    if isinstance(kind, ComponentKind):
        return kind
    try:
        return ComponentKind(kind)
    except ValueError as exc:
        raise ValidationError(
            "kind نامعتبر است",
            {"field": "kind", "allowed": [k.value for k in ComponentKind]},
        ) from exc


def ensure_component_status_transition(
    current: ComponentStatus, target: ComponentStatus
) -> None:
    if current == target:
        return
    allowed = COMPONENT_STATUS_TRANSITIONS.get(current, frozenset())
    if target not in allowed:
        raise AppError(
            "گذار وضعیت کامپوننت مجاز نیست",
            error_code="invalid_component_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )


def ensure_version_status_transition(
    current: ComponentVersionStatus, target: ComponentVersionStatus
) -> None:
    if current == target:
        return
    allowed = VERSION_STATUS_TRANSITIONS.get(current, frozenset())
    if target not in allowed:
        raise AppError(
            "گذار وضعیت نسخه کامپوننت مجاز نیست",
            error_code="invalid_component_version_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )


def ensure_version_mutable(version: Any) -> None:
    if getattr(version, "is_immutable", False) or getattr(
        version, "status", None
    ) == ComponentVersionStatus.PUBLISHED:
        raise AppError(
            "نسخه منتشرشده قابل ویرایش نیست",
            error_code="component_version_immutable",
            status_code=409,
        )
    if getattr(version, "status", None) == ComponentVersionStatus.RETIRED:
        raise AppError(
            "نسخه بازنشسته‌شده قابل ویرایش نیست",
            error_code="component_version_retired",
            status_code=409,
        )


def ensure_schema_payload(payload: dict | None, field: str) -> dict | None:
    if payload is None:
        return None
    if not isinstance(payload, dict):
        raise ValidationError(f"{field} باید شیء JSON باشد", {"field": field})
    return payload
