"""UI helpers and reusable Tk callbacks."""

from __future__ import annotations

import tkinter as tk
import tkinter.font as tkfont
from tkinter import ttk


class UIBuilderMixin:
    """Constructs the Tkinter UI and common widgets."""

    def setup_ui(self) -> None:
        self._create_titlebar()

        toolbar = ttk.Frame(self.root)
        toolbar.pack(fill=tk.X, padx=12, pady=0)
        buttons = [
            ("📂 Bild laden", self.load_image),
            ("📁 Ordner laden", self.load_folder),
            ("🎨 Farbe wählen", self.choose_color),
            ("🖱️Farbe aus Bild klicken", self.enable_pick_mode),
            ("💾 Overlay speichern", self.save_overlay),
            ("🧹 Excludes löschen", self.clear_excludes),
            ("↩️ Letztes Exclude entfernen", self.undo_exclude),
            ("🔄 Slider zurücksetzen", self.reset_sliders),
            ("🌓 Theme umschalten", self.toggle_theme),
        ]
        self._toolbar_buttons: list[dict[str, object]] = []

        buttons_frame = ttk.Frame(toolbar)
        buttons_frame.pack(side=tk.LEFT)
        for text, command in buttons:
            self._add_toolbar_button(buttons_frame, text, command)

        status_container = ttk.Frame(toolbar)
        status_container.pack(side=tk.RIGHT, expand=True, fill=tk.X)
        self.status = ttk.Label(
            status_container,
            text="Keine Datei geladen.",
            anchor="e",
            foreground="#efefef",
        )
        self.status.pack(fill=tk.X)
        self._attach_copy_menu(self.status)
        self.status_default_text = self.status.cget("text")
        self._status_palette = {"fg": self.status.cget("foreground")}

        palette_frame = ttk.Frame(self.root)
        palette_frame.pack(fill=tk.X, padx=12, pady=(0, 8))
        ttk.Label(palette_frame, text="Beispielfarben:").pack(side=tk.LEFT, padx=(0, 8))
        swatch_container = ttk.Frame(palette_frame)
        swatch_container.pack(side=tk.LEFT)
        for name, hex_code in self._preset_colours():
            self._add_palette_swatch(swatch_container, name, hex_code)

        sliders_frame = ttk.Frame(self.root)
        sliders_frame.pack(fill=tk.X, padx=12, pady=4)
        sliders = [
            ("Hue Min (°)", self.hue_min, 0, 360),
            ("Hue Max (°)", self.hue_max, 0, 360),
            ("Sättigung Min (%)", self.sat_min, 0, 100),
            ("Helligkeit Min (%)", self.val_min, 0, 100),
            ("Helligkeit Max (%)", self.val_max, 0, 100),
            ("Overlay Alpha", self.alpha, 0, 255),
        ]
        for index, (label, variable, minimum, maximum) in enumerate(sliders):
            self.add_slider_with_value(sliders_frame, label, variable, minimum, maximum, column=index)
            sliders_frame.grid_columnconfigure(index, weight=1)

        main = ttk.Frame(self.root)
        main.pack(fill=tk.BOTH, expand=True, padx=12, pady=12)

        left_column = ttk.Frame(main)
        left_column.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 6))
        left_column.grid_columnconfigure(1, weight=1)
        left_column.grid_rowconfigure(0, weight=1)

        self._create_navigation_button(left_column, "◀", self.show_previous_image, column=0)

        self.canvas_orig = tk.Canvas(left_column, bg="#1e1e1e", highlightthickness=0, relief="flat")
        self.canvas_orig.grid(row=0, column=1, sticky="nsew")
        self.canvas_orig.bind("<Button-1>", self.on_canvas_click)
        self.canvas_orig.bind("<ButtonPress-3>", self._exclude_start)
        self.canvas_orig.bind("<B3-Motion>", self._exclude_drag)
        self.canvas_orig.bind("<ButtonRelease-3>", self._exclude_end)

        right_column = ttk.Frame(main)
        right_column.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(6, 0))
        right_column.grid_columnconfigure(0, weight=1)
        right_column.grid_rowconfigure(0, weight=1)

        self.canvas_overlay = tk.Canvas(right_column, bg="#1e1e1e", highlightthickness=0, relief="flat")
        self.canvas_overlay.grid(row=0, column=0, sticky="nsew")
        self._create_navigation_button(right_column, "▶", self.show_next_image, column=1)


        info_frame = ttk.Frame(self.root)
        info_frame.pack(fill=tk.X, padx=12, pady=(0, 12))
        self.filename_label = ttk.Label(
            info_frame,
            text="—",
            foreground="#f2c744",
            font=("Segoe UI", 10, "bold"),
            anchor="center",
            justify="center",
        )
        self.filename_label.pack(anchor="center")
        self._attach_copy_menu(self.filename_label)

        self.ratio_label = ttk.Label(
            info_frame,
            text="Treffer (mit Excludes): —",
            foreground="#f2c744",
            font=("Segoe UI", 10, "bold"),
            anchor="center",
            justify="center",
        )
        self.ratio_label.pack(anchor="center", pady=(4, 0))
        self._attach_copy_menu(self.ratio_label)

        self.root.bind("<Escape>", self.disable_pick_mode)
        self.root.bind("<ButtonPress-1>", self._maybe_focus_window)

    def add_slider_with_value(self, parent, text, var, minimum, maximum, column=0):
        cell = ttk.Frame(parent)
        cell.grid(row=0, column=column, sticky="we", padx=6)
        header = ttk.Frame(cell)
        header.pack(fill="x")
        name_lbl = ttk.Label(header, text=text)
        name_lbl.pack(side="left")
        self._attach_copy_menu(name_lbl)
        val_lbl = ttk.Label(header, text=f"{float(var.get()):.0f}")
        val_lbl.pack(side="right")
        self._attach_copy_menu(val_lbl)
        style_name = getattr(self, "scale_style", "Horizontal.TScale")
        ttk.Scale(
            cell,
            from_=minimum,
            to=maximum,
            orient="horizontal",
            variable=var,
            style=style_name,
            command=self.on_slider_change,
        ).pack(fill="x", pady=(2, 8))

        def on_var_change(*_):
            val_lbl.config(text=f"{float(var.get()):.0f}")

        try:
            var.trace_add("write", on_var_change)
        except Exception:
            var.trace("w", lambda *_: on_var_change())  # type: ignore[attr-defined]

    def on_slider_change(self, *_):
        if self._update_job is not None:
            try:
                self.root.after_cancel(self._update_job)
            except Exception:
                pass
        self._update_job = self.root.after(self.update_delay_ms, self.update_preview)

    def _preset_colours(self):
        return [
            ("Rot", "#ff3b30"),
            ("Orange", "#ff9500"),
            ("Gelb", "#ffd60a"),
            ("Grün", "#34c759"),
            ("Türkis", "#5ac8fa"),
            ("Blau", "#0a84ff"),
            ("Violett", "#af52de"),
            ("Magenta", "#ff2d55"),
            ("Weiß", "#ffffff"),
            ("Grau", "#8e8e93"),
            ("Schwarz", "#000000"),
        ]

    def _add_palette_swatch(self, parent, name: str, hex_code: str) -> None:
        swatch = tk.Canvas(
            parent,
            width=24,
            height=24,
            highlightthickness=0,
            background=hex_code,
            bd=0,
            relief="flat",
            takefocus=1,
            cursor="hand2",
        )
        swatch.pack(side=tk.LEFT, padx=4, pady=2)

        def trigger(_event=None, colour=hex_code, label=name):
            self.apply_sample_colour(colour, label)

        swatch.bind("<Button-1>", trigger)
        swatch.bind("<space>", trigger)
        swatch.bind("<Return>", trigger)
        swatch.bind("<Enter>", lambda _e: swatch.configure(cursor="hand2"))
        swatch.bind("<Leave>", lambda _e: swatch.configure(cursor="arrow"))

    def _add_toolbar_button(self, parent, text: str, command) -> None:
        font = tkfont.Font(root=self.root, family="Segoe UI", size=9)
        padding_x = 12
        width = font.measure(text) + padding_x * 2
        height = 28
        radius = 9
        bg = self.root.cget("bg") if hasattr(self.root, "cget") else "#f2f2f7"
        canvas = tk.Canvas(
            parent,
            width=width,
            height=height,
            bd=0,
            highlightthickness=0,
            bg=bg,
            relief="flat",
            cursor="hand2",
            takefocus=1,
        )
        canvas.pack(side=tk.LEFT, padx=4, pady=1)

        palette = self._toolbar_palette()
        rect_id = self._create_round_rect(
            canvas,
            1,
            1,
            width - 1,
            height - 1,
            radius,
            fill=palette["normal"],
            outline=palette["outline"],
            width=1,
        )
        text_id = canvas.create_text(
            width / 2,
            height / 2,
            text=text,
            font=font,
            fill=palette["text"],
        )

        button_data = {
            "canvas": canvas,
            "rect": rect_id,
            "text_id": text_id,
            "command": command,
            "palette": palette.copy(),
            "dimensions": (width, height, radius),
        }
        self._toolbar_buttons.append(button_data)

        def set_fill(state: str) -> None:
            pal: dict[str, str] = button_data["palette"]  # type: ignore[index]
            canvas.itemconfigure(rect_id, fill=pal[state])  # type: ignore[index]

        def execute():
            command()

        def on_press(_event=None):
            set_fill("active")

        def on_release(event=None):
            if event is not None and (
                event.x < 0 or event.y < 0 or event.x > width or event.y > height
            ):
                set_fill("normal")
                return
            set_fill("hover")
            self.root.after_idle(execute)

        def on_enter(_event):
            set_fill("hover")

        def on_leave(_event):
            set_fill("normal")

        def on_focus_in(_event):
            pal: dict[str, str] = button_data["palette"]  # type: ignore[index]
            canvas.itemconfigure(rect_id, outline=pal["outline_focus"])  # type: ignore[index]

        def on_focus_out(_event):
            pal: dict[str, str] = button_data["palette"]  # type: ignore[index]
            canvas.itemconfigure(rect_id, outline=pal["outline"])  # type: ignore[index]

        def invoke_keyboard(_event=None):
            set_fill("active")
            canvas.after(120, lambda: set_fill("hover"))
            self.root.after_idle(execute)

        canvas.bind("<ButtonPress-1>", on_press)
        canvas.bind("<ButtonRelease-1>", on_release)
        canvas.bind("<Enter>", on_enter)
        canvas.bind("<Leave>", on_leave)
        canvas.bind("<FocusIn>", on_focus_in)
        canvas.bind("<FocusOut>", on_focus_out)
        canvas.bind("<space>", invoke_keyboard)
        canvas.bind("<Return>", invoke_keyboard)

    @staticmethod
    def _create_round_rect(canvas: tk.Canvas, x1, y1, x2, y2, radius, **kwargs):
        points = [
            x1 + radius,
            y1,
            x2 - radius,
            y1,
            x2,
            y1,
            x2,
            y1 + radius,
            x2,
            y2 - radius,
            x2,
            y2,
            x2 - radius,
            y2,
            x1 + radius,
            y2,
            x1,
            y2,
            x1,
            y2 - radius,
            x1,
            y1 + radius,
            x1,
            y1,
        ]
        return canvas.create_polygon(points, smooth=True, splinesteps=24, **kwargs)

    def _create_navigation_button(self, container, symbol: str, command, *, column: int) -> None:
        bg = self.root.cget("bg") if hasattr(self.root, "cget") else "#f2f2f7"
        container.grid_rowconfigure(0, weight=1)
        fg = "#f5f5f5" if getattr(self, "theme", "light") == "dark" else "#1f1f1f"
        btn = tk.Button(
            container,
            text=symbol,
            command=command,
            font=("Segoe UI", 26, "bold"),
            relief="flat",
            borderwidth=0,
            background=bg,
            activebackground=bg,
            highlightthickness=0,
            fg=fg,
            activeforeground=fg,
            cursor="hand2",
            width=2,
        )
        btn.grid(row=0, column=column, sticky="ns", padx=6)

    def _create_titlebar(self) -> None:
        bar_bg = "#1f1f1f"
        title_bar = tk.Frame(self.root, bg=bar_bg, relief="flat", height=34)
        title_bar.pack(fill=tk.X, side=tk.TOP)
        title_bar.pack_propagate(False)

        logo = None
        try:
            from PIL import Image, ImageTk  # type: ignore

            logo_path = Path(__file__).resolve().parent / "assets" / "logo.png"
            if logo_path.exists():
                image = Image.open(logo_path).convert("RGBA")
                image.thumbnail((26, 26))
                logo = ImageTk.PhotoImage(image)
        except Exception:
            logo = None

        if logo is not None:
            logo_label = tk.Label(title_bar, image=logo, bg=bar_bg)
            logo_label.image = logo  # keep reference
            logo_label.pack(side=tk.LEFT, padx=(10, 6), pady=4)

        title_label = tk.Label(
            title_bar,
            text="ICRS — Interactive Color Range Analyzer",
            bg=bar_bg,
            fg="#f5f5f5",
            font=("Segoe UI", 11, "bold"),
            anchor="w",
        )
        title_label.pack(side=tk.LEFT, padx=6)

        close_btn = tk.Button(
            title_bar,
            text="✕",
            command=self._close_app,
            bg=bar_bg,
            fg="#f5f5f5",
            activebackground="#ff3b30",
            activeforeground="#ffffff",
            borderwidth=0,
            highlightthickness=0,
            relief="flat",
            font=("Segoe UI", 10, "bold"),
            cursor="hand2",
            width=3,
        )
        close_btn.pack(side=tk.RIGHT, padx=8, pady=4)

        for widget in (title_bar, title_label):
            widget.bind("<ButtonPress-1>", self._start_window_drag)
            widget.bind("<B1-Motion>", self._perform_window_drag)

    def _close_app(self) -> None:
        try:
            self.root.destroy()
        except Exception:
            pass

    def _start_window_drag(self, event) -> None:
        self._drag_offset = (event.x_root - self.root.winfo_rootx(), event.y_root - self.root.winfo_rooty())

    def _perform_window_drag(self, event) -> None:
        offset = getattr(self, "_drag_offset", None)
        if offset is None:
            return
        x = event.x_root - offset[0]
        y = event.y_root - offset[1]
        self.root.geometry(f"+{x}+{y}")

    def _maybe_focus_window(self, _event) -> None:
        try:
            self.root.focus_set()
        except Exception:
            pass

    def _toolbar_palette(self) -> dict[str, str]:
        is_dark = getattr(self, "theme", "light") == "dark"
        if is_dark:
            return {
                "normal": "#2f2f35",
                "hover": "#3a3a40",
                "active": "#1f1f25",
                "outline": "#4d4d50",
                "outline_focus": "#7c7c88",
                "text": "#f1f1f5",
            }
        return {
            "normal": "#ffffff",
            "hover": "#ededf4",
            "active": "#dcdce6",
            "outline": "#d0d0d8",
            "outline_focus": "#a9a9b2",
            "text": "#1f1f1f",
        }

    def _refresh_toolbar_buttons_theme(self) -> None:
        if not getattr(self, "_toolbar_buttons", None):
            return
        bg = self.root.cget("bg") if hasattr(self.root, "cget") else "#f2f2f7"
        palette = self._toolbar_palette()
        for data in self._toolbar_buttons:
            canvas = data["canvas"]  # type: ignore[index]
            rect_id = data["rect"]  # type: ignore[index]
            text_id = data["text_id"]  # type: ignore[index]
            data["palette"] = palette.copy()
            canvas.configure(bg=bg)
            canvas.itemconfigure(rect_id, fill=palette["normal"], outline=palette["outline"])
            canvas.itemconfigure(text_id, fill=palette["text"])

    def _refresh_status_palette(self, fg: str) -> None:
        self.status.configure(foreground=fg)
        self._status_palette["fg"] = fg

    def _init_copy_menu(self):
        self._copy_target = None
        self.copy_menu = tk.Menu(self.root, tearoff=0)
        self.copy_menu.add_command(label="Kopieren", command=self._copy_current_label)

    def _attach_copy_menu(self, widget):
        widget.bind("<Button-3>", lambda event, w=widget: self._show_copy_menu(event, w))
        widget.bind("<Control-c>", lambda event, w=widget: self._copy_widget_text(w))

    def _show_copy_menu(self, event, widget):
        self._copy_target = widget
        try:
            self.copy_menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.copy_menu.grab_release()

    def _copy_current_label(self):
        if self._copy_target is not None:
            self._copy_widget_text(self._copy_target)

    def _copy_widget_text(self, widget):
        try:
            text = widget.cget("text")
        except Exception:
            text = ""
        if not text:
            return
        try:
            self.root.clipboard_clear()
            self.root.clipboard_append(text)
        except Exception:
            pass


__all__ = ["UIBuilderMixin"]
