import bpy
from databpy.object import LinkedObjectError
from ..entities import StreamingTrajectory, density, trajectory
from ..entities.base import EntityType
from ..nodes import nodes
from ..nodes.geometry import get_final_style_nodes
from ..session import get_session
from .pref import addon_preferences
from .utils import check_online_access_for_ui


def panel_wwpdb(layout, scene):
    layout.label(text="Download from PDB", icon="IMPORT")
    layout.separator()

    layout = check_online_access_for_ui(layout)

    row_import = layout.row().split(factor=0.5)
    row_import.prop(scene.mn, "import_code_pdb")
    row = row_import.split(factor=0.3)
    row.prop(scene.mn, "import_format_fetch", text="")
    op = row.operator("mn.import_fetch")
    op.code = scene.mn.import_code_pdb  # type: ignore
    op.database = "wwpdb"  # type: ignore
    op.file_format = scene.mn.import_format_fetch  # type: ignore
    op.node_setup = scene.mn.import_node_setup  # type: ignore
    op.remove_solvent = scene.mn.import_remove_solvent  # type: ignore
    op.assembly = scene.mn.import_build_assembly  # type: ignore
    op.style = scene.mn.import_style  # type: ignore
    op.centre = scene.mn.import_centre  # type: ignore
    op.centre_type = scene.mn.import_centre_type  # type: ignore
    prefs = addon_preferences()
    if prefs is not None:
        op.cache_dir = str(prefs.cache_dir)  # type: ignore
    else:
        op.cache_dir = str(bpy.app.tempdir)  # type: ignore
    layout.separator(factor=0.4)

    layout.separator()

    layout.label(text="Options", icon="MODIFIER")
    options = layout.column(align=True)

    row = options.row()
    row.prop(scene.mn, "import_node_setup", text="")
    col = row.column()
    col.prop(scene.mn, "import_style")
    col.enabled = scene.mn.import_node_setup

    row_centre = options.row()
    row_centre.prop(scene.mn, "import_centre", icon_value=0)
    col_centre = row_centre.column()
    col_centre.prop(scene.mn, "import_centre_type", text="")
    col_centre.enabled = scene.mn.import_centre
    options.separator()

    grid = options.grid_flow()
    grid.prop(scene.mn, "import_build_assembly")
    grid.prop(scene.mn, "import_remove_solvent")
    grid.prop(scene.mn, "import_del_hydrogen")


def panel_alphafold(layout, scene):
    layout.label(text="Download from the AlphaFold DataBase", icon="IMPORT")
    layout.separator()

    layout = check_online_access_for_ui(layout)

    row_import = layout.row().split(factor=0.5)
    row_import.prop(scene.mn, "import_code_alphafold")
    download = row_import.split(factor=0.3)
    download.prop(scene.mn, "import_format_fetch", text="")
    op = download.operator("mn.import_fetch")
    op.code = scene.mn.import_code_alphafold  # type: ignore
    op.database = "alphafold"  # type: ignore
    op.file_format = scene.mn.import_format_fetch  # type: ignore
    op.node_setup = scene.mn.import_node_setup  # type: ignore
    op.assembly = scene.mn.import_build_assembly  # type: ignore
    op.style = scene.mn.import_style  # type: ignore
    op.centre = scene.mn.import_centre  # type: ignore
    op.centre_type = scene.mn.import_centre_type  # type: ignore
    prefs = addon_preferences()
    if prefs is not None:
        op.cache_dir = str(prefs.cache_dir)  # type: ignore
    else:
        op.cache_dir = str(bpy.app.tempdir)  # type: ignore

    layout.separator(factor=0.4)

    row = layout.row().split(factor=0.3)
    layout.separator()

    layout.label(text="Options", icon="MODIFIER")
    options = layout.column(align=True)

    row = options.row()
    row.prop(scene.mn, "import_node_setup", text="")
    col = row.column()
    col.prop(scene.mn, "import_style")
    col.enabled = scene.mn.import_node_setup

    row_centre = options.row()
    row_centre.prop(scene.mn, "import_centre", icon_value=0)
    col_centre = row_centre.column()
    col_centre.prop(scene.mn, "import_centre_type", text="")
    col_centre.enabled = scene.mn.import_centre
    options.separator()


# operator that calls the function to import the structure from a local file


