# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
from collections.abc import Sequence
from typing import TYPE_CHECKING, Protocol, TypedDict, Union

from bpy.app.translations import pgettext
from bpy.props import BoolProperty, IntVectorProperty
from bpy.types import AddonPreferences, Context, Operator, UILayout

from . import version
from .logger import get_logger
from .shader import (
    add_mtoon1_auto_setup_shader_node_group,
    remove_mtoon1_auto_setup_shader_node_group,
)

logger = get_logger(__name__)

addon_package_name = ".".join(__name__.split(".")[:-2])


class ImportPreferencesProtocol(Protocol):
    extract_textures_into_folder: bool
    make_new_texture_folder: bool
    set_shading_type_to_material_on_import: bool
    set_view_transform_to_standard_on_import: bool
    set_armature_display_to_wire: bool
    set_armature_display_to_show_in_front: bool
    set_armature_bone_shape_to_default: bool
    enable_mtoon_outline_preview: bool


class ImportPreferencesDict(TypedDict):
    extract_textures_into_folder: bool
    make_new_texture_folder: bool
    set_shading_type_to_material_on_import: bool
    set_view_transform_to_standard_on_import: bool
    set_armature_display_to_wire: bool
    set_armature_display_to_show_in_front: bool
    set_armature_bone_shape_to_default: bool
    enable_mtoon_outline_preview: bool


def create_import_preferences_dict(
    source: ImportPreferencesProtocol,
) -> ImportPreferencesDict:
    return {
        "extract_textures_into_folder": source.extract_textures_into_folder,
        "make_new_texture_folder": source.make_new_texture_folder,
        "set_shading_type_to_material_on_import": (
            source.set_shading_type_to_material_on_import
        ),
        "set_view_transform_to_standard_on_import": (
            source.set_view_transform_to_standard_on_import
        ),
        "set_armature_display_to_wire": (source.set_armature_display_to_wire),
        "set_armature_display_to_show_in_front": (
            source.set_armature_display_to_show_in_front
        ),
        "set_armature_bone_shape_to_default": source.set_armature_bone_shape_to_default,
        "enable_mtoon_outline_preview": source.enable_mtoon_outline_preview,
    }


def copy_import_preferences(
    *, source: ImportPreferencesProtocol, destination: ImportPreferencesProtocol
) -> None:
    (
        destination.extract_textures_into_folder,
        destination.make_new_texture_folder,
        destination.set_shading_type_to_material_on_import,
        destination.set_view_transform_to_standard_on_import,
        destination.set_armature_display_to_wire,
        destination.set_armature_display_to_show_in_front,
        destination.set_armature_bone_shape_to_default,
        destination.enable_mtoon_outline_preview,
    ) = (
        source.extract_textures_into_folder,
        source.make_new_texture_folder,
        source.set_shading_type_to_material_on_import,
        source.set_view_transform_to_standard_on_import,
        source.set_armature_display_to_wire,
        source.set_armature_display_to_show_in_front,
        source.set_armature_bone_shape_to_default,
        source.enable_mtoon_outline_preview,
    )


def draw_import_preferences_layout(
    preferences: ImportPreferencesProtocol, layout: UILayout
) -> None:
    if not isinstance(preferences, (AddonPreferences, Operator)):
        return

    layout.prop(preferences, "extract_textures_into_folder")
    layout.prop(preferences, "make_new_texture_folder")
    layout.prop(preferences, "set_shading_type_to_material_on_import")
    layout.prop(preferences, "set_view_transform_to_standard_on_import")
    layout.prop(preferences, "set_armature_display_to_wire")
    layout.prop(preferences, "set_armature_display_to_show_in_front")
    layout.prop(preferences, "set_armature_bone_shape_to_default")
    layout.prop(preferences, "enable_mtoon_outline_preview")


class ExportPreferencesProtocol(Protocol):
    export_invisibles: bool
    export_only_selections: bool
    enable_advanced_preferences: bool
    export_all_influences: bool
    export_lights: bool
    export_gltf_animations: bool
    export_try_sparse_sk: bool


