from typing import Optional
import torch


def ConvNd(
    dim: int,
    in_channels: int,
    out_channels: int,
    kernel: int,
    stride: int = 1,
    dilation: int = 1,
    groups: int = 1,
    bias: bool = True,
    transposed: bool = False,
    output_padding: Optional[int] = None,
    dtype=None,
):
    """Convolutional layer.

    Args:
        dim (int): Dimensionality.
        in_channels (int): Number of input channels.
        out_channels (int): Number of output channels.
        kernel (int): Kernel size.
        stride (int, optional): Stride.
        dilation (int, optional): Dilation.
        groups (int, optional): Number of groups.
        bias (bool, optional): Use a bias. Defaults to `True`.
        transposed (bool, optional): Transposed convolution. Defaults to `False`.
        output_padding (int, optional): Output padding.
        dtype (dtype, optional): Data type.

    Returns:
        object: Convolutional layer.
    """
    # Only set `output_padding` if it is given.
    additional_args = {}
    if output_padding is not None:
        additional_args["output_padding"] = output_padding

    # Use same-padding.
    if kernel % 2 != 1:
        raise ValueError("Kernel size must be odd to achieve same-padding.")

    # Get the right layer kind.
    if transposed:
        suffix = "Transpose"
    else:
        suffix = ""

    return getattr(torch.nn, f"Conv{suffix}{dim}d")(
        in_channels=in_channels,
        out_channels=out_channels,
        kernel_size=kernel,
        stride=stride,
        padding=kernel // 2,
        dilation=dilation,
        groups=groups,
        bias=bias,
        dtype=dtype,
        **additional_args,
    )