"""
Components/BottomSheet
======================

.. seealso::

    `Material Design spec, Sheets: bottom <https://material.io/components/sheets-bottom>`_

.. rubric:: Bottom sheets are surfaces containing supplementary content that are anchored to the bottom of the screen.

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet.png
    :align: center

Two classes are available to you :class:`~MDListBottomSheet` and :class:`~MDGridBottomSheet`
for standard bottom sheets dialogs:

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/grid-list-bottomsheets.png
    :align: center

Usage :class:`~MDListBottomSheet`
=================================

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.toast import toast
    from kivymd.uix.bottomsheet import MDListBottomSheet
    from kivymd.app import MDApp

    KV = '''
    MDScreen:

        MDTopAppBar:
            title: "Example BottomSheet"
            pos_hint: {"top": 1}
            elevation: 4

        MDRaisedButton:
            text: "Open list bottom sheet"
            on_release: app.show_example_list_bottom_sheet()
            pos_hint: {"center_x": .5, "center_y": .5}
    '''


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

        def callback_for_menu_items(self, *args):
            toast(args[0])

        def show_example_list_bottom_sheet(self):
            bottom_sheet_menu = MDListBottomSheet()
            for i in range(1, 11):
                bottom_sheet_menu.add_item(
                    f"Standart Item {i}",
                    lambda x, y=i: self.callback_for_menu_items(
                        f"Standart Item {y}"
                    ),
                )
            bottom_sheet_menu.open()


    Example().run()

The :attr:`~MDListBottomSheet.add_item` method of the :class:`~MDListBottomSheet`
class takes the following arguments:

``text`` - element text;

``callback`` - function that will be called when clicking on an item;

There is also an optional argument ``icon``,
which will be used as an icon to the left of the item:

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/icon-list-bottomsheets.png
    :align: center

.. rubric:: Using the :class:`~MDGridBottomSheet` class is similar
    to using the :class:`~MDListBottomSheet` class:

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.toast import toast
    from kivymd.uix.bottomsheet import MDGridBottomSheet
    from kivymd.app import MDApp

    KV = '''
    MDScreen:

        MDTopAppBar:
            title: 'Example BottomSheet'
            pos_hint: {"top": 1}
            elevation: 4

        MDRaisedButton:
            text: "Open grid bottom sheet"
            on_release: app.show_example_grid_bottom_sheet()
            pos_hint: {"center_x": .5, "center_y": .5}
    '''


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

        def callback_for_menu_items(self, *args):
            toast(args[0])

        def show_example_grid_bottom_sheet(self):
            bottom_sheet_menu = MDGridBottomSheet()
            data = {
                "Facebook": "facebook-box",
                "YouTube": "youtube",
                "Twitter": "twitter-box",
                "Da Cloud": "cloud-upload",
                "Camera": "camera",
            }
            for item in data.items():
                bottom_sheet_menu.add_item(
                    item[0],
                    lambda x, y=item[0]: self.callback_for_menu_items(y),
                    icon_src=item[1],
                )
            bottom_sheet_menu.open()


    Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/grid-bottomsheet.png
    :align: center

.. rubric:: You can use custom content for bottom sheet dialogs:

.. code-block:: python

    from kivy.lang import Builder
    from kivy.factory import Factory

    from kivymd.uix.bottomsheet import MDCustomBottomSheet
    from kivymd.app import MDApp

    KV = '''
    <ItemForCustomBottomSheet@OneLineIconListItem>
        on_press: app.custom_sheet.dismiss()
        icon: ""

        IconLeftWidget:
            icon: root.icon


    <ContentCustomSheet@BoxLayout>:
        orientation: "vertical"
        size_hint_y: None
        height: "400dp"

        MDTopAppBar:
            title: 'Custom bottom sheet:'

        ScrollView:

            MDGridLayout:
                cols: 1
                adaptive_height: True

                ItemForCustomBottomSheet:
                    icon: "page-previous"
                    text: "Preview"

                ItemForCustomBottomSheet:
                    icon: "exit-to-app"
                    text: "Exit"


    MDScreen:

        MDTopAppBar:
            title: 'Example BottomSheet'
            pos_hint: {"top": 1}
            elevation: 4

        MDRaisedButton:
            text: "Open custom bottom sheet"
            on_release: app.show_example_custom_bottom_sheet()
            pos_hint: {"center_x": .5, "center_y": .5}
    '''


    class Example(MDApp):
        custom_sheet = None

        def build(self):
            return Builder.load_string(KV)

        def show_example_custom_bottom_sheet(self):
            self.custom_sheet = MDCustomBottomSheet(screen=Factory.ContentCustomSheet())
            self.custom_sheet.open()


    Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/custom-bottomsheet.png
    :align: center

.. note:: When you use the :attr:`~MDCustomBottomSheet` class, you must specify
    the height of the user-defined content exactly, otherwise ``dp(100)``
    heights will be used for your ``ContentCustomSheet`` class:

.. code-block:: kv

    <ContentCustomSheet@BoxLayout>:
        orientation: "vertical"
        size_hint_y: None
        height: "400dp"

.. note:: The height of the bottom sheet dialog will never exceed half
    the height of the screen!
"""

