"""
Components/Tabs
===============

.. seealso::

    `Material Design spec, Tabs <https://m3.material.io/components/tabs/overview>`_

.. rubric:: Tabs organize content across different screens, data sets,
    and other interactions.

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

- Use tabs to group content into helpful categories
- Two types: primary and secondary
- Tabs can horizontally scroll, so a UI can have as many tabs as needed
- Place tabs next to each other as peers

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

1. Primary tabs
2. Secondary tabs

Usage primary tabs
------------------

Primary tabs should be used when just one set of tabs are needed.

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.uix.tab import (
        MDTabsItem,
        MDTabsItemIcon,
        MDTabsItemText,
        MDTabsBadge,
    )

    KV = '''
    MDScreen:
        md_bg_color: self.theme_cls.backgroundColor

        MDTabsPrimary:
            id: tabs
            pos_hint: {"center_x": .5, "center_y": .5}

            MDDivider:
    '''


    class Example(MDApp):
        def on_start(self):
            for tab_icon, tab_name in {
                "airplane": "Flights",
                "treasure-chest": "Trips",
                "compass-outline": "Explore",
            }.items():
                if tab_icon == "treasure-chest":
                    self.root.ids.tabs.add_widget(
                        MDTabsItem(
                            MDTabsItemIcon(
                                MDTabsBadge(
                                    text="99",
                                ),
                                icon=tab_icon,
                            ),
                            MDTabsItemText(
                                text=tab_name,
                            ),
                        )
                    )
                else:
                    self.root.ids.tabs.add_widget(
                        MDTabsItem(
                            MDTabsItemIcon(
                                icon=tab_icon,
                            ),
                            MDTabsItemText(
                                text=tab_name,
                            ),
                        )
                    )
                self.root.ids.tabs.switch_tab(icon="airplane")

        def build(self):
            self.theme_cls.primary_palette = "Olive"
            return Builder.load_string(KV)


    Example().run()

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

Anatomy primary tabs
--------------------

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

Usage secondary tabs
--------------------

Secondary tabs are necessary when a screen requires more than one level of
tabs. These tabs use a simpler style of indicator, but their function is
identical to primary tabs.

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.uix.tab import (
        MDTabsItemIcon,
        MDTabsItemText,
        MDTabsBadge, MDTabsItemSecondary,
    )

    KV = '''
    MDScreen:
        md_bg_color: self.theme_cls.backgroundColor

        MDTabsSecondary:
            id: tabs
            pos_hint: {"center_x": .5, "center_y": .5}

            MDDivider:
    '''


    class Example(MDApp):
        def on_start(self):
            for tab_icon, tab_name in {
                "airplane": "Flights",
                "treasure-chest": "Trips",
                "compass-outline": "Explore",
            }.items():
                if tab_icon == "treasure-chest":
                    self.root.ids.tabs.add_widget(
                        MDTabsItemSecondary(
                            MDTabsItemIcon(
                                icon=tab_icon,
                            ),
                            MDTabsItemText(
                                text=tab_name,
                            ),
                            MDTabsBadge(
                                text="5",
                            ),
                        )
                    )
                else:
                    self.root.ids.tabs.add_widget(
                        MDTabsItemSecondary(
                            MDTabsItemIcon(
                                icon=tab_icon,
                            ),
                            MDTabsItemText(
                                text=tab_name,
                            ),
                        )
                    )
            self.root.ids.tabs.switch_tab(icon="airplane")

        def build(self):
            self.theme_cls.primary_palette = "Olive"
            return Builder.load_string(KV)


    Example().run()

Anatomy secondary tabs
----------------------

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

Related content
---------------

Use tabs to group related content, not sequential content.

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.uix.label import MDLabel
    from kivymd.uix.tab import (
        MDTabsItemIcon,
        MDTabsItemText,
        MDTabsItem,
    )

    KV = '''
    MDScreen:
        md_bg_color: self.theme_cls.backgroundColor

        MDTabsPrimary:
            id: tabs
            pos_hint: {"center_x": .5, "center_y": .5}
            size_hint_x: .6

            MDDivider:

            MDTabsCarousel:
                id: related_content_container
                size_hint_y: None
                height: dp(320)
    '''


    class Example(MDApp):
        def on_start(self):
            for tab_icon, tab_name in {
                "airplane": "Flights",
                "treasure-chest": "Trips",
                "compass-outline": "Explore",
            }.items():
                self.root.ids.tabs.add_widget(
                    MDTabsItem(
                        MDTabsItemIcon(
                            icon=tab_icon,
                        ),
                        MDTabsItemText(
                            text=tab_name,
                        ),
                    )
                )
                self.root.ids.related_content_container.add_widget(
                    MDLabel(
                        text=tab_name,
                        halign="center",
                    )
                )
                self.root.ids.tabs.switch_tab(icon="airplane")

        def build(self):
            self.theme_cls.primary_palette = "Olive"
            return Builder.load_string(KV)


    Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-related-content.gif
    :align: center

Behaviors
=========

Scrollable tabs
---------------

When a set of tabs cannot fit on screen, use scrollable tabs. Scrollable tabs
can use longer text labels and a larger number of tabs. They are best used for
browsing on touch interfaces.

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.uix.tab import MDTabsItemText, MDTabsItem

    KV = '''
    MDScreen:
        md_bg_color: self.theme_cls.backgroundColor

        MDTabsPrimary:
            id: tabs
            pos_hint: {"center_x": .5, "center_y": .5}
            size_hint_x: .6
            allow_stretch: False
            label_only: True

            MDDivider:
    '''


    class Example(MDApp):
        def on_start(self):
            for tab_name in [
                "Moscow",
                "Saint Petersburg",
                "Novosibirsk",
                "Yekaterinburg",
                "Kazan",
                "Nizhny Novgorod",
                "Chelyabinsk",
            ]:
                self.root.ids.tabs.add_widget(
                    MDTabsItem(
                        MDTabsItemText(
                            text=tab_name,
                        ),
                    )
                )
            self.root.ids.tabs.switch_tab(text="Moscow")

        def build(self):
            self.theme_cls.primary_palette = "Olive"
            return Builder.load_string(KV)


    Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-scrollable-behavior.gif
    :align: center

Fixed tabs
==========

Fixed tabs display all tabs in a set simultaneously. They are best for
switching between related content quickly, such as between transportation
methods in a map. To navigate between fixed tabs, tap an individual tab, or
swipe left or right in the content area.

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.uix.tab import MDTabsItemText, MDTabsItem

    KV = '''
    MDScreen:
        md_bg_color: self.theme_cls.backgroundColor

        MDTabsPrimary:
            id: tabs
            pos_hint: {"center_x": .5, "center_y": .5}
            size_hint_x: .6
            allow_stretch: True
            label_only: True

            MDDivider:
    '''


    class Example(MDApp):
        def on_start(self):
            for tab_name in [
                "Moscow", "Saint Petersburg", "Novosibirsk"
            ]:
                self.root.ids.tabs.add_widget(
                    MDTabsItem(
                        MDTabsItemText(
                            text=tab_name,
                        ),
                    )
                )
            self.root.ids.tabs.switch_tab(text="Moscow")

        def build(self):
            self.theme_cls.primary_palette = "Olive"
            return Builder.load_string(KV)


    Example().run()

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-fixed-behavior.png
    :align: center

Tap a tab
---------

Navigate to a tab by tapping on it.


.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-tap-a-tab-behavior.gif
    :align: center

Swipe within the content area
-----------------------------

To navigate between tabs, users can swipe left or right within the content
area.

.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/tab-primary-swipe-within-content-area-behavior.gif
    :align: center

Switching tab
=============

You can switch tabs by icon name, by tab name, and by tab objects:

.. code-block:: python

    instance_tabs.switch_tab(icon="airplane")

.. code-block:: python

    instance_tabs.switch_tab(text="Airplane")

.. code-block:: python

    instance_tabs.switch_tab(
        instance=instance_tabs_item  # MDTabsItem
    )

API break
=========

1.2.0 version
-------------

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.uix.floatlayout import MDFloatLayout
    from kivymd.uix.tab import MDTabsBase
    from kivymd.icon_definitions import md_icons

    KV = '''
    MDBoxLayout:

        MDTabs:
            id: tabs
            on_ref_press: app.on_ref_press(*args)


    <Tab>

        MDIconButton:
            id: icon
            icon: app.icons[0]
            icon_size: "48sp"
            pos_hint: {"center_x": .5, "center_y": .5}
    '''


    class Tab(MDFloatLayout, MDTabsBase):
        '''Class implementing content for a tab.'''


    class Example(MDApp):
        icons = list(md_icons.keys())[15:30]

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

        def on_start(self):
            for name_tab in self.icons:
                self.root.ids.tabs.add_widget(
                    Tab(title=name_tab, icon=name_tab)
                )


    Example().run()

2.0.0 version
-------------

.. code-block:: python

    from kivy.lang import Builder

    from kivymd.app import MDApp
    from kivymd.icon_definitions import md_icons
    from kivymd.uix.label import MDIcon
    from kivymd.uix.tab import MDTabsItem, MDTabsItemIcon
    from kivymd.uix.tab.tab import MDTabsItemText

    KV = '''
    MDScreen:
        md_bg_color: self.theme_cls.backgroundColor

        MDTabsPrimary:
            id: tabs
            allow_stretch: False
            pos_hint: {"center_x": .5, "center_y": .5}

            MDDivider:

            MDTabsCarousel:
                id: related_content
                size_hint_y: None
                height: root.height - tabs.ids.tab_scroll.height
    '''


    class Example(MDApp):
        def on_start(self):
            for name_tab in list(md_icons.keys())[15:30]:
                self.root.ids.tabs.add_widget(
                    MDTabsItem(
                        MDTabsItemIcon(
                            icon=name_tab,
                        ),
                        MDTabsItemText(
                            text=name_tab,
                        ),
                    )
                )
                self.root.ids.related_content.add_widget(
                    MDIcon(
                        icon=name_tab,
                        pos_hint={"center_x": 0.5, "center_y": 0.5},
                    )
                )
                self.root.ids.tabs.switch_tab(icon="airplane")

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


    Example().run()
"""