def panel_local(layout, scene):
    layout.label(text="Load a Local File", icon="FILE_TICK")
    layout.separator()

    row = layout.row()
    row.prop(scene.mn, "import_local_path")
    op = row.operator("mn.import_local")
    op.filepath = scene.mn.import_local_path
    op.node_setup = scene.mn.import_node_setup
    op.assembly = scene.mn.import_build_assembly
    op.style = scene.mn.import_style
    op.centre = scene.mn.import_centre
    op.remove_solvent = scene.mn.import_remove_solvent
    op.centre_type = scene.mn.import_centre_type
    layout.separator()

    layout.label(text="Options", icon="MODIFIER")
    options = layout.column(align=True)

    row = options.row()
    row.prop(scene.mn, "import_node_setup", text="")
    col = row.column()
    col.prop(scene.mn, "import_style")
    col.enabled = scene.mn.import_node_setup

    row_centre = options.row()

    row_centre.prop(scene.mn, "import_centre", icon_value=0)
    # row_centre.prop()
    col_centre = row_centre.column()
    col_centre.prop(scene.mn, "import_centre_type", text="")
    col_centre.enabled = scene.mn.import_centre
    options.separator()

    grid = options.grid_flow()
    grid.prop(scene.mn, "import_build_assembly")
    grid.prop(scene.mn, "import_remove_solvent", icon_value=0)
    grid.prop(scene.mn, "import_del_hydrogen", icon_value=0)


def panel_starfile(layout, scene):
    layout.label(text="Load Star File", icon="FILE_TICK")
    layout.separator()
    row_import = layout.row()
    row_import.prop(scene.mn, "import_star_file_path")
    op = row_import.operator("mn.import_star_file")
    op.filepath = scene.mn.import_star_file_path
    op.node_setup = scene.mn.import_node_setup


def panel_cellpack(layout, scene):
    layout.label(text="Load CellPack Model", icon="FILE_TICK")
    layout.separator()
    row = layout.row()
    row.prop(scene.mn, "import_cell_pack_path")
    op = row.operator("mn.import_cell_pack")
    op.filepath = scene.mn.import_cell_pack_path
    op.node_setup = scene.mn.import_node_setup


def panel_density(layout, scene):
    layout.label(text="Load Density Grids", icon="FILE_TICK")
    layout.separator()

    row = layout.row()
    row.prop(scene.mn, "import_density")
    row.operator("mn.import_density")

    layout.separator()
    col = layout.column()
    col.alignment = "LEFT"
    col.scale_y = 0.5
    label = f"\
    An intermediate file will be created: {scene.mn.import_density}.vdb\
    Please do not delete this file or the volume will not render.\
    Move the original .map file to change this location.\
    "
    for line in label.strip().split("    "):
        col.label(text=line)

    layout.separator()
    layout.label(text="Options", icon="MODIFIER")

    layout.prop(scene.mn, "import_density_invert")
    layout.prop(scene.mn, "import_density_center")
    layout.prop(scene.mn, "import_density_overwrite")
    row = layout.row()
    row.prop(scene.mn, "import_node_setup", text="")
    col = row.column()
    col.prop(scene.mn, "import_density_style")
    col.enabled = scene.mn.import_node_setup


def panel_trajectory(layout, scene):
    layout.label(text="Load MD Trajectories", icon="FILE_TICK")
    layout.separator()
    col = layout.column(align=True)
    row_import = col.row()
    row_import.prop(scene.mn, "import_md_name")
    op = row_import.operator("mn.import_trajectory", text="Load")
    op.topology = scene.mn.import_md_topology
    op.trajectory = scene.mn.import_md_trajectory
    op.name = scene.mn.import_md_name
    op.style = scene.mn.import_style
    op.setup_nodes = scene.mn.import_node_setup
    col.separator()
    col.prop(scene.mn, "import_md_topology")
    col.prop(scene.mn, "import_md_trajectory")

    layout.separator()
    layout.label(text="Options", icon="MODIFIER")
    row = layout.row()
    row.prop(scene.mn, "import_node_setup", text="")
    col = row.column()
    col.prop(scene.mn, "import_style")
    col.enabled = scene.mn.import_node_setup


