# pygui.py: Immediate–mode GUI library.
# This file contains no direct Pygame calls—it relies entirely on functions supplied by the backend.
# The backend must supply: Rect, get_mouse_pos, get_mouse_pressed,
# and event type constants: MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION.

class Window:
    TITLE_HEIGHT = 30
    PADDING = 5
    SPACING = 5

    def __init__(self, title, x, y, width=None, height=None):
        self.title = title
        self.x = x
        self.y = y
        self.width = width if width is not None else 200
        # Start with a minimal height if none is provided.
        self.height = height if height is not None else (self.TITLE_HEIGHT + self.PADDING)
        self.cursor_y = self.y + self.TITLE_HEIGHT + self.PADDING
        self.dragging = False
        self.drag_offset = (0, 0)

    @property
    def rect(self):
        return Window.backend.Rect(self.x, self.y, self.width, self.height)

    @property
    def title_rect(self):
        return Window.backend.Rect(self.x, self.y, self.width, self.TITLE_HEIGHT)

    def handle_event(self, ev):
        # ev is a dictionary with keys: type, button, pos.
        if ev.get('type') == 'MOUSEBUTTONDOWN':
            if ev.get('button') == 1 and self.title_rect.collidepoint(ev.get('pos')):
                self.dragging = True
                self.drag_offset = (ev.get('pos')[0] - self.x, ev.get('pos')[1] - self.y)
        elif ev.get('type') == 'MOUSEBUTTONUP':
            if ev.get('button') == 1:
                self.dragging = False
        elif ev.get('type') == 'MOUSEMOTION':
            if self.dragging:
                self.x = ev.get('pos')[0] - self.drag_offset[0]
                self.y = ev.get('pos')[1] - self.drag_offset[1]
                self.cursor_y = self.y + self.TITLE_HEIGHT + self.PADDING

    def begin(self, render):
        render.draw_rect((50, 50, 50), self.rect.rect)
        render.draw_rect((70, 70, 70), self.title_rect.rect)
        render.draw_text(self.title, (self.x + self.PADDING, self.y + 5))
        self.cursor_y = self.y + self.TITLE_HEIGHT + self.PADDING

    def layout(self):
        return (self.x + self.PADDING, self.cursor_y)

    def next(self, widget_height):
        self.cursor_y += widget_height + self.SPACING

