from __future__ import annotations

import tkinter as tk
from tkinter import ttk


class ScrollableFrame(ttk.Frame):
    def __init__(self, master: tk.Misc, *, padding: int | tuple[int, ...] = 0):
        super().__init__(master)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.canvas = tk.Canvas(self, highlightthickness=0, borderwidth=0)
        self.scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
        self.content = ttk.Frame(self.canvas, padding=padding)
        self._content_window = self.canvas.create_window((0, 0), window=self.content, anchor="nw")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.canvas.grid(row=0, column=0, sticky="nsew")
        self.content.bind("<Configure>", self._update_scrollregion, add="+")
        self.canvas.bind("<Configure>", self._fit_content_width, add="+")
        self.bind("<Enter>", self._bind_mousewheel, add="+")
        self.bind("<Leave>", self._unbind_mousewheel, add="+")
        self.content.bind("<Enter>", self._bind_mousewheel, add="+")
        self.content.bind("<Leave>", self._unbind_mousewheel, add="+")
        self.after_idle(self._apply_canvas_background)

    def _apply_canvas_background(self) -> None:
        background = ttk.Style(self).lookup("TFrame", "background")
        if background:
            self.canvas.configure(background=background)

    def _update_scrollregion(self, _event: tk.Event | None = None) -> None:
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))
        self._sync_scrollbar()

    def _fit_content_width(self, event: tk.Event) -> None:
        self.canvas.itemconfigure(self._content_window, width=event.width)
        self._sync_scrollbar()

    def _sync_scrollbar(self) -> None:
        bounds = self.canvas.bbox("all")
        if not bounds:
            self.scrollbar.grid_remove()
            return
        content_height = bounds[3] - bounds[1]
        canvas_height = self.canvas.winfo_height()
        if canvas_height > 1 and content_height > canvas_height:
            self.scrollbar.grid(row=0, column=1, sticky="ns")
        else:
            self.scrollbar.grid_remove()

    def _bind_mousewheel(self, _event: tk.Event | None = None) -> None:
        self.canvas.bind_all("<MouseWheel>", self._on_mousewheel, add="+")
        self.canvas.bind_all("<Button-4>", self._on_mousewheel, add="+")
        self.canvas.bind_all("<Button-5>", self._on_mousewheel, add="+")

    def _unbind_mousewheel(self, _event: tk.Event | None = None) -> None:
        self.canvas.unbind_all("<MouseWheel>")
        self.canvas.unbind_all("<Button-4>")
        self.canvas.unbind_all("<Button-5>")

    def _on_mousewheel(self, event: tk.Event) -> None:
        if getattr(event, "num", None) == 4:
            delta = -1
        elif getattr(event, "num", None) == 5:
            delta = 1
        else:
            delta = -1 * int(getattr(event, "delta", 0) / 120)
        self.canvas.yview_scroll(delta, "units")
