# ============================================================
# src/accountable_record/stage_010_identity/versioning.py
# ============================================================
"""Version alignment checks for repository identity.

If no version exists:
  skip version alignment.

If any version exists but CITATION.cff has no version:
  report missing version authority, unless the repo kind/schema says versions
  are not applicable and the other version field is itself unexpected.

If CITATION.cff has a version:
  compare any manifest/pyproject version fields to CITATION.cff.
  Diagnostic says CITATION.cff is authoritative.
"""

from dataclasses import dataclass
from pathlib import Path

from accountable_record.boundary.toml import is_list, is_table, load_toml
from accountable_record.stage_010_identity.base.constants import PYPROJECT_FILENAME
from accountable_record.stage_010_identity.base.scalars import required_string


@dataclass(frozen=True)
class PyprojectIdentity:
    """Identity fields read from pyproject.toml."""

    project_name: str
    package_name: str
    version: str
    version_is_dynamic: bool
    raw: dict[str, object]


@dataclass(frozen=True)
class VersionAlignment:
    """Version values collected from root identity files."""

    pyproject_version: str
    manifest_version: str | None
    citation_version: str | None

    @property
    def versions_present(self) -> tuple[str, ...]:
        """Return a tuple of all version values that are present (non-None)."""
        values = [self.pyproject_version]
        if self.manifest_version:
            values.append(self.manifest_version)
        if self.citation_version:
            values.append(self.citation_version)
        return tuple(values)

    @property
    def is_aligned(self) -> bool:
        """Return true if all present version values are identical."""
        return len(set(self.versions_present)) == 1


def _resolve_version(
    data: dict[str, object],
    project: dict[str, object],
) -> tuple[str, bool]:
    """Resolve the pyproject version, returning (version, is_dynamic).

    Resolution order:
        1. static project.version, if present and non-empty.
        2. if "version" is in project.dynamic: tool.hatch.version.fallback-version.
        3. otherwise: error (nothing to align on).
    """
    static = project.get("version")
    if isinstance(static, str) and static.strip():
        return static.strip(), False

    dynamic = project.get("dynamic")
    is_dynamic = is_list(dynamic) and "version" in dynamic
    if is_dynamic:
        tool = data.get("tool")
        hatch_version: dict[str, object] = {}

        if is_table(tool):
            hatch = tool.get("hatch")
            if is_table(hatch):
                hv = hatch.get("version")
                if is_table(hv):
                    hatch_version = hv

        fallback = hatch_version.get("fallback-version")
        if isinstance(fallback, str) and fallback.strip():
            return fallback.strip(), True

        msg = (
            f"{PYPROJECT_FILENAME} declares dynamic version but has no "
            f"[tool.hatch.version].fallback-version to align against"
        )
        raise ValueError(msg)

    msg = f"{PYPROJECT_FILENAME} [project] requires non-empty string field 'version'"
    raise ValueError(msg)


def read_pyproject_identity(repo_root: Path) -> PyprojectIdentity:
    """Read project identity from pyproject.toml."""
    path = repo_root / PYPROJECT_FILENAME
    if not path.is_file():
        msg = f"Missing required file: {path}"
        raise FileNotFoundError(msg)

    data = load_toml(path)

    project = data.get("project")
    if not is_table(project):
        msg = f"{PYPROJECT_FILENAME} is missing [project]"
        raise ValueError(msg)

    project_name = required_string(project, "name", f"{PYPROJECT_FILENAME} [project]")
    version, is_dynamic = _resolve_version(data, project)

    return PyprojectIdentity(
        project_name=project_name,
        package_name=project_name.replace("-", "_"),
        version=version,
        version_is_dynamic=is_dynamic,
        raw=data,
    )


def compare_versions(
    *,
    pyproject_version: str,
    manifest_version: str | None,
    citation_version: str | None,
) -> VersionAlignment:
    """Build a version alignment record."""
    return VersionAlignment(
        pyproject_version=pyproject_version,
        manifest_version=manifest_version,
        citation_version=citation_version,
    )
