import pygame

class PyGUI:
    _surface = None
    _font = None
    _events = []
    _window_stack = []

    # UI layout constants
    TITLE_BAR_HEIGHT = 30
    PADDING = 10
    SPACING = 5
    BOTTOM_PADDING = 10
    MIN_WINDOW_WIDTH = 220

    # Colors and style
    WINDOW_BG_COLOR    = (60, 60, 60)
    TITLE_BAR_COLOR    = (30, 30, 30)
    BORDER_COLOR       = (20, 20, 20)
    BUTTON_COLOR       = (100, 100, 100)
    BUTTON_HOVER_COLOR = (130, 130, 130)
    SLIDER_TRACK_COLOR = (100, 100, 100)
    SLIDER_KNOB_COLOR  = (150, 150, 150)
    WIDGET_TEXT_COLOR  = (255, 255, 255)
    CHECKBOX_BG_COLOR  = (80, 80, 80)
    CHECKBOX_CHECK_COLOR = (200, 200, 200)

    @staticmethod
    def init(surface, font_name='Arial', font_size=18):
        """Initialize PyGUI with the main Pygame surface and font."""
        PyGUI._surface = surface
        PyGUI._font = pygame.font.SysFont(font_name, font_size)

    @staticmethod
    def Begin(title, x, y):
        """
        Begin a new draggable, auto–sizing window.
        A new window context is created; widget calls will record
        their draw commands and update the context layout.
        """
        context = {
            'title': title,
            'x': x,
            'y': y,
            'width': PyGUI.MIN_WINDOW_WIDTH,
            'content_height': 0,    # the vertical extent of all widgets
            'commands': [],         # list of widget draw command lambdas
            'drag': False,
            'drag_offset': (0, 0),
            'local_y': 0            # current Y offset (relative to content area)
        }
        PyGUI._window_stack.append(context)

    @staticmethod
    def End():
        """
        Ends the current window, calculates final dimensions,
        draws the window background, title bar and border, then blits the window.
        """
        if not PyGUI._window_stack:
            return
        context = PyGUI._window_stack.pop()
        final_height = PyGUI.TITLE_BAR_HEIGHT + context['content_height'] + PyGUI.BOTTOM_PADDING

        # Create an offscreen surface for the entire window with alpha support.
        window_surf = pygame.Surface((context['width'], final_height), pygame.SRCALPHA)
        # Draw the window background with rounded corners.
        pygame.draw.rect(window_surf, PyGUI.WINDOW_BG_COLOR,
                         (0, 0, context['width'], final_height), border_radius=8)
        # Execute widget drawing commands onto window_surf.
        for cmd in context['commands']:
            cmd(window_surf)
        # Draw the title bar on top.
        title_bar_rect = pygame.Rect(0, 0, context['width'], PyGUI.TITLE_BAR_HEIGHT)
        pygame.draw.rect(window_surf, PyGUI.TITLE_BAR_COLOR, title_bar_rect, border_radius=8)
        # Render the window title.
        title_surf = PyGUI._font.render(context['title'], True, PyGUI.WIDGET_TEXT_COLOR)
        window_surf.blit(title_surf, (PyGUI.PADDING, (PyGUI.TITLE_BAR_HEIGHT - title_surf.get_height()) // 2))
        # Draw a border around the entire window.
        pygame.draw.rect(window_surf, PyGUI.BORDER_COLOR,
                         (0, 0, context['width'], final_height), 2, border_radius=8)
        # Blit the finished window onto the main surface.
        PyGUI._surface.blit(window_surf, (context['x'], context['y']))

    @staticmethod
    def _current_window():
        if not PyGUI._window_stack:
            return None
        return PyGUI._window_stack[-1]

    @staticmethod
    def Update(events):
        """Update the internal event cache and process dragging for all windows."""
        PyGUI._events = events
        for context in PyGUI._window_stack:
            PyGUI._process_dragging(context)

    @staticmethod
    def _process_dragging(context):
        """If the mouse clicks the title bar, allow dragging to update window position."""
        mouse_pos = pygame.mouse.get_pos()
        mouse_buttons = pygame.mouse.get_pressed()
        title_rect = pygame.Rect(context['x'], context['y'], context['width'], PyGUI.TITLE_BAR_HEIGHT)
        for event in PyGUI._events:
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
                if title_rect.collidepoint(mouse_pos):
                    context['drag'] = True
                    context['drag_offset'] = (mouse_pos[0] - context['x'],
                                              mouse_pos[1] - context['y'])
            if event.type == pygame.MOUSEBUTTONUP and event.button == 1:
                context['drag'] = False
        if context['drag'] and mouse_buttons[0]:
            context['x'] = mouse_pos[0] - context['drag_offset'][0]
            context['y'] = mouse_pos[1] - context['drag_offset'][1]

    @staticmethod
    def Button(label, width=100, height=30):
        """
        Create a button widget; returns True if the button is clicked.
        The interactive test is done immediately and the draw command is recorded.
        """
        context = PyGUI._current_window()
        if context is None:
            return False

        # Compute local widget position inside the window's content area.
        local_x = PyGUI.PADDING
        local_y = context['local_y'] + PyGUI.PADDING
        # Calculate absolute position for interaction.
        abs_x = context['x'] + local_x
        abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y
        widget_rect = pygame.Rect(abs_x, abs_y, width, height)

        clicked = False
        hovered = widget_rect.collidepoint(pygame.mouse.get_pos())
        for event in PyGUI._events:
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and hovered:
                clicked = True

        color = PyGUI.BUTTON_HOVER_COLOR if hovered else PyGUI.BUTTON_COLOR

        # Record the draw command (drawing relative to the window surface).
        def draw_command(surf):
            btn_rect = pygame.Rect(local_x, local_y, width, height)
            pygame.draw.rect(surf, color, btn_rect, border_radius=4)
            text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR)
            text_rect = text_surf.get_rect(center=btn_rect.center)
            surf.blit(text_surf, text_rect)

        context['commands'].append(draw_command)
        context['local_y'] += height + PyGUI.SPACING
        context['content_height'] = max(context['content_height'],
                                        context['local_y'] + PyGUI.PADDING)
        needed_width = local_x + width + PyGUI.PADDING
        if needed_width > context['width']:
            context['width'] = needed_width

        return clicked

    @staticmethod
    def Slider(label, value, min_val, max_val, width=150, height=20):
        """
        Create a slider widget; returns the updated value.
        A label is drawn above the slider track.
        """
        context = PyGUI._current_window()
        if context is None:
            return value

        local_x = PyGUI.PADDING
        local_y = context['local_y'] + PyGUI.PADDING
        abs_x = context['x'] + local_x
        abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y

        # Render label (with current value) and get its height.
        label_surf = PyGUI._font.render(f"{label}: {value:.2f}", True, PyGUI.WIDGET_TEXT_COLOR)
        label_height = label_surf.get_height()

        # Define slider track's absolute rectangle.
        track_rect = pygame.Rect(abs_x, abs_y + label_height + 2, width, height)
        new_value = value
        if track_rect.collidepoint(pygame.mouse.get_pos()) and pygame.mouse.get_pressed()[0]:
            mouse_x = pygame.mouse.get_pos()[0]
            relative = (mouse_x - track_rect.x) / track_rect.width
            relative = max(0.0, min(1.0, relative))
            new_value = min_val + relative * (max_val - min_val)

        relative = (new_value - min_val) / (max_val - min_val)

        def draw_command(surf):
            # Draw the label.
            surf.blit(label_surf, (local_x, local_y))
            # Draw the slider track.
            local_track_rect = pygame.Rect(local_x, local_y + label_height + 2, width, height)
            pygame.draw.rect(surf, PyGUI.SLIDER_TRACK_COLOR, local_track_rect, border_radius=4)
            # Draw the knob.
            knob_rect = pygame.Rect(local_track_rect.x + int(relative * local_track_rect.width) - 5,
                                    local_track_rect.y - 2, 10, local_track_rect.height + 4)
            pygame.draw.rect(surf, PyGUI.SLIDER_KNOB_COLOR, knob_rect, border_radius=4)

        context['commands'].append(draw_command)
        total_height = label_height + 2 + height + PyGUI.SPACING
        context['local_y'] += total_height
        context['content_height'] = max(context['content_height'],
                                        context['local_y'] + PyGUI.PADDING)
        if local_x + width + PyGUI.PADDING > context['width']:
            context['width'] = local_x + width + PyGUI.PADDING

        return new_value

    @staticmethod
    def Checkbox(label, value):
        """
        Create a checkbox widget; returns the toggled boolean value.
        """
        context = PyGUI._current_window()
        if context is None:
            return value

        local_x = PyGUI.PADDING
        local_y = context['local_y'] + PyGUI.PADDING
        abs_x = context['x'] + local_x
        abs_y = context['y'] + PyGUI.TITLE_BAR_HEIGHT + local_y

        box_size = 20
        box_rect = pygame.Rect(abs_x, abs_y, box_size, box_size)
        new_value = value
        hovered = box_rect.collidepoint(pygame.mouse.get_pos())
        for event in PyGUI._events:
            if event.type == pygame.MOUSEBUTTONDOWN and event.button == 1 and hovered:
                new_value = not value

        def draw_command(surf):
            local_box_rect = pygame.Rect(local_x, local_y, box_size, box_size)
            pygame.draw.rect(surf, PyGUI.CHECKBOX_BG_COLOR, local_box_rect, border_radius=3)
            if new_value:
                pygame.draw.line(surf, PyGUI.CHECKBOX_CHECK_COLOR,
                                 (local_box_rect.left, local_box_rect.top),
                                 (local_box_rect.right, local_box_rect.bottom), 2)
                pygame.draw.line(surf, PyGUI.CHECKBOX_CHECK_COLOR,
                                 (local_box_rect.left, local_box_rect.bottom),
                                 (local_box_rect.right, local_box_rect.top), 2)
            text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR)
            surf.blit(text_surf, (local_box_rect.right + 5, local_box_rect.top))

        context['commands'].append(draw_command)
        text_surf = PyGUI._font.render(label, True, PyGUI.WIDGET_TEXT_COLOR)
        line_height = max(box_size, text_surf.get_height())
        context['local_y'] += line_height + PyGUI.SPACING
        context['content_height'] = max(context['content_height'],
                                        context['local_y'] + PyGUI.PADDING)
        needed_width = local_x + box_size + 5 + text_surf.get_width() + PyGUI.PADDING
        if needed_width > context['width']:
            context['width'] = needed_width

        return new_value
