# pylint: disable=import-outside-toplevel,line-too-long,too-many-lines,too-many-arguments,too-many-positional-arguments,cyclic-import
"""TkEasyGUI dialogs."""

import base64
import os
import subprocess
import sys
import tempfile
import tkinter
from datetime import datetime, timedelta
from re import Pattern
from tkinter import colorchooser, filedialog, messagebox
from typing import Any, Callable, Optional, Union, cast

from . import locale_easy as le
from .utils import (
    ColorFormatType,
    FileTypeList,
    FontType,
    PopupGetFormItemType,
    copy_to_clipboard,
    get_root_window,
    is_mac,
    is_win,
)

YES = "Yes"
NO = "No"
OK = "OK"
CANCEL = "Cancel"

# save original print
_print = print  # pylint: disable=used-before-assignment

# ------------------------------------------------------------------------------
# popup options
# ------------------------------------------------------------------------------
# Global popup options (must be defined before any function uses them as default arguments)
POPUP_AUTO_SCREENSHOT = False
POPUP_AUTO_SCREENSHOT_DURATION = 500  # auto close duration (msec)
POPUP_AUTO_SCREENSHOT_FILENAME = "screenshot.png"
POPUP_OK_BUTTON_WIDTH = 12
POPUP_CANCEL_BUTTON_WIDTH = 9
POPUP_TTK_BUTTONS = True  # use ttk buttons


def popup_set_options(
    auto_screenshot: Optional[bool] = None,
    auto_screenshot_duration: Optional[int] = None,  # auto close duration (msec)
    auto_screenshot_filename: Optional[str] = None,
    ok_button_width: Optional[int] = None,
    cancel_button_width: Optional[int] = None,
    ttk_buttons: Optional[bool] = None,  # use ttk buttons
) -> None:
    """Set auto screenshot values."""
    global POPUP_AUTO_SCREENSHOT  # pylint: disable=global-statement
    global POPUP_AUTO_SCREENSHOT_DURATION  # pylint: disable=global-statement
    global POPUP_AUTO_SCREENSHOT_FILENAME  # pylint: disable=global-statement
    global POPUP_OK_BUTTON_WIDTH  # pylint: disable=global-statement
    global POPUP_CANCEL_BUTTON_WIDTH  # pylint: disable=global-statement
    global POPUP_TTK_BUTTONS  # pylint: disable=global-statement
    if auto_screenshot is not None:
        POPUP_AUTO_SCREENSHOT = auto_screenshot
    if auto_screenshot_duration is not None:
        POPUP_AUTO_SCREENSHOT_DURATION = auto_screenshot_duration
    if auto_screenshot_filename is not None:
        POPUP_AUTO_SCREENSHOT_FILENAME = auto_screenshot_filename
    if ok_button_width is not None:
        POPUP_OK_BUTTON_WIDTH = ok_button_width
    if cancel_button_width is not None:
        POPUP_CANCEL_BUTTON_WIDTH = cancel_button_width
    if ttk_buttons is not None:
        POPUP_TTK_BUTTONS = ttk_buttons


# ------------------------------------------------------------------------------
# Dialogs
# ------------------------------------------------------------------------------
# pylint: disable=too-many-locals, too-many-branches, too-many-statements, too-many-return-statements
def popup_buttons(
    message: str,
    title: Optional[str] = None,
    buttons: Union[
        list[str], None
    ] = None,  # button labels(default is ["OK", "Cancel"])
    auto_close_duration: int = -1,  # auto close duration (sec)
    timeout_key: str = "-TIMEOUT-",  # if auto_close_duration > 0, return this key
    non_blocking: bool = False,
    default: str = "",
    size: Union[tuple[int, int], None] = None,
    icon: str = "",  # filename or icon name(information/info, warning, error, question/?)
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,  # show copy message in popup menu
    use_ttk_buttons: bool = POPUP_TTK_BUTTONS,  # use ttk buttons
    auto_locale: bool = True,  # auto locale
) -> str:
    """
    Popup window with user defined buttons. Return button's label.

    #### Example:
    ```py
    color = eg.popup_buttons(
        "Which color do you like?",
        "Question",
        buttons=["red","yellow","green"])
    print(color)
    ```
    """
    from . import widgets as eg

    if buttons is None:
        buttons = ["OK", "Cancel"]
    if title is None:
        title = le.get_text("Question")
    result = buttons[-1] if len(buttons) > 0 else default
    popup_menu: Optional[tkinter.Menu] = None

    # create buttons
    eg_buttons: list[eg.Element] = []
    for label in buttons:
        width = 9
        if label == "OK":
            width = POPUP_OK_BUTTON_WIDTH
        elif label == "Cancel":
            width = POPUP_CANCEL_BUTTON_WIDTH
        if auto_locale:
            label = le.get_text(label)
        button_obj = eg.Button(label, width=width, use_ttk_buttons=use_ttk_buttons)
        eg_buttons.append(button_obj)
    eg_buttons_pad: list[eg.Element] = [eg.Push()] + eg_buttons + [eg.Push()]
    eg_messages: list[eg.Element] = []
    if icon != "":
        if os.path.exists(icon):
            eg_messages.append(eg.Image(icon, size=icon_size))
        else:
            font_info = ("", icon_size[0])
            icon = icon.lower()
            if icon in ["information", "info"]:
                eg_messages.append(eg.Label("📖", font=font_info))
            elif icon == "warning":
                # Windows環境で VS16(Variation Selector-16) 付きの警告絵文字
                # (U+26A0 + U+FE0F) が表示されないケースに対応
                # VS16 なしの U+26A0("⚠") を使用して互換性を確保 (#128)
                eg_messages.append(eg.Label("⚠", font=font_info))
            elif icon == "error":
                eg_messages.append(eg.Label("❌", font=font_info))
            elif icon in ["question", "?"]:
                eg_messages.append(eg.Label("❓", font=font_info))
            else:
                eg_messages.append(eg.Label("🌱", font=font_info))  # error
    if message != "":
        msg = eg.Text(
            message,
            key="-msg-",
            expand_x=True,
            expand_y=True,
            enable_events=can_copy_message,
        )
        eg_messages.append(msg)
        # popup menu
        popup_menu = tkinter.Menu(get_root_window(), tearoff=0)
        popup_menu.add_command(
            label=le.get_text("Copy Message"),
            command=lambda: copy_to_clipboard(message),
        )
    layout: eg.LayoutType = [
        eg_messages,
        eg_buttons_pad,
    ]

    # event loop
    timer_id = eg.time_checker_start()
    auto_screenshot = False
    if non_blocking:
        # popup non blocking window : to be implemented in the future
        pass
    # create window
    with eg.Window(
        title, layout=layout, size=size, icon=window_icon, modal=True
    ) as win:
        for event, values in win.event_iter(timeout=100, timeout_key=eg.WINDOW_TIMEOUT):
            if event in buttons:
                result = event
                break
            if event == eg.WINDOW_TIMEOUT:
                passing_time = eg.time_checker_end(timer_id)
                if POPUP_AUTO_SCREENSHOT and (auto_screenshot is False):
                    if passing_time > POPUP_AUTO_SCREENSHOT_DURATION:
                        auto_screenshot = True
                        screen_image = win.screenshot()
                        screen_image.save(POPUP_AUTO_SCREENSHOT_FILENAME)
                if auto_close_duration > 0:
                    if passing_time > auto_close_duration * 1000:
                        result = timeout_key  # timeout_key only use result
                        break
            if event == "-msg-" and "event" in values:  # click message
                e = values["event"]
                if popup_menu is not None:
                    popup_menu.post(e.x_root, e.y_root)
    return result


