"""
Components/SliverAppbar
=======================

.. versionadded:: 1.0.0

.. rubric:: MDSliverAppbar is a Material Design widget in KivyMD which gives
    scrollable or collapsible
    `MDTopAppBar <https://kivymd.readthedocs.io/en/latest/components/toolbar/>`_

.. note:: This widget is a modification of the
    `silverappbar.py <https://github.com/kivymd-extensions/akivymd/blob/main/kivymd_extensions/akivymd/uix/silverappbar.py>`_ module.

Usage
-----

.. code-block:: kv

    MDScreen:

        MDSliverAppbar:

            MDSliverAppbarHeader:

                # Custom content.
                ...

            # Custom list.
            MDSliverAppbarContent:

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-usage.png
    :align: center

Example
-------

.. code-block:: python

    from kivy.lang.builder import Builder

    from kivymd.app import MDApp
    from kivymd.uix.card import MDCard

    KV = '''
    <CardItem>
        size_hint_y: None
        height: "86dp"
        padding: "4dp"
        radius: 12

        FitImage:
            source: "avatar.jpg"
            radius: root.radius
            size_hint_x: None
            width: root.height

        MDBoxLayout:
            orientation: "vertical"
            adaptive_height: True
            spacing: "6dp"
            padding: "12dp", 0, 0, 0
            pos_hint: {"center_y": .5}

            MDLabel:
                text: "Title text"
                font_style: "H5"
                bold: True
                adaptive_height: True

            MDLabel:
                text: "Subtitle text"
                theme_text_color: "Hint"
                adaptive_height: True


    MDScreen:

        MDSliverAppbar:
            background_color: "2d4a50"

            MDSliverAppbarHeader:

                MDRelativeLayout:

                    FitImage:
                        source: "bg.jpg"

            MDSliverAppbarContent:
                id: content
                orientation: "vertical"
                padding: "12dp"
                spacing: "12dp"
                adaptive_height: True
    '''


    class CardItem(MDCard):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.elevation = 3


    class Example(MDApp):
        def build(self):
            return Builder.load_string(KV)

        def on_start(self):
            for x in range(10):
                self.root.ids.content.add_widget(CardItem())


    Example().run()
"""


__all__ = ("MDSliverAppbar", "MDSliverAppbarHeader", "MDSliverAppbarContent")

import os
from typing import Union

from kivy.clock import Clock
from kivy.core.window import Window
from kivy.lang.builder import Builder
from kivy.properties import (
    BooleanProperty,
    ColorProperty,
    NumericProperty,
    ObjectProperty,
    VariableListProperty,
)

from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.toolbar import MDTopAppBar

with open(
    os.path.join(uix_path, "sliverappbar", "sliverappbar.kv"), encoding="utf-8"
) as kv_file:
    Builder.load_string(kv_file.read())


class MDSliverAppbarException(Exception):
    pass