def panel_oxdna(layout: bpy.types.UILayout, scene: bpy.types.Scene) -> None:
    """
    Create the panel layout for oxDNA import.

    Parameters
    ----------
    layout : bpy.types.UILayout
        Layout to add elements to
    scene : bpy.types.Scene
        Current scene
    """
    layout.label(text="Load oxDNA File", icon="FILE_TICK")
    layout.separator()
    row = layout.row()
    row.prop(scene.mn, "import_oxdna_name")
    op = row.operator("mn.import_oxdna")
    op.name = scene.mn.import_oxdna_name
    op.topology = scene.mn.import_oxdna_topology
    op.trajectory = scene.mn.import_oxdna_trajectory
    col = layout.column(align=True)
    col.prop(scene.mn, "import_oxdna_topology")
    col.prop(scene.mn, "import_oxdna_trajectory")


chosen_panel = {
    "pdb": panel_wwpdb,
    "local": panel_local,
    "alphafold": panel_alphafold,
    "star": panel_starfile,
    "md": panel_trajectory,
    "density": panel_density,
    "cellpack": panel_cellpack,
    "dna": panel_oxdna,
}


def pt_object_context(self, context):
    return None


def is_style_node(context):
    node = context.space_data.edit_tree.nodes.active
    return node.name.startswith("Style")


def change_style_node_menu(self, context):
    layout = self.layout
    node = context.active_node

    # return early if not a node group
    if not hasattr(node, "node_tree"):
        return None

    # return early if the node group isn't one of the ones we want to swap easily
    prefix = node.node_tree.name.split(" ")[0].lower()
    if prefix not in ["color", "select", "is", "style", "topology", "animate"]:
        return None

    layout.label(text="Molecular Nodes", icon="MOD_PARTICLES")

    row = layout.row()
    op = row.operator_menu_enum("mn.node_swap", "node_items", text="Change Node")
    op.node_description = "The topology nodes"

    layout.separator()


def panel_import(layout, context):
    scene = context.scene
    selection = scene.mn.panel_import_type
    layout.prop(scene.mn, "panel_import_type")

    col = layout.column()
    chosen_panel[selection](col, scene)


def ui_from_node(
    layout: bpy.types.UILayout,
    node: bpy.types.GeometryNodeGroup,
    context: bpy.types.Context,
):
    """
    Generate the UI for a particular node, which displays the relevant node inputs
    for user control in a panel, rather than through the node editor.
    """
    col = layout.column(align=True)
    ntree = context.active_object.modifiers["MolecularNodes"].node_group

    tree = node.node_tree.interface.items_tree

    for item in tree.values():
        if item.item_type == "PANEL":
            col.label(text=item.name)
        elif item.name == "Selection":
            continue
        else:
            if item.in_out != "INPUT":
                continue
            if item.socket_type == "NodeSocketGeometry":
                continue
            col.template_node_view(ntree, node, node.inputs[item.identifier])