def popup(
    message: str,
    title: str = "",
    size: Union[tuple[int, int], None] = None,
    icon: str = "",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
) -> str:
    """
    Display a message in a popup window.

    #### Example:
    ```py
    eg.popup("I like an apple.", "Information")
    ```
    """
    # messagebox.showinfo(title, message)
    return popup_buttons(
        message=message,
        title=title,
        buttons=["OK"],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )


def popup_non_blocking(
    message: str,
    title: str = "",
    auto_close_duration: int = -1,
    size: Union[tuple[int, int], None] = None,
    icon: str = "",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message=True,
) -> str:
    """(TODO) Display a non blocking window"""
    return popup_buttons(
        message,
        title,
        buttons=["OK"],
        auto_close_duration=auto_close_duration,
        non_blocking=True,
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )


def popup_no_buttons(
    message: str,
    title: str = "",
    icon: str = "",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    size: Union[tuple[int, int], None] = None,
    can_copy_message: bool = True,
) -> None:
    """Display a message in a popup window without buttons."""
    popup_buttons(
        message,
        title,
        buttons=[],
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        size=size,
        can_copy_message=can_copy_message,
    )


def popup_auto_close(
    message: str,
    title: str = "",
    auto_close_duration: int = 3,  # auto close duration (sec)
    buttons: Optional[list[str]] = None,  # default is ["OK", "Cancel"]
    timeout_key="-TIMEOUT-",
    size: Union[tuple[int, int], None] = None,
    icon: str = "information",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
) -> str:
    """Display a message in a popup window that closes automatically after a specified time."""
    return popup_buttons(
        message,
        title,
        buttons=buttons,
        auto_close_duration=auto_close_duration,
        timeout_key=timeout_key,
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )


def popup_no_wait(
    message: str,
    title: str = "",
    size: Union[tuple[int, int], None] = None,
    icon: str = "information",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    **kw,
) -> str:
    """Display a message in a popup window without waiting."""
    return popup_auto_close(
        message,
        title,
        auto_close_duration=-1,
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        **kw,
    )


def popup_ok(
    message: str,
    title: str = "",
    size: Union[tuple[int, int], None] = None,
    icon: str = "information",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
) -> str:
    """Display a message in a popup window.(Alias popup)"""
    return popup_buttons(
        message,
        title,
        buttons=["OK"],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )


def popup_ok_cancel(
    message: str,
    title: Optional[str] = None,
    ok_label: Optional[str] = None,
    cancel_label: Optional[str] = None,
    ok_value: str = "OK",
    cancel_value: str = "Cancel",
    size: Union[tuple[int, int], None] = None,
    icon: str = "?",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
) -> str:
    """Display a message in a popup window with OK and Cancel buttons. Return "OK" or "Cancel" or eg.WINDOW_CLOSED."""
    if ok_label is None:
        ok_label = le.get_text("OK")
    if cancel_label is None:
        cancel_label = le.get_text("Cancel")
    result = popup_buttons(
        message,
        title,
        buttons=[ok_label, cancel_label],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )
    if result == ok_label:
        return ok_value
    if result == cancel_label:
        return cancel_value
    return result  # pushed close button


def popup_yes_no(
    message: str,  # question message
    title: Optional[str] = None,  # window title
    yes_label: Optional[str] = None,  # label for yes button
    no_label: Optional[str] = None,  # label for no button
    yes_value: str = "Yes",  # return value for yes
    no_value: str = "No",  # return value for no
    size: Union[tuple[int, int], None] = None,
    icon: str = "?",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
) -> str:
    """
    Display a message in a popup window with Yes and No buttons. Return "Yes" or "No" (or eg.WINDOW_CLOSED).

    @see [tests/localize_test.py](https://github.com/kujirahand/tkeasygui-python/blob/main/tests/localize_test.py)
    #### Example - simple:
    Ask user question, [Yes] or [No] buttons.
    ```py
    ans = eg.popup_yes_no("Do you like Sushi?", "Question")
    print(ans) # "Yes" or "No"
    ```
    #### Eample - custom label:
    Ask user question in special button
    ```py
    ans = eg.popup_yes_no("Do you eat Sushi?", yes_label="EAT", no_label="no")
    print(ans) # "Yes" or "No"
    ```
    #### Example - custom return value:
    ans = eg.popup_yes_no("Can you speak Japanese?", yes_value="can", no_value="no")
    print(ans) # "can" or "no"
    """
    # get locale text
    title = title if title is not None else le.get_text("Question")
    yes_label = yes_label if yes_label is not None else le.get_text("Yes")
    no_label = no_label if no_label is not None else le.get_text("No")
    # ask yes or no
    result = popup_buttons(
        message,
        title,
        buttons=[yes_label, no_label],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )
    # get result
    if result == yes_label:
        return yes_value
    if result == no_label:
        return no_value
    return result  # pushed close button


