# SPDX-FileCopyrightText: 2009-2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later

import bpy

from mathutils import Vector
from bpy.types import bpy_prop_array
from idprop.types import IDPropertyArray, IDPropertyGroup

ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector, bpy_prop_array)

# Maximum length of an array property for which a multi-line
# edit field will be displayed in the Custom Properties panel.
MAX_DISPLAY_ROWS = 8


def rna_idprop_quote_path(prop):
    return "[\"{:s}\"]".format(bpy.utils.escape_identifier(prop))


def rna_idprop_ui_prop_update(item, prop):
    prop_path = rna_idprop_quote_path(prop)
    prop_rna = item.path_resolve(prop_path, False)
    if isinstance(prop_rna, bpy.types.bpy_prop):
        prop_rna.update()


def rna_idprop_ui_prop_clear(item, prop):
    ui_data = item.id_properties_ui(prop)
    ui_data.clear()


def rna_idprop_context_value(context, context_member, property_type):
    space = context.space_data

    if space is None or isinstance(space, bpy.types.SpaceProperties):
        pin_id = space.pin_id
    else:
        pin_id = None

    if pin_id and isinstance(pin_id, property_type):
        rna_item = pin_id
        context_member = "space_data.pin_id"
    else:
        rna_item = context.path_resolve(context_member)

    return rna_item, context_member


def rna_idprop_has_properties(rna_item):
    keys = rna_item.keys()
    return bool(keys)


def rna_idprop_value_to_python(value):
    if isinstance(value, IDPropertyArray):
        return value.to_list()
    elif isinstance(value, IDPropertyGroup):
        return value.to_dict()
    else:
        return value


def rna_idprop_value_item_type(value):
    is_array = isinstance(value, ARRAY_TYPES) and len(value) > 0
    item_value = value[0] if is_array else value
    return type(item_value), is_array


def rna_idprop_ui_prop_default_set(item, prop, value):
    ui_data = item.id_properties_ui(prop)
    ui_data.update(default=value)


def rna_idprop_ui_create(
        item, prop, *, default,
        min=0, max=1,
        soft_min=None, soft_max=None,
        description=None,
        overridable=False,
        subtype=None,
        step=None,
        precision=None,
        id_type='OBJECT',
        items=None,
):
    """Create and initialize a custom property with limits, defaults and other settings."""

    # Assign the value
    item[prop] = default

    rna_idprop_ui_prop_update(item, prop)
    ui_data = item.id_properties_ui(prop)
    proptype, _ = rna_idprop_value_item_type(default)

    if soft_min is None:
        soft_min = min
    if soft_max is None:
        soft_max = max

    if (proptype is bool) or (proptype is str):
        ui_data.update(
            description=description,
            default=default,
        )
    elif proptype is type(None) or issubclass(proptype, bpy.types.ID):
        ui_data.update(
            description=description,
            id_type=id_type,
        )
    elif proptype is float:
        if step is None:
            step = 0.1
        if precision is None:
            precision = 3

        ui_data.update(
            subtype=subtype,
            min=min,
            max=max,
            soft_min=soft_min,
            soft_max=soft_max,
            step=step,
            precision=precision,
            description=description,
            default=default,
        )
    elif proptype is int:
        if step is None:
            step = 1

        if items is None:
            ui_data.update(
                subtype=subtype,
                min=min,
                max=max,
                soft_min=soft_min,
                soft_max=soft_max,
                step=step,
                description=description,
                default=default,
            )
        else:
            ui_data.update(
                subtype=subtype,
                description=description,
                default=default,
                items=items,
            )
    else:
        raise TypeError("Unexpected value type")

    prop_path = rna_idprop_quote_path(prop)

    item.property_overridable_library_set(prop_path, overridable)


def draw(layout, context, context_member, property_type, *, use_edit=True):
    rna_item, context_member = rna_idprop_context_value(context, context_member, property_type)
    # poll should really get this...
    if not rna_item:
        return

    if rna_item.id_data.library is not None:
        use_edit = False
    is_lib_override = rna_item.id_data.override_library and rna_item.id_data.override_library.reference

    assert isinstance(rna_item, property_type)

    items = list(rna_item.items())
    items.sort()

    # TODO: Allow/support adding new custom props to overrides.
    if use_edit and not is_lib_override:
        row = layout.row()
        props = row.operator("wm.properties_add", text="New", icon='ADD')
        props.data_path = context_member
        del row
        layout.separator()

    show_developer_ui = context.preferences.view.show_developer_ui
    rna_properties = {prop.identifier for prop in rna_item.bl_rna.properties if prop.is_runtime} if items else None

    layout.use_property_decorate = False

    for key, value in items:
        is_rna = (key in rna_properties)

        # Only show API defined properties to developers.
        if is_rna and not show_developer_ui:
            continue

        to_dict = getattr(value, "to_dict", None)
        to_list = getattr(value, "to_list", None)
        is_datablock = value is None or isinstance(value, bpy.types.ID)

        if to_dict:
            value = to_dict()
        elif to_list:
            value = to_list()

        split = layout.split(factor=0.4, align=True)
        label_row = split.row()
        label_row.alignment = 'RIGHT'
        label_row.label(text=key, translate=False)

        value_row = split.row(align=True)
        value_column = value_row.column(align=True)

        is_long_array = to_list and len(value) >= MAX_DISPLAY_ROWS

        if is_rna:
            value_column.prop(rna_item, key, text="")
        elif to_dict or is_long_array:
            props = value_column.operator("wm.properties_edit_value", text="Edit Value")
            props.data_path = context_member
            props.property_name = key
        elif is_datablock:
            value_column.template_ID(rna_item, rna_idprop_quote_path(key), text="")
        else:
            value_column.prop(rna_item, rna_idprop_quote_path(key), text="")

        operator_row = value_row.row(align=True)
        operator_row.alignment = 'RIGHT'

        # Do not allow editing of overridden properties (we cannot use a poll function
        # of the operators here since they have no access to the specific property).
        operator_row.enabled = not (is_lib_override and key in rna_item.id_data.override_library.reference)

        if use_edit:
            if is_rna:
                operator_row.label(text="API Defined")
            elif is_lib_override:
                operator_row.active = False
                operator_row.label(text="", icon='DECORATE_LIBRARY_OVERRIDE')
            else:
                props = operator_row.operator("wm.properties_edit", text="", icon='PREFERENCES', emboss=False)
                props.data_path = context_member
                props.property_name = key
                props = operator_row.operator("wm.properties_remove", text="", icon='X', emboss=False)
                props.data_path = context_member
                props.property_name = key


class PropertyPanel:
    """
    The subclass should have its own poll function
    and the variable '_context_path' MUST be set.
    """
    bl_label = "Custom Properties"
    bl_options = {'DEFAULT_CLOSED'}
    bl_order = 1000  # Order panel after all others

    @classmethod
    def poll(cls, context):
        rna_item, _context_member = rna_idprop_context_value(context, cls._context_path, cls._property_type)
        return bool(rna_item)

    """
    def draw_header(self, context):
        rna_item, context_member = rna_idprop_context_value(context, self._context_path, self._property_type)
        tot = len(rna_item.keys())
        if tot:
            self.layout().label(text="{:d}:".format(tot))
    """

    def draw(self, context):
        draw(self.layout, context, self._context_path, self._property_type)
