"""What codegen is allowed to author when no existing catalog pack covers a requirement:
WHAT is needed (actors, actions, fields), never HOW it's enforced. The enforcement
mechanism (rls_enabled vs owner_managed vs the plain-entity/service-token pattern) is
derived deterministically in composer.py, never chosen by the model -- see that module's
docstring for why, and the three confirmed access patterns it maps onto.
"""
from __future__ import annotations

from typing import Literal, Optional

from pydantic import BaseModel, ConfigDict, Field, model_validator

from src.domain.codegen.provision_manifest import ProvisionField

ActorName = Literal["anonymous", "member", "admin", "super_admin"]
ActionName = Literal["create", "read", "update", "delete"]

# Currently-supported primitives a composed pack's `requires` may name. Anything else
# is an honest primitive_gap (composer.py), never synthesized around.
#
# No speculative primitive proliferation: a new entry here requires a demonstrated
# composition gap (a real requirement returned primitive_gap first -- see
# tests/test_e2e_composed_capabilities.py), not an anticipated one. Before adding one,
# it should answer yes to all of:
#   1. Is the missing behavior generic across domains (not specific to one pack)?
#   2. Is it deterministic (Core derives it, never the model)?
#   3. Can it be represented declaratively (configuration, not generated code)?
#   4. Does enforcement belong centrally in Cloud, at a real mutation choke point?
#   5. Can one implementation serve many feature packs?
#   6. Is every existing primitive here genuinely insufficient for it?
# A primitive_gap is a signal for a human engineering decision -- never an automatic
# trigger to fall back to generated/executable extensions (that's a separate, much
# larger architectural decision, not a fallback path for this one).
SUPPORTED_PRIMITIVES = frozenset({
    "data.entity",
    "auth.roles",
    "email.send",
    "form.public_create",
    "workflow.transition",
})


class ActorRequirement(BaseModel):
    model_config = ConfigDict(extra="forbid", strict=True)
    action: ActionName
    resource: str = Field(pattern=r"^[a-z][a-z0-9_]{0,62}$")
    actor: ActorName


class WorkflowRequirement(BaseModel):
    """Declares an enforced state-transition graph on one select field -- see
    composer.py for how `field`/`transitions` get validated against `fields` and
    turned into the workflow.transition primitive Cloud enforces at the mutation
    boundary.

    Deliberately narrow, on purpose, not because these are unsolved:
    - Structural only. Says which state changes are valid, never who may trigger one
      -- actor authorization stays entirely in access_policy/RLS, permanently a
      separate concern (not "not yet supported"). Never encode actor names in
      `transitions` itself.
    - No side effects. A permitted transition means exactly that and nothing else --
      no implied email/audit-record/webhook. A reaction to a state change is a
      different requirement (e.g. email.send), not part of this primitive.
    - One workflow field per entity. Not `list[WorkflowRequirement]` -- generalize to
      multiple independent state machines on one entity only if a real requirement
      demonstrates the need (same "no speculative proliferation" bar as
      SUPPORTED_PRIMITIVES itself)."""
    model_config = ConfigDict(extra="forbid", strict=True)
    field: str = Field(pattern=r"^[a-z][a-z0-9_]{0,62}$")
    transitions: dict[str, list[str]] = Field(min_length=1)


class CompositionRequirement(BaseModel):
    """The `compose` entry shape in p2hs/feature-packs.json. Field names/types are not
    security-relevant -- codegen names them directly, same as provision.json entities."""
    model_config = ConfigDict(extra="forbid", strict=True)
    name: str = Field(pattern=r"^[a-z][a-z0-9_]{0,40}$")
    actors: list[ActorName] = Field(min_length=1)
    requirements: list[ActorRequirement] = Field(min_length=1)
    fields: list[ProvisionField] = Field(min_length=1)
    function_slugs: list[str] = Field(default_factory=list)
    requires: list[str] = Field(default_factory=list)
    workflow: Optional[WorkflowRequirement] = None
    # Defaults to "required": if this requirement can't be composed (primitive_gap),
    # the run's final status must reflect that the request wasn't actually fulfilled
    # ("blocked"), not read as a plain "completed". Only mark "optional" when the
    # requirement is genuinely a nice-to-have the owner can live without -- codegen
    # should not default to "optional" just to keep a run looking clean.
    criticality: Literal["required", "optional"] = "required"

    @model_validator(mode="after")
    def actors_and_requirements_agree(self):
        actor_set = set(self.actors)
        for req in self.requirements:
            if req.actor not in actor_set:
                raise ValueError(f"requirement actor '{req.actor}' not declared in actors")
        return self

    @model_validator(mode="after")
    def unique_field_keys(self):
        if len({f.key for f in self.fields}) != len(self.fields):
            raise ValueError("Duplicate field keys")
        return self