def popup_yes_no_cancel(
    message: str,
    title: Optional[str] = None,
    yes_label: Optional[str] = None,
    no_label: Optional[str] = None,
    cancel_label: Optional[str] = None,
    yes_value: str = "Yes",
    no_value: str = "No",
    cancel_value: str = "Cancel",
    size: Union[tuple[int, int], None] = None,
    icon: str = "?",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
) -> str:
    """Display a message in a popup window with Yes and No buttons. Return "Yes" or "No" or "Cancel"."""
    # return popup_buttons(message, title, buttons=["Yes", "No", "Cancel"])
    # get locale text
    title = title if title is not None else le.get_text("Question")
    yes_label = yes_label if yes_label is not None else le.get_text("Yes")
    no_label = no_label if no_label is not None else le.get_text("No")
    cancel_label = cancel_label if cancel_label is not None else le.get_text("Cancel")
    # ask yes or no
    result = popup_buttons(
        message,
        title,
        buttons=[yes_label, no_label, cancel_label],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )
    # get result
    if result == yes_label:
        return yes_value
    if result == no_label:
        return no_value
    if result == cancel_label:
        return cancel_value
    return cancel_label


def popup_cancel(
    message: str,
    title: str = "",
    size: Union[tuple[int, int], None] = None,
    icon: str = "information",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
) -> str:
    """Display a message in a popup window with OK and Cancel buttons. Return "Cancel" or eg.WINDOW_CLOSED."""
    cancel_label = le.get_text("Cancel")
    result = popup_buttons(
        message,
        title,
        buttons=["Cancel"],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )
    return cancel_label if result == cancel_label else result


def popup_input(
    message: str,
    title: Optional[str] = None,
    default: str = "",
    ok_label: Optional[str] = None,
    cancel_label: Optional[str] = None,
    cancel_value: Any = None,
    only_number: bool = False,
    font: Optional[FontType] = None,
    size: Union[tuple[int, int], None] = None,
    window_icon: Optional[str] = None,  # window icon, specify filename
    validation: Optional[
        Union[str, Pattern[str]]
    ] = None,  # validation regular expression
    validation_message: Optional[str] = None,
) -> Union[str, float, None]:
    """Display a message in a popup window with a text entry. Return the text entered. if canceled, return cancel_value."""
    from . import widgets as eg

    result = cancel_value
    if title is None:
        title = (
            le.get_text("Text input")
            if only_number is False
            else le.get_text("Number input")
        )
    if ok_label is None:
        ok_label = le.get_text("OK")
    if cancel_label is None:
        cancel_label = le.get_text("Cancel")
    win = eg.Window(
        title,
        layout=[
            [
                eg.Text(
                    message,
                    validation=validation,
                    validation_message=validation_message,
                )
            ],
            [eg.Input(default, key="-user-", width=40, enable_events=True)],
            [
                eg.Push(),
                eg.Button(
                    ok_label,
                    width=POPUP_OK_BUTTON_WIDTH,
                    use_ttk_buttons=POPUP_TTK_BUTTONS,
                ),
                eg.Button(
                    cancel_label,
                    width=POPUP_CANCEL_BUTTON_WIDTH,
                    use_ttk_buttons=POPUP_TTK_BUTTONS,
                ),
                eg.Push(),
            ],
        ],
        modal=True,
        font=font,
        size=size,
        icon=window_icon,
    )
    while win.is_alive():
        event, values = win.read()
        if (event == ok_label) or (
            (event == "-user-") and (values["event_type"] == "return")
        ):
            result = values["-user-"]
            if only_number:
                try:
                    result = float(result)
                except ValueError as _:
                    popup(le.get_text("Please enter a number."))
                    continue
            break
        if event in [cancel_label, eg.WINDOW_CLOSED]:
            # let result = cancel_value
            break
    win.close()
    return result


def popup_get_text(
    message: str,
    title: Optional[str] = None,
    default: Optional[str] = None,
    default_text: Optional[str] = None,  # same as default for compatibility
    font: Optional[FontType] = None,
    size: Union[tuple[int, int], None] = None,
    window_icon: Optional[str] = None,  # window icon, specify filename
) -> Union[str, None]:
    """Display a message in a popup window with a text entry. Return the text entered."""
    # return simpledialog.askstring(title, message, initialvalue=default)
    if default_text is not None:
        default = default_text
    default_str = ""
    if default is not None:
        default_str = default
    result: Union[str, float, None] = popup_input(
        message,
        title,
        default_str,
        font=font,
        size=size,
        window_icon=window_icon,
    )
    if result is None:
        return None
    return str(result)


def popup_error(
    message: str,
    title: Optional[str] = None,
    size: Union[tuple[int, int], None] = None,
    icon: str = "error",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
    use_tk_dialog: bool = False,
) -> None:
    """Display a message in a popup window with an error icon."""
    if title is None:
        title = le.get_text("Error")
    error_label = le.get_text("Error")
    if use_tk_dialog:
        messagebox.showerror(title, message)
        return
    # show error message
    popup_buttons(
        message,
        title,
        buttons=[error_label],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )


def popup_warning(
    message: str,
    title: Optional[str] = None,
    size: Union[tuple[int, int], None] = None,
    icon: str = "warning",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
    use_tk_dialog: bool = False,
) -> None:
    """Display a message in a popup window with an warning icon."""
    if title is None:
        title = le.get_text("Warning")
    if use_tk_dialog:
        messagebox.showwarning(title, message)
        return
    popup_buttons(
        message,
        title,
        buttons=[le.get_text("OK")],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )


