from typing import Tuple
from typing import Union
from typing import Collection
from typing import Any


def insert_into_tuple(tup: Tuple, pos: Union[int, Collection[int]], val: Any) -> Tuple[int, ...]:
    """ 
    insert_into_tuple(tup: Tuple, pos: Union[int, Collection[int]], 
                      val: Any) -> 
        Tuple[int, ...]:
    
    Insert values into a tuple. 

    Parameters
    ----------
    tup
        the tuple into which values are to be inserted
    pos
        The positions into which values are to be inserted
    val
        The values corresponding to the positions in `pos`

    Returns
    -------

        A copy of `tup` with values inserted.

    Raises
    ------
    ValueError
        If length of `pos` is not equal to length of `val`

    Examples
    --------
    >>> tup = (0, 1, 2, 3)
    >>> pos = (5, 1)
    >>> val = (9, 8)
    >>> insert_into_tuple(tup, pos, val)
    (0, 8, 1, 2, 3, 9)
    >>> insert_into_tuple(tup, (), ())
    (0, 1, 2, 3)
    """
    
    tl = list(tup)

    if isinstance(pos, int):
        tl.insert(pos, val)
    else:
        if len(pos) != len(val):
            raise ValueError("pos and val must be of the same length")

        if len(pos) == 0:
            return tup

        # sort pos so from low to high; sort val correspondingly
        stl = list(zip(*sorted(zip(pos, val))))
        for p, v in zip(stl[0], stl[1]):
            tl.insert(p, v)

    return tuple(tl)