# ============================================================
# src/accountable_record/stage_020_contract/version_authority.py
# ============================================================
"""Resolve canonical version authority for stage 020 contract context.

WHY:
    Stage 020 needs to know which version source is authoritative for the
    target repository. That source should be resolved from repository metadata
    and manifest-schema facts, not from hardcoded filenames.

OBS:
    This module does not perform validation diagnostics. It resolves a
    structural source description. Callers decide whether absence or mismatch is
    a diagnostic.

OBS:
    Interim policy, using the current manifest schema:
    - Contract repositories use the already-loaded manifest field
      [contract].contract_version.
    - Non-contract repositories use the citation file declared by
      [citation].cff, usually CITATION.cff but not hardcoded here.

    If manifest-schema.toml later grows an explicit version_authority section,
    this module can switch to that without changing stage callers.
"""

from dataclasses import dataclass
from pathlib import Path

from accountable_record.boundary.toml import is_table
from accountable_record.pipeline.manifest_schema import ManifestSchema


@dataclass(frozen=True)
class VersionAuthority:
    """Resolved canonical version authority.

    Attributes:
        source_path: Repository-relative source path.
        source_field: Field path inside the source, when applicable.
        source_kind: Human-readable kind, e.g. "manifest" or "citation".
        repo_class: Manifest-declared repository class.
    """

    source_path: str
    source_field: str
    source_kind: str
    repo_class: str


def resolve_version_authority(
    *,
    manifest: dict[str, object],
    manifest_path: Path,
    schema: ManifestSchema,
) -> VersionAuthority:
    """Resolve canonical version authority from manifest/schema data.

    Args:
        manifest: Parsed target SE_MANIFEST/MANIFEST data.
        manifest_path: Repository-relative or absolute manifest path being used.
        schema: Parsed manifest schema accessor.

    Returns:
        Resolved version authority.

    Raises:
        ValueError: If required manifest shape is missing.
    """
    repo = _required_table(manifest, "repo", "manifest")
    repo_class = _required_string(repo, "class", "[repo]")

    # Prefer future explicit schema policy if present.
    explicit = schema.canonical_version_authority(repo_class)
    if explicit is not None:
        return VersionAuthority(
            source_path=explicit.source_file,
            source_field=explicit.source_field or "version",
            source_kind="schema-declared",
            repo_class=repo_class,
        )

    if repo_class == "contract":
        return VersionAuthority(
            source_path=manifest_path.name,
            source_field="contract.contract_version",
            source_kind="manifest",
            repo_class=repo_class,
        )

    citation = _required_table(manifest, "citation", "manifest")
    cff_path = _required_string(citation, "cff", "[citation]")

    return VersionAuthority(
        source_path=cff_path,
        source_field="version",
        source_kind="citation",
        repo_class=repo_class,
    )


def _required_table(
    parent: dict[str, object],
    key: str,
    context: str,
) -> dict[str, object]:
    """Return a required mapping field."""
    value = parent.get(key)
    if not is_table(value):
        msg = f"{context} requires [{key}] table"
        raise ValueError(msg)
    return value


def _required_string(
    table: dict[str, object],
    key: str,
    context: str,
) -> str:
    """Return a required non-empty string field."""
    value = table.get(key)
    if not isinstance(value, str) or not value.strip():
        msg = f"{context} requires non-empty string field {key!r}"
        raise ValueError(msg)
    return value.strip()