def popup_info(
    message: str,
    title: Optional[str] = None,
    size: Union[tuple[int, int], None] = None,
    icon: str = "information",
    icon_size: tuple[int, int] = (48, 48),
    window_icon: Optional[str] = None,  # window icon, specify filename
    can_copy_message: bool = True,
    use_tk_dialog: bool = False,
) -> None:
    """Display a message in a popup window with an warning icon."""
    if title is None:
        title = le.get_text("Information")
    if use_tk_dialog:
        # use native info dialog when requested
        messagebox.showinfo(title, message)
        return
    popup_buttons(
        message,
        title,
        buttons=[le.get_text("OK")],
        size=size,
        icon=icon,
        icon_size=icon_size,
        window_icon=window_icon,
        can_copy_message=can_copy_message,
    )


def popup_get_file(
    message: str = "",
    title: Optional[str] = None,
    initial_folder: Optional[str] = None,
    save_as: bool = False,  # show `save as` dialog
    multiple_files: bool = False,  # can select multiple files
    file_types: Optional[FileTypeList] = None,
    default_extension: Optional[str] = None,
    # pylint: disable=unused-argument
    no_window: Optional[bool] = None,  # for compatibility
    **kw,
) -> Union[str, tuple[str], None]:
    """Popup a file selection dialog. Return the file selected."""
    if title is None:
        title = message
    if initial_folder is None:
        initial_folder = os.getcwd()
    if file_types is None:
        file_types = [("All Files", "*.*")]
    # check no_window
    if no_window is None:
        no_window = True
    if no_window is False:
        # --- no_window is False ---
        from . import widgets as eg

        # show base dialog
        layout_no_window: list[list[eg.Element]] = [
            [eg.Text(message)],
            [eg.Input(key="-file-", expand_x=True), eg.FileBrowse()],
            [eg.HSeparator()],
            [eg.Push(), eg.Button("OK"), eg.Button("Cancel"), eg.Push()],
        ]
        with eg.Window(title=title, layout=layout_no_window, modal=True) as win:
            while win.is_alive():
                event, values = win.read()
                if event in [eg.WINDOW_CLOSED, "Cancel"]:
                    return None
                if event == "OK":
                    return values["-file-"]
        # --- end no_window is False ---
    # check file types
    new_types = []
    for ft in file_types:
        if len(ft) != 2:
            ft = (ft, ft)
        # fix file types for mac (#52)
        # like ("Image Files", *.jpg;*.jpeg;*.jpe;*.heic")
        if is_mac():
            if ";" in ft[1]:
                desc = ft[0]
                exts = ft[1].split(";")
                for ext in exts:
                    new_types.append((desc, ext))
                continue
        new_types.append(ft)
    file_type_list = tuple(new_types)
    if save_as:
        result = filedialog.asksaveasfilename(
            title=title,
            initialdir=initial_folder,
            filetypes=file_type_list,
            defaultextension=default_extension,
        )
        if result and default_extension:
            if result.endswith(default_extension) is False:
                result += default_extension
    else:
        result = filedialog.askopenfilename(
            title=title,
            initialdir=initial_folder,
            filetypes=file_type_list,
            multiple=multiple_files,  # type: ignore
            **kw,
        )
    return result


def popup_get_folder(
    message: str = "",
    title: Optional[str] = None,
    default_path: Optional[str] = None,
    # pylint: disable=unused-argument
    no_window: Optional[bool] = None,  # for compatibility
    **kw,
) -> Union[str, None]:
    """Popup a folder selection dialog. Return the folder selected."""
    if title is None:
        title = message
    if default_path is None:
        default_path = os.getcwd()
    return filedialog.askdirectory(title=title, initialdir=default_path, **kw)


def popup_memo(
    message: str,  # Text to enter in a multi-line text box
    title: Optional[str] = None,  # Window title
    size: tuple[int, int] = (60, 8),  # Size of the text box
    readonly: bool = False,  # Read-only mode
    header: str = "",  # Label displayed above the text box
    resizable: bool = True,  # resizable
    window_icon: Optional[str] = None,  # window icon, specify filename
    ok_label: Optional[str] = None,
    cancel_label: Optional[str] = None,
    cancel_value: Optional[str] = None,
    font: Union[FontType, None] = None,
) -> Union[str, None]:
    """Display a multiline message in a popup window. Return the text entered. if canceled, return cancel_value(default is None)."""
    return popup_scrolled(
        message=message,
        title=title,
        size=size,
        readonly=readonly,
        header=header,
        resizable=resizable,
        ok_label=ok_label,
        cancel_label=cancel_label,
        cancel_value=cancel_value,
        font=font,
        window_icon=window_icon,
    )


def popup_scrolled(
    message: str,  # Text to enter in a multi-line text box
    title: Optional[str] = None,  # Window title
    size: tuple[int, int] = (40, 5),  # Size of the text box
    readonly: bool = False,  # Read-only mode
    header: str = "",  # Label displayed above the text box
    resizable: bool = True,  # resizable
    window_icon: Optional[str] = None,  # window icon, specify filename
    ok_label: Optional[str] = None,
    cancel_label: Optional[str] = None,
    cancel_value: Optional[str] = None,
    font: Union[FontType, None] = None,
) -> Union[str, None]:
    """
    Display a multiline message in a popup window. Return the text entered. if canceled, return cancel_value(default is None).

    #### Example:
    ```py
    import TkEasyGUI as eg
    text = eg.popup_scrolled("This is a long text.", "Information")
    eg.print(text)
    ```
    """
    from . import widgets as eg

    result = cancel_value
    if cancel_label is None:
        cancel_label = le.get_text("Cancel")
    if ok_label is None:
        ok_label = le.get_text("OK")
    if title is None:
        title = le.get_text("Information")
    layout = [
        [
            eg.Multiline(
                message,
                key="-text-",
                size=size,
                readonly=readonly,
                font=font,
                expand_x=True,
                expand_y=True,
            )
        ],
        [
            eg.Push(),
            eg.Button(
                ok_label, width=POPUP_OK_BUTTON_WIDTH, use_ttk_buttons=POPUP_TTK_BUTTONS
            ),
            eg.Button(
                cancel_label,
                width=POPUP_CANCEL_BUTTON_WIDTH,
                use_ttk_buttons=POPUP_TTK_BUTTONS,
            ),
            eg.Push(),
        ],
    ]
    if header != "":
        layout.insert(0, [eg.Text(header)])
    win = eg.Window(
        title,
        layout=cast(eg.LayoutType, layout),
        modal=True,
        resizable=resizable,
        icon=window_icon,
    )
    result = None
    while win.is_alive():
        event, _ = win.read()
        if event == ok_label:
            result = win["-text-"].get()
            break
        if event == cancel_label:
            break
    win.close()
    return result