class PyGUI:
    # These are assigned via set_backend.
    backend = None
    renderer = None

    current_window = None
    windows = []
    active_slider = None   # Stores (window_id, label, offset)
    prev_mouse = False     # For one–click widget toggling

    @staticmethod
    def set_backend(backend, renderer):
        """
        backend: an object that provides Rect, get_mouse_pos, get_mouse_pressed,
                 and event type constants: MOUSEBUTTONDOWN, MOUSEBUTTONUP, MOUSEMOTION.
        renderer: an instance of the backend’s Render class.
        """
        PyGUI.backend = backend
        PyGUI.renderer = renderer
        Window.backend = backend

    @staticmethod
    def Begin(title, x, y, width=None, height=None):
        win = Window(title, x, y, width, height)
        PyGUI.current_window = win
        PyGUI.windows.append(win)
        win.begin(PyGUI.renderer)

    @staticmethod
    def End():
        if PyGUI.current_window:
            win = PyGUI.current_window
            # Auto–resize window height to enclose all widgets.
            win.height = win.cursor_y - win.y + win.PADDING
        PyGUI.current_window = None

    @staticmethod
    def Label(text):
        if not PyGUI.current_window:
            return
        win = PyGUI.current_window
        pos = win.layout()
        PyGUI.renderer.draw_text(text, pos)
        win.next(20)

    @staticmethod
    def Button(text):
        if not PyGUI.current_window:
            return False
        win = PyGUI.current_window
        btn_rect = PyGUI.backend.Rect(win.x + win.PADDING, win.cursor_y, win.width - 2 * win.PADDING, 25)
        PyGUI.renderer.draw_rect((100, 100, 100), btn_rect.rect)
        PyGUI.renderer.draw_text(text, (btn_rect.x + 5, btn_rect.y + 5))
        clicked = False
        mouse_pos = PyGUI.backend.get_mouse_pos()
        if btn_rect.collidepoint(mouse_pos):
            PyGUI.renderer.draw_rect((150, 150, 150), btn_rect.rect, border=2)
            if PyGUI.backend.get_mouse_pressed()[0]:
                clicked = True
        win.next(25)
        return clicked

    @staticmethod
    def Slider(label, value, min_val, max_val):
        if not PyGUI.current_window:
            return value
        win = PyGUI.current_window
        pos = win.layout()
        text = f"{label}: {value:.2f}"
        PyGUI.renderer.draw_text(text, pos)
        win.next(20)
        slider_width = win.width - 2 * win.PADDING
        slider_rect = PyGUI.backend.Rect(win.x + win.PADDING, win.cursor_y, slider_width, 10)
        PyGUI.renderer.draw_rect((100, 100, 100), slider_rect.rect)
        norm = (value - min_val) / (max_val - min_val)
        knob_x = win.x + win.PADDING + norm * slider_width
        knob_rect = PyGUI.backend.Rect(knob_x - 5, win.cursor_y - 5, 10, 20)
        PyGUI.renderer.draw_rect((200, 200, 200), knob_rect.rect)

        slider_id = (id(win), label)
        mp = PyGUI.backend.get_mouse_pos()
        mp_pressed = PyGUI.backend.get_mouse_pressed()[0]
        # Start slider drag if none active and mouse is pressed inside slider_rect.
        if PyGUI.active_slider is None and slider_rect.collidepoint(mp) and mp_pressed:
            offset = mp[0] - knob_x
            PyGUI.active_slider = (slider_id, offset)
        if PyGUI.active_slider is not None and PyGUI.active_slider[0] == slider_id:
            offset = PyGUI.active_slider[1]
            rel = mp[0] - (win.x + win.PADDING) - offset
            norm = max(0, min(rel / slider_width, 1))
            value = min_val + norm * (max_val - min_val)
        if not mp_pressed:
            PyGUI.active_slider = None
        win.next(20)
        return value

    @staticmethod
    def Checkbox(label, value):
        if not PyGUI.current_window:
            return value
        win = PyGUI.current_window
        pos = win.layout()
        box_rect = PyGUI.backend.Rect(win.x + win.PADDING, win.cursor_y, 20, 20)
        PyGUI.renderer.draw_rect((100, 100, 100), box_rect.rect, border=2)
        if value:
            PyGUI.renderer.draw_rect((200, 200, 200), box_rect.rect)
        PyGUI.renderer.draw_text(label, (box_rect.x + box_rect.width + 5, win.cursor_y))
        new_value = value
        mp = PyGUI.backend.get_mouse_pos()
        mp_pressed = PyGUI.backend.get_mouse_pressed()[0]
        # Toggle only on the transition (mouse pressed now but not in the previous frame).
        if box_rect.collidepoint(mp) and mp_pressed and not PyGUI.prev_mouse:
            new_value = not value
        win.next(20)
        return new_value

    @staticmethod
    def handle_event(event):
        # Convert a backend event (e.g. a Pygame event) into a generic dictionary.
        generic = {}
        if event.type == PyGUI.backend.MOUSEBUTTONDOWN:
            generic['type'] = 'MOUSEBUTTONDOWN'
            generic['button'] = event.button
            generic['pos'] = event.pos
        elif event.type == PyGUI.backend.MOUSEBUTTONUP:
            generic['type'] = 'MOUSEBUTTONUP'
            generic['button'] = event.button
            generic['pos'] = event.pos
        elif event.type == PyGUI.backend.MOUSEMOTION:
            generic['type'] = 'MOUSEMOTION'
            generic['pos'] = event.pos
        for win in PyGUI.windows:
            win.handle_event(generic)

    @staticmethod
    def update():
        # Call at end of frame to record the current mouse state (for one–click toggling).
        PyGUI.prev_mouse = PyGUI.backend.get_mouse_pressed()[0]