"""PyVista Trame Viewer class for a Vue 2 client.

This class, derived from `pyvista.trame.ui.base_viewer`,
is intended for use with a trame application where the client type is "vue2".
Therefore, the `ui` method implemented by this class utilizes the API of Vuetify 2.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from trame.ui.vuetify2 import VAppLayout
from trame.widgets import html
from trame.widgets import vuetify

from pyvista.trame.views import PyVistaLocalView
from pyvista.trame.views import PyVistaRemoteLocalView
from pyvista.trame.views import PyVistaRemoteView

from .base_viewer import BaseViewer

if TYPE_CHECKING:
    from trame_client.ui.core import AbstractLayout


def button(click, icon, tooltip):  # numpydoc ignore=PR01
    """Create a vuetify button."""
    with vuetify.VTooltip(bottom=True):
        with vuetify.Template(v_slot_activator='{ on, attrs }'):
            with vuetify.VBtn(icon=True, v_bind='attrs', v_on='on', click=click):
                vuetify.VIcon(icon)
        html.Span(tooltip)


def checkbox(model, icons, tooltip):  # numpydoc ignore=PR01
    """Create a vuetify checkbox."""
    with vuetify.VTooltip(bottom=True):
        with vuetify.Template(v_slot_activator='{ on, attrs }'):
            with html.Div(v_on='on', v_bind='attrs'):
                vuetify.VCheckbox(
                    v_model=model,
                    on_icon=icons[0],
                    off_icon=icons[1],
                    dense=True,
                    hide_details=True,
                    classes='my-0 py-0 ml-1',
                )
        html.Span(tooltip)


def slider(model, tooltip, **kwargs):  # numpydoc ignore=PR01
    """Create a vuetify slider."""
    with vuetify.VTooltip(bottom=True):
        with vuetify.Template(v_slot_activator='{ on, attrs }'):
            with html.Div(v_on='on', v_bind='attrs'):
                vuetify.VSlider(v_model=model, **kwargs)
        html.Span(tooltip)


def text_field(model, tooltip, **kwargs):  # numpydoc ignore=PR01
    """Create a vuetify text field."""
    with vuetify.VTooltip(bottom=True):
        with vuetify.Template(v_slot_activator='{ on, attrs }'):
            with html.Div(v_on='on', v_bind='attrs'):
                vuetify.VTextField(v_model=model, **kwargs)
        html.Span(tooltip)


def select(model, tooltip, **kwargs):  # numpydoc ignore=PR01
    """Create a vuetify select menu."""
    with vuetify.VTooltip(bottom=True):
        with vuetify.Template(v_slot_activator='{ on, attrs }'):
            with html.Div(v_on='on', v_bind='attrs'):
                vuetify.VSelect(v_model=model, **kwargs)
        html.Span(tooltip)


def divider(**kwargs):  # numpydoc ignore=PR01
    """Create a vuetify divider."""
    vuetify.VDivider(**kwargs)


class Viewer(BaseViewer):
    """Viewer implementation compatible with Vue 2 Trame Applications."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def make_layout(self, *args, **kwargs) -> AbstractLayout:
        """Create instance of an AbstractLayout which is appropriate for this viewer.

        Parameters
        ----------
        *args : tuple
            Positional arguments.

        **kwargs : dict, optional
            Keyword arguments.

        Returns
        -------
        VAppLayout (vue2)
            A layout this viewer can be embedded in.

        """
        return VAppLayout(*args, **kwargs)

    def ui_controls(self, mode=None, default_server_rendering=True, v_show=None):
        """Create a VRow for the UI controls.

        Parameters
        ----------
        mode : str, default: 'trame'
            The UI view mode. Options are:

            * ``'trame'``: Uses a view that can switch between client and server
              rendering modes.
            * ``'server'``: Uses a view that is purely server rendering.
            * ``'client'``: Uses a view that is purely client rendering (generally
              safe without a virtual frame buffer)

        default_server_rendering : bool, default: True
            Whether to use server-side or client-side rendering on-start when
              using the ``'trame'`` mode.

        v_show : bool, optional
            Conditionally show the viewer controls.

        """
        with vuetify.VRow(
            v_show=v_show,
            classes='pa-0 ma-0 align-center',
        ) as row:
            server = row.server
            # Listen to state changes
            server.state.change(self.EDGES)(self.on_edge_visibility_change)
            server.state.change(self.GRID)(self.on_grid_visibility_change)
            server.state.change(self.OUTLINE)(self.on_outline_visibility_change)
            server.state.change(self.AXIS)(self.on_axis_visibility_change)
            server.state.change(self.PARALLEL)(self.on_parallel_projection_change)
            server.state.change(self.SERVER_RENDERING)(self.on_rendering_mode_change)
            vuetify.VDivider(vertical=True, classes='mr-1')
            button(
                click=self.reset_camera,
                icon='mdi-arrow-expand-all',
                tooltip='Reset Camera',
            )
            vuetify.VDivider(vertical=True, classes='mx-1')
            button(
                click=self.view_isometric,
                icon='mdi-axis-arrow',
                tooltip='Perspective view',
            )
            button(
                click=self.view_yz,
                icon='mdi-axis-x-arrow',
                tooltip='Reset Camera X',
            )
            button(
                click=self.view_xz,
                icon='mdi-axis-y-arrow',
                tooltip='Reset Camera Y',
            )
            button(
                click=self.view_xy,
                icon='mdi-axis-z-arrow',
                tooltip='Reset Camera Z',
            )
            vuetify.VDivider(vertical=True, classes='mx-1')
            checkbox(
                model=(self.EDGES, False),
                icons=('mdi-grid', 'mdi-grid-off'),
                tooltip=f"Toggle edge visibility ({{{{ {self.EDGES} ? 'on' : 'off' }}}})",
            )
            checkbox(
                model=(self.OUTLINE, False),
                icons=('mdi-cube', 'mdi-cube-off'),
                tooltip=f"Toggle bounding box ({{{{ {self.OUTLINE} ? 'on' : 'off' }}}})",
            )
            checkbox(
                model=(self.GRID, False),
                icons=('mdi-ruler-square', 'mdi-ruler-square'),
                tooltip=f"Toggle ruler ({{{{ {self.GRID} ? 'on' : 'off' }}}})",
            )
            checkbox(
                model=(self.AXIS, False),
                icons=('mdi-axis-arrow-info', 'mdi-axis-arrow-info'),
                tooltip=f"Toggle axis ({{{{ {self.AXIS} ? 'on' : 'off' }}}})",
            )
            # Server rendering options
            if mode == 'trame':
                vuetify.VDivider(vertical=True, classes='mx-1')
                checkbox(
                    model=(self.SERVER_RENDERING, default_server_rendering),
                    icons=('mdi-dns', 'mdi-open-in-app'),
                    tooltip=f'Toggle rendering mode '
                    f"({{{{ {self.SERVER_RENDERING} ? 'remote' : 'local' }}}})",
                )
            with vuetify.VRow(
                v_show=(self.SERVER_RENDERING, default_server_rendering),
                classes='pa-0 ma-0 align-center',
            ):
                checkbox(
                    model=(self.PARALLEL, False),
                    icons=('mdi-camera-off', 'mdi-camera-switch'),
                    tooltip=f'Toggle parallel projection '
                    f"({{{{ {self.PARALLEL} ? 'on' : 'off' }}}})",
                )

                def attach_screenshot():
                    return server.protocol.addAttachment(self.screenshot())

                button(
                    # Must use single-quote string for JS here
                    click=f"utils.download('screenshot.png', "
                    f"trigger('{server.trigger_name(attach_screenshot)}'), "
                    f"'image/png')",
                    icon='mdi-file-png-box',
                    tooltip='Save screenshot',
                )

            def attach_export():
                return server.protocol.addAttachment(self.export())

            button(
                # Must use single-quote string for JS here
                click="utils.download('scene-export.html', "
                f"trigger('{server.trigger_name(attach_export)}'), "
                "'application/octet-stream')",
                icon='mdi-download',
                tooltip='Export scene as HTML',
            )

    def ui(
        self,
        mode=None,
        default_server_rendering=True,
        collapse_menu=False,
        add_menu=True,
        add_menu_items=None,
        **kwargs,
    ):
        """Generate VContainer for PyVista Plotter.

        Parameters
        ----------
        mode : str, default: 'trame'
            The UI view mode. Options are:

            * ``'trame'``: Uses a view that can switch between client and server
              rendering modes.
            * ``'server'``: Uses a view that is purely server rendering.
            * ``'client'``: Uses a view that is purely client rendering (generally
              safe without a virtual frame buffer)

        default_server_rendering : bool, default: True
            Whether to use server-side or client-side rendering on-start when
            using the ``'trame'`` mode.

        collapse_menu : bool, default: False
            Collapse the UI menu (camera controls, etc.) on start.

        add_menu : bool, default: True
            Add a UI controls VCard to the VContainer.

        add_menu_items : callable, default: None
            Append more UI controls to the VCard menu. Should be a function similar to
            `Viewer.ui_controls()`.

        **kwargs : dict, optional
            Additional keyword arguments are passed to the view being created.

        Returns
        -------
        output : PyVistaRemoteLocalView | PyVistaRemoteView | PyVistaLocalView
            Trame view interface for pyvista.

        """
        if mode is None:
            mode = self.plotter._theme.trame.default_mode
        if mode not in self.VALID_UI_MODES:
            msg = f'`{mode}` is not a valid mode choice. Use one of: {self.VALID_UI_MODES}'
            raise ValueError(msg)
        if mode != 'trame':
            default_server_rendering = mode == 'server'

        with vuetify.VContainer(
            fluid=True,
            classes='pa-0 fill-height',
            style='position: relative',
            trame_server=self.server,
        ) as container:
            server = container.server
            # Initialize state variables
            server.state[self.EDGES] = False
            server.state[self.GRID] = self.plotter.renderer.cube_axes_actor is not None
            server.state[self.OUTLINE] = self.plotter.renderer._box_object is not None
            server.state[self.AXIS] = (
                self.plotter.renderer.axes_widget is not None
                and self.plotter.renderer.axes_widget.GetEnabled()
            )
            server.state[self.SERVER_RENDERING] = default_server_rendering
            if add_menu:
                server.state[self.SHOW_UI] = not collapse_menu
                with vuetify.VCard(
                    style='position: absolute; top: 20px; left: 20px; z-index: 1; height: 36px;',
                    classes=(f"{{ 'rounded-circle': !{self.SHOW_UI} }}",),
                ) as self.menu:
                    with vuetify.VRow(classes='pa-0 ma-0'):
                        button(
                            click=f'{self.SHOW_UI}=!{self.SHOW_UI}',
                            icon='mdi-dots-vertical',
                            tooltip=f"{{{{ {self.SHOW_UI} ? 'Hide' : 'Show' }}}} menu",
                        )
                        self.ui_controls(
                            mode=mode,
                            default_server_rendering=default_server_rendering,
                            v_show=(f'{self.SHOW_UI}',),
                        )
                        if callable(add_menu_items):
                            with vuetify.VRow(
                                v_show=(f'{self.SHOW_UI}',),
                                classes='pa-0 ma-0 align-center',
                            ):
                                add_menu_items()
            if mode == 'trame':
                view = PyVistaRemoteLocalView(
                    self.plotter,
                    mode=(
                        # Must use single-quote string for JS here
                        f"{self.SERVER_RENDERING} ? 'remote' : 'local'",
                        'remote' if default_server_rendering else 'local',
                    ),
                    **kwargs,
                )
            elif mode == 'server':
                view = PyVistaRemoteView(self.plotter, **kwargs)
            elif mode == 'client':
                view = PyVistaLocalView(self.plotter, **kwargs)

            self._html_views.add(view)
            if add_menu:
                view.menu = self.menu

        return view