def popup_get_date(
    message: str = "",
    title: Optional[str] = None,
    current_date: Union[datetime, None] = None,
    font: Union[tuple[str, int], None] = None,
    ok_label: Optional[str] = None,
    cancel_label: Optional[str] = None,
    date_format: Optional[str] = None,
    close_when_date_chosen: bool = False,
    sunday_first: bool = False,  # Sunday is the first day of the week
    window_icon: Optional[str] = None,  # window icon, specify filename
) -> Union[datetime, None]:
    """Display a calendar in a popup window. Return the datetime entered or None."""
    from . import widgets as eg

    if current_date is None:
        current_date = datetime.now()
    if date_format is None:
        date_format = le.get_text("__date_format__", "%Y-%m-%d")
    date_format_str: str = date_format
    if title is None:
        title = le.get_text("Select date")
    # week names
    week_names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
    week_colors = ["black", "black", "black", "black", "black", "blue", "red"]
    if sunday_first:
        week_names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
        week_colors = ["red", "black", "black", "black", "black", "black", "blue"]
    days_size = [4, 1]
    head_size = [4, 1]
    # set
    result = None
    layout: list[list[eg.Element]] = []
    # header
    if message != "":
        layout.append([eg.Text(message)])
        layout.append([eg.HSeparator()])
    # select month
    month_button: list[eg.Element] = [
        eg.Frame(
            "",
            layout=[
                [
                    eg.Button("ᐊ", key="-prev2-", padx=0, size=(1, 1)),
                    eg.Button("◀", key="-prev-", padx=0, size=(1, 1)),
                    eg.Button(
                        current_date.strftime(date_format_str),
                        key="-ymd-",
                        size=(10, 1),
                    ),
                    eg.Button("▶", key="-next-", padx=0, size=(1, 1)),
                    eg.Button("ᐅ", key="-next2-", padx=0, size=(1, 1)),
                ]
            ],
            expand_x=True,
            text_align="center",
            relief="flat",
            pady=[2, 4],
        )
    ]
    layout.append(month_button)
    week_labels: list[eg.Element] = [
        eg.Text(
            name,
            size=head_size,
            text_color=week_colors[i],
            text_align="center",
            # background_color="white",
            padx=2,
            pady=1,
            color=week_colors[i],
        )
        for i, name in enumerate(week_names)
    ]
    layout.append(week_labels)

    # get top left day
    def get_top_date(current: datetime) -> datetime:
        # Return day of the week, where Monday == 0 ... Sunday == 6.
        first_day: datetime = datetime(year=current.year, month=current.month, day=1)
        week_i: int = first_day.weekday()
        top_date = first_day
        if sunday_first:
            # Sunday first
            if week_i != 6:
                top_date = first_day - timedelta(days=week_i + 1)
        else:
            # Monday first
            if week_i >= 1:
                top_date = first_day - timedelta(days=week_i)
        return top_date

    # create calendar days buttons
    cols: list[eg.Element] = []
    cur = get_top_date(current_date)
    for i in range(42):
        cols.append(
            eg.Text(
                f"{cur.day}",
                size=days_size,
                key=f"-b{i}-",
                text_color=week_colors[i % 7],
                background_color="white",
                text_align="center",
                enable_events=True,
                disabled=(current_date.month != cur.month),
                padx=2,
                pady=1,
                metadata={"date": datetime.fromtimestamp(cur.timestamp())},
            )
        )
        if i % 7 == 6:
            layout.append(cols)
            cols = []
        cur = cur + timedelta(days=1)
    layout.append([eg.HSeparator()])
    # ok, cancel buttons
    if ok_label is None:
        ok_label = le.get_text("OK")
    if cancel_label is None:
        cancel_label = le.get_text("Cancel")
    layout.append(
        [
            eg.Push(),
            eg.Button(
                ok_label, width=POPUP_OK_BUTTON_WIDTH, use_ttk_buttons=POPUP_TTK_BUTTONS
            ),
            eg.Button(
                cancel_label,
                width=POPUP_CANCEL_BUTTON_WIDTH,
                use_ttk_buttons=POPUP_TTK_BUTTONS,
            ),
            eg.Push(),
        ]
    )

    # update label
    def update_date(top: datetime, current_date: datetime):
        cur = datetime(year=top.year, month=top.month, day=top.day)
        for i in range(42):
            btn: eg.Text = window[f"-b{i}-"]
            fg = week_colors[i % 7]
            bg = "white"
            # selected
            selected = current_date.month == cur.month and current_date.day == cur.day
            if selected:
                fg = "red"
                bg = "#aaeeff"
                if i % 7 == 6:
                    fg = "black"
            # update
            btn.set_text(str(cur.day))
            btn.set_disabled(current_date.month != cur.month)
            btn.update(text_color=fg, background_color=bg)
            if btn.metadata is not None:
                btn.metadata["date"] = datetime.fromtimestamp(cur.timestamp())
            # next day
            cur = cur + timedelta(days=1)
        window["-ymd-"].update(current_date.strftime(date_format_str))

    # update result
    def update_result(current_date):
        top = get_top_date(
            datetime(year=current_date.year, month=current_date.month, day=1)
        )
        update_date(top, current_date)

    result = None
    # calendar window
    with eg.Window(
        title, layout, font=font, modal=True, row_padding=0, icon=window_icon
    ) as window:
        for event, _ in window.event_iter():
            if event == ok_label:
                result = current_date
                break
            if event == cancel_label:
                result = None
                break
            if event in ["-today-", "-ymd-"]:
                current_date = datetime.now()
                update_date(get_top_date(current_date), current_date)
                continue
            if event == "-prev-":
                y, m = current_date.year, current_date.month - 1
                if m == 0:
                    y -= 1
                    m = 12
                current_date = datetime(year=y, month=m, day=1)
                update_date(get_top_date(current_date), current_date)
                continue
            if event == "-next-":
                y, m = current_date.year, current_date.month + 1
                if m == 13:
                    y += 1
                    m = 1
                current_date = datetime(year=y, month=m, day=1)
                update_date(get_top_date(current_date), current_date)
                continue
            if event == "-prev2-":
                y, m = current_date.year - 1, current_date.month
                current_date = datetime(year=y, month=m, day=1)
                update_date(get_top_date(current_date), current_date)
                continue
            if event == "-next2-":
                y, m = current_date.year + 1, current_date.month
                current_date = datetime(year=y, month=m, day=1)
                update_date(get_top_date(current_date), current_date)
                continue
            if event.startswith("-b"):
                elm: eg.Text = window[event]
                if elm is None or elm.metadata is None:
                    continue
                sel_date: datetime = elm.metadata["date"]
                if sel_date.month == current_date.month:
                    current_date = sel_date
                    update_result(current_date)
                    if close_when_date_chosen:
                        result = current_date
                        break
                else:
                    current_date = sel_date
                    update_date(get_top_date(current_date), current_date)
                continue
    return result


