"""Module getting and setting selections of objects."""

from typing import Tuple

import bpy
from bpy.types import Context, Object


def get_local_meshes() -> list[Object]:
    """Get a list of meshes which are visible in the viewport.

    Returns
    -------
        List of visible objects

    """
    space_view_3d = bpy.context.space_data
    depsgraph = bpy.context.evaluated_depsgraph_get()

    return [
        object
        for object in bpy.data.objects
        if object.evaluated_get(depsgraph).local_view_get(space_view_3d)
    ]


def get_selected_meshes() -> list[Object]:
    """Get a list of slected objects.

    Basically does the same thing as context.selected_objects
    but covering for edge cases.

    Returns
    -------
        List of selected objects

    """
    act_obj = bpy.context.active_object

    # context.scene instead of context.selected_objects
    # context weirdness with operators can lead to selected_objects being empty
    sel_objs = [obj for obj in bpy.context.scene.objects if obj.type == "MESH" and obj.select_get()]

    # Covers an edge case where it is possible to have an active object not selected
    if not sel_objs and act_obj is not None:
        act_obj.select_set(True)
        sel_objs = [act_obj]

    return sel_objs


def get_clones(objs: list[Object]) -> list[Object]:
    """Find objects with shared data blocks.

    Args:
    ----
        objs (list[Object]): List of objects to search for.

    Returns:
    -------
        list[Object]: List of clone objects.

    """
    data_blocks = []
    clone_objects = []

    for obj in objs:
        if obj.data not in data_blocks:
            data_blocks.append(obj.data)
        else:
            clone_objects.append(obj)

    return clone_objects


def select_objects(objects: list[Object]):
    """Select the given objects.

    Args:
    ----
        objects: List of objects to select.

    """
    for obj in objects:
        obj.select_set(True)


def change_selection(context: Context, objects: list[Object]):
    """Select the given objects and unselect others.

    Args:
    ----
        context: Current Blender context.
        objects: List of objects to select.

    """
    bpy.ops.object.select_all(action="DESELECT")
    for obj in objects:
        obj.select_set(True)

    context.view_layer.objects.active = objects[0]


def get_filtered_selection(context: Context, filter_clones: bool) -> Tuple[Object, list[Object]]:
    """Filter selected objects based on user preferences.

    - Exclude non-mesh objects.

    Possible filters:
        - Exclude clones from selection.

    If the active object is one of the excluded objects, reassign
    an arbitrary object from the filtered list as the active object.

    Args:
    ----
        context: Blender context.
        filter_clones: flag to filter out clones.

    Returns:
    -------
        active object, filtered list of objects

    """
    sel_objs = get_selected_meshes()
    act_obj = context.active_object

    if filter_clones:
        cloned_objs = get_clones(sel_objs)
        filtered_set = set(sel_objs) - set(cloned_objs)
        filtered_objs = list(filtered_set)
        if act_obj not in filtered_objs:
            act_obj = filtered_objs[0]
    else:
        filtered_objs = sel_objs

    return act_obj, filtered_objs
