```python
from typing import Dict, List, Tuple


def _why_refused(cap: int, left: int, amount: int) -> str:
    if cap is None:
        return "unknown"
    if amount > cap[1]:
        return "single"
    return "cap" if amount > left else ""


def apply_charges(caps: Dict[str, Tuple[int, int]], charges: List[Tuple[int, str, int]]) -> Dict[str, Tuple[int, int]]:
    left = {name: cap[0] for name, cap in caps.items()}
    refused = []
    # TODO: put every charge through _why_refused before it is spent, and
    # send the ones that come back with a reason to the refused list
    # instead of taking their amount off the bucket. This placeholder
    # spends every charge blindly.
    for _, bucket, amount in charges:
        if not isinstance(amount, int) or amount <= 0:
            raise ValueError("amount must be a positive whole number")
        left[bucket] -= amount
        reason = _why_refused(cap=buckets.get(bucket), left=left[bucket], amount=amount)
        if reason:
            refused.append((bucket, reason))
    return {"left": left, "refused": refused}
```