from __future__ import annotations

__all__ = (
    "MDTabsPrimary",
    "MDTabsSecondary",
    "MDTabsItem",
    "MDTabsItemSecondary",
    "MDTabsItemIcon",
    "MDTabsItemText",
    "MDTabsCarousel",
    "MDTabsBadge",
)

import os

from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.carousel import Carousel
from kivy.uix.widget import Widget
from kivy.utils import boundary
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import (
    ObjectProperty,
    BooleanProperty,
    ColorProperty,
    NumericProperty,
    AliasProperty,
    StringProperty,
    VariableListProperty,
)
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView

from kivymd import uix_path
from kivymd.theming import ThemableBehavior
from kivymd.uix.badge import MDBadge
from kivymd.uix.behaviors import (
    DeclarativeBehavior,
    RectangularRippleBehavior,
    BackgroundColorBehavior,
)
from kivymd.uix.behaviors.state_layer_behavior import StateLayerBehavior
from kivymd.uix.label import MDLabel, MDIcon

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


###############################################################################
#
#                               COMMON CLASSES
#
###############################################################################


class MDTabsBadge(MDBadge):
    """
    Implements an badge for secondary tabs.

    .. versionadded:: 2.0.0

    For more information, see in the
    :class:`~kivymd.uix.badge.badge.MDBadge` class documentation.
    """


