# ============================================================
# src/accountable_record/stage_010_identity/base/diagnostics.py
# ============================================================
"""Stage 010 identity diagnostic definitions."""

from accountable_record.pipeline.diagnostics import (
    Concern,
    DiagnosticSpec,
    Severity,
    ar_code,
    from_spec,
)
from accountable_record.stage_010_identity.base.constants import (
    CITATION_FILENAME,
    MANIFEST_FILENAME,
    PYPROJECT_FILENAME,
)

__all__ = [
    "CITATION_INVALID",
    "CITATION_MISSING",
    "DiagnosticSpec",
    "MANIFEST_INVALID",
    "MANIFEST_MISSING",
    "NAME_MISMATCH",
    "PYPROJECT_INVALID",
    "REPO_VERSION_FORBIDDEN",
    "ROOT_NOT_FOUND",
    "UNEXPECTED_ORGANIZATION",
    "UNEXPECTED_PACKAGE_NAME",
    "UNEXPECTED_PROJECT_NAME",
    "UNEXPECTED_REPOSITORY_NAME",
    "VERSION_AUTHORITY_MISSING",
    "VERSION_INVALID",
    "VERSION_MISMATCH",
    "from_spec",
]


def _root_not_found_message(*, detail: str) -> str:
    return detail


def _pyproject_invalid_message(*, detail: str) -> str:
    return f"{PYPROJECT_FILENAME} is invalid: {detail}"


def _manifest_invalid_message(*, detail: str) -> str:
    return f"{MANIFEST_FILENAME} is invalid: {detail}"


def _citation_invalid_message(*, detail: str) -> str:
    return f"{CITATION_FILENAME} is invalid: {detail}"


def _citation_missing_message(*, detail: str | None = None) -> str:
    if detail:
        return detail
    return f"{CITATION_FILENAME} is missing."


def _manifest_missing_message(*, detail: str | None = None) -> str:
    if detail:
        return detail
    return f"{MANIFEST_FILENAME} is missing."


def _version_authority_missing_message() -> str:
    return (
        f"A version is declared but {CITATION_FILENAME}, the canonical version "
        "source, has none."
    )


def _version_mismatch_message(
    *,
    pyproject_version: str | None,
    manifest_version: str | None,
    citation_version: str | None,
) -> str:
    return (
        "Version mismatch across identity files: "
        f"{PYPROJECT_FILENAME}={pyproject_version!r}, "
        f"{MANIFEST_FILENAME}={manifest_version!r}, "
        f"{CITATION_FILENAME}={citation_version!r}"
    )


def _name_mismatch_message(
    *,
    project_name: str,
    repository_name: str,
) -> str:
    return (
        "Repository name mismatch: "
        f"{PYPROJECT_FILENAME} project.name={project_name!r}, "
        f"{MANIFEST_FILENAME} repo.name={repository_name!r}"
    )


def _version_invalid_message(*, version: str | None, source: str) -> str:
    return f"{source} declares invalid version {version!r}."


def _repo_version_forbidden_message() -> str:
    return (
        f"{MANIFEST_FILENAME} repo.version is forbidden; "
        f"{CITATION_FILENAME} is the canonical version source."
    )


def _unexpected_value_message(*, expected: str, actual: str) -> str:
    return f"Expected {expected!r}, got {actual!r}."


ROOT_NOT_FOUND = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "ROOT_NOT_FOUND"),
    severity=Severity.ERROR,
    message=_root_not_found_message,
    description="The target repository root could not be resolved.",
)

PYPROJECT_INVALID = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "PYPROJECT_INVALID"),
    severity=Severity.ERROR,
    message=_pyproject_invalid_message,
    description="pyproject.toml could not be read or parsed as identity metadata.",
)

MANIFEST_INVALID = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "MANIFEST_INVALID"),
    severity=Severity.ERROR,
    message=_manifest_invalid_message,
    description="The repository manifest could not be read or parsed as identity metadata.",
)

CITATION_INVALID = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "CITATION_INVALID"),
    severity=Severity.ERROR,
    message=_citation_invalid_message,
    description="CITATION.cff could not be read or parsed as identity metadata.",
)

CITATION_MISSING = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "CITATION_MISSING"),
    severity=Severity.ERROR,
    message=_citation_missing_message,
    description="CITATION.cff is absent; canonical version source cannot be established.",
)

MANIFEST_MISSING = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "MANIFEST_MISSING"),
    severity=Severity.ERROR,
    message=_manifest_missing_message,
    description="Repository manifest is absent.",
)

VERSION_AUTHORITY_MISSING = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "VERSION_AUTHORITY_MISSING"),
    severity=Severity.ERROR,
    message=_version_authority_missing_message,
    description="A version is declared somewhere, but CITATION.cff declares no version.",
)

VERSION_MISMATCH = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "VERSION_MISMATCH"),
    severity=Severity.ERROR,
    message=_version_mismatch_message,
    description="Declared version disagrees across identity sources.",
)

NAME_MISMATCH = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "NAME_MISMATCH"),
    severity=Severity.ERROR,
    message=_name_mismatch_message,
    description="Repository name disagrees across identity sources.",
)

VERSION_INVALID = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "VERSION_INVALID"),
    severity=Severity.ERROR,
    message=_version_invalid_message,
    description="A version string is present but not a valid version.",
)

REPO_VERSION_FORBIDDEN = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "REPO_VERSION_FORBIDDEN"),
    severity=Severity.ERROR,
    message=_repo_version_forbidden_message,
    description="Manifest-level repo.version is rejected.",
)

UNEXPECTED_PROJECT_NAME = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "UNEXPECTED_PROJECT_NAME"),
    severity=Severity.ERROR,
    message=_unexpected_value_message,
    description="Project name does not match an external expected identity assertion.",
)

UNEXPECTED_PACKAGE_NAME = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "UNEXPECTED_PACKAGE_NAME"),
    severity=Severity.ERROR,
    message=_unexpected_value_message,
    description="Package name does not match an external expected identity assertion.",
)

UNEXPECTED_REPOSITORY_NAME = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "UNEXPECTED_REPOSITORY_NAME"),
    severity=Severity.ERROR,
    message=_unexpected_value_message,
    description="Repository name does not match an external expected identity assertion.",
)

UNEXPECTED_ORGANIZATION = DiagnosticSpec(
    code=ar_code(Concern.IDENTITY, "UNEXPECTED_ORGANIZATION"),
    severity=Severity.ERROR,
    message=_unexpected_value_message,
    description="Organization does not match an external expected identity assertion.",
)
