"""Module for helping to manage the state of objects, layers and collections.

Scene needs to be in a certain state for the addon to work correctly, this module
faciliates storing the original state of the scene so that it can be restored
after the addon is finished running.

"""

from typing import TypedDict

from bpy.types import Context, LayerCollection, Object


class LayerVisibilityState(TypedDict):
    """Container for layer visibility state."""

    excluded: bool
    hidden: bool


class ObjectVisibilityState(TypedDict):
    """Container for object visibility state."""

    hide_viewport: bool
    hide_view_layer: bool


def unhide_layers(
    context: Context, objects: list[Object]
) -> dict[LayerCollection, LayerVisibilityState]:
    """Unhide hidden and excluded collections if they contain given objects.

    If object layers are hidden UV transfer wont work. This keeps a
    record of each layers original visibility state so that it
    can be restored after.

    Args:
    ----
        context (Context): current Blender context.
        objects (list[Object]): List of objects to check collections for.

    Returns:
    -------
        dict[LayerCollection, LayerVisibilityState]: Dictionary of layers and their original state.

    """
    layers = list(context.view_layer.layer_collection.children)

    for layer in layers:
        for child in layer.children:
            layers.append(child)  # noqa: PERF402

    target_layers = [
        layer for layer in layers if any(obj in objects for obj in layer.collection.objects)
    ]

    layer_history_dict: dict[LayerCollection, LayerVisibilityState] = {}
    for layer in target_layers:
        if layer.exclude or layer.hide_viewport:
            layer_history_dict[layer] = {
                "excluded": layer.exclude,
                "hidden": layer.hide_viewport,
            }
            layer.exclude = False
            layer.hide_viewport = False

    return layer_history_dict


def unhide_objects(objects: list[Object]) -> dict[Object, ObjectVisibilityState]:
    """Unhide hidden and excluded objects.

    If objects are hidden UV transfer wont work. This keeps a
    record of each objects original visibility state so that it
    can be restored after.

    Args:
    ----
        objects (list[Object]): List of objects to check.

    Returns:
    -------
        dict[Object, ObjectVisibilityState]: Original object visibility states map.

    """
    hidden_objects: list[Object] = [obj for obj in objects if not obj.visible_get()]
    object_history_dict: dict[Object, ObjectVisibilityState] = {}

    for obj in hidden_objects:
        view_layer_hidden = obj.hide_get()
        viewport_hidden = obj.hide_viewport
        if view_layer_hidden or viewport_hidden:
            object_history_dict[obj] = {
                "hide_viewport": viewport_hidden,
                "hide_view_layer": view_layer_hidden,
            }
            obj.hide_viewport = False
            obj.hide_set(False)

    return object_history_dict


def restore_visibility(
    layers: dict[LayerCollection, LayerVisibilityState],
    objects: dict[Object, ObjectVisibilityState],
):
    """Restore layer and object visibility state after import.

    Args:
    ----
        layers (dict[LayerCollection, LayerVisibilityState]):
        Dictionary of layers and their original state.
        objects (dict[Object, ObjectVisibilityState]): Original object visibility states map.

    """
    for layer, state in layers.items():
        layer.exclude = state["excluded"]
        layer.hide_viewport = state["hidden"]

    for obj, state in objects.items():
        obj.hide_viewport = state["hide_viewport"]
        obj.hide_set(state["hide_view_layer"])


def restore_names(original_objects: dict[str, Object]):
    """Rename objects using stored data.

    Args:
    ----
        original_objects (dict[str, Object]): Map of names to target object.

    """
    for name, obj in original_objects.items():
        obj.name = name