def panel_md_properties(layout, context):
    obj = context.active_object
    session = get_session()
    traj: trajectory.Trajectory = session.match(obj)
    traj_is_linked = bool(traj)
    if traj is not None and not isinstance(traj, trajectory.Trajectory):
        raise TypeError(f"Expected a trajectory, got {type(traj)}")

    col = layout.column()
    col.enabled = False
    if not traj_is_linked:
        col.enabled = True
        col.label(text="Object not linked to a trajectory, please reload one")
        col.prop(obj.mn, "filepath_topology")
        col.prop(obj.mn, "filepath_trajectory")
        col.operator("mn.reload_trajectory")
        return None

    layout.label(text="Trajectory Playback", icon="OPTIONS")
    is_streaming = isinstance(traj, StreamingTrajectory)

    if is_streaming:
        label = "Streaming trajectory; cannot alter playback"
    else:
        label = "Trajectory has {} {}".format(
            obj.mn.n_frames, "frames" if obj.mn.n_frames else "frame"
        )

    layout.label(text=label)
    playback = layout.column()
    playback.enabled = not is_streaming
    row = playback.row()

    col = row.column()
    if obj.mn.update_with_scene:
        col.prop(obj.mn, "frame_hidden")
    else:
        col.prop(obj.mn, "frame")
    col.enabled = not obj.mn.update_with_scene
    row.prop(obj.mn, "update_with_scene")
    row = playback.row()
    col = row.column()
    col.enabled = obj.mn.update_with_scene
    col.prop(obj.mn, "average")
    col.prop(obj.mn, "subframes")
    col.prop(obj.mn, "offset")
    col = row.column()
    col.enabled = obj.mn.update_with_scene

    # only enable this as an option if the universe is orthothombic
    row = col.row()
    row.prop(obj.mn, "correct_periodic")
    row.enabled = traj._is_orthorhombic
    col.prop(obj.mn, "interpolate")

    layout.label(text="Selections", icon="RESTRICT_SELECT_OFF")
    row = layout.row()
    row = row.split(factor=0.9)
    row.template_list(
        "MN_UL_TrajectorySelectionListUI",
        "A list",
        obj,
        "mn_trajectory_selections",
        obj.mn,
        "trajectory_selection_index",
        rows=3,
    )
    col = row.column()
    col.operator("mn.trajectory_selection_add", icon="ADD", text="")
    col.operator("mn.trajectory_selection_remove", icon="REMOVE", text="")
    if obj.mn_trajectory_selections:
        item = obj.mn_trajectory_selections[obj.mn.trajectory_selection_index]

        col = layout.column(align=False)
        row = col.row()
        col.prop(item, "string")

        # disable editing for immutable selections
        # disable modifying updating and periodic
        if item.from_atomgroup:
            col.enabled = False

        if item.message != "":
            box = col.box()
            box.label(text="Invalid Selection", icon="ERROR")
            box.label(text=item.message)
            box.alert = True
            op = box.operator("wm.url_open", text="Selection Langauge Docs", icon="URL")
            op.url = (
                "https://docs.mdanalysis.org/stable/documentation_pages/selections.html"
            )


def panel_object(layout, context):
    object = context.active_object
    if object is None:
        # When an object is deleted, context.ative_object is None
        return
    row = layout.row()
    row.prop(object.mn, "entity_type")
    row.enabled = False
    try:
        mol_type = object.mn.entity_type
    except AttributeError:
        return None
    if mol_type == "None":
        layout.label(text="No MN object selected")
        return None
    if mol_type.startswith("md"):
        panel_md_properties(layout, context)
    if mol_type == "ensemble-star":
        layout.label(text="Ensemble")
        box = layout.box()
        ui_from_node(box, nodes.get_star_node(object), context=context)
        return None


def item_ui(layout, item):
    row = layout.row()
    row.label(text=item.name)
    col = row.column()
    op = col.operator("mn.session_create_object")
    op.uuid = item.uuid
    col.enabled = item.object is None

    op = row.operator("mn.session_remove_item", text="", icon="CANCEL")
    op.uuid = item.uuid

    if item.object is not None:
        row = layout.row()
        row.label(text=f"Object: {item.object.name}", icon="OUTLINER_OB_MESH")


def panel_session(layout, context):
    session = get_session(context)
    row = layout.row()
    row.label(text="Loaded items in the session")

    layout.label(text="Molecules")
    box = layout.box()
    for mol in session.molecules.values():
        item_ui(box, mol)

    layout.label(text="Universes")
    box = layout.box()
    for uni in session.trajectories.values():
        item_ui(box, uni)

    layout.label(text="Ensembles")
    box = layout.box()
    for ens in session.ensembles.values():
        item_ui(box, ens)


def panel_scene(layout, context):
    scene = context.scene

    cam = bpy.data.cameras[bpy.data.scenes["Scene"].camera.name]
    world_shader = bpy.data.worlds["World Shader"].node_tree.nodes["MN_world_shader"]
    grid = layout.grid_flow()
    col = grid.column()
    col.label(text="World Settings")
    world = col.box()
    world.prop(bpy.data.scenes["Scene"].render, "engine")
    if scene.render.engine == "CYCLES":
        world.prop(bpy.data.scenes["Scene"].cycles, "samples")
    else:
        world.prop(bpy.data.scenes["Scene"].eevee, "taa_render_samples")
    world.label(text="Background")
    world.prop(world_shader.inputs[1], "default_value", text="HDRI Strength")
    row = world.row()
    row.prop(scene.render, "film_transparent")
    row.prop(world_shader.inputs[2], "default_value", text="Background")

    col = grid.column()
    col.label(text="Camera Settings")
    camera = col.box()
    camera.prop(cam, "lens")
    col = camera.column(align=True)
    row = col.row(align=True)
    row.prop(bpy.data.scenes["Scene"].render, "resolution_x", text="X")
    row.prop(bpy.data.scenes["Scene"].render, "resolution_y", text="Y")
    row = camera.grid_flow()
    row.prop(cam.dof, "use_dof")
    row.prop(bpy.data.scenes["Scene"].render, "use_motion_blur")
    focus = camera.column()
    focus.enabled = cam.dof.use_dof
    focus.prop(cam.dof, "focus_object")
    distance = focus.row()
    distance.enabled = cam.dof.focus_object is None
    distance.prop(cam.dof, "focus_distance")
    focus.prop(cam.dof, "aperture_fstop")