def popup_get_form(
    form_items: list[
        PopupGetFormItemType
    ],  # list of form items(label[,selection or default][,type])
    title: str = "Form",  # window title
    size: Union[tuple[int, int], None] = None,
    window_icon: Optional[str] = None,  # window icon, specify filename
) -> Union[dict[str, Any], None]:
    """
    Displays a form that allows multiple items to be entered.

    By specifying the labels and input types for each item, the form is automatically generated and displayed in a dialog.
    When the user enters the items and clicks [OK], it returns `{label: value}`. If the user clicks [Cancel], it returns `None`.

    ### Arguments:
    - `form_items` (list): A list of form items. Each item can be:
      - A string (label only, default type is "text").
      - A tuple of `(label, default_value)` (default type is "text").
      - A tuple of `(label, default_value, type)` where `type` can be:
        - `"text"`: Single-line text input.
        - `"number"`: Numeric input.
        - `"password"`: Password input (masked).
        - `"combo"`: Dropdown menu.
        - `"list"`: List selection.
        - `"date"`: Date picker.
        - `"file"`: File selection.
        - `"files"`: Multiple file selection.
        - `"folder"`: Folder selection.
        - `"color"`: Color picker.

    - `title` (str): The title of the form window. Default is "Form".
    - `size` (tuple[int, int] | None): The size of the form window. Default is `None`.

    ### Returns:
    - `dict[str, Any] | None`: A dictionary with `{label: value}` pairs if the user clicks [OK]. Returns `None` if the user clicks [Cancel].

    ### Examples:
    #### Simple Example:
    ```python
    import TkEasyGUI as eg
    form = eg.popup_get_form(["Name", "Hobbies"])
    if form:
        name = form["Name"]
        hobbies = form["Hobbies"]
        eg.print(f"name={name}, hobbies={hobbies}")
    ```
    """
    from . import widgets as eg

    # make form layout
    item_labels = []
    item_converters: list[Union[Callable, None]] = []
    layout: list[list[eg.Element]] = []
    for i, it in enumerate(form_items):
        it_key = f"-formitem{i}"
        default_value: Union[str, tuple[str, Any], tuple[str, Any, str]] = ""
        if isinstance(it, (list, tuple)):
            label = it[0]
            default_value = it[1] if len(it) >= 2 else ""
            itype = it[2] if len(it) >= 3 else "text"
            if isinstance(default_value, (list, tuple)):
                itype = "list"
        else:
            label = it
            itype = "text"
        if itype == "":
            itype = "text"
        # for making result
        item_labels.append(label)
        item_converters.append(None)
        # make line
        line: list[eg.Element] = [
            eg.Text(label, key=f"{it_key}-label", size=(15, 1), text_align="right")
        ]
        # check type
        itype = itype.lower()
        sels: list = []
        if itype in ["text", "number", "password"]:
            text = ""
            if isinstance(default_value, (tuple, list)):
                default_value = ",".join(default_value)
            else:
                text = str(default_value)
            line.append(
                eg.Input(
                    text=text,
                    key=it_key,
                    size=(20, 1),
                    password_char="*" if itype == "password" else None,
                )
            )
            if itype == "number":
                item_converters[i] = float
        elif itype == "combo":
            sels = []
            if isinstance(default_value, (tuple, list)):
                sels = list(default_value)
            else:
                sels = default_value.split(",")
            val = sels[0] if len(sels) > 0 else ""
            line.append(eg.Combo(sels, default_value=val, key=it_key, size=(19, 1)))
        elif itype == "list":
            sels = []
            if isinstance(default_value, (tuple, list)):
                sels = list(default_value)
            else:
                sels = default_value.split(",")
            val = sels[0] if len(sels) > 0 else ""
            line.append(eg.Input(val, key=it_key, size=(15, 1)))
            line.append(eg.ListBrowse(sels))
        elif itype == "date":
            line.append(eg.Input(str(default_value), key=it_key, size=(15, 1)))
            line.append(eg.CalendarBrowse())
        elif itype == "file":
            line.append(eg.Input(str(default_value), key=it_key, size=(15, 1)))
            line.append(eg.FileBrowse())
        elif itype == "files":
            line.append(eg.Input(str(default_value), key=it_key, size=(15, 1)))
            line.append(eg.FileBrowse(multiple_files=True))
        elif itype == "folder":
            line.append(eg.Input(str(default_value), key=it_key, size=(15, 1)))
            line.append(eg.FolderBrowse())
        elif itype == "color":
            line.append(eg.Input(str(default_value), key=it_key, size=(15, 1)))
            line.append(eg.ColorBrowse())
        # append line
        layout.append(line)
    layout.append([eg.HSeparator()])
    cancel_label = le.get_text("Cancel")
    layout.append(
        [
            eg.Push(),
            eg.Button(
                "OK", width=POPUP_OK_BUTTON_WIDTH, use_ttk_buttons=POPUP_TTK_BUTTONS
            ),
            eg.Button(
                cancel_label,
                key="Cancel",
                width=POPUP_CANCEL_BUTTON_WIDTH,
                use_ttk_buttons=POPUP_TTK_BUTTONS,
            ),
            eg.Push(),
        ]
    )
    with eg.Window(title, layout=layout, size=size, icon=window_icon) as win:
        result: Union[dict[str, Any], None] = None
        for event, values in win.event_iter():
            if event == "Cancel":
                result = None
                break
            if event == "OK":
                result = {}
                for i, label in enumerate(item_labels):
                    it_key = f"-formitem{i}"
                    val = values[it_key] if it_key in values else ""
                    fn = item_converters[i]
                    try:
                        if fn is not None:
                            val = fn(val)
                    except ValueError as _:
                        popup(le.get_text("Please enter a number."))
                        win.focus_element(it_key)
                        result = None
                        break
                    result[label] = val
                # form error?
                if result is None:
                    continue
                break
    return result