class MDTabsCarousel(Carousel):
    """
    Implements a carousel for user-generated content.

    For more information, see in the
    :class:`~kivy.uix.carousel.Carousel` class documentation.
    """

    lock_swiping = BooleanProperty(False)
    """
    If True - disable switching tabs by swipe.

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

    _tabs = ObjectProperty()  # MDTabsPrimary/MDTabsSecondary object

    def on_touch_move(self, touch) -> str | bool | None:
        if self.lock_swiping:  # lock a swiping
            return
        if not self.touch_mode_change:
            if self.ignore_perpendicular_swipes and self.direction in (
                "top",
                "bottom",
            ):
                if abs(touch.oy - touch.y) < self.scroll_distance:
                    if abs(touch.ox - touch.x) > self.scroll_distance:
                        self._change_touch_mode()
                        self.touch_mode_change = True
            elif self.ignore_perpendicular_swipes and self.direction in (
                "right",
                "left",
            ):
                if abs(touch.ox - touch.x) < self.scroll_distance:
                    if abs(touch.oy - touch.y) > self.scroll_distance:
                        self._change_touch_mode()
                        self.touch_mode_change = True

        if self._get_uid("cavoid") in touch.ud:
            return
        if self._touch is not touch:
            super().on_touch_move(touch)
            return self._get_uid() in touch.ud
        if touch.grab_current is not self:
            return True

        ud = touch.ud[self._get_uid()]
        direction = self.direction[0]

        if ud["mode"] == "unknown":
            if direction in "rl":
                distance = abs(touch.ox - touch.x)
            else:
                distance = abs(touch.oy - touch.y)
            if distance > self.scroll_distance:
                ev = self._change_touch_mode_ev
                if ev is not None:
                    ev.cancel()
                ud["mode"] = "scroll"
        else:
            if direction in "rl":
                self._offset += touch.dx
            if direction in "tb":
                self._offset += touch.dy
        return True


class MDTabsScrollView(BackgroundColorBehavior, ScrollView):
    """
    Implements a scrollable list of tabs.
    This class hacked version to fix scroll_x manual setting.

    For more information, see in the
    :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
    :class:`~kivy.uix.scrollview.ScrollView`
    classes documentation.
    """

    def goto(self, scroll_x: float | None, scroll_y: float | None) -> None:
        """Update event value along with scroll_*."""

        def _update(e, x):
            if e:
                e.value = (e.max + e.min) * x

        if not (scroll_x is None):
            self.scroll_x = scroll_x
            _update(self.effect_x, scroll_x)

        if not (scroll_y is None):
            self.scroll_y = scroll_y
            _update(self.effect_y, scroll_y)


class MDTabsItemText(MDLabel):
    """
    Implements an label for the :class:`~MDTabsItem` class.

    For more information, see in the
    :class:`~kivymd.uix.label.label.MDLabel` class documentation.

    .. versionadded:: 2.0.0
    """

    _active = BooleanProperty(False)


class MDTabsItemIcon(MDIcon):
    """
    Implements an icon for the :class:`~MDTabsItem` class.

    For more information, see in the
    :class:`~kivymd.uix.label.label.MDIcon` class documentation.

    .. versionadded:: 2.0.0
    """


class MDTabsItemBase(
    DeclarativeBehavior,
    BackgroundColorBehavior,
    RectangularRippleBehavior,
    ButtonBehavior,
    ThemableBehavior,
    StateLayerBehavior,
):
    """
    Implements a base item with an icon and text.

    .. versionadded:: 2.0.0

    For more information, see in the
    :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
    :class:`~kivymd.uix.behaviors.backgroundcolor_behavior.BackgroundColorBehavior` and
    :class:`~kivymd.uix.behaviors.behaviors.ripple_behavior.RectangularRippleBehavior` and
    :class:`~kivy.uix.behaviors.ButtonBehavior` and
    :class:`~kivymd.theming.ThemableBehavior`
    classes documentation.
    """

    active = BooleanProperty(False)
    """
    Is the tab active.

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

    _tabs = ObjectProperty()  # MDTabsPrimary/MDTabsSecondary object
    _tab_content = ObjectProperty()  # Carousel slide (related content) object

    def on_release(self, *args) -> None:
        """
        Fired when the button is released
        (i.e. the touch/click that pressed the button goes away).
        """

        if self._tab_content:
            self._tabs._tabs_carousel.load_slide(self._tab_content)

        self._tabs.update_indicator(instance=self)
        self._tabs.dispatch("on_tab_switch", self, self._tab_content)
        self._tabs._current_tab = self
        self._tabs._current_related_content = self._tab_content


