"""Project-level desired-state capability manifest.

Lets Core answer "what capabilities does this project require" directly, without
inspecting generated source -- the gap noted in project memory
(``project_capability_provisioner_architecture``) and confirmed empty by inspection:
``ProjectCapabilityReconciliation.desired_snapshot``
(``src/domain/feature_packs/models.py``) only snapshots installed Feature Packs, and
nothing elsewhere aggregates Auth + Forms + Feature Packs + future capabilities into
one typed, per-project desired-state list.

This is deliberately the minimal typed shape needed to feed
``src.domain.capabilities.dependencies.resolve_dependencies`` from one project's
declared capabilities -- not a fourth parallel manifest format. Reconciling this with
``p2hs/app-blocks-plan.json`` / ``generation-plan.json`` is left to the following
Codex tranche; this module does not read or write either of those.
"""
from __future__ import annotations

from typing import Any

from pydantic import BaseModel, ConfigDict, Field

from src.domain.capabilities.dependencies import CapabilityDependency


class ProjectCapabilitySpec(BaseModel):
    """One capability's desired state for one project: which version, whether it's
    enabled, its configuration, and what it depends on."""

    model_config = ConfigDict(extra="forbid", frozen=True)

    key: str
    version: int
    enabled: bool = True
    config: dict[str, Any] = Field(default_factory=dict)
    dependencies: tuple[CapabilityDependency, ...] = Field(default_factory=tuple)


class ProjectCapabilityManifest(BaseModel):
    """The full set of capabilities one project declares. Disabled capabilities are
    kept (not omitted) so "declared but off" stays distinguishable from "never
    declared" -- the same distinction ``CapabilityState.DECLARED`` already draws for
    a single capability."""

    model_config = ConfigDict(extra="forbid", frozen=True)

    capabilities: tuple[ProjectCapabilitySpec, ...] = Field(default_factory=tuple)

    def get(self, key: str) -> ProjectCapabilitySpec | None:
        for spec in self.capabilities:
            if spec.key == key:
                return spec
        return None

    def enabled_roots(self) -> dict[str, int]:
        """Enabled capabilities as a ``resolve_dependencies(roots=...)`` input."""
        return {spec.key: spec.version for spec in self.capabilities if spec.enabled}

    def dependency_map(self) -> dict[str, tuple[CapabilityDependency, ...]]:
        """Enabled capabilities' own declared dependencies, as
        ``resolve_dependencies(dependencies=...)`` expects."""
        return {
            spec.key: spec.dependencies
            for spec in self.capabilities
            if spec.enabled and spec.dependencies
        }
