from enum import StrEnum

from pydantic import AliasChoices, Field

from napari.settings._base import EventedSettings
from napari.utils.colormap_backend import (
    ColormapBackend,
    set_backend as set_colormap_backend,
)
from napari.utils.events import Event
from napari.utils.triangulation_backend import (
    TriangulationBackend,
    set_backend as set_triangulation_backend,
)


class PaletteFuzzySearch(StrEnum):
    """
    Enum for palette fuzzy search.
    """

    enabled_if_available = 'Enabled if available'
    enabled = 'Enabled'
    disabled = 'Disabled'


# this class inherits from EventedSettings instead of EventedModel because
# it uses Field(validation_alias=...) for some of its attributes
class ExperimentalSettings(EventedSettings):
    def _connect_events(self) -> None:
        """Connects the events for the triangulation and colormap backends to their respective update functions."""
        self.events.triangulation_backend.connect(
            _update_triangulation_backend
        )
        self.events.triangulation_backend(value=self.triangulation_backend)
        self.events.colormap_backend.connect(_update_colormap_backend)
        self.events.colormap_backend(value=self.colormap_backend)

    async_: bool = Field(
        False,
        title='Render Layers Asynchronously',
        description='Asynchronous loading of layers. \nThis setting partially loads data while viewing.',
        validation_alias=AliasChoices('async_', 'async', 'napari_async'),
        json_schema_extra={'requires_restart': False},
    )
    autoswap_buffers: bool = Field(
        False,
        title='Enable autoswapping rendering buffers.',
        description='Autoswapping rendering buffers improves quality by reducing tearing artifacts, while sacrificing some performance.',
        validation_alias=AliasChoices('autoswap_buffers', 'napari_autoswap'),
        json_schema_extra={'requires_restart': True},
    )

    rdp_epsilon: float = Field(
        0.5,
        title='Shapes polygon lasso and path RDP epsilon',
        description='Setting this higher removes more points from polygons or paths. \nSetting this to 0 keeps all vertices of '
        'a given polygon or path.',
        ge=0,
    )

    lasso_vertex_distance: int = Field(
        10,
        title='Minimum distance threshold of shapes lasso and path tool',
        description='Value determines how many screen pixels one has to move before another vertex can be added to the polygon'
        'or path.',
        gt=0,
        lt=50,
    )

    completion_radius: int = Field(
        default=-1,
        title='Double-click Labels polygon completion radius (-1 to always complete)',
        description='Max radius in pixels from first vertex for double-click to complete a polygon; set -1 to always complete.',
    )

    triangulation_backend: TriangulationBackend = Field(
        TriangulationBackend.fastest_available,
        title='Triangulation backend to use for Shapes layer',
        description='Triangulation backend to use for Shapes layer.\n'
        "The 'bermuda' requires the optional 'bermuda' package.\n"
        "The 'partsegcore' requires the optional 'partsegcore-compiled-backend' package.\n"
        "The 'triangle' requires the optional 'triangle' package.\n"
        "The 'numba' backend requires the optional 'numba' package.\n"
        "The 'pure python' backend uses the default Python triangulation from vispy.\n"
        "The 'fastest available' backend will select the fastest available backend.\n",
        validation_alias=AliasChoices(
            'triangulation_backend', 'napari_triangulation_backend'
        ),
    )
    colormap_backend: ColormapBackend = Field(
        ColormapBackend.fastest_available,
        title='Colormap backend to use for Labels layer',
        description='Color mapping backend to use for Labels layer.\n'
        "'partsegcore' requires the optional 'partsegcore-compiled-backend' package.\n"
        "'numba' requires the optional 'numba' package.\n"
        "'pure python' uses only NumPy and Python.\n"
        "The 'fastest available' backend will select the fastest installed backend.\n",
        validation_alias=AliasChoices(
            'colormap_backend', 'napari_colormap_backend'
        ),
    )

    compiled_triangulation: bool = Field(
        default=False,
        title='Unused option. Use "triangulation backend" instead.',
        description='This option was removed in napari 0.6.0. Use \n'
        '"triangulation backend" instead.',
    )

    command_palette_fuzzy_search: PaletteFuzzySearch = Field(
        default=PaletteFuzzySearch.enabled_if_available,
        title='Enable fuzzy search in the command palette',
        description=(
            'When searching for commands via the command palette, use fuzzy finding\n'
            'instead of matching exact words.'
        ),
    )

    command_palette_fuzzy_search_threshold: int = Field(
        default=60,
        title='Similarity threshold (%) for fuzzy search in the command palette',
        description=(
            "When searching commands via the command palette, if a command's similarity\n"
            "to the query is lower than this threshold, it won't be shown as a match."
        ),
        ge=0,
        le=100,
    )

    dynamic_layer_controls: bool = Field(
        default=False,
        title='Generate GUI layer controls dynamically instead of using premade panels.',
        description=(
            'Based on the attributes of the currenty selected layer, generate layer controls\n'
            'dynamically, even when a single layer is selected. Happens by default with multiple layers.'
        ),
    )

    class NapariConfig:
        # Napari specific configuration
        preferences_exclude = ('schema_version', 'compiled_triangulation')


def _update_triangulation_backend(event: Event) -> None:
    experimental: ExperimentalSettings = event.source

    set_triangulation_backend(experimental.triangulation_backend)


def _update_colormap_backend(event: Event) -> None:
    experimental: ExperimentalSettings = event.source

    set_colormap_backend(experimental.colormap_backend)
