import json
from collections.abc import Mapping
from typing import Any, cast


def load_json_object(
    payload_json: str,
    *,
    operation: str,
    error_cls: type[Exception],
) -> dict[str, Any]:
    try:
        parsed = json.loads(payload_json)
    except json.JSONDecodeError as exc:
        raise error_cls(f"{operation} returned invalid JSON: {exc}") from exc
    if not isinstance(parsed, dict):
        raise error_cls(f"{operation} returned non-object JSON")
    return cast(dict[str, Any], parsed)


def require_str_field(
    payload: Mapping[str, object],
    *,
    key: str,
    operation: str,
    error_cls: type[Exception],
) -> str:
    value = payload.get(key)
    if not isinstance(value, str):
        raise error_cls(f"{operation} omitted required string field: {key}")
    return value


def require_optional_str_field(
    payload: Mapping[str, object],
    *,
    key: str,
    operation: str,
    error_cls: type[Exception],
) -> str | None:
    value = payload.get(key)
    if value is None:
        return None
    if not isinstance(value, str):
        raise error_cls(f"{operation} omitted required string field: {key}")
    return value


def require_object_field(
    payload: Mapping[str, object],
    *,
    key: str,
    operation: str,
    error_cls: type[Exception],
) -> dict[str, Any]:
    value = payload.get(key)
    if not isinstance(value, dict):
        raise error_cls(f"{operation} omitted required object field: {key}")
    return cast(dict[str, Any], value)


def require_str_list_field(
    payload: Mapping[str, object],
    *,
    key: str,
    operation: str,
    error_cls: type[Exception],
) -> list[str]:
    value = payload.get(key)
    if not isinstance(value, list) or any(not isinstance(item, str) for item in value):
        raise error_cls(f"{operation} omitted required list[str] field: {key}")
    return cast(list[str], value)