def copy_export_preferences(
    *, source: ExportPreferencesProtocol, destination: ExportPreferencesProtocol
) -> None:
    (
        destination.export_invisibles,
        destination.export_only_selections,
        destination.enable_advanced_preferences,
        destination.export_all_influences,
        destination.export_lights,
        destination.export_gltf_animations,
        destination.export_try_sparse_sk,
    ) = (
        source.export_invisibles,
        source.export_only_selections,
        source.enable_advanced_preferences,
        source.export_all_influences,
        source.export_lights,
        source.export_gltf_animations,
        source.export_try_sparse_sk,
    )


def draw_advanced_options_description(
    preferences: Union[AddonPreferences, Operator],
    property_name: str,
    layout: UILayout,
    description: str,
) -> None:
    column = layout.box().column(align=True)
    column.prop(preferences, property_name)
    description_column = column.box().column(align=True)
    for i, line in enumerate(description.splitlines()):
        icon = "ERROR" if i == 0 else "NONE"
        description_column.label(text=line, translate=False, icon=icon)


def draw_export_preferences_layout(
    preferences: ExportPreferencesProtocol,
    layout: UILayout,
    *,
    show_vrm1_options: bool,
) -> None:
    if not isinstance(preferences, (AddonPreferences, Operator)):
        return

    layout.prop(preferences, "export_invisibles")
    layout.prop(preferences, "export_only_selections")

    if not show_vrm1_options:
        return

    layout.prop(preferences, "enable_advanced_preferences")
    if not preferences.enable_advanced_preferences:
        return

    advanced_options_column = layout.box().column()

    # UniVRM 0.115.0 doesn't support `export_try_sparse_sk`
    # https://github.com/saturday06/VRM-Addon-for-Blender/issues/381#issuecomment-1838365762
    draw_advanced_options_description(
        preferences,
        "export_try_sparse_sk",
        advanced_options_column,
        pgettext(
            "The file size will be reduced,\n"
            + "but it will no longer be readable by\n"
            + "older apps with UniVRM 0.115.0 or\n"
            + "earlier."
        ),
    )

    # The upstream says that Models may appear incorrectly in many viewers.
    # https://github.com/KhronosGroup/glTF-Blender-IO/blob/356b3dda976303d3ecce8b3bd1591245e576db38/addons/io_scene_gltf2/__init__.py#L760
    draw_advanced_options_description(
        preferences,
        "export_all_influences",
        advanced_options_column,
        pgettext(
            "By default, 4 bone influences\n"
            + "are exported for each vertex. Many\n"
            + "apps truncate to 4. Increasing it\n"
            + "may cause jagged meshes."
        ),
    )

    draw_advanced_options_description(
        preferences,
        "export_lights",
        advanced_options_column,
        pgettext(
            "There is no consensus on how\n"
            + "to handle lights in VRM, so it is\n"
            + "impossible to predict what the\n"
            + "outcome will be."
        ),
    )

    draw_advanced_options_description(
        preferences,
        "export_gltf_animations",
        advanced_options_column,
        pgettext(
            "UniVRM does not export\n"
            + "glTF Animations, so it is disabled\n"
            + "by default. Please consider using\n"
            + "VRM Animation."
        ),
    )


