"""Smart-Widgets der PilotNG-App.

Bausteine, die die alten Roh-Formulare ersetzen: durchsuchbare Auswahl
statt blinder Dropdowns, Karten mit Titel, Prozent-Slider mit Live-Wert,
Hotkey-Erfassung an einer Stelle (statt 6x dupliziert) und ein
einheitlicher Dialog mit Inline-Validierung statt Popups.
"""
import re
import tkinter as tk

import customtkinter

from src.ui import theme
from src.ui.glass import GlassPanel

MAX_DROPDOWN_ROWS = 8


class SearchableComboBox(customtkinter.CTkFrame):
    """Entry mit live gefilterter Vorschlagsliste (Tippen statt Scrollen)."""

    def __init__(self, master, values=(), placeholder='Suchen …',
                 command=None, **kwargs):
        super().__init__(master, fg_color='transparent', **kwargs)
        self.all_values = list(values)
        self.command = command
        self.grid_columnconfigure(0, weight=1)

        self.entry = customtkinter.CTkEntry(
            self, placeholder_text=placeholder, font=theme.font(13),
        )
        self.entry.grid(row=0, column=0, sticky='ew')
        self.entry.bind('<KeyRelease>', self._onType)
        self.entry.bind('<Return>', self._onReturn)
        self.entry.bind('<Escape>', lambda _e: self._hideDropdown())
        self.entry.bind('<FocusOut>', self._onFocusOut)
        self.entry.bind('<Down>', lambda _e: self._focusDropdown())

        self.dropdown = customtkinter.CTkScrollableFrame(
            self, fg_color=theme.ELEVATED, corner_radius=8,
            border_width=1, border_color=theme.BORDER, height=140,
        )
        self._dropdownVisible = False

    # ------------------------------------------------------------------ API
    def get(self):
        return self.entry.get()

    def set(self, value):
        self.entry.delete(0, tk.END)
        if value:
            self.entry.insert(0, value)

    def configure_values(self, values):
        self.all_values = list(values)
        if self._dropdownVisible:
            self._renderDropdown(self.entry.get())

    # ------------------------------------------------------------------ Intern
    def _filtered(self, text):
        needle = (text or '').strip().lower()
        if not needle:
            return self.all_values
        starts = [v for v in self.all_values if v.lower().startswith(needle)]
        contains = [
            v for v in self.all_values
            if needle in v.lower() and not v.lower().startswith(needle)
        ]
        return starts + contains

    def _onType(self, event):
        if event.keysym in ('Return', 'Escape', 'Up', 'Down', 'Tab'):
            return
        self._renderDropdown(self.entry.get())

    def _onReturn(self, _event):
        matches = self._filtered(self.entry.get())
        if matches:
            self._choose(matches[0])
        return 'break'

    def _onFocusOut(self, _event):
        self.after(150, self._hideDropdown)

    def _focusDropdown(self):
        if self._dropdownVisible:
            children = self.dropdown.winfo_children()
            if children:
                children[0].focus_set()

    def _renderDropdown(self, text):
        for child in self.dropdown.winfo_children():
            child.destroy()
        matches = self._filtered(text)[:MAX_DROPDOWN_ROWS]
        if not matches:
            self._hideDropdown()
            return
        for value in matches:
            button = customtkinter.CTkButton(
                self.dropdown, text=value, anchor='w', height=30,
                fg_color='transparent', text_color=theme.TEXT,
                hover_color=theme.ACCENT_SOFT, font=theme.font(12),
                command=lambda v=value: self._choose(v),
            )
            button.pack(fill='x', padx=2, pady=1)
        if not self._dropdownVisible:
            self.dropdown.grid(row=1, column=0, sticky='ew', pady=(2, 0))
            self._dropdownVisible = True

    def _hideDropdown(self):
        if self._dropdownVisible:
            self.dropdown.grid_remove()
            self._dropdownVisible = False

    def _choose(self, value):
        self.set(value)
        self._hideDropdown()
        if self.command is not None:
            self.command(value)