__all__ = (
    "MDGridBottomSheet",
    "GridBottomSheetItem",
    "MDListBottomSheet",
    "MDCustomBottomSheet",
    "MDBottomSheet",
)

import os

from kivy.animation import Animation
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
    BooleanProperty,
    ColorProperty,
    NumericProperty,
    ObjectProperty,
    OptionProperty,
    StringProperty,
)
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.modalview import ModalView
from kivy.uix.scrollview import ScrollView

from kivymd import images_path, uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.behaviors import BackgroundColorBehavior
from kivymd.uix.label import MDIcon
from kivymd.uix.list import ILeftBody, OneLineIconListItem, OneLineListItem

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


class SheetList(ScrollView):
    pass


class BsPadding(ButtonBehavior, FloatLayout):
    pass


class BottomSheetContent(BackgroundColorBehavior, GridLayout):
    pass


class MDBottomSheet(ThemableBehavior, ModalView):
    background = f"{images_path}transparent.png"
    """Private attribute."""

    duration_opening = NumericProperty(0.15)
    """
    The duration of the bottom sheet dialog opening animation.

    :attr:`duration_opening` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `0.15`.
    """

    duration_closing = NumericProperty(0.15)
    """
    The duration of the bottom sheet dialog closing animation.

    :attr:`duration_closing` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `0.15`.
    """

    radius = NumericProperty(25)
    """
    The value of the rounding of the corners of the dialog.

    :attr:`radius` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `25`.
    """

    radius_from = OptionProperty(
        None,
        options=[
            "top_left",
            "top_right",
            "top",
            "bottom_right",
            "bottom_left",
            "bottom",
        ],
        allownone=True,
    )
    """
    Sets which corners to cut from the dialog. Available options are:
    (`"top_left"`, `"top_right"`, `"top"`, `"bottom_right"`, `"bottom_left"`, `"bottom"`).

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/bottomsheet-radius-from.png
        :align: center

    :attr:`radius_from` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `None`.
    """

    animation = BooleanProperty(False)
    """
    Whether to use animation for opening and closing of the bottomsheet or not.

    :attr:`animation` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    bg_color = ColorProperty(None)
    """
    Dialog background color in in (r, g, b, a) or string format.

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

    value_transparent = ColorProperty([0, 0, 0, 0.8])
    """
    Background color in (r, g, b, a) or string format transparency value when
    opening a dialog.

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

    _upper_padding = ObjectProperty()
    _gl_content = ObjectProperty()
    _position_content = NumericProperty()

    def open(self, *args):
        super().open(*args)

    def add_widget(self, widget, index=0, canvas=None):
        super().add_widget(widget, index, canvas)

    def dismiss(self, *args, **kwargs):
        def dismiss(*args):
            self.dispatch("on_pre_dismiss")
            self._gl_content.clear_widgets()
            self._real_remove_widget()
            self.dispatch("on_dismiss")

        if self.animation:
            a = Animation(height=0, d=self.duration_closing)
            a.bind(on_complete=dismiss)
            a.start(self._gl_content)
        else:
            dismiss()

    def resize_content_layout(self, content, layout, interval=0):
        if not layout.ids.get("box_sheet_list"):
            _layout = layout
        else:
            _layout = layout.ids.box_sheet_list

        if _layout.height > Window.height / 2:
            height = Window.height / 2
        else:
            height = _layout.height

        if self.animation:
            Animation(height=height, d=self.duration_opening).start(_layout)
            Animation(height=height, d=self.duration_opening).start(content)
        else:
            layout.height = height
            content.height = height


class ListBottomSheetIconLeft(ILeftBody, MDIcon):
    pass


class MDCustomBottomSheet(MDBottomSheet):
    screen = ObjectProperty()
    """
    Custom content.

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

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._gl_content.add_widget(self.screen)
        Clock.schedule_once(
            lambda x: self.resize_content_layout(self._gl_content, self.screen),
            0,
        )