class VrmAddonPreferences(AddonPreferences):
    bl_idname = addon_package_name

    INITIAL_ADDON_VERSION: tuple[int, int, int] = (0, 0, 0)

    addon_version: IntVectorProperty(  # type: ignore[valid-type]
        size=3,
        default=INITIAL_ADDON_VERSION,
    )

    def update_add_mtoon_shader_node_group(self, context: Context) -> None:
        if self.add_mtoon_shader_node_group:
            add_mtoon1_auto_setup_shader_node_group(context)
        else:
            remove_mtoon1_auto_setup_shader_node_group(context)

    add_mtoon_shader_node_group: BoolProperty(  # type: ignore[valid-type]
        name="Add MToon shader node group",
        default=True,
        update=update_add_mtoon_shader_node_group,
    )

    extract_textures_into_folder: BoolProperty(  # type: ignore[valid-type]
        name="Extract texture images into the folder",
        default=False,
    )
    make_new_texture_folder: BoolProperty(  # type: ignore[valid-type]
        name="Don't overwrite existing texture folder",
        default=True,
    )
    set_shading_type_to_material_on_import: BoolProperty(  # type: ignore[valid-type]
        name='Set shading type to "Material"',
        default=True,
    )
    set_view_transform_to_standard_on_import: BoolProperty(  # type: ignore[valid-type]
        name='Set view transform to "Standard"',
        default=True,
    )
    set_armature_display_to_wire: BoolProperty(  # type: ignore[valid-type]
        name='Set an imported armature display to "Wire"',
        default=True,
    )
    set_armature_display_to_show_in_front: BoolProperty(  # type: ignore[valid-type]
        name='Set an imported armature display to show "In-Front"',
        default=True,
    )
    set_armature_bone_shape_to_default: BoolProperty(  # type: ignore[valid-type]
        name="Set an imported bone shape to default",
        default=True,
    )

    enable_mtoon_outline_preview: BoolProperty(  # type: ignore[valid-type]
        name="Enable MToon Outline Preview",
        default=True,
    )

    export_invisibles: BoolProperty(  # type: ignore[valid-type]
        name="Export Invisible Objects",
    )
    export_only_selections: BoolProperty(  # type: ignore[valid-type]
        name="Export Only Selections",
    )
    enable_advanced_preferences: BoolProperty(  # type: ignore[valid-type]
        name="Enable Advanced Options",
    )
    export_all_influences: BoolProperty(  # type: ignore[valid-type]
        name="Export All Bone Influences",
    )
    export_lights: BoolProperty(  # type: ignore[valid-type]
        name="Export Lights",
    )
    export_gltf_animations: BoolProperty(  # type: ignore[valid-type]
        name="Export glTF Animations",
    )
    export_try_sparse_sk: BoolProperty(  # type: ignore[valid-type]
        name="Use Sparse Accessors",
    )

    def draw(self, _context: Context) -> None:
        layout = self.layout

        warning_message = version.preferences_warning_message()
        if warning_message:
            box = layout.box()
            warning_column = box.column()
            for index, warning_line in enumerate(warning_message.splitlines()):
                warning_column.label(
                    text=warning_line,
                    translate=False,
                    icon="NONE" if index else "ERROR",
                )

        layout.prop(self, "add_mtoon_shader_node_group")

        import_box = layout.box()
        import_box.label(text="Import", icon="IMPORT")
        draw_import_preferences_layout(self, import_box)

        export_box = layout.box()
        export_box.label(text="Export", icon="EXPORT")
        draw_export_preferences_layout(self, export_box, show_vrm1_options=True)

    if TYPE_CHECKING:
        # This code is auto generated.
        # To regenerate, run the `uv run tools/property_typing.py` command.
        addon_version: Sequence[int]  # type: ignore[no-redef]
        add_mtoon_shader_node_group: bool  # type: ignore[no-redef]
        extract_textures_into_folder: bool  # type: ignore[no-redef]
        make_new_texture_folder: bool  # type: ignore[no-redef]
        set_shading_type_to_material_on_import: bool  # type: ignore[no-redef]
        set_view_transform_to_standard_on_import: bool  # type: ignore[no-redef]
        set_armature_display_to_wire: bool  # type: ignore[no-redef]
        set_armature_display_to_show_in_front: bool  # type: ignore[no-redef]
        set_armature_bone_shape_to_default: bool  # type: ignore[no-redef]
        enable_mtoon_outline_preview: bool  # type: ignore[no-redef]
        export_invisibles: bool  # type: ignore[no-redef]
        export_only_selections: bool  # type: ignore[no-redef]
        enable_advanced_preferences: bool  # type: ignore[no-redef]
        export_all_influences: bool  # type: ignore[no-redef]
        export_lights: bool  # type: ignore[no-redef]
        export_gltf_animations: bool  # type: ignore[no-redef]
        export_try_sparse_sk: bool  # type: ignore[no-redef]


def get_preferences(context: Context) -> VrmAddonPreferences:
    addon = context.preferences.addons.get(addon_package_name)
    if not addon:
        message = f"No add-on preferences for {addon_package_name}"
        raise AssertionError(message)

    preferences = addon.preferences
    if not isinstance(preferences, VrmAddonPreferences):
        raise TypeError(
            f"Add-on preferences for {addon_package_name} is not a VrmAddonPreferences"
            + f" but {type(preferences)}"
        )

    return preferences