class Card(GlassPanel):
    """Milchglas-Sektion mit optionalem Schalter direkt im Kopf."""

    def __init__(self, master, title, subtitle=None, toggled=None,
                 toggle_initial=False, accentVeil=False):
        super().__init__(master, accentVeil=accentVeil)
        container = self.body
        header = customtkinter.CTkFrame(container, fg_color='transparent')
        header.grid(row=0, column=0, padx=16, pady=(12, 0), sticky='ew')
        header.grid_columnconfigure(0, weight=1)
        self.header = header
        customtkinter.CTkLabel(
            header, text=title, font=theme.font(13, 'bold'),
        ).grid(row=0, column=0, sticky='w')
        if subtitle:
            customtkinter.CTkLabel(
                header, text=subtitle, font=theme.font(11),
                text_color=theme.MUTED,
            ).grid(row=1, column=0, sticky='w')
        self.toggle = None
        if toggled is not None:
            self.toggle = customtkinter.CTkSwitch(
                header, text='', command=toggled,
                progress_color=theme.ACCENT,
            )
            self.toggle.grid(row=0, column=1, rowspan=2, sticky='e')
            if toggle_initial:
                self.toggle.select()
        self.body = customtkinter.CTkFrame(container, fg_color='transparent')
        self.body.grid(row=1, column=0, padx=16, pady=(8, 14), sticky='nsew')
        self.body.grid_columnconfigure(0, weight=1)


class CollapsibleCard(Card):
    """Card, deren Inhalt sich per Klick auf den Kopf einklappt (∨/›)."""

    def __init__(self, master, title, subtitle=None, collapsed=False):
        super().__init__(master, title, subtitle)
        self._collapsed = False
        self.chevron = customtkinter.CTkButton(
            self.header, text='∨', width=30, height=26, corner_radius=8,
            font=theme.font(13, 'bold'), fg_color='transparent',
            hover_color=theme.ELEVATED, text_color=theme.MUTED,
            command=self.toggleCollapsed,
        )
        self.chevron.grid(row=0, column=2, rowspan=2, sticky='e')
        for widget in (self.header, *self.header.winfo_children()):
            if widget is not self.chevron:
                widget.bind('<Button-1>', lambda _e: self.toggleCollapsed())
        if collapsed:
            self.toggleCollapsed()

    def toggleCollapsed(self):
        self._collapsed = not self._collapsed
        if self._collapsed:
            self.body.grid_remove()
            self.chevron.configure(text='›')
        else:
            self.body.grid()
            self.chevron.configure(text='∨')


class InfoTip(customtkinter.CTkLabel):
    """Kleines "?"-Zeichen mit Hover-Erklaerung (einfach formuliert)."""

    def __init__(self, master, text):
        super().__init__(
            master, text='?', width=18, height=18, corner_radius=9,
            fg_color=theme.ELEVATED, text_color=theme.MUTED,
            font=theme.font(10, 'bold'),
        )
        self._tipText = text
        self._tip = None
        self.bind('<Enter>', self._show)
        self.bind('<Leave>', self._hide)

    def _show(self, _event=None):
        if self._tip is not None:
            return
        tip = tk.Toplevel(self)
        tip.wm_overrideredirect(True)
        tip.wm_geometry(
            f'+{self.winfo_rootx() + 22}+{self.winfo_rooty() + 16}')
        tk.Label(
            tip, text=self._tipText, justify='left', wraplength=260,
            bg='#1B2330', fg='#E6EAF2', padx=10, pady=8,
            font=('Segoe UI', 10), borderwidth=1, relief='solid',
        ).pack()
        self._tip = tip

    def _hide(self, _event=None):
        if self._tip is not None:
            self._tip.destroy()
            self._tip = None


class PercentSlider(customtkinter.CTkFrame):
    """Slider mit Live-Prozentwert und optional farbigem Wert."""

    def __init__(self, master, label, value=0, command=None,
                 color=theme.ACCENT):
        super().__init__(master, fg_color='transparent')
        self.grid_columnconfigure(1, weight=1)
        self.command = command
        customtkinter.CTkLabel(
            self, text=label, font=theme.font(12), text_color=theme.MUTED,
        ).grid(row=0, column=0, sticky='w')
        self.valueLabel = customtkinter.CTkLabel(
            self, text=f'{value} %', font=theme.font(12, 'bold'),
            text_color=color, width=52,
        )
        self.valueLabel.grid(row=0, column=2, sticky='e')
        self.variable = tk.IntVar(value=value)
        self.slider = customtkinter.CTkSlider(
            self, from_=0, to=100, variable=self.variable,
            button_color=color, button_hover_color=theme.ACCENT_HOVER,
            progress_color=color, command=self._onChange,
        )
        self.slider.grid(row=1, column=0, columnspan=3, sticky='ew')

    def _onChange(self, _raw):
        value = self.variable.get()
        self.valueLabel.configure(text=f'{value} %')
        if self.command is not None:
            self.command(value)

    def get(self):
        return self.variable.get()


