#!/usr/bin/env python3
#
# Copyright (c) 2025 Stormux
# Copyright (c) 2022 Chrys (original ocrdesktop)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.

"""OCRDesktop plugin for Cthulhu screen reader."""

import logging
import os
import sys
import locale
import time
import re
import tempfile
import threading
from mimetypes import MimeTypes

import gi
gi.require_version('Atspi', '2.0')
from gi.repository import Atspi

from cthulhu.plugin import Plugin, cthulhu_hookimpl
from cthulhu import debug
from cthulhu import settings_manager
from cthulhu.wnck_support import load_wnck

# Note: Removed complex beep system - simple announcements work perfectly!

# PIL
try:
    from PIL import Image
    from PIL import ImageOps
    PIL_AVAILABLE = True
except ImportError:
    PIL_AVAILABLE = False

# pytesseract
try:
    import pytesseract
    from pytesseract import Output
    PYTESSERACT_AVAILABLE = True
except ImportError:
    PYTESSERACT_AVAILABLE = False

# pdf2image
try:
    from pdf2image import convert_from_path
    PDF2IMAGE_AVAILABLE = True
except ImportError:
    PDF2IMAGE_AVAILABLE = False

# scipy
try:
    from scipy.spatial import KDTree
    SCIPY_AVAILABLE = True
except ImportError:
    SCIPY_AVAILABLE = False

# webcolors
try:
    from webcolors import CSS3_HEX_TO_NAMES
    from webcolors import hex_to_rgb
    WEBCOLORS_AVAILABLE = True
except ImportError:
    WEBCOLORS_AVAILABLE = False

# GTK/GDK
try:
    gi.require_version("Gtk", "3.0")
    gi.require_version("Gdk", "3.0")
    from gi.repository import Gtk, Gdk
    GTK_AVAILABLE = True
except Exception:
    GTK_AVAILABLE = False

Wnck = load_wnck()
WNCK_AVAILABLE = Wnck is not None

logger = logging.getLogger(__name__)

