# ============================================================
# src/accountable_record/stage_070_resolution/base/diagnostics.py
# ============================================================
"""Stage 070 resolution 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.RS.* diagnostics stage 070 may emit. It does
    not load locks, build graphs, reconcile exports, 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,
)
from accountable_record.stage_070_resolution.base.constants import (
    ELEMENTS_LOCK_PATH,
    LOCKS_DIRNAME,
)

__all__ = [
    "DiagnosticSpec",
    "EXPORTS_REQUIRED",
    "LOCK_DUPLICATE_ID",
    "LOCK_EXPORT_MISSING",
    "LOCK_JSON_INVALID",
    "LOCK_PACKAGE_INVALID",
    "LOCK_ROOT_MISSING",
    "from_spec",
    "from_spec_at",
]


def _exports_required_message() -> str:
    """Return a message for a missing upstream exports artifact."""
    return "s070_resolution requires s060_exports to complete first."


def _lock_root_missing_message(*, lock_path: str = LOCKS_DIRNAME) -> str:
    """Return a message for an expected-but-absent lock root."""
    return (
        f"resolution locks are expected but the lock root {lock_path!r} does not exist"
    )


def _lock_json_invalid_message(
    *,
    file_path: str = ELEMENTS_LOCK_PATH,
    detail: str,
) -> str:
    """Return a message for an unparseable or mis-shaped lock file."""
    return f"{file_path} is invalid: {detail}"


def _lock_package_invalid_message(*, compact_id: str, detail: str) -> str:
    """Return a message for a malformed package lock record or package reference."""
    return f"package lock record {compact_id!r} is invalid: {detail}"


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


def _lock_duplicate_id_message(*, section: str, compact_id: str) -> str:
    """Return a message for a duplicate compact_id in a lock section."""
    return f"{section} lock section contains duplicate compact_id {compact_id!r}"


# --- AR.RS.* : base resolution diagnostics ----------------------------------

_EXPORTS_REQUIRED_CODE: DiagnosticCode = ar_code(
    Concern.RESOLUTION,
    "EXPORTS_REQUIRED",
)
_LOCK_ROOT_MISSING_CODE: DiagnosticCode = ar_code(
    Concern.RESOLUTION,
    "LOCK_ROOT_MISSING",
)
_LOCK_JSON_INVALID_CODE: DiagnosticCode = ar_code(
    Concern.RESOLUTION,
    "LOCK_JSON_INVALID",
)
_LOCK_PACKAGE_INVALID_CODE: DiagnosticCode = ar_code(
    Concern.RESOLUTION,
    "LOCK_PACKAGE_INVALID",
)
_LOCK_EXPORT_MISSING_CODE: DiagnosticCode = ar_code(
    Concern.RESOLUTION,
    "LOCK_EXPORT_MISSING",
)
_LOCK_DUPLICATE_ID_CODE: DiagnosticCode = ar_code(
    Concern.RESOLUTION,
    "LOCK_DUPLICATE_ID",
)


EXPORTS_REQUIRED = DiagnosticSpec(
    code=_EXPORTS_REQUIRED_CODE,
    severity=Severity.ERROR,
    message=_exports_required_message,
    description="Stage 070 requires the stage 060 exports artifact to be present.",
)

LOCK_ROOT_MISSING = DiagnosticSpec(
    code=_LOCK_ROOT_MISSING_CODE,
    severity=Severity.ERROR,
    message=_lock_root_missing_message,
    description="Resolution locks are expected but the lock root is missing.",
)

LOCK_JSON_INVALID = DiagnosticSpec(
    code=_LOCK_JSON_INVALID_CODE,
    severity=Severity.ERROR,
    message=_lock_json_invalid_message,
    description="The elements lock file is unreadable, unparseable, or malformed.",
)

LOCK_PACKAGE_INVALID = DiagnosticSpec(
    code=_LOCK_PACKAGE_INVALID_CODE,
    severity=Severity.ERROR,
    message=_lock_package_invalid_message,
    description="A package lock record or package reference is structurally invalid.",
)

LOCK_EXPORT_MISSING = DiagnosticSpec(
    code=_LOCK_EXPORT_MISSING_CODE,
    severity=Severity.ERROR,
    message=_lock_export_missing_message,
    description="A locked element has no corresponding export artifact from stage 060.",
)

LOCK_DUPLICATE_ID = DiagnosticSpec(
    code=_LOCK_DUPLICATE_ID_CODE,
    severity=Severity.ERROR,
    message=_lock_duplicate_id_message,
    description="A lock section contains duplicate compact_id values.",
)
