from typing import Any, Dict, Optional

import numpy as np
import torch

# Make renderers optional - they require pyrender which is not needed for inference
try:
    from .mesh_renderer import MeshRenderer
    from .renderer import Renderer
    from .skeleton_renderer import SkeletonRenderer
except ImportError:
    MeshRenderer = None
    Renderer = None
    SkeletonRenderer = None

from .pose_utils import Evaluator, eval_pose


def recursive_to(x: Any, target: torch.device):
    """
    Recursively transfer a batch of data to the target device
    Args:
        x (Any): Batch of data.
        target (torch.device): Target device.
    Returns:
        Batch of data where all tensors are transfered to the target device.
    """
    if isinstance(x, dict):
        return {k: recursive_to(v, target) for k, v in x.items()}
    elif isinstance(x, torch.Tensor):
        return x.to(target)
    elif isinstance(x, list):
        return [recursive_to(i, target) for i in x]
    else:
        return x


def recursive_stack(list_of_dicts: list[dict]) -> dict:
    """
    Recursively stack a dictionaries of list and dictionaries into numpy arrays.
    """
    stacked_dict = {}
    assert len(list_of_dicts) > 0, "Input list must not be empty"
    for key, sample in list_of_dicts[0].items():
        if isinstance(sample, dict):
            stacked_dict[key] = recursive_stack([d[key] for d in list_of_dicts])
        elif isinstance(sample, list):
            stacked_dict[key] = np.concatenate([np.array(d[key]) for d in list_of_dicts], axis=0)
        elif isinstance(sample, np.ndarray):
            stacked_dict[key] = np.concatenate([d[key] for d in list_of_dicts], axis=0)
    return dict(stacked_dict)
