import pathlib
from enum import StrEnum
from tkinter import Toplevel

from .base import Base
from .button import Button
from .images import Image
from .labels import Label
from .views import View


class Dialog(Base):
    """A modal dialog window with configurable buttons and content."""

    class Replies(StrEnum):
        """Standard reply values returned by dialog buttons."""

        Ok = "Ok"
        Cancel = "Cancel"
        Abort = "Abort"
        Timedout = "Timedout"

    @classmethod
    def showinfo(cls, message, title="Info", auto_click=(None, None), auto_position="center"):
        """Show an informational dialog with the given message."""
        diag = SimpleDialog(
            dialog_type="info",
            title=title,
            message=message,
            auto_click=auto_click,
            auto_position=auto_position,
        )
        return diag.run()

    @classmethod
    def showwarning(cls, message, title="Warning", auto_click=(None, None), auto_position="center"):
        """Show a warning dialog with the given message."""
        diag = SimpleDialog(
            dialog_type="warning",
            title=title,
            message=message,
            auto_click=auto_click,
            auto_position=auto_position,
        )
        return diag.run()

    @classmethod
    def showerror(cls, message, title="Error", auto_click=(None, None), auto_position="center"):
        """Show an error dialog with the given message."""
        diag = SimpleDialog(
            dialog_type="error",
            title=title,
            message=message,
            auto_click=auto_click,
            auto_position=auto_position,
        )
        return diag.run()

    @classmethod
    def showprogress(cls, message, title="Info", auto_click=(None, None)):
        """Show a progress dialog with the given message."""
        diag = Dialog(
            dialog_type="error", title=title, message=message, auto_click=auto_click
        )
        return diag.run()

    def __init__(
        self,
        title,
        buttons_labels=None,
        geometry=None,
        auto_position=None,
        auto_click=(None, None),
        *args,
        **kwargs
    ):
        from .utils import parse_geometry
        super().__init__(*args, **kwargs)

        _, offset_str = parse_geometry(geometry)
        if offset_str is not None and auto_position is not None:
            raise ValueError(
                f"Conflicting position: geometry already contains an offset "
                f"({offset_str!r}) and auto_position={auto_position!r} was also given. "
                f"Use one or the other."
            )

        self.title = title
        self.geometry = geometry
        self.auto_position = auto_position
        self.reply = None
        self.auto_click = auto_click[0]
        self.timeout = auto_click[1]
        self.entries = {}

        if buttons_labels is None:
            self.buttons_labels = [Dialog.Replies.Ok]
        else:
            self.buttons_labels = buttons_labels
        self.buttons = {}

    @property
    def is_disabled(self):
        """Whether the dialog and its children are disabled."""
        return getattr(self, '_disabled', False)

    @is_disabled.setter
    def is_disabled(self, value):
        self._disabled = value
        if self.widget is not None:
            self._propagate_disabled(self.widget, value)

    def create_widget(self, master, **kwargs):
        """Create the Toplevel dialog window and populate its contents."""
        self.parent = None
        self.widget = Toplevel()
        self.widget.title(self.title)
        if self.geometry is not None:
            self.widget.geometry(self.geometry)

        self.populate_widget_body()
        self.populate_buttons()
        self.all_resize_weight(1)
        if self.auto_position is not None:
            from .utils import apply_window_position, parse_geometry
            size_str, _ = parse_geometry(self.geometry)
            apply_window_position(self.widget, self.auto_position, size_str)

    def populate_buttons(self):
        """Create and layout the dialog action buttons."""
        cols, rows = self.widget.grid_size()

        control_buttons = View(width=200, height=30)
        control_buttons.grid_into(
            widget=self.widget,
            column=0,
            row=rows,
            columnspan=cols,
            pady=10,
            padx=10,
            sticky="sew",
        )
        self.row_resize_weight(rows, 1)
        control_buttons.column_resize_weight(1, 1)

        self.buttons = self.create_behavior_buttons()

        for i, button_label in enumerate(self.buttons_labels):
            button = self.buttons[button_label]
            button.grid_into(
                control_buttons,
                column=2 - i,
                row=1,
                pady=5,
                padx=5,
                sticky="nse",
            )

    def populate_widget_body(self):
        """Populate the main body of the dialog. Override in subclasses."""
        pass

    def run(self):
        """Display the dialog modally and return the user reply."""
        self.create_widget(master=None)

        if self.auto_click is not None:
            _button = self.buttons[self.auto_click]
            if (
                self.auto_click == Dialog.Replies.Ok
            ):  # I am unable to get button.widget.invoke to work
                self.widget.after(
                    self.timeout, lambda: self.user_clicked_ok(None, None)
                )
            elif self.auto_click == Dialog.Replies.Cancel:
                self.widget.after(
                    self.timeout, lambda: self.user_clicked_cancel(None, None)
                )
        elif self.timeout is not None:
            self.widget.after(self.timeout, self.user_timeout)

        self.widget.grab_set()  # ensure all input goes to our window, including shortcut enter
        self.widget.wait_window()
        return self.reply

    def create_behavior_buttons(self):
        """Create Ok and Cancel buttons based on the configured button labels."""
        if not self.buttons:
            if Dialog.Replies.Ok in self.buttons_labels:
                button = Button(
                    Dialog.Replies.Ok, user_event_callback=self.user_clicked_ok
                )
                self.buttons[Dialog.Replies.Ok] = button
            if Dialog.Replies.Cancel in self.buttons_labels:
                self.buttons[Dialog.Replies.Cancel] = Button(
                    Dialog.Replies.Cancel,
                    user_event_callback=self.user_clicked_cancel,
                )

        return self.buttons

    def user_clicked_ok(self, event, button=None):
        """Handle the Ok button click by setting the reply and closing."""
        self.reply = Dialog.Replies.Ok
        self.widget.destroy()

    def user_clicked_cancel(self, event, button=None):
        """Handle the Cancel button click by setting the reply and closing."""
        self.reply = Dialog.Replies.Cancel
        self.widget.destroy()


