"""Semantic registry: entities, fields, relations, actions, workflows.

This is the *metadata layer* AI uses to understand the platform:

* What entities exist (``Lead``, ``Ticket``, ``Invoice``, ...).
* Which fields they expose and what types they are.
* Which entities are related and how.
* Which actions an agent may invoke against them.
* Which workflows are wired in.

All registration happens in-process at app start. No DB tables — this is a
declarative description of the codebase, not user data.
"""

from __future__ import annotations

import re
from dataclasses import dataclass

_IDENT = re.compile(r"^[a-z][a-z0-9_]*$")
_DOTTED = re.compile(r"^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$")


class SemanticError(ValueError):
    """Raised by the semantic registry on invalid input or duplicate keys."""


_FIELD_TYPES = frozenset(
    {"string", "text", "integer", "decimal", "boolean", "date", "datetime", "uuid", "json", "ref"},
)
_RELATION_KINDS = frozenset({"one_to_one", "one_to_many", "many_to_one", "many_to_many"})


@dataclass(frozen=True)
class FieldSpec:
    name: str
    type: str
    label_key: str = ""
    required: bool = False
    description: str = ""

    def __post_init__(self) -> None:
        if not _IDENT.match(self.name):
            raise SemanticError(f"invalid field name {self.name!r}")
        if self.type not in _FIELD_TYPES:
            raise SemanticError(
                f"field {self.name!r}: unknown type {self.type!r}; "
                f"allowed: {sorted(_FIELD_TYPES)}",
            )


@dataclass(frozen=True)
class RelationSpec:
    name: str
    target: str           # dotted "module.entity"
    kind: str             # one_to_one | one_to_many | many_to_one | many_to_many
    description: str = ""

    def __post_init__(self) -> None:
        if not _IDENT.match(self.name):
            raise SemanticError(f"invalid relation name {self.name!r}")
        if not _DOTTED.match(self.target):
            raise SemanticError(
                f"relation {self.name!r}: target must be 'module.entity', got {self.target!r}",
            )
        if self.kind not in _RELATION_KINDS:
            raise SemanticError(
                f"relation {self.name!r}: unknown kind {self.kind!r}; "
                f"allowed: {sorted(_RELATION_KINDS)}",
            )


@dataclass(frozen=True)
class EntitySpec:
    key: str                                # dotted "module.entity"
    label_key: str
    fields: tuple[FieldSpec, ...] = ()
    relations: tuple[RelationSpec, ...] = ()
    description: str = ""
    actions: tuple[str, ...] = ()           # action keys defined elsewhere
    workflows: tuple[str, ...] = ()         # workflow definition keys

    def __post_init__(self) -> None:
        if not _DOTTED.match(self.key):
            raise SemanticError(f"entity key must be 'module.entity', got {self.key!r}")
        names = [f.name for f in self.fields]
        if len(set(names)) != len(names):
            raise SemanticError(f"entity {self.key!r}: duplicate field names")
        rel_names = [r.name for r in self.relations]
        if len(set(rel_names)) != len(rel_names):
            raise SemanticError(f"entity {self.key!r}: duplicate relation names")

    def field(self, name: str) -> FieldSpec:
        for f in self.fields:
            if f.name == name:
                return f
        raise SemanticError(f"entity {self.key!r}: unknown field {name!r}")


_ENTITIES: dict[str, EntitySpec] = {}


def register_entity(spec: EntitySpec) -> EntitySpec:
    """Register or replace an entity. Idempotent on equal specs."""
    existing = _ENTITIES.get(spec.key)
    if existing is not None and existing != spec:
        raise SemanticError(
            f"entity {spec.key!r} already registered with a different definition",
        )
    _ENTITIES[spec.key] = spec
    return spec


def get_entity(key: str) -> EntitySpec:
    try:
        return _ENTITIES[key]
    except KeyError as exc:
        raise SemanticError(f"unknown entity {key!r}") from exc


def list_entities() -> list[EntitySpec]:
    return sorted(_ENTITIES.values(), key=lambda s: s.key)


def reset_for_tests() -> None:
    _ENTITIES.clear()


def serialize_entity(spec: EntitySpec) -> dict:
    return {
        "key": spec.key,
        "label_key": spec.label_key,
        "description": spec.description,
        "fields": [
            {
                "name": f.name,
                "type": f.type,
                "label_key": f.label_key,
                "required": f.required,
                "description": f.description,
            }
            for f in spec.fields
        ],
        "relations": [
            {
                "name": r.name,
                "target": r.target,
                "kind": r.kind,
                "description": r.description,
            }
            for r in spec.relations
        ],
        "actions": list(spec.actions),
        "workflows": list(spec.workflows),
    }