class MN_PT_Scene(bpy.types.Panel):
    bl_label = "Molecular Nodes"
    bl_idname = "MN_PT_panel"
    bl_space_type = "PROPERTIES"
    bl_region_type = "WINDOW"
    bl_context = "scene"
    bl_order = 0
    bl_options = {"HEADER_LAYOUT_EXPAND"}
    bl_ui_units_x = 0

    def draw(self, context):
        layout = self.layout
        scene = context.scene
        row = layout.row()

        row = layout.row(align=True)

        for p in ["import", "object", "session"]:
            row.prop_enum(scene.mn, "panel_selection", p)

        # the possible panel functions to choose between
        which_panel = {
            "import": panel_import,
            "object": panel_object,
            "session": panel_session,
        }
        # call the required panel function with the layout and context
        which_panel[scene.mn.panel_selection](layout, context)


class MN_UL_EntitiesList(bpy.types.UIList):
    """
    UIList of entities in Entities panel (Viewport)
    """

    def draw_item(
        self,
        context,
        layout,
        data,
        item,
        icon,
        active_data,
        active_property,
        index=0,
        flt_flag=0,
    ):
        custom_icon = "WORLD"
        if self.layout_type in {"DEFAULT", "COMPACT"}:
            row = layout.row()
            seqno = f"{index + 1}. "
            split = row.split(factor=0.1)
            col = split.column()
            col.label(text=seqno)
            col = split.column()
            session = context.scene.MNSession
            entity = session.get(item.name)
            col.prop(entity.object, "name", text="", emboss=False)
            # use the object viewport visibility to determine the icon
            # we do not have direct callbacks for raw object visibility changes
            hide_icon = "HIDE_OFF" if not entity.object.hide_get() else "HIDE_ON"
            row.prop(
                item,
                "visible",
                icon_only=True,
                icon=hide_icon,
            )
        elif self.layout_type in {"GRID"}:
            layout.alignment = "CENTER"
            layout.label(text="", icon=custom_icon)

    def filter_items(self, context, data, propname):
        items = getattr(data, propname)
        filtered = [self.bitflag_filter_item] * len(items)
        for i, item in enumerate(items):
            try:
                _ = context.scene.MNSession.get(item.name).name
            except (LinkedObjectError, AttributeError):
                filtered[i] &= ~self.bitflag_filter_item
        return filtered, []


class MN_PT_Entities(bpy.types.Panel):
    """
    Panel to list MN Entities in Viewport
    """

    bl_idname = "MN_PT_Entities"
    bl_label = "Entities"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Molecular Nodes"

    def draw(self, context):
        layout = self.layout
        props = context.scene.mn
        row = layout.row()
        row.template_list(
            "MN_UL_EntitiesList",
            "entities_list",
            props,
            "entities",
            props,
            "entities_active_index",
            rows=3,
        )
        col = row.column()
        row = col.row()
        row.operator("mn.session_prune", icon="FILE_REFRESH", text="")
        row = col.row()
        row.operator("mn.session_create_object", icon="ADD", text="")
        row.enabled = False  # TODO: create object or create entity or remove?
        row = col.row()
        op = row.operator("mn.session_remove_item", icon="REMOVE", text="")
        if props.entities_active_index == -1:
            row.enabled = False
        else:
            op.uuid = props.entities[props.entities_active_index].name

        if props.entities_active_index == -1:
            return
        # display entity type of the selected entity
        uuid = props.entities[props.entities_active_index].name
        entity = context.scene.MNSession.get(uuid)
        if not entity:
            return
        row = layout.row()
        row.prop(entity.object.mn, "entity_type")
        row.enabled = False