class SimpleDialog(Dialog):
    """A ready-made dialog that displays an icon and message for info, warning, or error."""

    def __init__(self, dialog_type, message, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.dialog_type = dialog_type
        self.message = message

    def populate_widget_body(self):
        """Display the dialog icon and message label in the body area."""
        self.widget.wait_visibility()  # can't grab until window appears, so we wait

        resource_directory = pathlib.Path(__file__).parent / "resources"

        if self.dialog_type == "error":
            icon = Image(filepath=resource_directory / "error.png")
        elif self.dialog_type == "warning":
            icon = Image(filepath=resource_directory / "warning.png")
        elif self.dialog_type == "info":
            icon = Image(filepath=resource_directory / "info.png")
        else:
            icon = Image(filepath=resource_directory / "info.png")

        icon.is_rescalable = False
        icon.grid_into(self, column=0, row=0, pady=20, padx=20, sticky="")

        label1 = Label(
            text=self.message,
            wrapping=True,
            width=30,
            wraplength=300,
            justify="center",
        )
        label1.grid_into(
            widget=self.widget,
            column=1,
            columnspan=2,
            row=0,
            pady=5,
            padx=5,
            sticky="nsew",
        )

        self.column_resize_weight(0, 0)
        self.column_resize_weight(1, 1)
        self.widget.resizable(False, False)

    def populate_buttons(self):
        """Create buttons and assign default keyboard shortcuts."""
        super().populate_buttons()
        self.assign_default_key_shortcuts()

    def assign_default_key_shortcuts(self):
        """Bind Return to Ok and Escape to Cancel."""
        if Dialog.Replies.Ok in self.buttons:
            self.widget.bind("<Return>", self.user_clicked_ok)
            self.buttons[Dialog.Replies.Ok].set_as_default()

        self.widget.bind("<Escape>", self.user_clicked_cancel)
