import tkinter as tk


class ToolTip:
    def __init__(self, widget, text_getter):
        self.widget = widget
        self.text_getter = text_getter if callable(text_getter) else lambda: str(text_getter)
        self.tip_window = None
        widget.bind("<Enter>", self.show)
        widget.bind("<Leave>", self.hide)

    def show(self, event=None):
        text = self.text_getter()
        if not text:
            return
        self.hide()
        self.tip_window = tw = tk.Toplevel(self.widget)
        tw.wm_overrideredirect(True)
        label = tk.Label(
            tw,
            text=text,
            background="#0F172A",
            foreground="white",
            relief=tk.FLAT,
            font=("Microsoft YaHei UI", 9),
            padx=10,
            pady=6,
        )
        label.pack()
        tw.update_idletasks()
        screen_w = self.widget.winfo_screenwidth()
        screen_h = self.widget.winfo_screenheight()
        x = self.widget.winfo_rootx() + self.widget.winfo_width() + 12
        y = self.widget.winfo_rooty() + 2
        if x + tw.winfo_width() > screen_w - 8:
            x = self.widget.winfo_rootx() - tw.winfo_width() - 12
        if y + tw.winfo_height() > screen_h - 48:
            y = self.widget.winfo_rooty() - tw.winfo_height() - 10
        if y < 8:
            y = 8
        tw.wm_geometry(f"+{x}+{y}")

    def hide(self, event=None):
        if self.tip_window:
            self.tip_window.destroy()
            self.tip_window = None


def bind_hover(widget, normal_bg, normal_fg, hover_bg, hover_fg):
    widget.bind("<Enter>", lambda e: widget.configure(bg=hover_bg, fg=hover_fg))
    widget.bind("<Leave>", lambda e: widget.configure(bg=normal_bg, fg=normal_fg))


def create_card(parent, colors, **pack_kwargs):
    frame = tk.Frame(parent, bg=colors["card"], highlightthickness=1, highlightbackground=colors["border"])
    if pack_kwargs:
        frame.pack(**pack_kwargs)
    return frame


def create_button(parent, text, command, colors, filled=False, width=None):
    normal_bg = colors["primary"] if filled else colors["card"]
    normal_fg = "white" if filled else colors["text"]
    hover_bg = colors["primary_dark"] if filled else colors["primary_light"]
    hover_fg = "white" if filled else colors["primary"]
    btn = tk.Button(
        parent,
        text=text,
        command=command,
        bg=normal_bg,
        fg=normal_fg,
        activebackground=hover_bg,
        activeforeground=hover_fg,
        relief=tk.FLAT,
        bd=0,
        font=("Microsoft YaHei UI", 10, "bold"),
        padx=14,
        pady=8,
        width=width,
        cursor="hand2",
    )
    bind_hover(btn, normal_bg, normal_fg, hover_bg, hover_fg)
    return btn