class MN_PT_trajectory(bpy.types.Panel):
    """
    Panel for trajectory details
    """

    bl_idname = "MN_PT_trajectory"
    bl_label = "Trajectory"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Molecular Nodes"

    @classmethod
    def poll(cls, context):
        """Visible only if entity selected is a trajectory"""
        scene = context.scene
        active_index = scene.mn.entities_active_index
        if active_index == -1:
            return False
        uuid = scene.mn.entities[active_index].name
        try:
            return scene.MNSession.get(uuid).object.mn.entity_type in (
                EntityType.MD.value,
                EntityType.MD_OXDNA.value,
            )
        except (LinkedObjectError, AttributeError):
            return False

    def draw(self, context):
        layout = self.layout
        # To enable the animatate dot next to property in UI
        # layout.use_property_split = True
        # layout.use_property_decorate = True
        scene = context.scene
        active_index = scene.mn.entities_active_index
        uuid = scene.mn.entities[active_index].name
        # Use the object corresponding to the entity
        object = scene.MNSession.get(uuid).object
        props = object.mn
        row = layout.row()
        label = "This trajectory has " + str(props.n_frames) + " frames"
        row.label(text=label)
        row = layout.row()
        row.prop(props, "update_with_scene")
        box = layout.box()
        row = box.row()
        row.prop(props, "frame")
        box.enabled = not props.update_with_scene


class MN_UL_StylesList(bpy.types.UIList):
    """
    UIList of styles for an entity
    """

    seqno = 1

    def draw_item(
        self,
        context,
        layout,
        data,
        item,
        icon,
        active_data,
        active_property,
        index=0,
        flt_flag=0,
    ):
        custom_icon = "WORLD"
        if self.layout_type in {"DEFAULT", "COMPACT"}:
            row = layout.row()
            seqno = f"{self.seqno}. "
            MN_UL_StylesList.seqno += 1
            split = row.split(factor=0.1)
            col = split.column()
            col.label(text=seqno)
            col = split.column()
            col.prop(item, "label", text="", emboss=False)
            if "Visible" in item.inputs:
                input = item.inputs["Visible"]
                hide_icon = "HIDE_OFF" if input.default_value else "HIDE_ON"
                row.prop(input, "default_value", icon_only=True, icon=hide_icon)
        elif self.layout_type in {"GRID"}:
            layout.alignment = "CENTER"
            layout.label(text="", icon=custom_icon)

    def filter_items(self, context, data, propname):
        items = getattr(data, propname)
        helper_funcs = bpy.types.UI_UL_list
        filtered = []
        # Filtering by name
        if self.filter_name:
            filtered = helper_funcs.filter_items_by_name(
                self.filter_name,
                self.bitflag_filter_item,
                items,
                "label",
                reverse=False,
            )
        if not filtered:
            filtered = [self.bitflag_filter_item] * len(items)
        style_nodes = get_final_style_nodes(data)
        ordered = [-1] * len(items)
        for i, item in enumerate(items):
            if item in style_nodes:
                ordered[i] = style_nodes.index(item)
            else:
                filtered[i] &= ~self.bitflag_filter_item
        index = len(style_nodes)
        for i, item in enumerate(items):
            if ordered[i] == -1:
                ordered[i] = index
                index += 1
        return filtered, ordered


