"""Browser chrome (Adwaita UI)."""

import gi
from typing import Optional
import logging
from functools import partial

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")

from gi.repository import Gtk, Gdk, GdkPixbuf, Adw
import skia


class Chrome:
    def __init__(self, browser):
        self.logger = logging.getLogger("bowser.chrome")
        self.browser = browser
        self.window: Optional[Adw.ApplicationWindow] = None
        self.address_bar: Optional[Gtk.Entry] = None
        self.back_btn: Optional[Gtk.Button] = None
        self.forward_btn: Optional[Gtk.Button] = None
        self.reload_btn: Optional[Gtk.Button] = None
        self.go_btn: Optional[Gtk.Button] = None
        self.drawing_area: Optional[Gtk.DrawingArea] = None
        self.tab_view: Optional[Adw.TabView] = None
        self.tab_bar: Optional[Adw.TabBar] = None
        self.skia_surface: Optional[skia.Surface] = None
        self.tab_pages: dict = {}  # Map tab objects to AdwTabPage
        self._closing_tabs: set = set()  # Track tabs being closed to prevent re-entry
        
        # Debug mode state
        self.debug_mode = False
        
        # Scroll state
        self.scroll_y = 0
        self.document_height = 0  # Total document height for scroll limits
        self.viewport_height = 0  # Current viewport height
        
        # Scrollbar fade state
        self.scrollbar_opacity = 0.0
        self.scrollbar_fade_timeout = None
        
        # Selection state
        self.selection_start = None  # (x, y) of selection start
        self.selection_end = None    # (x, y) of selection end
        self.is_selecting = False    # True while mouse is dragging
        
        # Layout information for text selection
        self.text_layout = []  # List of {text, x, y, width, height, font_size}

    def create_window(self):
        """Initialize the Adwaita application window."""
        # Initialize Adwaita application
        if not hasattr(self.browser.app, '_adw_init'):
            Adw.init()
            self.browser.app._adw_init = True
        
        # Create Adwaita window instead of standard GTK window
        self.window = Adw.ApplicationWindow(application=self.browser.app)
        self.window.set_default_size(1024, 768)
        self.window.set_title("Bowser")

        # Main vertical box for the window structure
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
        self.window.set_content(vbox)

        # Header bar with navigation and address bar
        header_bar = Gtk.HeaderBar()
        vbox.append(header_bar)
        
        # Navigation buttons in header bar
        nav_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        nav_box.add_css_class("linked")
        
        self.back_btn = Gtk.Button(label="◀")
        self.back_btn.set_tooltip_text("Back")
        self.forward_btn = Gtk.Button(label="▶")
        self.forward_btn.set_tooltip_text("Forward")
        self.reload_btn = Gtk.Button(label="⟳")
        self.reload_btn.set_tooltip_text("Reload")
        
        nav_box.append(self.back_btn)
        nav_box.append(self.forward_btn)
        nav_box.append(self.reload_btn)
        header_bar.pack_start(nav_box)
        
        # Address bar - centered in header
        self.address_bar = Gtk.Entry()
        self.address_bar.set_placeholder_text("Enter URL...")
        self.address_bar.set_hexpand(True)
        self.address_bar.set_max_width_chars(40)
        header_bar.set_title_widget(self.address_bar)
        
        # Go button in header bar end
        self.go_btn = Gtk.Button(label="Go")
        self.go_btn.add_css_class("suggested-action")
        header_bar.pack_end(self.go_btn)

        # Create TabView for managing tabs
        self.tab_view = Adw.TabView()
        
        # Create TabBar for tab display
        self.tab_bar = Adw.TabBar()
        self.tab_bar.set_view(self.tab_view)
        self.tab_bar.set_autohide(False)
        
        # Add New Tab button to the tab bar
        new_tab_btn = Gtk.Button()
        new_tab_btn.set_icon_name("list-add-symbolic")
        new_tab_btn.set_tooltip_text("New Tab")
        new_tab_btn.connect("clicked", lambda _: self.browser.new_tab("about:startpage"))
        self.tab_bar.set_end_action_widget(new_tab_btn)
        
        vbox.append(self.tab_bar)
        
        # Create a container box for content that will hold the drawing area
        content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        content_box.set_vexpand(True)
        content_box.set_hexpand(True)
        
        # Create the drawing area for rendering page content
        self.drawing_area = Gtk.DrawingArea()
        self.drawing_area.set_vexpand(True)
        self.drawing_area.set_hexpand(True)
        self.drawing_area.set_draw_func(self.on_draw)
        self.drawing_area.set_can_focus(True)  # Allow focus for keyboard events
        self.drawing_area.set_focusable(True)
        content_box.append(self.drawing_area)
        
        # Add scroll controller for mouse wheel
        scroll_controller = Gtk.EventControllerScroll.new(
            Gtk.EventControllerScrollFlags.VERTICAL
        )
        scroll_controller.connect("scroll", self._on_scroll)
        self.drawing_area.add_controller(scroll_controller)
        
        # Add mouse button controller for selection
        click_controller = Gtk.GestureClick.new()
        click_controller.connect("pressed", self._on_mouse_pressed)
        click_controller.connect("released", self._on_mouse_released)
        self.drawing_area.add_controller(click_controller)
        
        # Add motion controller for drag selection
        motion_controller = Gtk.EventControllerMotion.new()
        motion_controller.connect("motion", self._on_mouse_motion)
        self.drawing_area.add_controller(motion_controller)
        
        # Add content box to vbox (not to TabView - we use a single drawing area for all tabs)
        vbox.append(content_box)

        # Status bar with Adwaita styling
        status_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
        status_box.add_css_class("toolbar")
        status_bar = Gtk.Label(label="Ready")
        status_bar.set_xalign(0)
        status_bar.set_margin_start(8)
        status_bar.set_margin_end(8)
        status_box.append(status_bar)
        vbox.append(status_box)


        # Wire handlers
        if self.address_bar:
            self.address_bar.connect("activate", self._on_addressbar_activate)
        if self.go_btn:
            self.go_btn.connect("clicked", self._on_go_clicked)
        if self.back_btn:
            self.back_btn.connect("clicked", lambda _b: self.browser.go_back())
        if self.forward_btn:
            self.forward_btn.connect("clicked", lambda _b: self.browser.go_forward())
        if self.reload_btn:
            self.reload_btn.connect("clicked", lambda _b: self.browser.reload())
        
        # Connect TabView signals
        if self.tab_view:
            self.tab_view.connect("page-attached", self._on_page_attached)
            self.tab_view.connect("close-page", self._on_close_page)
            self.tab_view.connect("notify::selected-page", self._on_selected_page_changed)
        
        # Setup keyboard shortcuts
        self._setup_keyboard_shortcuts()
        
        # Add any tabs that were created before the window
        for tab in self.browser.tabs:
            if tab not in self.tab_pages:
                self._add_tab_to_ui(tab)
        
        # Set the active tab in the UI
        if self.browser.active_tab and self.browser.active_tab in self.tab_pages:
            page = self.tab_pages[self.browser.active_tab]
            self.tab_view.set_selected_page(page)
        
        # Show the window
        self.window.present()
    
    def _add_tab_to_ui(self, tab):
        """Internal method to add a tab to the TabView UI."""
        # Create a simple placeholder for the tab
        # (Actual rendering happens in the shared drawing_area)
        placeholder = Gtk.Box()
        
        # Create the tab page
        page = self.tab_view.append(placeholder)
        page.set_title(tab.title)
        
        # Store mapping
        self.tab_pages[tab] = page
        
        # Select this tab
        self.tab_view.set_selected_page(page)
    
    def add_tab(self, tab):
        """Add a tab to the TabView."""
        if not self.tab_view:
            # Window not created yet, tab will be added when window is created
            return
        
        self._add_tab_to_ui(tab)
    
    def update_tab(self, tab):
        """Update tab title and other properties."""
        if tab in self.tab_pages:
            page = self.tab_pages[tab]
            page.set_title(tab.title)
    
    def remove_tab(self, tab):
        """Remove a tab from the TabView programmatically."""
        if tab not in self.tab_pages:
            return
        page = self.tab_pages[tab]
        del self.tab_pages[tab]
        # Directly close the page - this triggers _on_close_page but we've already removed from tab_pages
        self.tab_view.close_page(page)
    
    def set_active_tab(self, tab):
        """Set the active tab in the TabView."""
        if tab in self.tab_pages:
            page = self.tab_pages[tab]
            self.tab_view.set_selected_page(page)
    
    def _on_page_attached(self, tab_view, page, position):
        """Handle when a page is attached to the TabView."""
        self.logger.debug(f"Page attached at position {position}")
    
    def _on_close_page(self, tab_view, page):
        """Handle tab close request from UI.
        
        This is called when close_page() is invoked. We must call close_page_finish()
        to actually complete the page removal.
        """
        # Find the tab associated with this page
        tab_to_close = None
        for tab, tab_page in list(self.tab_pages.items()):
            if tab_page == page:
                tab_to_close = tab
                break
        
        if tab_to_close:
            # Remove from our tracking
            del self.tab_pages[tab_to_close]
            # Confirm the close - this actually removes the page from TabView
            self.tab_view.close_page_finish(page, True)
            # Call browser cleanup (but don't call remove_tab since we already handled it)
            self.browser.close_tab(tab_to_close)
            return True
        else:
            # Page not in our tracking - just confirm the close
            self.tab_view.close_page_finish(page, True)
            return True
    
    def _on_selected_page_changed(self, tab_view, pspec):
        """Handle tab selection change."""
        selected_page = tab_view.get_selected_page()
        if selected_page:
            # Find the tab associated with this page
            for tab, page in self.tab_pages.items():
                if page == selected_page:
                    self.browser.set_active_tab(tab)
                    break

    def _on_new_tab_clicked(self, btn: Gtk.Button):
        """Handle new tab button click."""
        self.browser.new_tab("about:startpage")

    def update_address_bar(self):
        if not self.address_bar:
            return
        url = None
        if self.browser.active_tab and self.browser.active_tab.current_url:
            url = str(self.browser.active_tab.current_url)
        self.address_bar.set_text(url or "")

    # Handlers
    def _on_addressbar_activate(self, entry: Gtk.Entry):
        self.browser.navigate_to(entry.get_text())

    def _on_go_clicked(self, _btn: Gtk.Button):
        if self.address_bar:
            self.browser.navigate_to(self.address_bar.get_text())

    def on_draw(self, drawing_area, context, width, height):
        """Callback for drawing the content area using Skia."""
        self.logger.debug(f"on_draw start {width}x{height}")
        # Create Skia surface for this frame
        self.skia_surface = skia.Surface(width, height)
        canvas = self.skia_surface.getCanvas()
        
        # Store viewport height
        self.viewport_height = height

        # White background
        canvas.clear(skia.ColorWHITE)

        # Render DOM content
        frame = self.browser.active_tab.main_frame if self.browser.active_tab else None
        document = frame.document if frame else None
        if document:
            self._render_dom_content(canvas, document, width, height)
            # Draw scrollbar on top
            self._draw_scrollbar(canvas, width, height)
        else:
            paint = skia.Paint()
            paint.setAntiAlias(True)
            paint.setColor(skia.ColorBLACK)
            font = skia.Font(skia.Typeface.MakeDefault(), 20)
            canvas.drawString("Bowser — Enter a URL to browse", 20, 50, font, paint)

        # Convert Skia surface to GTK Pixbuf and blit to Cairo context
        image = self.skia_surface.makeImageSnapshot()
        png_data = image.encodeToData().bytes()

        # Load PNG data into a Pixbuf
        from io import BytesIO

        loader = GdkPixbuf.PixbufLoader.new_with_type("png")
        loader.write(png_data)
        loader.close()
        pixbuf = loader.get_pixbuf()

        # Render pixbuf to Cairo context
        Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0)
        context.paint()
        self.logger.debug("on_draw end")
    
    def _render_dom_content(self, canvas, document, width: int, height: int):
        """Render a basic DOM tree with headings, paragraphs, and lists."""
        from ..parser.html import Element, Text

        body = self._find_body(document)
        if not body:
            return

        # Clear text layout for this render
        self.text_layout = []
        
        # Apply scroll offset
        canvas.save()
        canvas.translate(0, -self.scroll_y)
        
        blocks = self._collect_blocks(body)
        paint = skia.Paint()
        paint.setAntiAlias(True)
        paint.setColor(skia.ColorBLACK)

        x_margin = 20
        max_width = max(10, width - 2 * x_margin)
        y = 30

        # Track layout for debug mode
        layout_rects = []

        for block in blocks:
            font_size = block.get("font_size", 14)
            font = skia.Font(skia.Typeface.MakeDefault(), font_size)
            text = block.get("text", "")
            if not text:
                y += font_size * 0.6
                continue

            # Optional bullet prefix
            if block.get("bullet"):
                text = f"• {text}"

            # Word wrapping per block
            words = text.split()
            lines = []
            current_line = []
            current_width = 0
            for word in words:
                word_width = font.measureText(word + " ")
                if current_width + word_width > max_width and current_line:
                    lines.append(" ".join(current_line))
                    current_line = [word]
                    current_width = word_width
                else:
                    current_line.append(word)
                    current_width += word_width
            if current_line:
                lines.append(" ".join(current_line))

            line_height = font_size * 1.4
            top_margin = block.get("margin_top", 6)
            y += top_margin
            
            block_start_y = y
            for line in lines:
                # Only render if visible (accounting for scroll)
                visible_y = y - self.scroll_y
                if visible_y > -50 and visible_y < height + 50:
                    canvas.drawString(line, x_margin, y, font, paint)
                
                # Store text layout for selection
                line_width = font.measureText(line)
                self.text_layout.append({
                    "text": line,
                    "x": x_margin,
                    "y": y - font_size,  # Top of line
                    "width": line_width,
                    "height": line_height,
                    "font_size": font_size
                })
                
                y += line_height
            
            block_end_y = y
            y += block.get("margin_bottom", 10)
            
            # Store layout for debug mode
            if self.debug_mode:
                block_type = block.get("block_type", "block")
                layout_rects.append({
                    "x": x_margin - 5,
                    "y": block_start_y - font_size,
                    "width": max_width + 10,
                    "height": block_end_y - block_start_y + 5,
                    "type": block_type
                })
        
        # Store total document height
        self.document_height = y + 50  # Add some padding at the bottom
        
        # Draw selection highlight based on text layout
        if self.selection_start and self.selection_end:
            self._draw_text_selection(canvas)
        
        # Draw debug overlays
        if self.debug_mode:
            self._draw_debug_overlays(canvas, layout_rects, document)
        
        canvas.restore()

    def _find_body(self, document):
        from ..parser.html import Element
        if isinstance(document, Element) and document.tag == "body":
            return document
        if hasattr(document, "children"):
            for child in document.children:
                if isinstance(child, Element) and child.tag == "body":
                    return child
                found = self._find_body(child)
                if found:
                    return found
        return None

    def _collect_blocks(self, node):
        """Flatten DOM into renderable blocks with basic styling."""
        from ..parser.html import Element, Text

        blocks = []

        def text_of(n):
            if isinstance(n, Text):
                return n.text
            if isinstance(n, Element):
                parts = []
                for c in n.children:
                    parts.append(text_of(c))
                return " ".join([p for p in parts if p]).strip()
            return ""

        for child in getattr(node, "children", []):
            if isinstance(child, Text):
                txt = child.text.strip()
                if txt:
                    blocks.append({"text": txt, "font_size": 14})
                continue

            if isinstance(child, Element):
                tag = child.tag.lower()
                content = text_of(child)
                if not content:
                    continue

                if tag == "h1":
                    blocks.append({"text": content, "font_size": 24, "margin_top": 12, "margin_bottom": 12, "block_type": "block", "tag": "h1"})
                elif tag == "h2":
                    blocks.append({"text": content, "font_size": 20, "margin_top": 10, "margin_bottom": 10, "block_type": "block", "tag": "h2"})
                elif tag == "h3":
                    blocks.append({"text": content, "font_size": 18, "margin_top": 8, "margin_bottom": 8, "block_type": "block", "tag": "h3"})
                elif tag == "p":
                    blocks.append({"text": content, "font_size": 14, "margin_top": 6, "margin_bottom": 12, "block_type": "block", "tag": "p"})
                elif tag == "li":
                    blocks.append({"text": content, "font_size": 14, "bullet": True, "margin_top": 4, "margin_bottom": 4, "block_type": "list-item", "tag": "li"})
                elif tag in {"ul", "ol"}:
                    blocks.extend(self._collect_blocks(child))
                elif tag in {"span", "a", "strong", "em", "b", "i", "code"}:
                    # Inline elements
                    blocks.append({"text": content, "font_size": 14, "block_type": "inline", "tag": tag})
                else:
                    # Generic element: render text
                    blocks.append({"text": content, "font_size": 14, "block_type": "block", "tag": tag})

        return blocks
    
    def _draw_selection_highlight(self, canvas, width: int):
        """Draw selection highlight rectangle."""
        if not self.selection_start or not self.selection_end:
            return
        
        x1, y1 = self.selection_start
        x2, y2 = self.selection_end
        
        # Normalize coordinates
        left = min(x1, x2)
        right = max(x1, x2)
        top = min(y1, y2)
        bottom = max(y1, y2)
        
        paint = skia.Paint()
        paint.setColor(skia.Color(100, 149, 237, 80))  # Cornflower blue, semi-transparent
        paint.setStyle(skia.Paint.kFill_Style)
        
        rect = skia.Rect.MakeLTRB(left, top, right, bottom)
        canvas.drawRect(rect, paint)
    
    def _draw_debug_overlays(self, canvas, layout_rects: list, document):
        """Draw debug overlays showing element boxes."""
        from ..parser.html import Element, Text
        
        # Color scheme for different element types
        colors = {
            "block": skia.Color(255, 0, 0, 60),      # Red - block elements
            "inline": skia.Color(0, 0, 255, 60),     # Blue - inline elements
            "list-item": skia.Color(0, 255, 0, 60), # Green - list items
            "text": skia.Color(255, 255, 0, 60),    # Yellow - text nodes
        }
        
        border_colors = {
            "block": skia.Color(255, 0, 0, 180),
            "inline": skia.Color(0, 0, 255, 180),
            "list-item": skia.Color(0, 255, 0, 180),
            "text": skia.Color(255, 255, 0, 180),
        }
        
        for rect_info in layout_rects:
            block_type = rect_info.get("type", "block")
            
            # Fill
            fill_paint = skia.Paint()
            fill_paint.setColor(colors.get(block_type, colors["block"]))
            fill_paint.setStyle(skia.Paint.kFill_Style)
            
            rect = skia.Rect.MakeLTRB(
                rect_info["x"],
                rect_info["y"],
                rect_info["x"] + rect_info["width"],
                rect_info["y"] + rect_info["height"]
            )
            canvas.drawRect(rect, fill_paint)
            
            # Border
            border_paint = skia.Paint()
            border_paint.setColor(border_colors.get(block_type, border_colors["block"]))
            border_paint.setStyle(skia.Paint.kStroke_Style)
            border_paint.setStrokeWidth(1)
            canvas.drawRect(rect, border_paint)
        
        # Draw legend in top-right corner
        self._draw_debug_legend(canvas)
    
    def _draw_debug_legend(self, canvas):
        """Draw debug mode legend."""
        # Position in screen coordinates (add scroll offset back)
        legend_x = 10
        legend_y = self.scroll_y + 10
        
        font = skia.Font(skia.Typeface.MakeDefault(), 11)
        
        # Background
        bg_paint = skia.Paint()
        bg_paint.setColor(skia.Color(0, 0, 0, 200))
        bg_paint.setStyle(skia.Paint.kFill_Style)
        canvas.drawRect(skia.Rect.MakeLTRB(legend_x, legend_y, legend_x + 150, legend_y + 85), bg_paint)
        
        text_paint = skia.Paint()
        text_paint.setColor(skia.ColorWHITE)
        text_paint.setAntiAlias(True)
        
        canvas.drawString("DEBUG MODE (Ctrl+Shift+O)", legend_x + 5, legend_y + 15, font, text_paint)
        
        items = [
            ("Red", "Block elements", skia.Color(255, 100, 100, 255)),
            ("Blue", "Inline elements", skia.Color(100, 100, 255, 255)),
            ("Green", "List items", skia.Color(100, 255, 100, 255)),
        ]
        
        y_offset = 30
        for label, desc, color in items:
            color_paint = skia.Paint()
            color_paint.setColor(color)
            canvas.drawRect(skia.Rect.MakeLTRB(legend_x + 5, legend_y + y_offset, legend_x + 15, legend_y + y_offset + 10), color_paint)
            canvas.drawString(f"{label}: {desc}", legend_x + 20, legend_y + y_offset + 10, font, text_paint)
            y_offset += 18

    def paint(self):
        """Trigger redraw of the drawing area."""
        if self.drawing_area:
            self.drawing_area.queue_draw()
    
    def _setup_keyboard_shortcuts(self):
        """Setup keyboard event handling for shortcuts."""
        if not self.window:
            return
        
        # Create event controller for key presses
        key_controller = Gtk.EventControllerKey()
        key_controller.connect("key-pressed", self._on_key_pressed)
        self.window.add_controller(key_controller)
    
    def _on_key_pressed(self, controller, keyval, keycode, state):
        """Handle keyboard shortcuts."""
        # Check for Ctrl+Shift+D (DOM graph visualization)
        ctrl_pressed = state & Gdk.ModifierType.CONTROL_MASK
        shift_pressed = state & Gdk.ModifierType.SHIFT_MASK
        
        key_name = Gdk.keyval_name(keyval)
        
        # Ctrl+Shift+D: DOM graph visualization
        if ctrl_pressed and shift_pressed and key_name in ('D', 'd'):
            self._show_dom_graph()
            return True
        
        # Ctrl+Shift+O: Toggle debug mode (DOM outline visualization)
        if ctrl_pressed and shift_pressed and key_name in ('O', 'o'):
            self._toggle_debug_mode()
            return True
        
        # Page scrolling with arrow keys, Page Up/Down, Home/End
        scroll_amount = 50
        if key_name == 'Down':
            self._scroll_by(scroll_amount)
            return True
        elif key_name == 'Up':
            self._scroll_by(-scroll_amount)
            return True
        elif key_name == 'Page_Down':
            self._scroll_by(400)
            return True
        elif key_name == 'Page_Up':
            self._scroll_by(-400)
            return True
        elif key_name == 'Home' and ctrl_pressed:
            self.scroll_y = 0
            self.paint()
            return True
        elif key_name == 'End' and ctrl_pressed:
            self.scroll_y = 10000  # Will be clamped
            self.paint()
            return True
        elif key_name == 'space':
            # Space scrolls down, Shift+Space scrolls up
            if shift_pressed:
                self._scroll_by(-400)
            else:
                self._scroll_by(400)
            return True
        
        return False  # Event not handled
    
    def _toggle_debug_mode(self):
        """Toggle debug mode for DOM visualization."""
        self.debug_mode = not self.debug_mode
        mode_str = "ON" if self.debug_mode else "OFF"
        self.logger.info(f"Debug mode: {mode_str}")
        self.paint()
    
    def _scroll_by(self, delta: int):
        """Scroll the page by the given amount, clamped to document bounds."""
        max_scroll = max(0, self.document_height - self.viewport_height)
        self.scroll_y = max(0, min(max_scroll, self.scroll_y + delta))
        self._show_scrollbar()
        self.paint()
    
    def _show_scrollbar(self):
        """Show scrollbar and schedule fade out."""
        from gi.repository import GLib
        
        self.scrollbar_opacity = 1.0
        
        # Cancel any existing fade timeout
        if self.scrollbar_fade_timeout:
            GLib.source_remove(self.scrollbar_fade_timeout)
        
        # Schedule fade out after 1 second
        self.scrollbar_fade_timeout = GLib.timeout_add(1000, self._fade_scrollbar)
    
    def _fade_scrollbar(self):
        """Gradually fade out the scrollbar."""
        from gi.repository import GLib
        
        self.scrollbar_opacity -= 0.1
        if self.scrollbar_opacity <= 0:
            self.scrollbar_opacity = 0
            self.scrollbar_fade_timeout = None
            self.paint()
            return False  # Stop the timeout
        
        self.paint()
        # Continue fading
        self.scrollbar_fade_timeout = GLib.timeout_add(50, self._fade_scrollbar)
        return False  # This instance is done
    
    def _draw_scrollbar(self, canvas, width: int, height: int):
        """Draw the scrollbar overlay."""
        if self.scrollbar_opacity <= 0 or self.document_height <= height:
            return
        
        # Calculate scrollbar dimensions
        scrollbar_width = 8
        scrollbar_margin = 4
        scrollbar_x = width - scrollbar_width - scrollbar_margin
        
        # Track height (full viewport)
        track_height = height - 2 * scrollbar_margin
        
        # Thumb size proportional to viewport/document ratio
        thumb_ratio = height / self.document_height
        thumb_height = max(30, track_height * thumb_ratio)
        
        # Thumb position based on scroll position
        max_scroll = max(1, self.document_height - height)
        scroll_ratio = self.scroll_y / max_scroll
        thumb_y = scrollbar_margin + scroll_ratio * (track_height - thumb_height)
        
        # Draw track (subtle)
        alpha = int(30 * self.scrollbar_opacity)
        track_paint = skia.Paint()
        track_paint.setColor(skia.Color(0, 0, 0, alpha))
        track_paint.setStyle(skia.Paint.kFill_Style)
        track_rect = skia.RRect.MakeRectXY(
            skia.Rect.MakeLTRB(scrollbar_x, scrollbar_margin, 
                              scrollbar_x + scrollbar_width, height - scrollbar_margin),
            scrollbar_width / 2, scrollbar_width / 2
        )
        canvas.drawRRect(track_rect, track_paint)
        
        # Draw thumb
        alpha = int(150 * self.scrollbar_opacity)
        thumb_paint = skia.Paint()
        thumb_paint.setColor(skia.Color(100, 100, 100, alpha))
        thumb_paint.setStyle(skia.Paint.kFill_Style)
        thumb_rect = skia.RRect.MakeRectXY(
            skia.Rect.MakeLTRB(scrollbar_x, thumb_y,
                              scrollbar_x + scrollbar_width, thumb_y + thumb_height),
            scrollbar_width / 2, scrollbar_width / 2
        )
        canvas.drawRRect(thumb_rect, thumb_paint)
    
    def _on_scroll(self, controller, dx, dy):
        """Handle mouse wheel scroll."""
        scroll_amount = int(dy * 50)  # Scale scroll amount
        self._scroll_by(scroll_amount)
        return True
    
    def _on_mouse_pressed(self, gesture, n_press, x, y):
        """Handle mouse button press for text selection."""
        self.selection_start = (x, y + self.scroll_y)
        self.selection_end = None
        self.is_selecting = True
        self.drawing_area.grab_focus()
    
    def _on_mouse_released(self, gesture, n_press, x, y):
        """Handle mouse button release for text selection."""
        if self.is_selecting:
            self.selection_end = (x, y + self.scroll_y)
            self.is_selecting = False
            # Extract selected text
            selected_text = self._get_selected_text()
            if selected_text:
                self.logger.info(f"Selected text: {selected_text[:100]}...")
                # Copy to clipboard
                self._copy_to_clipboard(selected_text)
            self.paint()
    
    def _on_mouse_motion(self, controller, x, y):
        """Handle mouse motion for drag selection."""
        if self.is_selecting:
            self.selection_end = (x, y + self.scroll_y)
            self.paint()
    
    def _draw_text_selection(self, canvas):
        """Draw selection highlight for selected text lines."""
        if not self.selection_start or not self.selection_end:
            return
        
        # Normalize selection coordinates
        y1 = min(self.selection_start[1], self.selection_end[1])
        y2 = max(self.selection_start[1], self.selection_end[1])
        x1 = self.selection_start[0] if self.selection_start[1] <= self.selection_end[1] else self.selection_end[0]
        x2 = self.selection_end[0] if self.selection_start[1] <= self.selection_end[1] else self.selection_start[0]
        
        paint = skia.Paint()
        paint.setColor(skia.Color(100, 149, 237, 100))  # Cornflower blue
        paint.setStyle(skia.Paint.kFill_Style)
        
        for line_info in self.text_layout:
            line_top = line_info["y"]
            line_bottom = line_info["y"] + line_info["height"]
            line_left = line_info["x"]
            line_right = line_info["x"] + line_info["width"]
            
            # Check if this line is in the selection range
            if line_bottom < y1 or line_top > y2:
                continue
            
            # Calculate highlight bounds for this line
            hl_left = line_left
            hl_right = line_right
            
            # First line: start from selection start x
            if line_top <= y1 < line_bottom:
                hl_left = max(line_left, x1)
            
            # Last line: end at selection end x
            if line_top < y2 <= line_bottom:
                hl_right = min(line_right, x2)
            
            # Draw highlight
            rect = skia.Rect.MakeLTRB(hl_left, line_top, hl_right, line_bottom)
            canvas.drawRect(rect, paint)
    
    def _get_selected_text(self) -> str:
        """Extract text from the current selection."""
        if not self.selection_start or not self.selection_end or not self.text_layout:
            return ""
        
        # Normalize selection coordinates
        y1 = min(self.selection_start[1], self.selection_end[1])
        y2 = max(self.selection_start[1], self.selection_end[1])
        
        selected_lines = []
        for line_info in self.text_layout:
            line_top = line_info["y"]
            line_bottom = line_info["y"] + line_info["height"]
            
            # Check if this line is in the selection range
            if line_bottom >= y1 and line_top <= y2:
                selected_lines.append(line_info["text"])
        
        return "\n".join(selected_lines)
    
    def _copy_to_clipboard(self, text: str):
        """Copy text to system clipboard."""
        clipboard = Gdk.Display.get_default().get_clipboard()
        clipboard.set(text)
    
    def _show_dom_graph(self):
        """Generate and display DOM graph for current tab."""
        from ..debug.dom_graph import render_dom_graph_to_svg, save_dom_graph, print_dom_tree
        import os
        from pathlib import Path
        
        if not self.browser.active_tab:
            self.logger.warning("No active tab to visualize")
            return
        
        frame = self.browser.active_tab.main_frame
        if not frame or not frame.document:
            self.logger.warning("No document to visualize")
            return
        
        # Generate output path
        output_dir = Path.home() / ".cache" / "bowser"
        output_dir.mkdir(parents=True, exist_ok=True)
        
        # Try SVG first, fallback to DOT
        svg_path = output_dir / "dom_graph.svg"
        dot_path = output_dir / "dom_graph.dot"
        
        self.logger.info("Generating DOM graph...")
        
        # Print tree to console for debugging
        tree_text = print_dom_tree(frame.document, max_depth=15)
        print("\n" + "="*60)
        print("DOM TREE STRUCTURE:")
        print("="*60)
        print(tree_text)
        print("="*60 + "\n")
        
        # Try to render as SVG
        if render_dom_graph_to_svg(frame.document, str(svg_path)):
            # Open in new browser tab
            self.logger.info(f"Opening DOM graph in new tab: {svg_path}")
            self.browser.new_tab(f"about:dom-graph?path={svg_path}")
        else:
            # Fallback to DOT file
            if save_dom_graph(frame.document, str(dot_path)):
                self.logger.info(f"Opening DOM graph (DOT format) in new tab: {dot_path}")
                self.browser.new_tab(f"about:dom-graph?path={dot_path}")
    
    def _show_info_dialog(self, title: str, message: str):
        """Show an information dialog."""
        dialog = Gtk.MessageDialog(
            transient_for=self.window,
            modal=True,
            message_type=Gtk.MessageType.INFO,
            buttons=Gtk.ButtonsType.OK,
            text=title
        )
        dialog.set_property("secondary-text", message)
        dialog.connect("response", lambda d, r: d.destroy())
        dialog.present()