def center_window(window, parent=None, width=760, height=560, min_width=None, min_height=None):
    """Place a dialog in the visual center of its parent or current screen.

    v2026.06.20.22: center twice after Tk/Windows has settled the real
    Toplevel size.  Some high-DPI systems briefly report stale geometry during
    construction, which made profile and custom dialogs open on the left edge.
    This helper keeps dialogs centered over the app, clamps them into the
    current visible screen only when the parent is also on that screen, and
    repeats the placement shortly after creation without rebuilding the dialog.
    """
    try:
        requested_width = int(width)
        requested_height = int(height)
    except Exception:
        requested_width, requested_height = 760, 560

    def _safe_int(value, fallback=0):
        try:
            return int(value)
        except Exception:
            return fallback

    def _parent_geometry():
        nonlocal parent
        if parent is None:
            parent = getattr(window, "master", None)
        if parent is None:
            return None
        try:
            parent.update_idletasks()
        except Exception:
            pass
        try:
            if not int(parent.winfo_ismapped()):
                return None
            px = _safe_int(parent.winfo_rootx())
            py = _safe_int(parent.winfo_rooty())
            pw = max(_safe_int(parent.winfo_width(), 1), 1)
            ph = max(_safe_int(parent.winfo_height(), 1), 1)
            if pw <= 1 or ph <= 1:
                return None
            return px, py, pw, ph
        except Exception:
            return None

    def _place():
        try:
            window.update_idletasks()
        except Exception:
            pass
        screen_w = max(_safe_int(window.winfo_screenwidth(), 1280), 640)
        screen_h = max(_safe_int(window.winfo_screenheight(), 720), 480)
        margin = 18
        usable_w = max(320, screen_w - margin * 2)
        usable_h = max(240, screen_h - margin * 2 - 28)
        w = min(requested_width, usable_w)
        h = min(requested_height, usable_h)
        pg = _parent_geometry()
        if pg:
            px, py, pw, ph = pg
            x = px + max((pw - w) // 2, 0)
            y = py + max((ph - h) // 2, 0)
            # If the parent is on the primary screen, clamp inside it.  If the
            # parent is on another monitor, do not drag the dialog to x=0.
            if -4 <= px < screen_w and -4 <= py < screen_h:
                x = max(margin, min(x, screen_w - w - margin))
                y = max(margin, min(y, screen_h - h - margin - 28))
        else:
            x = (screen_w - w) // 2
            y = (screen_h - h) // 2
            x = max(margin, min(x, screen_w - w - margin))
            y = max(margin, min(y, screen_h - h - margin - 28))
        try:
            window.geometry(f"{w}x{h}+{int(x)}+{int(y)}")
            if min_width or min_height:
                window.minsize(min_width or w, min_height or h)
            try:
                window.lift(parent)
            except Exception:
                window.lift()
        except Exception:
            try:
                window.geometry(f"{w}x{h}")
            except Exception:
                pass

    _place()
    # Re-center after Tk knows the actual decorated window size.  This fixes
    # dialogs that initially appear at the left edge on high-DPI machines.
    try:
        window.after(60, _place)
        window.after(180, _place)
    except Exception:
        pass


def create_dialog_window(parent, title, colors, width=760, height=560, modal=True, resizable=True, min_width=None, min_height=None):
    win = tk.Toplevel(parent)
    try:
        win.withdraw()
        win.attributes("-alpha", 0.0)
        win.geometry("1x1-20000-20000")
    except Exception:
        pass
    win.title(title)
    win.configure(bg=colors["card"])
    try:
        win.transient(parent)
    except Exception:
        pass
    try:
        win.resizable(resizable, resizable)
    except Exception:
        pass
    center_window(win, parent, width, height, min_width=min_width, min_height=min_height)
    def _show_centered_dialog():
        try:
            win.update_idletasks()
        except Exception:
            pass
        try:
            center_window(win, parent, width, height, min_width=min_width, min_height=min_height)
            try:
                win.attributes("-alpha", 0.01)
            except Exception:
                pass
            win.deiconify()
            try:
                win.lift(parent)
            except Exception:
                win.lift()
            if modal:
                try:
                    win.grab_set()
                except Exception:
                    pass
            try:
                win.focus_force()
            except Exception:
                pass
            try:
                center_window(win, parent, width, height, min_width=min_width, min_height=min_height)
                win.after(34, lambda: win.attributes("-alpha", 1.0))
            except Exception:
                pass
        except Exception:
            try:
                win.deiconify()
            except Exception:
                pass
    try:
        win.after_idle(_show_centered_dialog)
        win.after(120, _show_centered_dialog)
    except Exception:
        _show_centered_dialog()
    return win


def prompt_text_dialog(
    parent,
    title,
    prompt,
    colors,
    initialvalue="",
    confirm_text="OK",
    cancel_text="Cancel",
    width=520,
    height=248,
    multiline=False,
):
    result = {"value": None}
    win = create_dialog_window(
        parent,
        title,
        colors,
        width=width,
        height=height,
        modal=True,
        resizable=multiline,
        min_width=480,
        min_height=220,
    )

    header = tk.Frame(win, bg=colors["card"])
    header.pack(fill=tk.X, padx=22, pady=(18, 8))
    tk.Label(header, text=title, font=("Microsoft YaHei UI", 16, "bold"), fg=colors["text"], bg=colors["card"]).pack(anchor="w")
    tk.Label(
        header,
        text=prompt,
        font=("Microsoft YaHei UI", 10),
        fg=colors["text_secondary"],
        bg=colors["card"],
        wraplength=max(width - 64, 320),
        justify=tk.LEFT,
    ).pack(anchor="w", pady=(6, 0))

    body = tk.Frame(win, bg=colors["card"])
    body.pack(fill=tk.BOTH, expand=True, padx=22, pady=(0, 12))

    if multiline:
        editor = tk.Text(
            body,
            wrap=tk.WORD,
            relief=tk.FLAT,
            bd=0,
            bg=colors["bg"],
            fg=colors["text"],
            font=("Microsoft YaHei UI", 10),
            insertbackground=colors["text"],
            height=7,
        )
        editor.pack(fill=tk.BOTH, expand=True, ipady=6)
        if initialvalue:
            editor.insert("1.0", str(initialvalue))
        field = editor
    else:
        box = tk.Frame(body, bg=colors["bg"], highlightthickness=1, highlightbackground=colors["border"])
        box.pack(fill=tk.X, expand=False)
        editor = tk.Entry(
            box,
            relief=tk.FLAT,
            bd=0,
            bg=colors["bg"],
            fg=colors["text"],
            font=("Microsoft YaHei UI", 11),
            insertbackground=colors["text"],
        )
        editor.pack(fill=tk.X, padx=12, pady=10, ipady=4)
        if initialvalue:
            editor.insert(0, str(initialvalue))
        field = editor

    footer = tk.Frame(win, bg=colors["card"])
    footer.pack(fill=tk.X, padx=22, pady=(0, 18))

    def do_cancel(event=None):
        result["value"] = None
        win.destroy()
        return "break"

    def do_confirm(event=None):
        if multiline:
            value = editor.get("1.0", tk.END).strip()
        else:
            value = editor.get().strip()
        result["value"] = value
        win.destroy()
        return "break"

    cancel_btn = create_button(footer, cancel_text, do_cancel, colors)
    cancel_btn.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
    confirm_btn = create_button(footer, confirm_text, do_confirm, colors, filled=True)
    confirm_btn.pack(side=tk.LEFT, fill=tk.X, expand=True)

    if multiline:
        editor.bind("<Escape>", do_cancel)
        win.bind("<Escape>", do_cancel)
    else:
        editor.bind("<Return>", do_confirm)
        editor.bind("<Escape>", do_cancel)
        win.bind("<Return>", do_confirm)
        win.bind("<Escape>", do_cancel)

    editor.focus_set()
    if initialvalue:
        try:
            if multiline:
                editor.tag_add("sel", "1.0", tk.END)
            else:
                editor.selection_range(0, tk.END)
        except Exception:
            pass
    win.wait_window()
    return result["value"]


def show_empty(parent, text, colors):
    tk.Label(parent, text=text, font=("Microsoft YaHei UI", 10), fg=colors["text_light"], bg=colors["card"]).pack(pady=24)