class MDListBottomSheet(MDBottomSheet):
    sheet_list = ObjectProperty()
    """
    :attr:`sheet_list` is an :class:`~kivy.properties.ObjectProperty`
    and defaults to `None`.
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.sheet_list = SheetList(size_hint_y=None)
        self._gl_content.add_widget(self.sheet_list)
        Clock.schedule_once(
            lambda x: self.resize_content_layout(
                self._gl_content, self.sheet_list
            ),
            0,
        )

    def add_item(self, text, callback, icon=None):
        """
        :arg text: element text;
        :arg callback: function that will be called when clicking on an item;
        :arg icon: which will be used as an icon to the left of the item;
        """

        if icon:
            item = OneLineIconListItem(text=text, on_release=callback)
            item.add_widget(ListBottomSheetIconLeft(icon=icon))
        else:
            item = OneLineListItem(text=text, on_release=callback)
        item.bind(on_release=lambda x: self.dismiss())
        self.sheet_list.ids.box_sheet_list.add_widget(item)


class GridBottomSheetItem(ButtonBehavior, BoxLayout):
    source = StringProperty()
    """
    Icon path if you use a local image or icon name
    if you use icon names from a file ``kivymd/icon_definitions.py``.

    :attr:`source` is an :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    caption = StringProperty()
    """
    Item text.

    :attr:`caption` is an :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    icon_size = NumericProperty("24sp")
    """
    Icon size.

    :attr:`caption` is an :class:`~kivy.properties.StringProperty`
    and defaults to `'24sp'`.
    """


class MDGridBottomSheet(MDBottomSheet):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.sheet_list = SheetList(size_hint_y=None)
        self.sheet_list.ids.box_sheet_list.cols = 3
        self.sheet_list.ids.box_sheet_list.padding = (dp(16), 0, dp(16), dp(96))
        self._gl_content.add_widget(self.sheet_list)
        Clock.schedule_once(
            lambda x: self.resize_content_layout(
                self._gl_content, self.sheet_list
            ),
            0,
        )

    def add_item(self, text, callback, icon_src):
        """
        :arg text: element text;
        :arg callback: function that will be called when clicking on an item;
        :arg icon_src: icon item;
        """

        def tap_on_item(instance):
            callback(instance)
            self.dismiss()

        item = GridBottomSheetItem(
            caption=text, on_release=tap_on_item, source=icon_src
        )
        if len(self._gl_content.children) % 3 == 0:
            self._gl_content.height += dp(96)
        self.sheet_list.ids.box_sheet_list.add_widget(item)