class MN_PT_Styles(bpy.types.Panel):
    """
    Panel for styles
    """

    bl_idname = "MN_PT_styles"
    bl_label = "Styles"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Molecular Nodes"

    @classmethod
    def poll(cls, context):
        """Visible only if entity selected is a trajectory or molecule"""
        scene = context.scene
        active_index = scene.mn.entities_active_index
        if active_index == -1:
            return False
        uuid = scene.mn.entities[active_index].name
        try:
            return scene.MNSession.get(uuid).object.mn.entity_type in (
                EntityType.MD.value,
                EntityType.MD_STREAMING.value,
                EntityType.MOLECULE.value,
                EntityType.DENSITY.value,
            )
        except (LinkedObjectError, AttributeError):
            return False

    def draw(self, context):
        scene = context.scene
        entities_active_index = scene.mn.entities_active_index
        uuid = scene.mn.entities[entities_active_index].name
        entity = scene.MNSession.get(uuid)
        node_group = entity.node_group
        if node_group is None:
            return
        styles_active_index = entity.object.mn.styles_active_index
        valid_selection = False
        style_nodes = get_final_style_nodes(node_group)
        if 0 <= styles_active_index < len(node_group.nodes):
            if node_group.nodes[styles_active_index] in style_nodes:
                valid_selection = True

        layout = self.layout
        row = layout.row()
        MN_UL_StylesList.seqno = 1
        row.template_list(
            "MN_UL_StylesList",
            "styles_list",
            node_group,
            "nodes",
            entity.object.mn,
            "styles_active_index",
            rows=3,
        )
        if not isinstance(entity, density.Density):
            col = row.column()
            row = col.row()
            op = row.operator("mn.add_style", icon="ADD", text="")
            op.uuid = uuid
            row = col.row()
            op = row.operator("mn.remove_style", icon="REMOVE", text="")
            if valid_selection:
                op.uuid = uuid
                op.style_node_index = styles_active_index
            else:
                row.enabled = False

        if not valid_selection:
            return

        box = layout.box()
        row = box.row()
        style_node = node_group.nodes[styles_active_index]

        panels = {}
        for item in style_node.node_tree.interface.items_tree.values():
            if item.item_type == "PANEL":
                header = None
                if item.parent.name and item.parent.name in panels:
                    panel = panels[item.parent.name]
                    if panel:
                        header, panel = panel.panel(item.name, default_closed=False)
                else:
                    header, panel = box.panel(item.name, default_closed=False)
                if header:
                    header.label(text=item.name)
                panels[item.name] = panel
            elif item.name == "Selection":
                continue
            else:
                if item.in_out != "INPUT":
                    continue
                if item.name in ("Visible"):
                    continue
                input = style_node.inputs[item.identifier]
                if not hasattr(input, "default_value"):
                    continue
                row = None
                if item.parent.name and item.parent.name in panels:
                    panel = panels[item.parent.name]
                    if panel:
                        row = panel.row()
                else:
                    row = box.row()
                if row:
                    row.prop(data=input, property="default_value", text=input.name)
        row = box.row()


class MN_UL_AnnotationsList(bpy.types.UIList):
    """
    UIList of annotations for an entity
    """

    def draw_item(
        self,
        context,
        layout,
        data,
        item,
        icon,
        active_data,
        active_property,
        index=0,
        flt_flag=0,
    ):
        custom_icon = "WORLD"
        if self.layout_type in {"DEFAULT", "COMPACT"}:
            row = layout.row()
            split = row.split(factor=0.1)
            col = split.column()
            col.label(text=f"{index + 1}. ")
            col = split.column()
            col.prop(item, "label", text="", emboss=False)
            hide_icon = "HIDE_OFF" if item.visible else "HIDE_ON"
            row.prop(item, "visible", icon_only=True, icon=hide_icon)
        elif self.layout_type in {"GRID"}:
            layout.alignment = "CENTER"
            layout.label(text="", icon=custom_icon)

    def filter_items(self, context, data, propname):
        if data is None:
            return [], []
        items = getattr(data, propname)
        helper_funcs = bpy.types.UI_UL_list
        filtered = []
        # Filtering by name
        if self.filter_name:
            filtered = helper_funcs.filter_items_by_name(
                self.filter_name,
                self.bitflag_filter_item,
                items,
                "label",
                reverse=False,
            )
        if not filtered:
            filtered = [self.bitflag_filter_item] * len(items)
        return filtered, []


