# ============================================================
# src/accountable_record/stage_090_docs/base/diagnostics.py
# ============================================================
"""Stage 090 documentation diagnostic definitions.

WHY:
    Diagnostic codes, severities, and messages belong together. Keeping each
    diagnostic as one definition prevents code constants and message builders
    from drifting independently.

OBS:
    This module defines the AR.DC.* diagnostics stage 090 may emit. It does
    not plan pages, render content, reconcile docs, mutate pipeline state, or
    decide when diagnostics are emitted.

REQ:
    Public diagnostic names in this module are DiagnosticSpec objects.
"""

from accountable_record.pipeline.diagnostics import (
    Concern,
    DiagnosticSpec,
    Severity,
    ar_code,
    from_spec,
    from_spec_at,
)
from accountable_record.stage_090_docs.base.constants import DOCS_DIRNAME

__all__ = [
    "DOCS_DUPLICATE_PAGE",
    "DOCS_PAGE_PLAN_EMPTY",
    "DOCS_ROOT_MISSING",
    "GENERATED_PAGE_MISSING",
    "GENERATED_REQUIRED",
    "PAGE_GENERATED_MISSING",
    "from_spec",
    "from_spec_at",
]


def _generated_required_message() -> str:
    """Return a message for a missing upstream generated artifact."""
    return "s090_docs requires s080_generated to complete first."


def _docs_root_missing_message() -> str:
    """Return a message for an expected-but-absent docs root."""
    return f"documentation is expected but {DOCS_DIRNAME}/ does not exist"


def _docs_page_plan_empty_message(*, generated_count: int) -> str:
    """Return a message for an empty plan over a non-empty generated set."""
    return f"{generated_count} generated element(s) yielded no documentation page plan"


def _docs_duplicate_page_message(*, rel_path: str) -> str:
    """Return a message for two planned pages colliding on one path."""
    return f"documentation plan contains duplicate page path {rel_path!r}"


def _generated_page_missing_message(*, compact_id: str) -> str:
    """Return a message for a generated element with no planned page."""
    return f"generated element {compact_id!r} has no corresponding documentation page"


def _page_generated_missing_message(*, compact_id: str) -> str:
    """Return a message for a planned page with no generated element."""
    return (
        f"documentation page for {compact_id!r} has no corresponding generated element"
    )


GENERATED_REQUIRED = DiagnosticSpec(
    code=ar_code(Concern.DOCS, "GENERATED_REQUIRED"),
    severity=Severity.ERROR,
    message=_generated_required_message,
    description="Stage 090 requires the stage 080 generated artifact to be present.",
)

DOCS_ROOT_MISSING = DiagnosticSpec(
    code=ar_code(Concern.DOCS, "DOCS_ROOT_MISSING"),
    severity=Severity.ERROR,
    message=_docs_root_missing_message,
    description="Documentation is expected but the docs root directory is absent.",
)

DOCS_PAGE_PLAN_EMPTY = DiagnosticSpec(
    code=ar_code(Concern.DOCS, "DOCS_PAGE_PLAN_EMPTY"),
    severity=Severity.ERROR,
    message=_docs_page_plan_empty_message,
    description=(
        "The generated element set is non-empty but yielded no documentation page plan."
    ),
)

DOCS_DUPLICATE_PAGE = DiagnosticSpec(
    code=ar_code(Concern.DOCS, "DOCS_DUPLICATE_PAGE"),
    severity=Severity.ERROR,
    message=_docs_duplicate_page_message,
    description="Two planned documentation pages collide on one relative path.",
)

GENERATED_PAGE_MISSING = DiagnosticSpec(
    code=ar_code(Concern.DOCS, "GENERATED_PAGE_MISSING"),
    severity=Severity.ERROR,
    message=_generated_page_missing_message,
    description="A generated element has no corresponding planned documentation page.",
)

PAGE_GENERATED_MISSING = DiagnosticSpec(
    code=ar_code(Concern.DOCS, "PAGE_GENERATED_MISSING"),
    severity=Severity.ERROR,
    message=_page_generated_missing_message,
    description="A planned documentation page has no corresponding generated element.",
)
