```ts
export function applyCharges(caps: Record<string, [number, number]>, charges: [string, string, number][]): { left: Record<string, number>; refused: [string, string][] } {
  const result = { left: {}, refused: [] as [string, string][] };
  
  for (const [chargeId, bucketName, amount] of charges) {
    if (typeof amount !== 'number' || amount <= 0 || !Number.isInteger(amount)) {
      throw new Error('Invalid charge amount');
    }
    
    const cap = caps[bucketName];
    if (!cap) {
      result.refused.push([chargeId, 'unknown']);
    } else {
      const [allowance, maxSingleCharge] = cap;
      if (amount > maxSingleCharge) {
        result.refused.push([chargeId, 'single']);
      } else if (amount > allowance) {
        result.refused.push([chargeId, 'cap']);
      } else {
        result.left[bucketName] = (result.left[bucketName] || 0) + amount;
      }
    }
  }

  for (const bucket in caps) {
    if (!result.left[bucket]) {
      result.left[bucket] = 0;
    }
  }

  return result;
}
```