class MN_PT_Annotations(bpy.types.Panel):
    """
    Panel for annotations
    """

    bl_idname = "MN_PT_annotations"
    bl_label = "Annotations"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Molecular Nodes"

    @classmethod
    def poll(cls, context):
        """Visible only if entity selected is a trajectory or molecule"""
        scene = context.scene
        active_index = scene.mn.entities_active_index
        if active_index == -1:
            return False
        uuid = scene.mn.entities[active_index].name
        try:
            return scene.MNSession.get(uuid).object.mn.entity_type in (
                EntityType.MD.value,
                EntityType.MD_STREAMING.value,
                EntityType.MOLECULE.value,
                EntityType.DENSITY.value,
            )
        except (LinkedObjectError, AttributeError):
            return False

    def draw(self, context):
        scene = context.scene
        entities_active_index = scene.mn.entities_active_index
        uuid = scene.mn.entities[entities_active_index].name
        entity = scene.MNSession.get(uuid)
        object = entity.object
        annotations_active_index = object.mn.annotations_active_index
        valid_selection = annotations_active_index != -1

        layout = self.layout
        row = layout.row()
        row.prop(object.mn, "annotations_visible", text="Visible")

        row = layout.row()
        MN_UL_AnnotationsList.seqno = 1
        row.template_list(
            "MN_UL_AnnotationsList",
            "annotations_list",
            object,
            "mn_annotations",
            object.mn,
            "annotations_active_index",
            rows=3,
        )
        col = row.column()
        row = col.row()
        op = row.operator("mn.add_annotation", icon="ADD", text="")
        op.uuid = uuid
        row = col.row()
        op = row.operator("mn.remove_annotation", icon="REMOVE", text="")
        if valid_selection:
            op.uuid = uuid
            op.annotation_uuid = object.mn_annotations[annotations_active_index].name
        else:
            row.enabled = False

        if not valid_selection:
            return

        item = object.mn_annotations[annotations_active_index]
        box = layout.box()
        row = box.row()
        row.prop(item, "type")
        row.enabled = False
        entity_annotation_type = f"{entity._mn_entity_type}_{item.type}"
        inputs = getattr(item, entity_annotation_type, None)
        instance = entity.annotations._interfaces.get(inputs.uuid)._instance
        if inputs is not None:
            if not inputs.valid_inputs:
                col = layout.column()
                box = col.box()
                box.label(text="Invalid inputs", icon="ERROR")
                box.alert = True

            for prop_name in inputs.__annotations__.keys():
                if prop_name in ("uuid", "valid_inputs"):
                    continue
                row = box.row()
                if hasattr(instance, f"_{prop_name}"):
                    # indicate use of non blender property in draw
                    row.label(icon="ERROR")
                    row.alert = True
                row.prop(inputs, prop_name)

        # Add all the common annotation params within the 'Options' panel
        header, panel = box.panel("annotation_options", default_closed=True)
        header.label(text="Options")

        if panel:
            text_header, text_panel = panel.panel("text_options", default_closed=True)
            text_header.label(text="Text")
            line_header, line_panel = panel.panel("line_options", default_closed=True)
            line_header.label(text="Lines")
            mesh_header, mesh_panel = panel.panel("mesh_options", default_closed=True)
            mesh_header.label(text="Meshes")

            for prop in item.bl_rna.properties:
                if not prop.is_runtime:
                    continue
                if prop.identifier in ("label", "type", "visible"):
                    continue
                if prop.type == "POINTER" and prop.identifier != "mesh_material":
                    continue
                if text_panel and prop.identifier.startswith("text_"):
                    row = text_panel.row()
                    row.prop(item, prop.identifier)
                    if prop.identifier == "text_falloff":
                        row.enabled = item.text_depth
                elif line_panel and prop.identifier.startswith("line_"):
                    row = line_panel.row()
                    row.prop(item, prop.identifier)
                    if prop.identifier == "line_mode":
                        continue
                    row.enabled = item.line_mode != "mesh"
                elif mesh_panel and prop.identifier.startswith("mesh_"):
                    row = mesh_panel.row()
                    row.prop(item, prop.identifier)

        row = box.row()


class MN_PT_Compositor(bpy.types.Panel):
    """
    Panel for Compositor
    """

    bl_idname = "MN_PT_compositor"
    bl_label = "Options"
    bl_space_type = "NODE_EDITOR"
    bl_region_type = "UI"
    bl_category = "Molecular Nodes"

    @classmethod
    def poll(cls, context):
        return (
            context.area.type == "NODE_EDITOR"
            and context.space_data.tree_type == "CompositorNodeTree"
        )

    def draw(self, context):
        layout = self.layout
        box = layout.box()
        row = box.row()
        row.operator("mn.setup_compositor")


CLASSES = [
    MN_PT_Scene,
    MN_UL_EntitiesList,
    MN_PT_Entities,
    MN_PT_trajectory,
    MN_UL_StylesList,
    MN_PT_Styles,
    MN_UL_AnnotationsList,
    MN_PT_Annotations,
    MN_PT_Compositor,
]