# ------------------------------------------------------------------------------
# for notify
# ------------------------------------------------------------------------------
def popup_notify(message: str, title: str = "") -> None:
    """Popup a information"""
    result = False
    if is_mac():
        result = send_notification_mac(message, title)
    elif is_win():
        result = send_notification_win(message, title)
    if not result:
        popup_buttons(message, title, buttons=["OK"], auto_close_duration=3)


def send_notification_mac(message: str, title: str = "") -> bool:
    """Send Notification on mac"""
    # check osascript
    oascript_path = "/usr/bin/osascript"
    if title == "":
        title = "Notification"
    if not os.path.exists(oascript_path):
        popup_auto_close(message, title)
        return False
    script = r"""
on run argv
    set msg to item 1 of argv
    set ttl to item 2 of argv
    display notification msg with title ttl
end run
""".strip()
    subprocess.run([oascript_path, "-e", script, "--", message, title], check=False)
    return True


def send_notification_win(message: str, title: str = "") -> bool:
    """Send Notification on Windows using PowerShell"""
    # get powershell path
    powershell_path = os.path.join(
        os.environ["SystemRoot"],
        "System32",
        "WindowsPowerShell",
        "v1.0",
        "powershell.exe",
    )
    if not os.path.exists(powershell_path):
        return False
    if title == "":
        title = "Notification"

    # Base64 encode the title and message
    def to_base64(s: str) -> str:
        return base64.b64encode(s.encode("utf-8")).decode("ascii")

    encoded_title = to_base64(title)
    encoded_message = to_base64(message)

    # PowerShell Script using Base64
    script_content = r"""
param($encodedTitle, $encodedMessage, $appPath)
$decodedTitle = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encodedTitle))
$decodedMessage = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($encodedMessage))
$bodyText = "$decodedTitle`n$decodedMessage"

$ToastText01 = [Windows.UI.Notifications.ToastTemplateType, Windows.UI.Notifications, ContentType = WindowsRuntime]::ToastText01
$TemplateContent = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]::GetTemplateContent($ToastText01)
$TemplateContent.SelectSingleNode('//text[@id="1"]').InnerText = $bodyText
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appPath).Show($TemplateContent)
"""

    # generate temp script file
    with tempfile.NamedTemporaryFile(
        "w", suffix=".ps1", delete=False, encoding="utf-8"
    ) as script_file:
        script_file.write(script_content)
        script_path = script_file.name
    try:
        subprocess.run(
            [
                powershell_path,
                "-ExecutionPolicy",
                "Bypass",
                "-File",
                script_path,
                encoded_title,
                encoded_message,
                sys.executable,
            ],
            check=True,
        )
        return True
    finally:
        # 実行後に一時ファイルを削除
        os.remove(script_path)


# ------------------------------------------------------------------------------
# TkEasyGUI original dialogs
# ------------------------------------------------------------------------------
def popup_color(
    title: str = "",
    default_color: Optional[str] = None,
    format: ColorFormatType = "html",  # pylint: disable=redefined-builtin
) -> Union[str, tuple[int, int, int], None]:
    """
    Popup a color selection dialog. Return the color selected.

    format: "html", "rgb", "tuple"
    """
    col = colorchooser.askcolor(title=title, color=default_color)
    if col[1] is None:
        return default_color
    html = col[1].upper()
    if format == "html":
        return html
    if format == "rgb":
        return html[1:]
    if format == "tuple":
        return col[0]
    return html


def popup_listbox(
    values: list[str],  # list of items
    message: str = "",
    title: str = "",
    size: tuple[int, int] = (20, 7),
    font: Union[FontType, None] = None,
    default_value: Optional[str] = None,  # default value
    default_index: Optional[int] = None,  # default index
    multiple: bool = False,  # multiple selection
    resizable: bool = True,  # resizable
    window_icon: Optional[str] = None,  # window icon, specify filename
) -> Union[str, None]:
    """Display Listbox in a popup window"""
    from . import widgets as eg

    select_mode: eg.ListboxSelectMode = (
        eg.LISTBOX_SELECT_MODE_BROWSE
        if multiple is False
        else eg.LISTBOX_SELECT_MODE_MULTIPLE
    )
    # create window
    layout: list[list[eg.Element]] = []
    if message != "":
        layout.append([eg.Text(message)])
    layout.append(
        [
            eg.Listbox(
                values=values,
                key="-list-",
                default_value=default_value,
                default_index=default_index,
                size=size,
                font=font,
                select_mode=select_mode,
                expand_x=True,
                expand_y=True,
            )
        ]
    )
    layout.append(
        [
            eg.Push(),
            eg.Button(
                "OK", width=POPUP_OK_BUTTON_WIDTH, use_ttk_buttons=POPUP_TTK_BUTTONS
            ),
            eg.Button(
                "Cancel",
                width=POPUP_CANCEL_BUTTON_WIDTH,
                use_ttk_buttons=POPUP_TTK_BUTTONS,
            ),
            eg.Push(),
        ]
    )
    with eg.Window(
        title, layout=layout, modal=True, resizable=resizable, icon=window_icon
    ) as win:
        # event loop
        result = None
        for event, _values in win.event_iter():
            if event == "Cancel":
                result = None
                break
            if event == "OK":
                selected = win["-list-"].get()
                if multiple:
                    result = selected
                else:
                    if len(selected) == 1:
                        result = selected[0]
                break
    return result


