# ============================================================
# src/accountable_record/stage_070_resolution/base/scalars.py
# ============================================================
"""Scalar/shape parsing helpers for stage 070 resolution locks.

WHY:
    Mirrors the scalar helper layer of earlier stages: small, dependency-free
    functions that check the minimal structural shape of parsed lock data and
    raise ValueError on shape problems, so graph/lock/resolve/verify code reads
    as decisions rather than isinstance tangles.

OBS (narrow by design):
    Parse helpers only. No filesystem walking, no diagnostics, no state
    mutation, no digest recomputation, no semantic version solving, and no
    graph resolution. The caller converts ValueError into a precise AR.RS.*
    diagnostic with the offending path or record label.
"""

from accountable_record.boundary.toml import is_list, is_table


def require_object(value: object, context: str) -> dict[str, object]:
    """Verify a parsed JSON value is an object and return it."""
    if not is_table(value):
        msg = f"{context} must be a JSON object"
        raise ValueError(msg)
    return value


def require_sub_object(
    parent: dict[str, object],
    key: str,
    context: str,
) -> dict[str, object]:
    """Return a required nested object from a parent object."""
    value = parent.get(key)
    if not is_table(value):
        msg = f"{context} requires object field {key!r}"
        raise ValueError(msg)
    return value


def require_string(table: dict[str, object], key: str, context: str) -> str:
    """Return a required non-empty string field."""
    value = table.get(key)
    if not isinstance(value, str) or not value.strip():
        msg = f"{context} requires non-empty string field {key!r}"
        raise ValueError(msg)
    return value.strip()


def require_bool(table: dict[str, object], key: str, context: str) -> bool:
    """Return a required boolean field."""
    value = table.get(key)
    if not isinstance(value, bool):
        msg = f"{context} requires boolean field {key!r}"
        raise ValueError(msg)
    return value


def require_object_list(
    parent: dict[str, object],
    key: str,
    context: str,
) -> list[dict[str, object]]:
    """Return a required list of objects from a parent object."""
    value = parent.get(key)
    if not is_list(value):
        msg = f"{context} requires {key!r} to be an array"
        raise ValueError(msg)

    records: list[dict[str, object]] = []
    for index, item in enumerate(value):
        if not is_table(item):
            msg = f"{context} {key!r}[{index}] must be an object"
            raise ValueError(msg)
        records.append(item)

    return records


def require_string_list(
    parent: dict[str, object],
    key: str,
    context: str,
) -> list[str]:
    """Return a required list of strings from a parent object."""
    value = parent.get(key)
    if not is_list(value):
        msg = f"{context} requires {key!r} to be an array"
        raise ValueError(msg)

    values: list[str] = []
    for index, item in enumerate(value):
        if not isinstance(item, str) or not item.strip():
            msg = f"{context} {key!r}[{index}] must be a non-empty string"
            raise ValueError(msg)
        values.append(item.strip())

    return values


def require_keys(
    table: dict[str, object],
    keys: tuple[str, ...],
    context: str,
) -> None:
    """Verify that a table contains all required keys."""
    for key in keys:
        if key not in table:
            msg = f"{context} is missing required key {key!r}"
            raise ValueError(msg)


def expect_schema_tag(
    table: dict[str, object],
    expected: str,
    context: str,
) -> None:
    """Verify a top-level schema tag matches the expected value."""
    actual = table.get("schema")
    if actual != expected:
        msg = f"{context} expected schema tag {expected!r}, got {actual!r}"
        raise ValueError(msg)


def expect_literal(
    table: dict[str, object],
    key: str,
    expected: str,
    context: str,
) -> None:
    """Verify a required string field equals an expected literal."""
    actual = require_string(table, key, context)
    if actual != expected:
        msg = f"{context} expected {key!r} to be {expected!r}, got {actual!r}"
        raise ValueError(msg)


def expect_sha256_hex(value: str, context: str) -> None:
    """Verify a string has the minimal shape of a SHA-256 hex digest."""
    if len(value) != 64:
        msg = f"{context} digest must be 64 hex characters"
        raise ValueError(msg)

    try:
        int(value, 16)
    except ValueError as exc:
        msg = f"{context} digest must be hexadecimal"
        raise ValueError(msg) from exc
