"""
Components/ExpansionPanel
=========================

.. seealso::

    `Material Design spec, Expansion panel <https://material.io/archive/guidelines/components/expansion-panels.html#>`_

.. rubric:: Expansion panels contain creation flows and allow lightweight editing of an element.

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

Usage
-----

.. code-block:: python

    self.add_widget(
        MDExpansionPanel(
            icon="logo.png",  # panel icon
            content=Content(),  # panel content
            panel_cls=MDExpansionPanelOneLine(text="Secondary text"),  # panel class
        )
    )

To use :class:`~MDExpansionPanel` you must pass one of the following classes
to the :attr:`~MDExpansionPanel.panel_cls` parameter:

- :class:`~MDExpansionPanelOneLine`
- :class:`~MDExpansionPanelTwoLine`
- :class:`~MDExpansionPanelThreeLine`

These classes are inherited from the following classes:

- :class:`~kivymd.uix.list.OneLineAvatarIconListItem`
- :class:`~kivymd.uix.list.TwoLineAvatarIconListItem`
- :class:`~kivymd.uix.list.ThreeLineAvatarIconListItem`

.. code-block:: python

    self.root.ids.box.add_widget(
        MDExpansionPanel(
            icon="logo.png",
            content=Content(),
            panel_cls=MDExpansionPanelThreeLine(
                text="Text",
                secondary_text="Secondary text",
                tertiary_text="Tertiary text",
            )
        )
    )

Example
-------

.. code-block:: python

    import os

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.uix.boxlayout import MDBoxLayout
    from kivymd.uix.expansionpanel import MDExpansionPanel, MDExpansionPanelThreeLine
    from kivymd import images_path

    KV = '''
    <Content>
        adaptive_height: True

        TwoLineIconListItem:
            text: "(050)-123-45-67"
            secondary_text: "Mobile"

            IconLeftWidget:
                icon: 'phone'


    MDScrollView:

        MDGridLayout:
            id: box
            cols: 1
            adaptive_height: True
    '''


    class Content(MDBoxLayout):
        '''Custom content.'''


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

        def on_start(self):
            for i in range(10):
                self.root.ids.box.add_widget(
                    MDExpansionPanel(
                        icon=os.path.join(images_path, "logo", "kivymd-icon-128.png"),
                        content=Content(),
                        panel_cls=MDExpansionPanelThreeLine(
                            text="Text",
                            secondary_text="Secondary text",
                            tertiary_text="Tertiary text",
                        )
                    )
                )


    Test().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/expansion-panel.gif
    :align: center

Two events are available for :class:`~MDExpansionPanel`
-------------------------------------------------------

- :attr:`~MDExpansionPanel.on_open`
- :attr:`~MDExpansionPanel.on_close`

.. code-block:: kv

        MDExpansionPanel:
            on_open: app.on_panel_open(args)
            on_close: app.on_panel_close(args)

The user function takes one argument - the object of the panel:

.. code-block:: python

    def on_panel_open(self, instance_panel):
        print(instance_panel)

.. seealso:: `See Expansion panel example <https://github.com/kivymd/KivyMD/wiki/Components-Expansion-Panel>`_

    `Expansion panel and MDCard <https://github.com/kivymd/KivyMD/wiki/Components-Expansion-Panel-and-MDCard>`_
"""

__all__ = (
    "MDExpansionPanel",
    "MDExpansionPanelOneLine",
    "MDExpansionPanelTwoLine",
    "MDExpansionPanelThreeLine",
    "MDExpansionPanelLabel",
)

import os
from typing import Union

from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import NumericProperty, ObjectProperty, StringProperty
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.widget import WidgetException

import kivymd.material_resources as m_res
from kivymd import uix_path
from kivymd.icon_definitions import md_icons
from kivymd.uix.button import MDIconButton
from kivymd.uix.list import (
    IconLeftWidget,
    ImageLeftWidget,
    IRightBodyTouch,
    OneLineAvatarIconListItem,
    ThreeLineAvatarIconListItem,
    TwoLineAvatarIconListItem,
    TwoLineListItem,
)

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


class MDExpansionChevronRight(IRightBodyTouch, MDIconButton):
    """Chevron icon on the right panel."""

    _angle = NumericProperty(0)


class MDExpansionPanelOneLine(OneLineAvatarIconListItem):
    """Single line panel."""


class MDExpansionPanelTwoLine(TwoLineAvatarIconListItem):
    """Two-line panel."""


class MDExpansionPanelThreeLine(ThreeLineAvatarIconListItem):
    """Three-line panel."""


class MDExpansionPanelLabel(TwoLineListItem):
    """
    Label panel.

    ..warning:: This class is created for use in the
        :class:`~kivymd.uix.stepper.MDStepperVertical` and
        :class:`~kivymd.uix.stepper.MDStepper` classes, and has not
        been tested for use outside of these classes.
    """

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

    def set_paddings(self, interval: Union[int, float]) -> None:
        self._txt_bot_pad = dp(36)
        self._txt_left_pad = dp(0)