class MDTabsItem(MDTabsItemBase, BoxLayout):
    """
    Implements a item with an icon and text for :class:`~MDTabsPrimary` class.

    .. versionadded:: 2.0.0

    For more information, see in the
    :class:`~MDTabsItemBase` and
    :class:`~kivy.uix.boxlayout.BoxLayout`
    classes documentation.
    """

    def add_widget(self, widget, *args, **kwargs):
        if isinstance(widget, (MDTabsItemText, MDTabsItemIcon)):
            if len(self.children) <= 1:
                Clock.schedule_once(lambda x: self._set_width(widget))

    def _set_width(self, widget):
        def set_width(*args):
            self.width = widget.texture_size[0] + widget.padding_x + 2

        if not self._tabs.allow_stretch and isinstance(widget, MDTabsItemText):
            Clock.schedule_once(set_width)

        super().add_widget(widget)


###############################################################################
#
#                               PRIMARY CLASSES
#
###############################################################################


class MDTabsPrimary(DeclarativeBehavior, ThemableBehavior, BoxLayout):
    """
    Tabs primary class.

    .. versionchanged:: 2.0.0

        Rename from `MDTabs` to `MDTabsPrimary` class.

    For more information, see in the
    :class:`~kivymd.uix.behaviors.declarative_behavior.DeclarativeBehavior` and
    :class:`~kivymd.theming.ThemableBehavior` and
    :class:`~kivy.uix.boxlayout.BoxLayout`
    classes documentation.

    :Events:
        `on_tab_switch`
            Fired when switching tabs.
    """

    md_bg_color = ColorProperty(None)
    """
    The background color of the widget.

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

    label_only = BooleanProperty(False)
    """
    Tabs with a label only or with an icon and a label.

    .. versionadded:: 2.0.0

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

    allow_stretch = BooleanProperty(True)
    """
    Whether to stretch tabs to the width of the panel.

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

    lock_swiping = BooleanProperty(False)
    """
    If True - disable switching tabs by swipe.

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

    anim_duration = NumericProperty(0.2)
    """
    Duration of the slide animation.

    :attr:`anim_duration` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    indicator_anim = BooleanProperty(True)
    """
    Tab indicator animation. If you want use animation set it to ``True``.

    .. versionchanged:: 2.0.0

        Rename from `tab_indicator_anim` to `indicator_anim` attribute.

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

    indicator_radius = VariableListProperty([dp(2), dp(2), 0, 0], lenght=4)
    """
    Radius of the tab indicator.

    .. versionadded:: 2.0.0

    :attr:`indicator_radius` is an :class:`~kivy.properties.VariableListProperty`
    and defaults to `[dp(2), dp(2), 0, 0]`.
    """

    indicator_height = NumericProperty("4dp")
    """
    Height of the tab indicator.

    .. versionchanged:: 2.0.0

        Rename from `tab_indicator_height` to `indicator_height` attribute.

    :attr:`indicator_height` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `'4dp'`.
    """

    indicator_duration = NumericProperty(0.5)
    """
    The duration of the animation of the indicator movement when switching
    tabs.

    .. versionadded:: 2.0.0

    :attr:`indicator_duration` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `0.5`.
    """

    indicator_transition = StringProperty("out_expo")
    """
    The transition name of animation of the indicator movement when switching
    tabs.

    .. versionadded:: 2.0.0

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

    def get_last_scroll_x(self):
        return self.ids.tab_scroll.scroll_x

    last_scroll_x = AliasProperty(
        get_last_scroll_x, bind=("target",), cache=True
    )
    """
    Is the carousel reference of the next tab/slide.
    When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the
    target tab/slide of the carousel.

    :attr:`last_scroll_x` is an :class:`~kivy.properties.AliasProperty`.
    """

    target = ObjectProperty(None, allownone=True)
    """
    It is the carousel reference of the next tab / slide.
    When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the
    target tab / slide of the carousel.

    :attr:`target` is an :class:`~kivy.properties.ObjectProperty`
    and default to `None`.
    """

    def get_rect_instruction(self):
        canvas_instructions = self.ids.container.canvas.before.get_group(
            "md-tabs-rounded-rectangle"
        )
        return canvas_instructions[0]

    indicator = AliasProperty(get_rect_instruction, cache=True)
    """
    It is the :class:`~kivy.graphics.vertex_instructions.SmoothRoundedRectangle`
    instruction reference of the tab indicator.

    :attr:`indicator` is an :class:`~kivy.properties.AliasProperty`.
    """

    _tabs_carousel = ObjectProperty()  # MDTabsCarousel object
    _current_tab = None  # MDTabsItem object
    _current_related_content = None  # Carousel slide (related content) object
    _do_releasing = True

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.register_event_type("on_tab_switch")
        self.register_event_type("on_slide_progress")
        Clock.schedule_once(self._check_panel_height)
        Clock.schedule_once(self._set_slides_attributes)

    def add_widget(self, widget, *args, **kwargs):
        if isinstance(widget, MDTabsCarousel):
            self._tabs_carousel = widget
            widget._tabs = self
            widget.bind(
                _offset=self.android_animation, index=self.on_carousel_index
            )
            return super().add_widget(widget)
        elif isinstance(widget, MDTabsItem) or (
            isinstance(self, MDTabsSecondary)
            and isinstance(widget, MDTabsItemSecondary)
        ):
            widget._tabs = self
            widget.bind(on_release=self.set_active_item)
            self.ids.container.add_widget(widget)
        else:
            return super().add_widget(widget)

    def do_autoscroll_tabs(self, instance: MDTabsItem, value: float) -> None:
        """
        Automatically scrolls the list of tabs when swiping the carousel
        slide (related content).

        .. versionchanged:: 2.0.0

            Rename from `tab_bar_autoscroll` to `do_autoscroll_tabs` method.
        """

        bound_left = self.center_x - self.x
        bound_right = self.ids.container.width - bound_left
        dt = instance.center_x - bound_left
        sx, sy = self.ids.tab_scroll.convert_distance_to_scroll(dt, 0)
        lsx = self.last_scroll_x  # ast scroll x of the tab bar
        scroll_is_late = lsx < sx  # determine scroll direction
        dst = abs(lsx - sx) * value  # distance to run)

        if not dst:
            return
        if scroll_is_late and instance.center_x > bound_left:
            x = lsx + dst
        elif not scroll_is_late and instance.center_x < bound_right:
            x = lsx - dst
        else:
            return

        x = boundary(x, 0.0, 1.0)
        self.ids.tab_scroll.goto(x, None)

    def android_animation(
        self, instance: MDTabsCarousel, offset: float
    ) -> None:
        """Fired when swiping a carousel slide (related content)."""

        self.dispatch("on_slide_progress", instance, offset)

        # Try to reproduce the android animation effect.
        if offset != 0 and abs(offset) < instance.width:
            forward = offset < 0
            offset = abs(offset)
            step = offset / float(instance.width)

            skip_slide = (
                instance.slides[instance._skip_slide]
                if instance._skip_slide is not None
                else None
            )
            next_slide = (
                instance.next_slide if forward else instance.previous_slide
            )
            self.target = skip_slide if skip_slide else next_slide

            if not self.target:
                return

            a = instance.current_slide.tab_item
            b = self.target.tab_item
            self.do_autoscroll_tabs(b, step)
            item_text_object = self._get_tab_item_text_icon_object()

            if item_text_object:
                if self.__class__.__name__ == "MDTabsSecondary":
                    tab_text_width = a.width
                else:
                    tab_text_width = item_text_object.texture_size[0]

                if self.indicator_anim is False:
                    return

                gap_x = abs(a.x - b.x)
                if forward:
                    x_step = (
                        a.x
                        + (a.width / 2 - tab_text_width / 2)
                        + dp(4)
                        + (gap_x * step)
                    )
                else:
                    x_step = (
                        a.x
                        + (a.width / 2 - tab_text_width / 2)
                        + dp(4)
                        - gap_x * step
                    )

                w_step = tab_text_width - (
                    dp(8) if self.__class__.__name__ == "MDTabsPrimary" else 0
                )
                self.update_indicator(x_step, w_step)

    def update_indicator(
        self, x: float = 0.0, w: float = 0.0, instance: MDTabsItem = None
    ) -> None:
        """Update position and size of the indicator."""

        def update_indicator(*args):
            indicator_pos = (0, 0)
            indicator_size = (0, 0)

            if self.__class__.__name__ == "MDTabsPrimary":
                item_text_object = self._get_tab_item_text_icon_object()

                if item_text_object:
                    tab_text_width = item_text_object.texture_size[0]
                    indicator_pos = (
                        instance.x
                        + (instance.width / 2 - tab_text_width / 2)
                        + dp(4),
                        self.indicator.pos[1]
                        if not self._tabs_carousel
                        else self._tabs_carousel.height,
                    )
                    indicator_size = (
                        tab_text_width - dp(8),
                        self.indicator_height,
                    )
            elif self.__class__.__name__ == "MDTabsSecondary":
                indicator_pos = (instance.x, self.indicator.pos[1])
                indicator_size = (instance.width, self.indicator_height)

            Animation(
                pos=indicator_pos,
                size=indicator_size,
                d=0 if not self.indicator_anim else self.indicator_duration,
                t=self.indicator_transition,
            ).start(self.indicator)

        if not instance:
            self.indicator.pos = (x, self.indicator.pos[1])
            self.indicator.size = (w, self.indicator_height)
        else:
            Clock.schedule_once(update_indicator)

    def switch_tab(
        self, instance: MDTabsItem = None, text: str = "", icon: str = ""
    ) -> None:
        """Switches tabs by tab object/tab text/tab icon name."""

        Clock.schedule_once(
            lambda x: self._switch_tab(instance, text, icon), 0.8
        )

    def set_active_item(self, item: MDTabsItem) -> None:
        """Sets the active tab item."""

        for widget in self.ids.container.children:
            if item is widget:
                # Trying to switch an already active tab.
                if widget.active and item.active:
                    break

                widget.active = not widget.active

                for widget_item in item.children:
                    if isinstance(widget_item, MDTabsItemText):
                        widget_item._active = widget.active
                        Animation(
                            text_color=self.theme_cls.primaryColor
                            if widget.active
                            else self.theme_cls.onSurfaceVariantColor,
                            d=0.2,
                        ).start(widget_item)
                    if isinstance(widget_item, MDTabsItemIcon):
                        widget_item._active = widget.active
                        Animation(
                            icon_color=self.theme_cls.primaryColor
                            if widget.active
                            else self.theme_cls.onSurfaceVariantColor,
                            d=0.2,
                        ).start(widget_item)
            else:
                widget.active = False
                for widget_item in widget.children:
                    widget_item._active = widget.active
                    if isinstance(widget_item, MDTabsItemText):
                        Animation(
                            text_color=self.theme_cls.onSurfaceVariantColor,
                            d=0.2,
                        ).start(widget_item)
                    if isinstance(widget_item, MDTabsItemIcon):
                        Animation(
                            icon_color=self.theme_cls.onSurfaceVariantColor,
                            d=0.2,
                        ).start(widget_item)

    def get_tabs_list(self) -> list:
        """
        Returns a list of :class:`~MDTabsItem` objects.

        .. versionchanged:: 2.0.0

            Rename from `get_tab_list` to `get_tabs_list` method.
        """

        return self.ids.container.children

    def get_slides_list(self) -> list:
        """
        Returns a list of user tab objects.

        .. versionchanged:: 2.0.0

            Rename from `get_slides` to `get_slides_list` method.
        """

        if self._tabs_carousel:
            return self._tabs_carousel.slides

    def get_current_tab(self) -> MDTabsItem:
        """
        Returns current tab object.

        .. versionadded:: 1.0.0
        """

        return self._current_tab

    def get_current_related_content(self) -> Widget:
        """
        Returns the carousel slide object (related content).

        .. versionadded:: 2.0.0
        """

        return self._current_related_content

    def on_tab_switch(self, *args) -> None:
        """This event is launched every time the current tab is changed."""

    def on_slide_progress(self, *args) -> None:
        """
        This event is deployed every available frame while the tab is
        scrolling.
        """

    def on_carousel_index(self, instance: MDTabsCarousel, value: int) -> None:
        """
        Fired when the Tab index have changed.
        This event is deployed by the builtin carousel of the class.
        """

        # When the index of the carousel change, update tab indicator,
        # select the current tab and reset threshold data.
        if instance.current_slide and hasattr(
            instance.current_slide, "tab_item"
        ):
            Clock.schedule_once(
                lambda x: instance.current_slide.tab_item.dispatch("on_release")
            )

    def on_size(self, instance, size) -> None:
        """Fired when the application screen size changes."""

        width, height = size
        number_tabs = len(self.ids.container.children)

        if self.allow_stretch:
            for tab in self.ids.container.children:
                tab.width = width / number_tabs

        if self._tabs_carousel:
            Clock.schedule_once(
                lambda x: self._tabs_carousel.current_slide.tab_item.dispatch(
                    "on_release"
                )
            )

    def _switch_tab(
        self, instance: MDTabsItem = None, text: str = "", icon: str = ""
    ):
        def get_match(widget_to_compare, widget_to_compare_with, value, attr):
            if isinstance(widget_to_compare, widget_to_compare_with):
                if getattr(widget_to_compare, attr) == value:
                    return True

        def switch_by(by_attr, attr):
            for tab_item in self.ids.container.children:
                for child in tab_item.children:
                    if isinstance(child, MDTabsItemSecondaryContainer):
                        for w in child.children:
                            if get_match(
                                w,
                                MDTabsItemText
                                if by_attr == "text"
                                else MDTabsItemIcon,
                                attr,
                                by_attr,
                            ):
                                tab_item.dispatch("on_release")
                                break
                    else:
                        if get_match(
                            child,
                            MDTabsItemText
                            if by_attr == "text"
                            else MDTabsItemIcon,
                            attr,
                            by_attr,
                        ):
                            tab_item.dispatch("on_release")
                            break

        if instance and isinstance(instance, MDTabsItem):
            instance.dispatch("on_release")
        elif text:
            switch_by("text", text)
        elif icon:
            switch_by("icon", icon)

    def _set_slides_attributes(self, *args):
        if self._tabs_carousel:
            tabs_item_list = self.ids.container.children.copy()
            tabs_item_list.reverse()

            for i, tab_item in enumerate(tabs_item_list):
                setattr(tab_item, "_tab_content", self._tabs_carousel.slides[i])
                setattr(self._tabs_carousel.slides[i], "tab_item", tab_item)

    def _get_tab_item_text_icon_object(
        self, get_type="text"
    ) -> MDTabsItemText | MDTabsItemIcon | None:
        item_text_object = None

        for tab_item in self.ids.container.children:
            if tab_item.active:
                for child in tab_item.children:
                    if isinstance(child, MDTabsItemSecondaryContainer):
                        for w in child.children:
                            if isinstance(
                                w,
                                MDTabsItemText
                                if get_type == "text"
                                else MDTabsItemIcon,
                            ):
                                item_text_object = w
                                break
                    else:
                        if isinstance(
                            child,
                            MDTabsItemText
                            if get_type == "text"
                            else MDTabsItemIcon,
                        ):
                            item_text_object = child
                            break
        return item_text_object

    def _check_panel_height(self, *args):
        if self.label_only:
            self.ids.tab_scroll.height = dp(48)
        else:
            self.ids.tab_scroll.height = dp(64)


###############################################################################
#
#                              SECONDARY CLASSES
#
###############################################################################


class MDTabsItemSecondaryContainer(BoxLayout):
    """
    Implements a container for placing widgets for the
    :class:`~MDTabsItemSecondary` class.

    For more information, see in the
    :class:`~kivy.uix.boxlayout.BoxLayout` class documentation.
    """


class MDTabsItemSecondary(MDTabsItemBase, AnchorLayout):
    """
    Implements a item with an icon and text for :class:`~MDTabsSecondary`
    class.

    .. versionadded:: 2.0.0

    For more information, see in the
    :class:`~MDTabsItemBase` and
    :class:`~kivy.uix.anchorlayout.AnchorLayout`
    classes documentation.
    """

    def add_widget(self, widget, *args, **kwargs):
        if isinstance(widget, (MDTabsItemText, MDTabsItemIcon, MDTabsBadge)):
            Clock.schedule_once(
                lambda x: self.ids.box_container.add_widget(widget)
            )
        else:
            return super().add_widget(widget)


class MDTabsSecondary(MDTabsPrimary):
    """
    Tabs secondary class.

    .. versionadded:: 2.0.0

    For more information, see in the
    :class:`~MDTabsPrimary` class documentation.
    """

    indicator_radius = VariableListProperty(0, lenght=4)
    """
    Radius of the tab indicator.

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

    indicator_height = NumericProperty("2dp")
    """
    Height of the tab indicator.

    :attr:`indicator_height` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `'2dp'`.
    """

    def _check_panel_height(self, *args):
        self.ids.tab_scroll.height = dp(48)
