"""Scalar parsing helpers for stage 030 source declarations.

WHY:
    Mirrors the scalar helpers in stages 010 and 020: small, dependency-free
    functions that pull structural fields out of a parsed declaration and raise
    on shape problems, so the discover/validation code reads as decisions
    rather than isinstance tangles.

OBS:
    These helpers read only structural scalars and lists, e.g., identifiers,
    paths, roles. They never read or interpret source content. They raise
    ValueError on shape problems; the calling op converts that to a
    StageInputMalformed, which the validation layer maps to
    SOURCE_DECLARATION_INVALID.

OBS:
    Narrowing is delegated to the shared TOML guards in stage_010. These scalars
    stay stage-local (their function set and signatures differ across stages);
    only the narrowing primitive is shared.
"""

from accountable_record.boundary.toml import is_list, is_table


def required_string(table: dict[str, object], key: str, context: str) -> str:
    """Return a required non-empty string field from a declaration table."""
    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 optional_string(table: dict[str, object], key: str) -> str | None:
    """Return an optional non-empty string field, or None."""
    value = table.get(key)
    if value is None:
        return None
    if not isinstance(value, str):
        msg = f"Expected string field {key!r}"
        raise ValueError(msg)
    stripped = value.strip()
    return stripped or None


def entry_list(parent: dict[str, object], key: str) -> list[dict[str, object]]:
    """Return a list of declaration tables, or an empty list when absent."""
    value = parent.get(key)
    if value is None:
        return []
    if not is_list(value):
        msg = f"Expected {key!r} to be an array of tables"
        raise ValueError(msg)

    result: list[dict[str, object]] = []
    for index, item in enumerate(value):
        if not is_table(item):
            msg = f"Expected {key!r}[{index}] to be a table"
            raise ValueError(msg)
        result.append(item)
    return result


def required_table(
    parent: dict[str, object],
    key: str,
    context: str,
) -> dict[str, object]:
    """Return a required sub-table from a parsed mapping."""
    value = parent.get(key)
    if not is_table(value):
        msg = f"{context} requires [{key}] table"
        raise ValueError(msg)
    return value


def required_string_list(
    table: dict[str, object],
    key: str,
    context: str,
) -> tuple[str, ...]:
    """Return a required list of non-empty strings."""
    value = table.get(key)
    if not is_list(value):
        msg = f"{context} requires list field {key!r}"
        raise ValueError(msg)

    result: 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)
        result.append(item.strip())

    return tuple(result)