class OCRDesktop(Plugin):
    """OCR Desktop accessibility plugin for reading inaccessible windows."""
    
    def __init__(self, *args, **kwargs):
        """Initialize the plugin."""
        super().__init__(*args, **kwargs)
        debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Plugin initialized", True)
        
        # Keybinding storage
        self._kb_binding_window = None
        self._kb_binding_desktop = None
        self._kb_binding_clipboard = None
        self._kb_binding_results_window = None
        
        # Settings manager
        self._settings_manager = settings_manager.getManager()
        
        # OCR settings (will be loaded from settings)
        self._languageCode = 'eng'
        self._scaleFactor = 3
        self._grayscaleImg = False
        self._invertImg = False
        self._blackWhiteImg = False
        self._blackWhiteImgValue = 200
        self._colorCalculation = False
        self._colorCalculationMax = 3
        self._copyToClipboard = False
        
        # Internal state
        self._img = []
        self._modifiedImg = []
        self._OCRText = ''
        self._OCRWords = {}
        self._OCRWordList = []
        self._offsetXpos = 0
        self._offsetYpos = 0
        self._activated = False
        
        # OCR Results Window
        self._results_window = None
        self._results_tree = None
        self._results_textview = None
        self._current_view_mode = 0  # 0 = text, 1 = tree
        
        # Progress feedback
        self._is_processing = False
        
        # Color analysis
        self._kdtDB = None
        self.colorNames = []
        self.colorCache = {}
        
        # Set locale for tesseract
        locale.setlocale(locale.LC_ALL, 'C')
        
        # Load OCR settings from configuration
        self._loadOCRSettings()
        
        # Check dependencies
        self._checkDependencies()
    
    def _checkDependencies(self):
        """Check if required dependencies are available."""
        missing_deps = []
        
        if not PIL_AVAILABLE:
            missing_deps.append("python3-pillow")
        if not PYTESSERACT_AVAILABLE:
            missing_deps.append("python-pytesseract")
        if not GTK_AVAILABLE:
            missing_deps.append("GTK3/GDK")
            
        if missing_deps:
            debug.printMessage(debug.LEVEL_INFO, 
                f"OCRDesktop: Missing dependencies: {', '.join(missing_deps)}", True)
            return False
        return True
    
    def _loadOCRSettings(self):
        """Load OCR settings from Cthulhu configuration."""
        try:
            self._languageCode = self._settings_manager.getSetting('ocrLanguageCode') or 'eng'
            self._scaleFactor = self._settings_manager.getSetting('ocrScaleFactor') or 3
            self._grayscaleImg = self._settings_manager.getSetting('ocrGrayscaleImg') or False
            self._invertImg = self._settings_manager.getSetting('ocrInvertImg') or False
            self._blackWhiteImg = self._settings_manager.getSetting('ocrBlackWhiteImg') or False
            self._blackWhiteImgValue = self._settings_manager.getSetting('ocrBlackWhiteImgValue') or 200
            self._colorCalculation = self._settings_manager.getSetting('ocrColorCalculation') or False
            self._colorCalculationMax = self._settings_manager.getSetting('ocrColorCalculationMax') or 3
            self._copyToClipboard = self._settings_manager.getSetting('ocrCopyToClipboard') or False
            
            debug.printMessage(debug.LEVEL_INFO, f"OCR settings loaded: lang={self._languageCode}, scale={self._scaleFactor}, clipboard={self._copyToClipboard}", True)
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCR settings load error: {e}, using defaults", True)
    
    @cthulhu_hookimpl
    def activate(self, plugin=None):
        """Activate the plugin."""
        if plugin is not None and plugin is not self:
            return
            
        if self._activated:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Already activated", True)
            return
            
        try:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Plugin activation starting", True)
            
            if not self.app:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: ERROR - No app reference", True)
                return
                
            if not self._checkDependencies():
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Cannot activate - missing dependencies", True)
                return
            
            # Register keybindings
            self._registerKeybindings()
            
            self._activated = True
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Plugin activated successfully", True)
            
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error activating: {e}", True)
            import traceback
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: {traceback.format_exc()}", True)
    
    @cthulhu_hookimpl
    def deactivate(self, plugin=None):
        """Deactivate the plugin."""
        if plugin is not None and plugin is not self:
            return
            
        self._activated = False
        debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Plugin deactivated", True)
    
    def refresh_settings(self):
        """Refresh plugin settings when configuration changes."""
        try:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Refreshing settings", True)
            
            # Reload OCR settings from configuration
            self._loadOCRSettings()
            
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Settings refreshed successfully", True)
            
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error refreshing settings: {e}", True)
    
    def _registerKeybindings(self):
        """Register plugin keybindings."""
        try:
            # OCR active window
            self._kb_binding_window = self.registerGestureByString(
                self._ocrActiveWindow,
                "OCR read active window",
                'kb:cthulhu+control+w'
            )
            
            # OCR entire desktop
            self._kb_binding_desktop = self.registerGestureByString(
                self._ocrDesktop,
                "OCR read entire desktop",
                'kb:cthulhu+control+d'
            )
            
            # OCR from clipboard
            self._kb_binding_clipboard = self.registerGestureByString(
                self._ocrClipboard,
                "OCR read image from clipboard",
                'kb:cthulhu+control+shift+c'
            )
            
            # OCR results window
            self._kb_binding_results_window = self.registerGestureByString(
                self._showOCRResultsWindow,
                "Show OCR results window for current window",
                'kb:cthulhu+control+f'
            )
            
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Keybindings registered", True)
            
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error registering keybindings: {e}", True)
    
    
    def _announceOCRStart(self, ocr_type):
        """Announce the start of OCR operation."""
        try:
            message = f"Performing OCR on {ocr_type}"
            if self.app:
                state = self.app.getDynamicApiManager().getAPI('CthulhuState')
                if state and state.activeScript:
                    state.activeScript.presentMessage(message, resetStyles=False)
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: {message}", True)
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error announcing OCR start: {e}", True)
    
    def _ocrActiveWindow(self, script=None, inputEvent=None):
        """OCR the active window."""
        try:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: OCR active window requested", True)
            
            if self._is_processing:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Already processing, ignoring request", True)
                return True
                
            self._is_processing = True
            self._announceOCRStart("window")
            
            try:
                if self._screenShotWindow():
                    self._performOCR()
                    self._presentOCRResult()
            finally:
                self._is_processing = False
                
            return True
        except Exception as e:
            self._is_processing = False
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error in OCR window: {e}", True)
            return False
    
    def _ocrDesktop(self, script=None, inputEvent=None):
        """OCR the entire desktop."""
        try:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: OCR desktop requested", True)
            
            if self._is_processing:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Already processing, ignoring request", True)
                return True
                
            self._is_processing = True
            self._announceOCRStart("desktop")
            
            try:
                if self._screenShotDesktop():
                    self._performOCR()
                    self._presentOCRResult()
            finally:
                self._is_processing = False
                
            return True
        except Exception as e:
            self._is_processing = False
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error in OCR desktop: {e}", True)
            return False
    
    def _ocrClipboard(self, script=None, inputEvent=None):
        """OCR image from clipboard."""
        try:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: OCR clipboard requested", True)
            
            if self._is_processing:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Already processing, ignoring request", True)
                return True
                
            self._is_processing = True
            self._announceOCRStart("clipboard")
            
            try:
                if self._readClipboard():
                    self._performOCR()
                    self._presentOCRResult()
            finally:
                self._is_processing = False
                
            return True
        except Exception as e:
            self._is_processing = False
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error in OCR clipboard: {e}", True)
            return False
    
    def _screenShotWindow(self):
        """Take screenshot of active window."""
        if not GTK_AVAILABLE:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: GTK not available for screenshots", True)
            return False

        if not WNCK_AVAILABLE:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Wnck not available for active window screenshots", True)
            return False
            
        try:
            time.sleep(0.3)  # Brief delay
            gdkCurrDesktop = Gdk.get_default_root_window()
            
            currWnckScreen = Wnck.Screen.get_default()
            currWnckScreen.force_update()
            currWnckWindow = currWnckScreen.get_active_window()
            
            if not currWnckWindow:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: No active window found", True)
                return False
                
            self._offsetXpos, self._offsetYpos, wnckWidth, wnckHeight = currWnckWindow.get_geometry()
            pixBuff = Gdk.pixbuf_get_from_window(gdkCurrDesktop, self._offsetXpos, self._offsetYpos, wnckWidth, wnckHeight)
            
            if pixBuff:
                self._img = [self._pixbuf2image(pixBuff)]
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Window screenshot captured", True)
                return True
            else:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Failed to capture window screenshot", True)
                return False
                
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error taking window screenshot: {e}", True)
            return False
    
    def _screenShotDesktop(self):
        """Take screenshot of entire desktop."""
        if not GTK_AVAILABLE:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: GTK not available for screenshots", True)
            return False
            
        try:
            time.sleep(0.3)  # Brief delay
            currDesktop = Gdk.get_default_root_window()
            pixBuff = Gdk.pixbuf_get_from_window(currDesktop, 0, 0, currDesktop.get_width(), currDesktop.get_height())
            
            if pixBuff:
                self._img = [self._pixbuf2image(pixBuff)]
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Desktop screenshot captured", True)
                return True
            else:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Failed to capture desktop screenshot", True)
                return False
                
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error taking desktop screenshot: {e}", True)
            return False
    
    def _readClipboard(self):
        """Read image from clipboard."""
        if not GTK_AVAILABLE:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: GTK not available for clipboard", True)
            return False
            
        try:
            clipboardObj = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
            pixBuff = clipboardObj.wait_for_image()
            
            if pixBuff:
                self._img = [self._pixbuf2image(pixBuff)]
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Image read from clipboard", True)
                return True
            else:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: No image found in clipboard", True)
                return False
                
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error reading clipboard: {e}", True)
            return False
    
    def _pixbuf2image(self, pix):
        """Convert GdkPixbuf to PIL Image."""
        data = pix.get_pixels()
        w = pix.props.width
        h = pix.props.height
        stride = pix.props.rowstride
        mode = "RGB"
        if pix.props.has_alpha:
            mode = "RGBA"
        im = Image.frombytes(mode, (w, h), data, "raw", mode, stride)
        return im
    
    def _scaleImg(self, img):
        """Scale image for better OCR results."""
        width_screen, height_screen = img.size
        width_screen = width_screen * self._scaleFactor
        height_screen = height_screen * self._scaleFactor
        scaledImg = img.resize((width_screen, height_screen), Image.Resampling.BICUBIC)
        return scaledImg
    
    def _transformImg(self, img):
        """Transform image with various filters for better OCR."""
        modifiedImg = self._scaleImg(img)
        
        if self._invertImg:
            modifiedImg = ImageOps.invert(modifiedImg)
        if self._grayscaleImg:
            modifiedImg = ImageOps.grayscale(modifiedImg)
        if self._blackWhiteImg:
            lut = [255 if v > self._blackWhiteImgValue else 0 for v in range(256)]
            modifiedImg = modifiedImg.point(lut)
            
        return modifiedImg
    
    def _performOCR(self):
        """Perform OCR on captured images with coordinate data extraction."""
        if not PYTESSERACT_AVAILABLE:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Tesseract not available", True)
            return
            
        debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Starting OCR", True)
        self._OCRText = ''
        self._OCRWords = {}
        self._OCRWordList = []
        
        for img in self._img:
            modifiedImg = self._transformImg(img)
            try:
                # Extract coordinate data using image_to_data
                from pytesseract import Output
                OCRWords = pytesseract.image_to_data(modifiedImg, output_type=Output.DICT, 
                                                   lang=self._languageCode, config='--psm 4')
                self._appendToOCRWords(OCRWords)
                self._processOCRWords(OCRWords, modifiedImg)
                
                # Also extract simple text for speech output
                text = pytesseract.image_to_string(modifiedImg, lang=self._languageCode, config='--psm 4')
                self._OCRText += text + '\n'
            except Exception as e:
                debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: OCR error: {e}", True)
        
        # Clean up text
        self._cleanOCRText()
        debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: OCR completed", True)
    
    def _cleanOCRText(self):
        """Clean up OCR text output."""
        # Remove multiple spaces
        regexSpace = re.compile(r'[^\S\r\n]{2,}')
        self._OCRText = regexSpace.sub(' ', self._OCRText)
        
        # Remove empty lines
        regexSpace = re.compile(r'\n\s*\n')
        self._OCRText = regexSpace.sub('\n', self._OCRText)
        
        # Remove trailing spaces
        regexSpace = re.compile(r'\s*\n')
        self._OCRText = regexSpace.sub('\n', self._OCRText)
        
        # Remove leading spaces
        regexSpace = re.compile(r'^\s')
        self._OCRText = regexSpace.sub('', self._OCRText)
        
        # Remove trailing newlines
        self._OCRText = self._OCRText.strip()
    
    def _presentOCRResult(self):
        """Present OCR result to user via speech and optionally copy to clipboard."""
        try:
            if not self._OCRText.strip():
                message = "No text found in OCR scan"
            else:
                message = f"OCR result: {self._OCRText}"
                
                # Copy to clipboard if enabled
                if self._copyToClipboard:
                    self._copyTextToClipboard(self._OCRText)
                    message += " (copied to clipboard)"
            
            if self.app:
                state = self.app.getDynamicApiManager().getAPI('CthulhuState')
                if state and state.activeScript:
                    state.activeScript.presentMessage(message, resetStyles=False)
                    
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Presented result: {len(self._OCRText)} characters", True)
            
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error presenting result: {e}", True)
    
    def _appendToOCRWords(self, OCRWords):
        """Append OCR words to the main OCR data structure."""
        for k, v in OCRWords.items():
            try:
                x = self._OCRWords[k]
                if isinstance(v, list):
                    self._OCRWords[k].extend(v)
            except KeyError:
                self._OCRWords[k] = v
    
    def _processOCRWords(self, OCRWords, img):
        """Process OCR words to extract coordinate data."""
        boxCounter = len(OCRWords['level'])
        if boxCounter == 0:
            return False
            
        lastPage = -1
        lastBlock = -1
        lastPar = -1
        lastLine = -1
        
        for i in range(boxCounter):
            if (len(OCRWords['text'][i]) == 0) or OCRWords['text'][i].isspace():
                continue
                
            # Add word to coordinate list
            self._OCRWordList.append([
                OCRWords['text'][i],  # Text
                round(OCRWords['height'][i] / 3 * 0.78, 0),  # Calculated fontsize
                self._getColorString(OCRWords, i, img),  # Color info
                'text',  # Object type
                int(OCRWords['width'][i] / 2 + OCRWords['left'][i]),  # X coordinate (center)
                int(OCRWords['height'][i] / 2 + OCRWords['top'][i]),  # Y coordinate (center)
                int(float(OCRWords['conf'][i]))  # Confidence
            ])
            
            lastPage = OCRWords['page_num'][i]
            lastBlock = OCRWords['block_num'][i]
            lastPar = OCRWords['par_num'][i]
            lastLine = OCRWords['line_num'][i]
            
        return True
    
    def _getColorString(self, box, index, img):
        """Get color information for OCR text (simplified version)."""
        if not self._colorCalculation:
            return 'unknown'
        if not SCIPY_AVAILABLE or not WEBCOLORS_AVAILABLE:
            return 'unknown'
        
        # Simplified color calculation - just return "unknown" for now
        # Full implementation would require the color analysis from ocrdesktop
        return 'unknown'
    
    def _showOCRResultsWindow(self, script=None, inputEvent=None):
        """Show OCR results window for current window with coordinate mapping."""
        try:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: OCR results window requested", True)
            
            if self._is_processing:
                debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Already processing, ignoring request", True)
                return True
                
            self._is_processing = True
            self._announceOCRStart("window for interactive results")
            
            try:
                if self._screenShotWindow():
                    self._performOCR()
                    self._createOCRResultsWindow()
            finally:
                self._is_processing = False
                
            return True
        except Exception as e:
            self._is_processing = False
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error in OCR results window: {e}", True)
            return False
    
    def _createOCRResultsWindow(self):
        """Create and show the OCR results window with coordinate mapping."""
        if not GTK_AVAILABLE:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: GTK not available for results window", True)
            return
            
        try:
            # Create main window
            self._results_window = Gtk.Window(title="OCR Results - Cthulhu")
            self._results_window.set_default_size(800, 600)
            self._results_window.set_modal(True)
            
            # Create main container
            vbox = Gtk.VBox()
            
            # Create menu bar
            menubar = self._createResultsMenuBar()
            vbox.pack_start(menubar, False, False, 0)
            
            # Create text view for OCR text
            scrolled_text = Gtk.ScrolledWindow()
            self._results_textview = Gtk.TextView()
            self._results_textview.set_editable(False)
            buffer = self._results_textview.get_buffer()
            buffer.set_text(self._OCRText)
            scrolled_text.add(self._results_textview)
            
            # Create tree view for coordinate data
            scrolled_tree = Gtk.ScrolledWindow()
            self._results_tree = self._createResultsTreeView()
            scrolled_tree.add(self._results_tree)
            
            # Add both views to container
            vbox.pack_start(scrolled_text, True, True, 0)
            vbox.pack_start(scrolled_tree, True, True, 0)
            
            # Set initial view (text only)
            scrolled_tree.hide()
            
            self._results_window.add(vbox)
            self._results_window.connect("destroy", self._onResultsWindowDestroy)
            self._results_window.connect("key-press-event", self._onResultsKeyPress)
            
            # Show window
            self._results_window.show_all()
            scrolled_tree.hide()  # Hide tree initially
            
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Results window created", True)
            
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error creating results window: {e}", True)
    
    def _createResultsMenuBar(self):
        """Create menu bar for results window."""
        menubar = Gtk.MenuBar()
        
        # View menu
        view_menu = Gtk.Menu()
        view_item = Gtk.MenuItem(label="View")
        view_item.set_submenu(view_menu)
        
        # Toggle view option
        toggle_item = Gtk.MenuItem(label="Toggle View (Alt+V)")
        toggle_item.connect("activate", self._toggleResultsView)
        view_menu.append(toggle_item)
        
        menubar.append(view_item)
        
        # Actions menu
        actions_menu = Gtk.Menu()
        actions_item = Gtk.MenuItem(label="Actions")
        actions_item.set_submenu(actions_menu)
        
        # Click action
        click_item = Gtk.MenuItem(label="Click Selected (Enter)")
        click_item.connect("activate", self._clickSelectedText)
        actions_menu.append(click_item)
        
        menubar.append(actions_item)
        
        return menubar
    
    def _createResultsTreeView(self):
        """Create tree view for OCR results with coordinates."""
        # Create list store
        store = Gtk.ListStore(str, str, int, str, str, int, int, int)
        
        # Create tree view
        tree = Gtk.TreeView(model=store)
        tree.set_search_column(0)
        
        # Add columns
        columns = [
            ("Text", 0),
            ("Font Size", 2),
            ("Color", 3),
            ("Type", 4),
            ("X Position", 5),
            ("Y Position", 6),
            ("Confidence", 7)
        ]
        
        for title, col_id in columns:
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(title, renderer, text=col_id)
            column.set_sort_column_id(col_id)
            tree.append_column(column)
        
        # Populate with OCR data
        for row in self._OCRWordList:
            # Transform coordinates back to screen coordinates
            x_coord = int(row[4] / self._scaleFactor + self._offsetXpos)
            y_coord = int(row[5] / self._scaleFactor + self._offsetYpos)
            
            store.append([
                row[0],  # Text
                str(row[1]),  # Font size (as string for display)
                int(row[1]),  # Font size (as int for sorting)
                row[2],  # Color
                row[3],  # Type
                x_coord,  # X coordinate (screen)
                y_coord,  # Y coordinate (screen)
                row[6]   # Confidence
            ])
        
        tree.connect("row-activated", self._onTreeRowActivated)
        
        return tree
    
    def _toggleResultsView(self, widget):
        """Toggle between text and tree view."""
        if not self._results_window:
            return
            
        # Get the container
        vbox = self._results_window.get_child()
        scrolled_text = vbox.get_children()[1]  # Second child (after menubar)
        scrolled_tree = vbox.get_children()[2]  # Third child
        
        if self._current_view_mode == 0:  # Currently showing text
            scrolled_text.hide()
            scrolled_tree.show()
            self._current_view_mode = 1
            self._results_tree.grab_focus()
        else:  # Currently showing tree
            scrolled_tree.hide()
            scrolled_text.show()
            self._current_view_mode = 0
            self._results_textview.grab_focus()
    
    def _onTreeRowActivated(self, tree, path, column):
        """Handle double-click or Enter on tree row."""
        self._clickSelectedText(None)
    
    def _clickSelectedText(self, widget):
        """Click at the coordinates of the selected text."""
        if not self._results_tree:
            return
            
        selection = self._results_tree.get_selection()
        if not selection:
            return
            
        model, tree_iter = selection.get_selected()
        if not tree_iter:
            return
            
        # Get coordinates
        x_coord = model.get_value(tree_iter, 5)  # X position
        y_coord = model.get_value(tree_iter, 6)  # Y position
        text = model.get_value(tree_iter, 0)  # Text for confirmation
        
        # Confirm click action
        dialog = Gtk.MessageDialog(
            self._results_window,
            Gtk.DialogFlags.MODAL,
            Gtk.MessageType.QUESTION,
            Gtk.ButtonsType.YES_NO,
            f"Click at coordinates ({x_coord}, {y_coord}) for text '{text}'?"
        )
        
        response = dialog.run()
        dialog.destroy()
        
        if response == Gtk.ResponseType.YES:
            try:
                # Hide window before clicking
                self._results_window.hide()
                
                # Perform click using AT-SPI2
                time.sleep(0.5)  # Brief delay
                Atspi.generate_mouse_event(x_coord, y_coord, "b1c")
                
                debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Clicked at ({x_coord}, {y_coord})", True)
                
                # Destroy window after successful click
                self._results_window.destroy()
                self._results_window = None
                
            except Exception as e:
                debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error clicking: {e}", True)
                # Show window again on error
                self._results_window.show()
    
    def _onResultsKeyPress(self, widget, event):
        """Handle key presses in results window."""
        keyval = event.keyval
        state = event.state
        
        # Alt+V to toggle view
        if (keyval == Gdk.KEY_v or keyval == Gdk.KEY_V) and (state & Gdk.ModifierType.MOD1_MASK):
            self._toggleResultsView(None)
            return True
            
        # Enter to click selected
        if keyval == Gdk.KEY_Return or keyval == Gdk.KEY_KP_Enter:
            if self._current_view_mode == 1:  # Tree view
                self._clickSelectedText(None)
                return True
                
        # Escape to close
        if keyval == Gdk.KEY_Escape:
            self._results_window.destroy()
            return True
            
        return False
    
    def _onResultsWindowDestroy(self, widget):
        """Handle results window destruction."""
        self._results_window = None
        self._results_tree = None
        self._results_textview = None
        self._current_view_mode = 0
        debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: Results window destroyed", True)
    
    def _copyTextToClipboard(self, text):
        """Copy text to system clipboard."""
        if not GTK_AVAILABLE:
            debug.printMessage(debug.LEVEL_INFO, "OCRDesktop: GTK not available for clipboard", True)
            return False
            
        try:
            clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
            clipboard.set_text(text, -1)
            clipboard.store()
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Copied {len(text)} characters to clipboard", True)
            return True
        except Exception as e:
            debug.printMessage(debug.LEVEL_INFO, f"OCRDesktop: Error copying to clipboard: {e}", True)
            return False
