import ctypes
import json
import platform
import tkinter as tk
from tkinter import colorchooser, filedialog
from tkinter.scrolledtext import ScrolledText
from typing import Any, Callable, Literal, TypeVar, Union

_TkVar = TypeVar('_TkVar', bound=Union[tk.StringVar, tk.IntVar, tk.DoubleVar, tk.BooleanVar])


class BForm(tk.Tk):
    _row_index = -1
    _init_handler_list: list[Callable[..., None]] = []
    _var_list: list[tk.Variable] = []  # 用于强制引用 var 否则没有引用回导致界面异常（可能是被回收了变量）
    _is_cancel = False  # 是否取消操作（点击右上角按钮）

    def __init__(
        self,
        *,
        title: str = '',
        resizable: bool = False,
        mini_window: bool = True,
    ):
        super().__init__()
        self.title(title)
        self.resizable(resizable, resizable)
        if mini_window:
            self.add_init_handler(self._set_mini_window)
        self.bind('<Map>', self._on_init)
        self.protocol('WM_DELETE_WINDOW', self.cancel)

    def _set_mini_window(self):
        if platform.system() != 'Windows':
            return
        GWL_STYLE = -16
        WS_MINIMIZEBOX = 0x00020000
        WS_MAXIMIZEBOX = 0x00010000
        SWP_NOSIZE = 0x0001
        SWP_NOMOVE = 0x0002
        SWP_NOZORDER = 0x0004
        SWP_FRAMECHANGED = 0x0020
        self.update_idletasks()
        hwnd = ctypes.windll.user32.GetParent(self.winfo_id())

        # 获取原窗口样式
        style = ctypes.windll.user32.GetWindowLongW(hwnd, GWL_STYLE)

        # 移除最大化、最小化按钮（保留关闭按钮）
        style &= ~WS_MAXIMIZEBOX
        style &= ~WS_MINIMIZEBOX

        # 设置新的窗口样式
        ctypes.windll.user32.SetWindowLongW(hwnd, GWL_STYLE, style)

        # 强制刷新窗口，让样式生效
        ctypes.windll.user32.SetWindowPos(hwnd, None, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED)

    def _on_init(self, evt: tk.Event):
        if evt.widget == self:
            for callback in self._init_handler_list:
                callback()
            self._init_handler_list.clear()

    def _init_var(self, var: _TkVar) -> _TkVar:
        self._var_list.append(var)
        return var

    def _init_focus(self, widget: tk.Widget):
        self.add_init_handler(lambda: widget.focus_set())

    def add_init_handler(self, handler: Callable[..., None]):
        self._init_handler_list.append(handler)

    def run(self) -> Any:
        self.center()
        self.mainloop()
        if self._is_cancel:
            return None
        else:
            return self.get_result()

    def get_result(self) -> Any:
        return None

    def cancel(self):
        self._is_cancel = True
        self.destroy()

    def center(self):
        self.withdraw()  # 先隐藏窗口，避免闪动
        self.update_idletasks()  # 确保获取正确的窗口尺寸
        width = self.winfo_width()  # 获取窗口宽度
        height = self.winfo_height()  # 获取窗口高度
        screen_width = self.winfo_screenwidth()  # 屏幕宽度
        screen_height = self.winfo_screenheight()  # 屏幕高度
        x = (screen_width - width) // 2  # 水平居中
        y = (screen_height - height) // 2  # 垂直居中
        self.geometry(f'+{x}+{y}')  # 设置窗口位置
        self.deiconify()  # 恢复显示窗口

    def add_row(self, desc: str, widget: tk.Widget, label_sticky: str = 'E', *, pady: int = 5):
        self._row_index += 1
        tk.Label(text=desc).grid(row=self._row_index, column=0, padx=10, pady=5, sticky=label_sticky)
        widget.grid(row=self._row_index, column=1, padx=10, pady=pady, sticky='W')

    def add_row_frame(self):
        self._row_index += 1
        frame = tk.Frame(self)
        frame.grid(row=self._row_index, column=0, columnspan=2, padx=10, pady=5)
        return frame

    def add_row_frame_with_desc(self, desc: str):
        self._row_index += 1
        tk.Label(text=desc).grid(row=self._row_index, column=0, padx=10, pady=5, sticky='e')
        frame = tk.Frame(self)
        frame.grid(row=self._row_index, column=1, padx=10, pady=5, sticky='w')
        return frame

    def add_label(self, desc: str, text: str):
        self.add_row(desc, tk.Label(text=text))

    def add_btn(self, label: str, command: Callable[..., None], *, width: int = 20, focus: bool = False):
        frame = self.add_row_frame()
        btn = tk.Button(frame, text=label, width=width, command=command)
        btn.pack(side='left', expand=True, padx=15)
        if focus:
            self._init_focus(btn)

    def add_entry(
        self,
        desc: str,
        var: tk.StringVar,
        *,
        width: int = 60,
        focus: bool = False,
        justify: Literal['left', 'right', 'center'] = tk.LEFT,
        password: bool = False,
        command: Callable[..., Any] | None = None,
    ):
        self._init_var(var)
        entry = tk.Entry(self, width=width, justify=justify, textvariable=var)
        entry.icursor(tk.END)
        self.add_row(desc, entry)
        if password:
            entry.config(show='*')
        if focus:
            self._init_focus(entry)
        if command:
            entry.bind('<Return>', lambda event: command())
        return entry

    def add_color_chooser(
        self,
        desc: str,
        var: tk.StringVar,
    ):
        self._init_var(var)
        frame = self.add_row_frame_with_desc(desc)

        # 色块展示（可点击）
        swatch_size = 20
        swatch = tk.Canvas(frame, width=swatch_size, height=swatch_size, bd=1, relief='solid', highlightthickness=0)
        swatch.pack(side='left')

        def _update_swatch(color: str | None):
            if not color:
                color = '#FFFFFF'
            try:
                swatch.itemconfig(_rect_id, fill=color, outline=color)
            except Exception:
                # 如果还没创建 rect 或颜色无效则忽略
                pass

        # 初始颜色
        initial_color = var.get() or '#FFFFFF'
        _rect_id = swatch.create_rectangle(0, 0, swatch_size, swatch_size, fill=initial_color, outline=initial_color)

        # 打开颜色选择器并更新变量与色块
        def choose_color(event: tk.Event | None = None):
            result = colorchooser.askcolor(title='选择颜色', initialcolor=var.get() or initial_color)
            if result and result[1]:
                color_hex = result[1]
                var.set(color_hex)
                _update_swatch(color_hex)

        # 点击色块打开选择器
        swatch.bind('<Button-1>', choose_color, add='+')

        # 允许点击时获得焦点（类似其它控件的行为）
        set_widget_click_focus(swatch)

        return swatch

    def add_scrolled_text(
        self,
        desc: str,
        var: tk.StringVar,
        *,
        width: int = 60,
        height: int = 3,
        focus: bool = False,
    ):
        self._init_var(var)
        scrolled_text = ScrolledText(self, width=width, height=height)
        scrolled_text.insert(tk.END, var.get())
        self.add_row(desc, scrolled_text)
        scrolled_text.bind('<KeyRelease>', lambda event: on_text_change(event))
        scrolled_text.bind('<Tab>', lambda event: on_tab(event))

        def on_text_change(event: tk.Event):
            new_value = scrolled_text.get('1.0', 'end-1c')
            if new_value != var.get():
                var.set(new_value)

        def on_tab(event: tk.Event):
            widget = event.widget.tk_focusNext()
            assert widget
            widget.focus_set()
            return 'break'

        if focus:
            self._init_focus(scrolled_text)
        return scrolled_text

    def add_radio_btn_list(
        self,
        desc: str,
        option_list: list[str],
        var: tk.StringVar,
        *,
        focus_index: int | None = None,
        on_changed: Callable[[str], None] | None = None,
    ):
        self._init_var(var)
        frame = tk.Frame()
        self.add_row(desc, frame)
        radio_btn_list: list[tk.Radiobutton] = []
        for version in option_list:
            radio_btn = tk.Radiobutton(frame, text=version, variable=var, value=version)
            radio_btn.pack(side='left', padx=(0, 15))
            set_widget_click_focus(radio_btn)
            radio_btn_list.append(radio_btn)
        if focus_index is not None:
            self._init_focus(radio_btn_list[focus_index])
        if on_changed:
            var.trace_add('write', lambda a, b, c: on_changed(var.get()))
            self.add_init_handler(lambda: on_changed(var.get()))  # 外面有侦听，初始化的状态如果缺少这个会有问题

        return radio_btn_list

    def add_check_box(
        self,
        desc: str,
        text: str,
        var: tk.BooleanVar,
        *,
        focus: bool = False,
    ):
        self._init_var(var)
        check_box = tk.Checkbutton(text=text, variable=var)
        self.add_row(desc, check_box)
        set_widget_click_focus(check_box)
        if focus:
            self._init_focus(check_box)
        return check_box

    def add_check_box_list(
        self,
        desc: str,
        data_list: list[tuple[str, tk.BooleanVar]],
    ):
        frame = tk.Frame(self)
        self.add_row(desc, frame)
        check_box_list: list[tk.Checkbutton] = []
        for label, var in data_list:
            self._init_var(var)
            checkbox = tk.Checkbutton(frame, text=label, variable=var)
            checkbox.pack(side='left', expand=True, padx=(0, 15))
            check_box_list.append(checkbox)
            set_widget_click_focus(checkbox)
        return check_box_list

    def add_choice_path(
        self,
        desc: str,
        var: tk.StringVar,
        *,
        width: int = 47,
        focus: bool = False,
        is_dir: bool = False,
        is_multi: bool = False,
    ):
        self._init_var(var)
        frame = self.add_row_frame_with_desc(desc)
        entry = tk.Entry(frame, width=width, textvariable=var)
        entry.icursor(tk.END)
        entry.pack(side='left')
        btn = tk.Button(frame, text=f'选择{"目录" if is_dir else "文件"} ...', width=10, command=lambda: on_btn())
        btn.pack(side='left', padx=(10, 0))
        if focus:
            self._init_focus(btn)

        def on_btn():
            if is_dir:
                var.set(filedialog.askdirectory())
            elif is_multi:
                result = filedialog.askopenfilenames()
                if result:
                    var.set(json.dumps(result))
            else:
                var.set(filedialog.askopenfilename())

    def add_scale(
        self,
        desc: str,
        var: tk.IntVar | tk.DoubleVar,
        *,
        length: int = 138,
        from_: int | float = 0,
        to: int | float = 100,
        resolution: float = 1.0,
    ):
        self._init_var(var)
        scale = tk.Scale(
            variable=var,
            from_=from_,
            to=to,
            orient=tk.HORIZONTAL,
            length=length,
            resolution=resolution,
        )
        self.add_row(desc, scale, label_sticky='SE', pady=0)


def set_widget_enabled(widget: tk.Widget, value: bool):
    widget['state'] = tk.NORMAL if value else tk.DISABLED


def set_widget_click_focus(widget: tk.Widget):
    # 使用 add='+' 追加绑定，避免覆盖已有的 "<Button-1>" 处理器（例如色块的 choose_color）
    widget.bind('<Button-1>', lambda event: event.widget.focus_set(), add='+')
