# ============================================================
# src/accountable_record/stage_060_exports/base/diagnostics.py
# ============================================================
"""Stage 060 export 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.EX.* diagnostics stage 060 may emit. It does
    not discover exports, reconcile catalogs, mutate pipeline state, or decide
    when diagnostics are emitted.

REQ:
    Public diagnostic names in this module are DiagnosticSpec objects. The
    underlying DiagnosticCode values are private implementation details held
    by each spec.
"""

from accountable_record.pipeline.diagnostics import (
    Concern,
    DiagnosticCode,
    DiagnosticSpec,
    Severity,
    ar_code,
    from_spec,
    from_spec_at,
)

__all__ = [
    "CATALOG_ENTRY_EXPORT_MISSING",
    "CATALOG_REQUIRED",
    "DiagnosticSpec",
    "EXPORT_ELEMENT_UNCATALOGED",
    "EXPORT_JSON_INVALID",
    "EXPORT_ROOT_MISSING",
    "from_spec",
    "from_spec_at",
]


def _catalog_required_message() -> str:
    """Return a message for a missing upstream catalog artifact."""
    return "s060_exports requires s050_catalog to complete first."


def _export_root_missing_message(*, detail: str) -> str:
    """Return a message for a missing export root."""
    return detail


def _export_json_invalid_message(*, detail: str) -> str:
    """Return a message for invalid export JSON."""
    return f"export JSON is invalid: {detail}"


def _catalog_entry_export_missing_message(*, compact_id: str) -> str:
    """Return a message for a catalog element with no export."""
    return f"catalog element {compact_id!r} has no corresponding element export"


def _export_element_uncataloged_message(*, compact_id: str) -> str:
    """Return a message for an exported element not present in the catalog."""
    return f"element export {compact_id!r} has no corresponding catalog entry"


# --- AR.EX.* : base export diagnostics --------------------------------------

_CATALOG_REQUIRED_CODE: DiagnosticCode = ar_code(
    Concern.EXPORTS,
    "CATALOG_REQUIRED",
)
_EXPORT_ROOT_MISSING_CODE: DiagnosticCode = ar_code(
    Concern.EXPORTS,
    "EXPORT_ROOT_MISSING",
)
_EXPORT_JSON_INVALID_CODE: DiagnosticCode = ar_code(
    Concern.EXPORTS,
    "EXPORT_JSON_INVALID",
)
_CATALOG_ENTRY_EXPORT_MISSING_CODE: DiagnosticCode = ar_code(
    Concern.EXPORTS,
    "CATALOG_ENTRY_EXPORT_MISSING",
)
_EXPORT_ELEMENT_UNCATALOGED_CODE: DiagnosticCode = ar_code(
    Concern.EXPORTS,
    "EXPORT_ELEMENT_UNCATALOGED",
)


CATALOG_REQUIRED = DiagnosticSpec(
    code=_CATALOG_REQUIRED_CODE,
    severity=Severity.ERROR,
    message=_catalog_required_message,
    description="Stage 060 requires the stage 050 catalog artifact to be present.",
)

EXPORT_ROOT_MISSING = DiagnosticSpec(
    code=_EXPORT_ROOT_MISSING_CODE,
    severity=Severity.ERROR,
    message=_export_root_missing_message,
    description="The expected export root is missing.",
)

EXPORT_JSON_INVALID = DiagnosticSpec(
    code=_EXPORT_JSON_INVALID_CODE,
    severity=Severity.ERROR,
    message=_export_json_invalid_message,
    description="An export JSON artifact is unreadable, unparseable, or malformed.",
)

CATALOG_ENTRY_EXPORT_MISSING = DiagnosticSpec(
    code=_CATALOG_ENTRY_EXPORT_MISSING_CODE,
    severity=Severity.ERROR,
    message=_catalog_entry_export_missing_message,
    description="A catalog element has no corresponding element export.",
)

EXPORT_ELEMENT_UNCATALOGED = DiagnosticSpec(
    code=_EXPORT_ELEMENT_UNCATALOGED_CODE,
    severity=Severity.ERROR,
    message=_export_element_uncataloged_message,
    description="An element export has no corresponding catalog entry.",
)
