"""Theme & layout validators — Phase 11.3 (versioned)."""
from __future__ import annotations

from typing import Any

from app.models.types import (
    DirectionMode,
    LayoutKind,
    LayoutStatus,
    LayoutVersionStatus,
    ThemeStatus,
    ThemeVersionStatus,
)
from app.validators.foundation import ValidationError
from shared.exceptions import AppError

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

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

THEME_VERSION_STATUS_TRANSITIONS: dict[
    ThemeVersionStatus, frozenset[ThemeVersionStatus]
] = {
    ThemeVersionStatus.DRAFT: frozenset({ThemeVersionStatus.PUBLISHED}),
    ThemeVersionStatus.PUBLISHED: frozenset({ThemeVersionStatus.RETIRED}),
    ThemeVersionStatus.RETIRED: frozenset(),
}

LAYOUT_VERSION_STATUS_TRANSITIONS: dict[
    LayoutVersionStatus, frozenset[LayoutVersionStatus]
] = {
    LayoutVersionStatus.DRAFT: frozenset({LayoutVersionStatus.PUBLISHED}),
    LayoutVersionStatus.PUBLISHED: frozenset({LayoutVersionStatus.RETIRED}),
    LayoutVersionStatus.RETIRED: frozenset(),
}


def validate_direction(value: DirectionMode | str | None) -> DirectionMode | None:
    if value is None:
        return None
    if isinstance(value, DirectionMode):
        return value
    try:
        return DirectionMode(value)
    except ValueError as exc:
        raise ValidationError(
            "direction_mode نامعتبر است",
            {"field": "direction_mode", "allowed": [d.value for d in DirectionMode]},
        ) from exc


# Backwards-compatible alias used by some callers/tests.
validate_direction_mode = validate_direction


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


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


def ensure_layout_status_transition(
    current: LayoutStatus, target: LayoutStatus
) -> None:
    if current == target:
        return
    allowed = LAYOUT_STATUS_TRANSITIONS.get(current, frozenset())
    if target not in allowed:
        raise AppError(
            "گذار وضعیت لایه‌بندی مجاز نیست",
            error_code="invalid_layout_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )


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


def ensure_layout_version_status_transition(
    current: LayoutVersionStatus, target: LayoutVersionStatus
) -> None:
    if current == target:
        return
    allowed = LAYOUT_VERSION_STATUS_TRANSITIONS.get(current, frozenset())
    if target not in allowed:
        raise AppError(
            "گذار وضعیت نسخه لایه‌بندی مجاز نیست",
            error_code="invalid_layout_version_status_transition",
            status_code=409,
            details={"current": current.value, "target": target.value},
        )


def ensure_theme_version_mutable(version: Any) -> None:
    status = getattr(version, "status", None)
    if status != ThemeVersionStatus.DRAFT:
        raise AppError(
            "فقط نسخه پیش‌نویس تم قابل ویرایش است",
            error_code="theme_version_not_mutable",
            status_code=409,
            details={"status": getattr(status, "value", status)},
        )


def ensure_layout_version_mutable(version: Any) -> None:
    status = getattr(version, "status", None)
    if status != LayoutVersionStatus.DRAFT:
        raise AppError(
            "فقط نسخه پیش‌نویس لایه‌بندی قابل ویرایش است",
            error_code="layout_version_not_mutable",
            status_code=409,
            details={"status": getattr(status, "value", status)},
        )


def ensure_json_object(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


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


def ensure_tokens_payload(payload: dict | None, field: str = "tokens") -> dict | None:
    if payload is None:
        return None
    if not isinstance(payload, dict) or not payload:
        raise ValidationError(
            f"{field} باید شیء JSON غیرخالی باشد", {"field": field}
        )
    return payload


def ensure_shell_payload(payload: dict | None, field: str) -> dict | None:
    return ensure_json_object(payload, field)


def ensure_regions_payload(payload: list | None, field: str = "regions") -> list | None:
    if payload is None:
        return None
    if not isinstance(payload, list) or not payload:
        raise ValidationError(f"{field} باید آرایه غیرخالی باشد", {"field": field})
    for item in payload:
        if not isinstance(item, dict) or "key" not in item:
            raise ValidationError(
                f"هر عنصر {field} باید دارای کلید key باشد", {"field": field}
            )
    return payload


def ensure_assignable_theme(theme: Any) -> None:
    status = getattr(theme, "status", None)
    if status != ThemeStatus.ACTIVE:
        raise AppError(
            "فقط تم فعال قابل انتساب است",
            error_code="theme_not_active",
            status_code=409,
            details={"status": getattr(status, "value", status)},
        )
    if getattr(theme, "is_deleted", False):
        raise AppError(
            "تم حذف‌شده قابل انتساب نیست",
            error_code="theme_deleted",
            status_code=409,
        )


def ensure_assignable_layout(layout: Any) -> None:
    status = getattr(layout, "status", None)
    if status != LayoutStatus.ACTIVE:
        raise AppError(
            "فقط لایه‌بندی فعال قابل انتساب است",
            error_code="layout_not_active",
            status_code=409,
            details={"status": getattr(status, "value", status)},
        )
    if getattr(layout, "is_deleted", False):
        raise AppError(
            "لایه‌بندی حذف‌شده قابل انتساب نیست",
            error_code="layout_deleted",
            status_code=409,
        )


def ensure_assignable_theme_version(version: Any) -> None:
    status = getattr(version, "status", None)
    if status != ThemeVersionStatus.PUBLISHED:
        raise AppError(
            "فقط نسخه منتشرشده تم قابل انتساب است",
            error_code="theme_version_not_published",
            status_code=409,
            details={"status": getattr(status, "value", status)},
        )
    if getattr(version, "is_deleted", False):
        raise AppError(
            "نسخه حذف‌شده قابل انتساب نیست",
            error_code="theme_version_deleted",
            status_code=409,
        )


def ensure_assignable_layout_version(version: Any) -> None:
    status = getattr(version, "status", None)
    if status != LayoutVersionStatus.PUBLISHED:
        raise AppError(
            "فقط نسخه منتشرشده لایه‌بندی قابل انتساب است",
            error_code="layout_version_not_published",
            status_code=409,
            details={"status": getattr(status, "value", status)},
        )
    if getattr(version, "is_deleted", False):
        raise AppError(
            "نسخه حذف‌شده قابل انتساب نیست",
            error_code="layout_version_deleted",
            status_code=409,
        )
