from typing import Optional
from typing import List
from typing import Dict
from typing import Any
from typing import cast


def _build_bitcoin_transaction_body(
    satoshis_per_byte: Optional[int] = None,
    data: Optional[str] = None,
    change_address: Optional[str] = None,
    outputs: Optional[List[Dict[str, Any]]] = None,
) -> Dict[str, Any]:
    """Build the json (dictionary) body for a bitcoin transaction given its inputs

    Args:
        satoshis_per_byte (int, optional): fee to pay in satoshis/byte. If not supplied, it will be estimated for you.
        data (str, optional): string to embed in the transaction as null-data output type
        change_address (str, optional): address to send change to. If not supplied, it will default to the address you are sending from
        outputs (list, optional): (list of {'to': str, 'value': float} dictionaries. Value float is in BTC)

    Raises:
        TypeError: with bad parameter types

    Returns:
        Dictionary body to use for sending a bitcoin transaction
    """
    if satoshis_per_byte is not None and not isinstance(satoshis_per_byte, int):
        raise TypeError('Parameter "satoshis_per_byte" must be of type int.')
    if data is not None and not isinstance(data, str):
        raise TypeError('Parameter "data" must be of type str.')
    if change_address is not None and not isinstance(change_address, str):
        raise TypeError('Parameter "change_address" must be of type str.')
    if outputs is not None and not isinstance(outputs, list):
        raise TypeError('Parameter "outputs" must be of type list.')

    body = cast(Dict[str, Any], {"version": "1"})
    if outputs:
        body["outputs"] = outputs
    if satoshis_per_byte:
        body["fee"] = satoshis_per_byte
    if data:
        body["data"] = data
    if change_address:
        body["change"] = change_address

    return body