class MDExpansionPanel(RelativeLayout):
    """
    :Events:
        :attr:`on_open`
            Called when a panel is opened.
        :attr:`on_close`
            Called when a panel is closed.
    """

    content = ObjectProperty()
    """
    Content of panel. Must be `Kivy` widget.

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

    icon = StringProperty()
    """
    Icon of panel.

    Icon Should be either be a path to an image or
    a logo name in :class:`~kivymd.icon_definitions.md_icons`

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

    opening_transition = StringProperty("out_cubic")
    """
    The name of the animation transition type to use when animating to
    the :attr:`state` `'open'`.

    :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    opening_time = NumericProperty(0.2)
    """
    The time taken for the panel to slide to the :attr:`state` `'open'`.

    :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    closing_transition = StringProperty("out_sine")
    """
    The name of the animation transition type to use when animating to
    the :attr:`state` 'close'.

    :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_sine'`.
    """

    closing_time = NumericProperty(0.2)
    """
    The time taken for the panel to slide to the :attr:`state` `'close'`.

    :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    panel_cls = ObjectProperty()
    """
    Panel object. The object must be one of the classes
    :class:`~MDExpansionPanelOneLine`, :class:`~MDExpansionPanelTwoLine` or
    :class:`~MDExpansionPanelThreeLine`.

    :attr:`panel_cls` is a :class:`~kivy.properties.ObjectProperty`
    and defaults to `None`.
    """

    _state = StringProperty("close")
    _anim_playing = False

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type("on_open")
        self.register_event_type("on_close")

        if self.panel_cls and isinstance(
            self.panel_cls,
            (
                MDExpansionPanelOneLine,
                MDExpansionPanelTwoLine,
                MDExpansionPanelThreeLine,
                MDExpansionPanelLabel,
            ),
        ):
            self.panel_cls.pos_hint = {"top": 1}
            self.panel_cls._no_ripple_effect = True
            self.panel_cls.bind(
                on_release=lambda x: self.check_open_panel(self.panel_cls)
            )
            if not isinstance(self.panel_cls, MDExpansionPanelLabel):
                self.chevron = MDExpansionChevronRight()
                self.panel_cls.add_widget(self.chevron)
                if self.icon:
                    if self.icon in md_icons.keys():
                        self.panel_cls.add_widget(
                            IconLeftWidget(
                                icon=self.icon,
                                pos_hint={"center_y": 0.5},
                            )
                        )
                    else:
                        self.panel_cls.add_widget(
                            ImageLeftWidget(
                                source=self.icon, pos_hint={"center_y": 0.5}
                            )
                        )
                else:
                    self.panel_cls.remove_widget(
                        self.panel_cls.ids._left_container
                    )
                    self.panel_cls._txt_left_pad = 0
            else:
                # if no icon
                self.panel_cls._txt_left_pad = m_res.HORIZ_MARGINS
            self.add_widget(self.panel_cls)
        else:
            raise ValueError(
                "KivyMD: `panel_cls` object must be must be one of the "
                "objects from the list\n"
                "[MDExpansionPanelOneLine, MDExpansionPanelTwoLine, "
                "MDExpansionPanelThreeLine]"
            )

    def on_open(self, *args):
        """Called when a panel is opened."""

    def on_close(self, *args):
        """Called when a panel is closed."""

    def check_open_panel(
        self,
        instance_panel: [
            MDExpansionPanelThreeLine,
            MDExpansionPanelTwoLine,
            MDExpansionPanelThreeLine,
            MDExpansionPanelLabel,
        ],
    ) -> None:
        """
        Called when you click on the panel. Called methods to open or close
        a panel.
        """

        press_current_panel = False
        for panel in self.parent.children:
            if isinstance(panel, MDExpansionPanel):
                if len(panel.children) == 2:
                    if instance_panel is panel.children[1]:
                        press_current_panel = True
                    panel.remove_widget(panel.children[0])
                    if not isinstance(self.panel_cls, MDExpansionPanelLabel):
                        chevron = panel.children[0].children[0].children[0]
                        self.set_chevron_up(chevron)
                    self.close_panel(panel, press_current_panel)
                    self.dispatch("on_close")
                    break
        if not press_current_panel:
            self.set_chevron_down()

    def set_chevron_down(self) -> None:
        """Sets the chevron down."""

        if not isinstance(self.panel_cls, MDExpansionPanelLabel):
            Animation(_angle=-90, d=self.opening_time).start(self.chevron)
        self.open_panel()
        self.dispatch("on_open")

    def set_chevron_up(self, instance_chevron: MDExpansionChevronRight) -> None:
        """Sets the chevron up."""

        if not isinstance(self.panel_cls, MDExpansionPanelLabel):
            Animation(_angle=0, d=self.closing_time).start(instance_chevron)

    def close_panel(
        self, instance_expansion_panel, press_current_panel: bool
    ) -> None:
        """Method closes the panel."""

        if self._anim_playing:
            return

        if press_current_panel:
            self._anim_playing = True

        self._state = "close"

        anim = Animation(
            height=self.panel_cls.height,
            d=self.closing_time,
            t=self.closing_transition,
        )
        anim.bind(on_complete=self._disable_anim)
        anim.start(instance_expansion_panel)

    def open_panel(self, *args) -> None:
        """Method opens a panel."""

        if self._anim_playing:
            return

        self._anim_playing = True
        self._state = "open"

        anim = Animation(
            height=self.content.height + self.height,
            d=self.opening_time,
            t=self.opening_transition,
        )
        anim.bind(on_complete=self._add_content)
        anim.bind(on_complete=self._disable_anim)
        anim.start(self)

    def get_state(self) -> str:
        """Returns the state of panel. Can be `close` or `open` ."""

        return self._state

    def add_widget(self, widget, index=0, canvas=None):
        if isinstance(
            widget,
            (
                MDExpansionPanelOneLine,
                MDExpansionPanelTwoLine,
                MDExpansionPanelThreeLine,
                MDExpansionPanelLabel,
            ),
        ):
            self.height = widget.height
        return super().add_widget(widget)

    def _disable_anim(self, *args):
        self._anim_playing = False

    def _add_content(self, *args):
        if self.content:
            try:
                if isinstance(self.panel_cls, MDExpansionPanelLabel):
                    self.content.y = dp(36)
                self.add_widget(self.content)
            except WidgetException:
                pass
