```python
from typing import *
from collections import *

def topo_sort(n: int, edges: list[tuple[int, int]]) -> list[int]:
    if n == 0:
        return []

    # Build adjacency list representation of the graph
    adj_list = defaultdict(list)
    for a, b in edges:
        if a >= n or b >= n:
            raise ValueError("Invalid edge: node index out of range")
        adj_list[a].append(b)

    # Calculate in-degree for each node
    in_degree = [0] * n
    for neighbors in adj_list.values():
        for neighbor in neighbors:
            in_degree[neighbor] += 1

    # Initialize queue with nodes having no incoming edges
    queue = deque([i for i in range(n) if in_degree[i] == 0])

    # Perform topological sorting
    topo_order = []
    while queue:
        node = queue.popleft()
        topo_order.append(node)
        for neighbor in adj_list[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)

    # Check if all nodes were visited
    if len(topo_order) != n:
        raise ValueError("Graph contains a cycle")

    return topo_order
```