from typing import Optional

import omni.ui as ui

from .option_item import OptionItem
from .option_menu_item import OptionMenuItem
from .options_model import OptionsModel
from .option_separator import OptionSeparator
from .style import OPTIONS_MENU_STYLE


class OptionsMenuDelegate(ui.MenuDelegate):
    """
    Delegate for options menu.
    It has a header to show label and reset button

    Args:
        model (OptionsModel): Model for options to show in this menu.
    """
    def __init__(self, model: OptionsModel, **kwargs):
        self._model = model
        self._reset_all: Optional[ui.Button] = None
        self.__sub = self._model.subscribe_item_changed_fn(self.__on_item_changed)
        
        super().__init__(**kwargs)

    def destroy(self) -> None:
        self.__sub = None

    def build_title(self, item: ui.Menu) -> None:
        with ui.ZStack(content_clipping=True, height=24):
            ui.Rectangle(style_type_name_override="Title.Background")
            with ui.HStack(style_type_name_override="Title.Header"):
                if item.text:
                    ui.Label(self._model.name, width=0, style_type_name_override="Title.Label")

                # Extra spacer here to make sure menu window has min width
                ui.Spacer(width=45)
                ui.Spacer()
                self._reset_all = ui.Button(
                    "Reset All",
                    width=0,
                    height=24,
                    enabled=self._model.dirty,
                    style_type_name_override="ResetButton",
                    clicked_fn=self._model.reset,
                    identifier="reset_all",
                )

    def __on_item_changed(self, model: OptionsModel, item: OptionItem) -> None:
        if self._reset_all:
            self._reset_all.enabled = self._model.dirty


class OptionsMenu(ui.Menu):
    """
    Represent a menu to show options.
    A options menu includes a header and a list of menu items for options.

    Args:
        model (OptionsModel): Model of option items to show in this menu.
        hide_on_click (bool): Hide menu when item clicked. Default False.
        width (ui.Length): Width of menu item. Default ui.Fraction(1).
        style (dict): Additional style. Default empty.
    """
    def __init__(self, model: OptionsModel, hide_on_click: bool = False, width: ui.Length = ui.Fraction(1), style: dict = {}):
        self._model = model
        self._hide_on_click = hide_on_click
        self._width = width
        menu_style = OPTIONS_MENU_STYLE.copy()
        menu_style.update(style)

        self._delegate = OptionsMenuDelegate(self._model)
        super().__init__(self._model.name, delegate=self._delegate, menu_compatibility=False, on_build_fn=self._build_menu_items, style=menu_style)
        self.__sub = self._model.subscribe_item_changed_fn(self.__on_model_changed)

    def destroy(self):
        self.__sub = None
        self._delegate.destroy()

    def show_by_widget(self, widget: ui.Widget) -> None:
        """
        Show menu around widget (bottom left).

        Args:
            widget (ui.Widget): Widget to align for this menu.
        """
        if widget:
            x = widget.screen_position_x
            y = widget.screen_position_y + widget.computed_height
            self.show_at(x, y)
        else:
            self.show()

    def _build_menu_items(self):
        for item in self._model.get_item_children():
            if item.text is None:
                # Separator
                OptionSeparator()
            else:
                OptionMenuItem(item, hide_on_click=self._hide_on_click, width=self._width)

    def __on_model_changed(self, model: OptionsModel, item: Optional[OptionItem]) -> None:
        if item is None:
            self.invalidate()
