"""Scalar/shape parsing helpers for stage 060 export data.

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

OBS (narrow by design):
    Parse helpers only. No filesystem walking, no diagnostics, no state
    mutation, no JSON-Schema validation, no semantic rule interpretation. The
    caller converts a raised ValueError into a precise AR.EX.* diagnostic with
    the offending path.
"""

from accountable_record.boundary.toml import is_list, is_table


def require_string(table: dict[str, object], key: str, context: str) -> str:
    """Return a required non-empty string field.

    Args:
        table: Parsed mapping.
        key: Field name to read.
        context: Human-readable description, for error messages.

    Returns:
        The stripped string value.

    Raises:
        ValueError: If the field is missing, not a string, or empty.
    """
    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_object(value: object, context: str) -> dict[str, object]:
    """Verify a parsed JSON value is an object (mapping) and return it.

    Args:
        value: A parsed JSON value.
        context: Human-readable description, for error messages.

    Returns:
        The value as a dict.

    Raises:
        ValueError: If the value is not a JSON object.
    """
    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 (mapping) from a parent object.

    Args:
        parent: Parsed mapping.
        key: Nested object name to read.
        context: Human-readable description, for error messages.

    Returns:
        The nested object as a dict.

    Raises:
        ValueError: If the key is missing or not an 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_list(
    parent: dict[str, object],
    key: str,
    context: str,
) -> list[str]:
    """Return a required list of strings from a parent object.

    WHY:
        The export index artifacts value is a list of path strings. This
        helper gives a single call for "this key is a list of strings" with a
        clear failure when the shape is wrong.

    Args:
        parent: Parsed mapping.
        key: Field name holding the list.
        context: Human-readable description, for error messages.

    Returns:
        The list of strings.

    Raises:
        ValueError: If the value is absent, not a list, or contains a
            non-string element.
    """
    value = parent.get(key)
    if not is_list(value):
        msg = f"{context} requires {key!r} to be an array"
        raise ValueError(msg)

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

    return items


def expect_schema_tag(table: dict[str, object], expected: str, context: str) -> None:
    """Verify a top-level schema tag matches the observed value.

    Args:
        table: Parsed mapping (the file root).
        expected: The observed schema tag value.
        context: Human-readable description, for error messages.

    Raises:
        ValueError: If the schema tag is absent or does not match.
    """
    actual = table.get("schema")
    if actual != expected:
        msg = f"{context} expected schema tag {expected!r}, got {actual!r}"
        raise ValueError(msg)