def popup_image(
    message: str,
    title: Optional[str] = None,
    image_path: Optional[str] = None,
    image_data: Union[bytes, None] = None,
    size: tuple[int, int] = (400, 300),
    ok_label: Optional[str] = None,
    ok_value: str = "OK",
    cancel_label: Optional[str] = None,
    cancel_value: Optional[str] = None,
    font: Union[FontType, None] = None,
    window_icon: Optional[str] = None,  # window icon, specify filename
    auto_close_duration: int = -1,  # auto close duration in seconds
    timeout_key: str = "-TIMEOUT-",  # timeout key if auto_close_duration > 0
) -> Union[str, None]:
    """Display an image in a popup window. Return the pushed Button("OK" or None)."""
    from . import widgets as eg

    if title is None:
        title = message
    if ok_label is None:
        ok_label = le.get_text("OK")
    if cancel_label is None:
        cancel_label = le.get_text("Cancel")
    layout: list[list[eg.Element]] = [
        [eg.Text(message)],
        [eg.Image(image_path, image_data, size=size)],
        [
            eg.Push(),
            eg.Button(
                ok_label, width=POPUP_OK_BUTTON_WIDTH, use_ttk_buttons=POPUP_TTK_BUTTONS
            ),
            eg.Button(
                cancel_label,
                width=POPUP_CANCEL_BUTTON_WIDTH,
                use_ttk_buttons=POPUP_TTK_BUTTONS,
            ),
            eg.Push(),
        ],
    ]
    win = eg.Window(title, layout=layout, modal=True, font=font, icon=window_icon)
    result = cancel_value
    timer_id = eg.time_checker_start()
    auto_screenshot = False
    while win.is_alive():
        event, _ = win.read(timeout=100)
        if event == ok_label:
            result = ok_value
            break
        if event == cancel_label:
            break
        if event == eg.WINDOW_TIMEOUT:
            passing_time = eg.time_checker_end(timer_id)
            # screenshot?
            if POPUP_AUTO_SCREENSHOT and auto_screenshot is False:
                if passing_time > POPUP_AUTO_SCREENSHOT_DURATION:
                    auto_screenshot = True
                    screen_image = win.screenshot()
                    screen_image.save(POPUP_AUTO_SCREENSHOT_FILENAME)
            # auto close?
            if auto_close_duration > 0:
                if eg.time_checker_end(timer_id) >= auto_close_duration * 1000:
                    result = timeout_key
                    break
    win.close()
    return result


# ------------------------------------------------------------------------------
# TKinter alias
# ------------------------------------------------------------------------------
def ask_yes_no(message: str, title: str = "Question") -> bool:
    """Display a message in a popup window with Yes and No buttons. Return True or False. (use Tkinter)"""
    return messagebox.askyesno(title, message)


def ask_ok_cancel(message: str, title: str = "Question") -> bool:
    """Display a message in a popup window with OK and Cancel buttons. Return True or False. (use Tkinter)"""
    return messagebox.askokcancel(title, message)


def ask_retry_cancel(message: str, title: str = "Question") -> bool:
    """Display a message in a popup window with Retry and Cancel buttons. Return True or False. (use Tkinter)"""
    return messagebox.askretrycancel(title, message)


def show_message(message: str, title: Optional[str] = None) -> None:
    """Show message in a popup window"""
    title = title if title is not None else le.get_text("Information")
    messagebox.showinfo(title, message)


def show_info(message: str, title: Optional[str] = None) -> None:
    """Show message in a popup window"""
    title = title if title is not None else le.get_text("Information")
    messagebox.showinfo(title, message)


def msgbox(message: str, title: Optional[str] = None) -> None:  # message
    """Show message in a popup window like VB"""
    title = title if title is not None else le.get_text("Information")
    messagebox.showinfo(title, message)


# ------------------------------------------------------------------------------
# TkEasyGUI original dialogs
# ------------------------------------------------------------------------------
# pylint: disable=redefined-builtin
def input(
    message: str,
    title: Optional[str] = None,
    default: str = "",
    only_number: bool = False,
    window_icon: Optional[str] = None,  # window icon, specify filename
    validation: Optional[
        Union[str, Pattern[str]]
    ] = None,  # validation regular expression
    validation_message: Optional[str] = None,
) -> Union[str, float, None]:
    """Display a message in a popup window with a text entry. Return the text entered."""
    return popup_input(
        message,
        title,
        default,
        only_number=only_number,
        window_icon=window_icon,
        validation=validation,
        validation_message=validation_message,
    )


def print(*args, **kw) -> None:
    """Print message to popup window.(call default print function if no_window is True)"""
    if "no_window" in kw and kw["no_window"]:
        _print(*args)
        return
    lines = " ".join([str(a) for a in args])
    popup(lines)


def input_number(
    message: str,
    title: Optional[str] = None,
    default: str = "",
    window_icon: Optional[str] = None,  # window icon, specify filename
) -> Union[float, None]:
    """Display a message in a popup window with a number entry. Return the text entered."""
    result = popup_input(
        message, title, default, only_number=True, window_icon=window_icon
    )
    if isinstance(result, float):
        return result
    return None


def confirm(question: str, title: Optional[str] = None) -> bool:
    """Display a message in a popup window with Yes and No buttons. Return True or False."""
    return popup_yes_no(question, title, yes_value="Yes") == "Yes"


# ------------------------------------------------------------------------------
# To prevent the display of an empty window
_ = get_root_window()