class MDSliverAppbarContent(ThemableBehavior, MDBoxLayout):
    """Implements a box for a scrollable list of custom items."""

    md_bg_color = ColorProperty([0, 0, 0, 0])
    """
    See :attr:`~kivymd.uix.sliverappbar.sliverappbar.MDSliverAppbar.background_color`.

    :attr:`md_bg_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `[0, 0, 0, 0]`.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_once(self.set_bg_color)

    def set_bg_color(self, interval: Union[int, float]) -> None:
        if self.md_bg_color == [0, 0, 0, 0]:
            self.md_bg_color = self.theme_cls.bg_normal


class MDSliverAppbarHeader(MDBoxLayout):
    pass


class MDSliverAppbar(MDBoxLayout, ThemableBehavior):
    """
    MDSliverAppbar class.
    See module documentation for more information.

    :Events:
        :attr:`on_scroll_content`
            Called when the list of custom content is being scrolled.
    """

    toolbar_cls = ObjectProperty()
    """
    Must be an object of the :class:`~kivymd.uix.toolbar.toolbar.MDTopAppBar' class.
    See :class:`~kivymd.uix.toolbar.toolbar.MDTopAppBar` class documentation
    for more information.

    By default, MDSliverAppbar widget uses the MDTopAppBar class with no
    parameters.

    .. code-block:: python

        from kivy.lang.builder import Builder

        from kivymd.uix.card import MDCard
        from kivymd.uix.toolbar import MDTopAppBar

        KV = '''
        #:import SliverToolbar __main__.SliverToolbar


        <CardItem>
            size_hint_y: None
            height: "86dp"
            padding: "4dp"
            radius: 12

            FitImage:
                source: "avatar.jpg"
                radius: root.radius
                size_hint_x: None
                width: root.height

            MDBoxLayout:
                orientation: "vertical"
                adaptive_height: True
                spacing: "6dp"
                padding: "12dp", 0, 0, 0
                pos_hint: {"center_y": .5}

                MDLabel:
                    text: "Title text"
                    font_style: "H5"
                    bold: True
                    adaptive_height: True

                MDLabel:
                    text: "Subtitle text"
                    theme_text_color: "Hint"
                    adaptive_height: True


        MDScreen:

            MDSliverAppbar:
                background_color: "2d4a50"
                toolbar_cls: SliverToolbar()

                MDSliverAppbarHeader:

                    MDRelativeLayout:

                        FitImage:
                            source: "bg.jpg"

                MDSliverAppbarContent:
                    id: content
                    orientation: "vertical"
                    padding: "12dp"
                    spacing: "12dp"
                    adaptive_height: True
        '''


        class CardItem(MDCard):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.elevation = 3


        class SliverToolbar(MDTopAppBar):
            def __init__(self, **kwargs):
                super().__init__(**kwargs)
                self.shadow_color = (0, 0, 0, 0)
                self.type_height = "medium"
                self.headline_text = "Headline medium"
                self.left_action_items = [["arrow-left", lambda x: x]]
                self.right_action_items = [
                    ["attachment", lambda x: x],
                    ["calendar", lambda x: x],
                    ["dots-vertical", lambda x: x],
                ]


        class Example(MDApp):
            def build(self):
                self.theme_cls.material_style = "M3"
                return Builder.load_string(KV)

            def on_start(self):
                for x in range(10):
                    self.root.ids.content.add_widget(CardItem())


        Example().run()

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-toolbar-cls.gif
        :align: center

    :attr:`toolbar_cls` is an :class:`~kivy.properties.ObjectProperty`
    and defaults to `None`.
    """

    background_color = ColorProperty(None)
    """
    Background color of toolbar in (r, g, b, a) format.

    .. code-block:: kv

        MDSliverAppbar:
            background_color: "2d4a50"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-background-color.png
        :align: center

    :attr:`background_color` is an :class:`~kivy.properties.ColorProperty`
    and defaults to `None`.
    """

    max_height = NumericProperty(Window.height / 2)
    """
    Distance from top of screen to start of custom list content.

    .. code-block:: kv

        MDSliverAppbar:
            max_height: "200dp"

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-max-height.png
        :align: center

    :attr:`max_height` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `Window.height / 2`.
    """

    hide_toolbar = BooleanProperty(True)
    """
    Whether to hide the toolbar when scrolling through a list
    of custom content.

    .. code-block:: kv

        MDSliverAppbar:
            hide_toolbar: False

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-hide-toolbar.gif
        :align: center

        MDSliverAppbar:
            hide_toolbar: True

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-hide-toolbar-true.gif
        :align: center

    :attr:`hide_toolbar` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `True`.
    """

    radius = VariableListProperty([20], length=4)
    """
    Box radius for custom item list.

    .. code-block:: kv

        MDSliverAppbar:
            radius: 20

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-radius.png
        :align: center

    :attr:`radius` is an :class:`~kivy.properties.VariableListProperty`
    and defaults to `[20]`.
    """

    max_opacity = NumericProperty(1)
    """
    Maximum background transparency value for the
    :class:`~kivymd.uix.sliverappbar.sliverappbar.MDSliverAppbarHeader` class.

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-max-opacity.gif
        :align: center

    .. code-block:: kv

        MDSliverAppbar:
            max_opacity: .5

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/sliver-app-bar-max-opacity-05.gif
        :align: center

    :attr:`max_opacity` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `1`.
    """

    _opacity = NumericProperty()
    _scroll_was_moving = BooleanProperty(False)
    _last_scroll_y_pos = 0.0

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

    def on_scroll_content(
        self,
        instance_sliverappbar: object = None,
        value: float = 1.0,
        direction: str = "up",
    ):
        """
        Called when the list of custom content is being scrolled.

        :param instance_sliverappbar: :class:`~MDSliverAppbar`
        :param value: see :attr:`~kivy.uix.scrollview.ScrollView.scroll_y`
        :param direction: scroll direction: 'up/down'
        """

    def on_background_color(
        self, instance_sliver_appbar, color_value: list
    ) -> None:
        if self.toolbar_cls:
            self.toolbar_cls.md_bg_color = color_value

    def on_toolbar_cls(
        self, instance_sliver_appbar, instance_toolbar_cls: MDTopAppBar
    ) -> None:
        """Called when a value is set to the :attr:`toolbar_cls` parameter."""

        def on_toolbar_cls(*args):
            # If an MDTopAppBar object is already in use, delete it
            # before adding a new MDTopAppBar object.
            for widget in self.ids.float_box.children:
                if issubclass(widget.__class__, MDTopAppBar):
                    self.ids.float_box.remove_widget(widget)

            # Adding a custom MDTopAppBar object.
            if issubclass(instance_toolbar_cls.__class__, MDTopAppBar):
                instance_toolbar_cls.pos_hint = {"top": 1}
                instance_toolbar_cls.elevation = 0
                self.ids.float_box.add_widget(instance_toolbar_cls)
            else:
                raise MDSliverAppbarException(
                    "The `toolbar_cls` parameter must be an object of the "
                    "`kivymd.uix.toolbar.MDTopAppBar class`"
                )

        # Schedule using for declarative style.
        # Otherwise get AttributeError exception.
        Clock.schedule_once(on_toolbar_cls)

    def on_vbar(self) -> None:
        if not self.background_color:
            self.background_color = self.theme_cls.primary_color

        if not self.toolbar_cls:
            self.toolbar_cls = self.get_default_toolbar()

        scroll_box = self.ids.scroll_box
        vbar = self.ids.scroll.vbar
        toolbar_percent = (self.toolbar_cls.height / scroll_box.height) * 100
        current_percent = (vbar[0] + vbar[1]) * 100
        percent_min = (
            1 - self.max_height / scroll_box.height
        ) * 100 + toolbar_percent

        if self._scroll_was_moving:
            direction = self._get_direction_swipe(self.ids.scroll.scroll_y)
            self._last_scroll_y_pos = self.ids.scroll.scroll_y
            self.dispatch(
                "on_scroll_content", self.ids.scroll.scroll_y, direction
            )

        if self.hide_toolbar:
            if percent_min <= current_percent:
                opacity = (current_percent - percent_min) / (100 - percent_min)
                self._opacity = self.max_opacity * (1 - opacity)
                self.background_color = self.background_color[0:3] + [
                    1 - opacity
                ]
                self.toolbar_cls._hard_shadow_a = 1 - opacity
                self.toolbar_cls._soft_shadow_a = 1 - opacity
            else:
                self.background_color = self.background_color[0:3] + [1]

    def get_default_toolbar(self) -> MDTopAppBar:
        """Called if no value is passed for the toolbar_cls attribute."""

        return MDTopAppBar(
            pos_hint={"top": 1}, md_bg_color=self.background_color
        )

    def add_widget(self, widget, index=0, canvas=None):
        if issubclass(widget.__class__, MDSliverAppbarContent):
            Clock.schedule_once(lambda x: self._set_radius(widget))
            self.ids.scroll_box.add_widget(widget)
        elif issubclass(widget.__class__, MDSliverAppbarHeader):
            self.ids.header.add_widget(widget)
        else:
            super().add_widget(widget, index=index, canvas=canvas)

    def _set_radius(self, instance: MDSliverAppbarContent) -> None:
        instance.radius = self.radius

    def _get_direction_swipe(self, current_percent: float) -> str:
        if self._last_scroll_y_pos > current_percent:
            direction = "up"
        else:
            direction = "down"
        return direction