class HotkeyEntry(customtkinter.CTkEntry):
    """Taste-drücken-statt-tippen Erfassung (ersetzt 6 duplizierte Blöcke)."""

    PATTERN = re.compile(r'^[a-z0-9]$', re.IGNORECASE)

    def __init__(self, master, initial='', on_change=None, **kwargs):
        super().__init__(master, justify='center', **kwargs)
        self.on_change = on_change
        if initial:
            self.insert(0, initial)
        self.bind('<Key>', self._capture)

    def _capture(self, event):
        if event.char == '\b':
            self.delete(0, tk.END)
            return 'break'
        if self.PATTERN.match(event.char or ''):
            key = event.char.lower()
        else:
            key = event.keysym
        self.delete(0, tk.END)
        self.insert(0, key)
        if self.on_change is not None:
            self.on_change(key)
        return 'break'


class FormModal(customtkinter.CTkToplevel):
    """Einheitlicher Dialog: echter Titel, Kartenkörper,
    Inline-Fehler statt Popup, Confirm/Cancel."""

    def __init__(self, parent, title, confirm_label='Übernehmen'):
        super().__init__(parent)
        self.title(title)
        self.resizable(False, False)
        self.configure(fg_color=theme.BG)
        self.transient(parent.winfo_toplevel())
        self.grid_columnconfigure((0, 1), weight=1)

        self.body = customtkinter.CTkFrame(
            self, fg_color=theme.SURFACE, corner_radius=14,
            border_width=1, border_color=theme.BORDER,
        )
        self.body.grid(row=0, column=0, columnspan=2, padx=14,
                       pady=(14, 6), sticky='nsew')
        self.body.grid_columnconfigure((0, 1), weight=1)

        self.errorLabel = customtkinter.CTkLabel(
            self, text='', font=theme.font(11), text_color=theme.DANGER,
        )
        self.errorLabel.grid(row=1, column=0, columnspan=2, padx=16,
                             sticky='w')
        customtkinter.CTkButton(
            self, text=confirm_label, height=38, corner_radius=10,
            font=theme.font(13, 'bold'), fg_color=theme.ACCENT,
            hover_color=theme.ACCENT_HOVER, command=self.confirm,
        ).grid(row=2, column=0, padx=(14, 6), pady=(4, 14), sticky='ew')
        customtkinter.CTkButton(
            self, text='Abbrechen', height=38, corner_radius=10,
            font=theme.font(13), fg_color=theme.ELEVATED,
            hover_color=theme.BORDER, text_color=theme.MUTED,
            command=self.destroy,
        ).grid(row=2, column=1, padx=(6, 14), pady=(4, 14), sticky='ew')

    def fail(self, message):
        self.errorLabel.configure(text=message)

    def confirm(self):
        raise NotImplementedError


def positiveInt(text):
    """Validator: leer oder positive Ganzzahl (ohne Popup)."""
    return text == '' or (text.isdigit() and int(text) > 0)


class GlassButton(customtkinter.CTkButton):
    """Button mit weicher Hover-Animation (Farbe gleitet, nicht springt)."""

    ANIMATION_STEPS = 4
    ANIMATION_MS = 18

    def __init__(self, master, base_color=None, hover_color=None, **kwargs):
        self._base = base_color or theme.ACCENT
        self._hover = hover_color or theme.ACCENT_HOVER
        kwargs.setdefault('fg_color', self._base)
        kwargs.setdefault('hover_color', self._hover)
        kwargs.setdefault('corner_radius', 12)
        super().__init__(master, **kwargs)
        self._animating = 0
        self.bind('<Enter>', lambda _e: self._glide(self._base, self._hover))
        self.bind('<Leave>', lambda _e: self._glide(self._hover, self._base))

    def _glide(self, start, end):
        self._animating += 1
        run = self._animating
        steps = self.ANIMATION_STEPS

        def step(i):
            if run != self._animating:
                return  # neue Richtung uebernimmt
            if i > steps:
                return
            self.configure(fg_color=theme.mix(start, end, i / steps))
            self.after(self.ANIMATION_MS, lambda: step(i + 1))
        step(0)


def styleEntry(entry):
    """Milchglas-Input: ruhige Kante, Accent-Gluehen bei Fokus."""
    entry.configure(
        fg_color=theme.ELEVATED, border_color=theme.BORDER,
        corner_radius=10,
    )
    entry.bind(
        '<FocusIn>', lambda _e: entry.configure(border_color=theme.ACCENT))
    entry.bind(
        '<FocusOut>', lambda _e: entry.configure(border_color=theme.BORDER))
    return entry
