from __future__ import annotations

import re
from collections.abc import Iterable
from dataclasses import dataclass
from typing import Literal, Protocol, runtime_checkable


VIEWS_MODULE_ID = "views"
CAPABILITY_VIEWS_RESOLVER = "views.resolver"
VIEW_SURFACE_CONTRACT_VERSION = "1"

ViewSurfaceKind = Literal[
    "module",
    "navigation",
    "route",
    "section",
    "action",
    "selector",
]

_SURFACE_ID_RE = re.compile(r"^[a-z][a-z0-9_.-]{2,159}$")
_SURFACE_SLUG_RE = re.compile(r"[^a-z0-9]+")


@dataclass(frozen=True, slots=True)
class ViewSurface:
    id: str
    module_id: str
    kind: ViewSurfaceKind
    label: str
    parent_id: str | None = None
    description: str | None = None
    order: int = 100
    default_visible: bool = True
    required: bool = False


@dataclass(frozen=True, slots=True)
class EffectiveView:
    view_id: str | None
    revision_id: str | None
    name: str | None
    visible_surface_ids: frozenset[str]
    locked: bool = False
    projection_active: bool = False
    provenance: tuple[dict[str, object], ...] = ()


@runtime_checkable
class ViewResolver(Protocol):
    def resolve_effective_view(
        self,
        session: object,
        *,
        tenant_id: str,
        account_id: str,
        group_ids: Iterable[str] = (),
        workflow_view_id: str | None = None,
        workflow_revision_id: str | None = None,
        workflow_surface_ids: Iterable[str] = (),
    ) -> EffectiveView: ...


def views_resolver(registry: object | None) -> ViewResolver | None:
    if (
        registry is None
        or not hasattr(registry, "has_capability")
        or not registry.has_capability(CAPABILITY_VIEWS_RESOLVER)
    ):
        return None
    capability = registry.capability(CAPABILITY_VIEWS_RESOLVER)
    return capability if isinstance(capability, ViewResolver) else None


def module_view_surface_id(module_id: str) -> str:
    return f"{module_id}.module"


def navigation_view_surface_id(module_id: str, path: str) -> str:
    return f"{module_id}.nav.{_surface_slug(path)}"


def route_view_surface_id(module_id: str, path: str) -> str:
    return f"{module_id}.route.{_surface_slug(path)}"


def validate_view_surface_id(value: str) -> bool:
    return bool(_SURFACE_ID_RE.fullmatch(value))


def _surface_slug(value: str) -> str:
    normalized = _SURFACE_SLUG_RE.sub(".", value.strip().lower()).strip(".")
    return normalized or "root"


__all__ = [
    "CAPABILITY_VIEWS_RESOLVER",
    "EffectiveView",
    "VIEWS_MODULE_ID",
    "VIEW_SURFACE_CONTRACT_VERSION",
    "ViewResolver",
    "ViewSurface",
    "ViewSurfaceKind",
    "module_view_surface_id",
    "navigation_view_surface_id",
    "route_view_surface_id",
    "validate_view_surface_id",
    "views_resolver",
]
