function cmd::rule::list() {
  local rules_dir
  rules_dir="$(ctx::rules)"

  local rules=("${rules_dir}"/*.rule)
  if [[ ! -f "${rules[0]}" ]]; then
    log::wg "No rules configured"
    return 0
  fi

  log::section "Firewall Rules"

  printf "\n  %-20s %-35s %-8s %-8s %s\n" \
    "NAME" "DESCRIPTION" "ALLOWS" "BLOCKS" "PEERS"
  printf "  %s\n" "$(printf '─%.0s' {1..80})"

  for rule_file in "${rules_dir}"/*.rule; do
    [[ -f "$rule_file" ]] || continue

    local name desc blocks peer_count
    name=$(json::get "$rule_file" "name")
    desc=$(json::get "$rule_file" "desc")

    local n_allows n_block_ips n_block_ports blocks
    n_allows=$(json::get "$rule_file" "allow_ips" | wc -l)
    n_block_ips=$(json::get "$rule_file" "block_ips" | wc -l)
    n_block_ports=$(json::get "$rule_file" "block_ports" | wc -l)
    # wc -l returns 0 for empty, so this is safe
    blocks=$(( n_block_ips + n_block_ports ))

    # Count assigned peers
    peer_count=$(peers::with_rule "$name" | grep -c . || echo 0)

    printf "  %-20s %-35s %-8s %-8s %s\n" \
      "$name" \
      "${desc:-—}" \
      "${n_allows}" \
      "${blocks}" \
      "${peer_count} peers"
  done

  printf "\n"
}