Source: renderer/UIController.js

// Import manager classes
const NotificationManager = require('./managers/NotificationManager');
const EditorManager = require('./managers/EditorManager');
const TabManager = require('./managers/TabManager');
const SearchManager = require('./managers/SearchManager');
const FileOperationsManager = require('./managers/FileOperationsManager');

// Import utilities
const fileTypeUtils = require('./utils/fileTypeUtils');

/**
 * Main UI Controller - Coordinates all managers and components
 * 
 * This is the central coordinator for the entire application UI. It manages
 * the interaction between different managers and handles global UI state.
 * 
 * @class UIController
 * @author CTrace GUI Team
 * @version 1.0.0
 * 
 * @example
 * // UIController is automatically instantiated in the HTML
 * const uiController = new UIController();
 */
class UIController {
  /**
   * Creates an instance of UIController and initializes all managers.
   * 
   * @constructor
   * @memberof UIController
   */
  constructor() {
    /**
     * Notification manager instance
     * @type {NotificationManager}
     * @private
     */
    this.notificationManager = new NotificationManager();
    
    /**
     * Editor manager instance
     * @type {EditorManager}
     * @private
     */
    this.editorManager = new EditorManager();
    
    /**
     * Tab manager instance
     * @type {TabManager}
     * @private
     */
    this.tabManager = new TabManager(this.editorManager, this.notificationManager);
    
    /**
     * Search manager instance
     * @type {SearchManager}
     * @private
     */
    this.searchManager = new SearchManager(this.editorManager, this.notificationManager);
    
    /**
     * File operations manager instance
     * @type {FileOperationsManager}
     * @private
     */
    this.fileOpsManager = new FileOperationsManager(this.tabManager, this.notificationManager);

    /**
     * Flag indicating if UI is being resized
     * @type {boolean}
     * @private
     */
    this.isResizing = false;
    
    /**
     * Type of resize operation (sidebar, toolsPanel)
     * @type {string|null}
     * @private
     */
    this.resizeType = null;
    
    /**
     * Currently active menu
     * @type {string|null}
     * @private
     */
    this.activeMenu = null;

    /**
     * WSL availability status
     * @type {boolean}
     * @private
     */
    this.wslAvailable = true;

    /**
     * Current platform
     * @type {string}
     * @private
     */
    this.platform = 'unknown';

    this.init();
  }

  /**
   * Initializes the UI Controller and sets up all necessary components.
   * 
   * This method is called automatically by the constructor and sets up:
   * - Event listeners for UI interactions
   * - Keyboard shortcuts
   * - Resizing functionality
   * - Menu systems
   * - UI components
   * - Manager interconnections
   * - File tree watcher
   * 
   * @memberof UIController
   * @private
   */
  init() {
    this.setupEventListeners();
    this.setupKeyboardShortcuts();
    this.setupResizing();
    this.setupMenus();
    this.setupUIComponents();

    // Connect managers
    this.connectManagers();

    // Initialize with explorer view and welcome screen
    this.showExplorer();
    this.tabManager.showWelcomeScreen();

    // Add refresh button event listener
    const refreshBtn = document.getElementById('refresh-file-tree');
    if (refreshBtn) {
      refreshBtn.addEventListener('click', () => {
        this.refreshFileTree();
      });
    }

    // Set up file system watcher for auto-refresh
    this.setupFileTreeWatcher();
    
    // Set up custom title bar controls
    this.setupTitleBarControls();
    
    // Set up WSL status listener
    this.setupWSLStatusListener();
  }
  /**
   * Set up file system watcher to auto-refresh file tree
   */
  setupFileTreeWatcher() {
    const workspacePath = this.fileOpsManager.getCurrentWorkspacePath();
    if (!workspacePath) return;
    // Listen for file system change events from main process
    window.ipcRenderer.on('workspace-changed', (event, changedPath) => {
      // Only refresh if the change is in the current workspace
      if (changedPath && changedPath.startsWith(workspacePath)) {
        this.refreshFileTree();
      }
    });
    // Request main process to start watching
    window.ipcRenderer.invoke('watch-workspace', workspacePath);
  }
  /**
   * Refreshes the file tree in the explorer view.
   * 
   * This method manually triggers a refresh of the file tree to show any
   * new files or folders that may have been added to the workspace. It
   * communicates with the main process to get an updated file tree structure.
   * 
   * @async
   * @memberof UIController
   * @throws {Error} When file tree refresh fails
   * 
   * @example
   * // Refresh is typically triggered by the refresh button
   * await uiController.refreshFileTree();
   */
  async refreshFileTree() {
    // Only refresh if workspace is open
    const workspacePath = this.fileOpsManager.getCurrentWorkspacePath();
    if (!workspacePath) {
      this.notificationManager.showWarning('No workspace open to refresh');
      return;
    }
    // Request updated file tree from main process
    try {
      const result = await window.ipcRenderer.invoke('get-file-tree', workspacePath);
      if (result.success) {
        const folderName = workspacePath.split(/[/\\]/).pop();
        this.fileOpsManager.updateWorkspaceUI(folderName, result.fileTree);
        this.notificationManager.showSuccess('File tree refreshed');
      } else {
        this.notificationManager.showError('Failed to refresh file tree: ' + (result.error || 'Unknown error'));
      }
    } catch (error) {
      this.notificationManager.showError('Error refreshing file tree: ' + error.message);
    }
  }

  /**
   * Setup file tree watcher to listen for automatic updates
   */
  setupFileTreeWatcher() {
    // Listen for workspace changes from file watcher in main process
    window.ipcRenderer.on('workspace-changed', (event, data) => {
      if (data.success) {
        const folderName = data.folderPath.split(/[/\\]/).pop();
        this.fileOpsManager.updateWorkspaceUI(folderName, data.fileTree);
        console.log('File tree auto-refreshed due to file system changes');
      } else {
        console.error('Error in workspace change notification:', data.error);
      }
    });
  }

  /**
   * Setup WSL status listener to handle WSL availability updates
   */
  setupWSLStatusListener() {
    // Listen for WSL status updates from main process
    window.ipcRenderer.on('wsl-status', (event, data) => {
      this.wslAvailable = data.available && data.hasDistros;
      
      if (this.platform === 'win32') {
        // Update WSL status indicator in UI
        this.updateWSLStatusIndicator(data);
        
        if (!data.available) {
          this.notificationManager.showWarning(
            'WSL is not installed. CTrace requires WSL on Windows. Please install WSL to access all functionality.'
          );
          console.warn('WSL not detected on Windows platform');
        } else if (!data.hasDistros) {
          this.notificationManager.showWarning(
            'WSL is installed but no Linux distributions are available. Please install a distribution (e.g., Ubuntu) to use CTrace.'
          );
          console.warn('WSL detected but no distributions installed');
        } else {
          console.log('WSL is available and ready with distributions');
        }
      }
    });

    // Listen for WSL installation dialog responses
    window.ipcRenderer.on('wsl-install-response', (event, data) => {
      if (data.action === 'install') {
        this.notificationManager.showInfo(
          'WSL installation initiated. Please follow the installation prompts and restart the application when complete.'
        );
      } else if (data.action === 'cancel') {
        this.notificationManager.showWarning(
          'WSL installation cancelled. Some features may be limited without WSL.'
        );
      }
    });

    // Request initial WSL status check
    window.ipcRenderer.send('check-wsl-status');
  }

  /**
   * Update WSL status indicator in the UI
   * @param {Object} wslStatus - WSL status object with available, hasDistros, and error properties
   */
  updateWSLStatusIndicator(wslStatus) {
    // Find or create WSL status indicator
    let statusEl = document.getElementById('wsl-status-indicator');
    if (!statusEl) {
      statusEl = document.createElement('div');
      statusEl.id = 'wsl-status-indicator';
      statusEl.style.cssText = `
        position: fixed;
        bottom: 20px;
        right: 20px;
        padding: 8px 12px;
        border-radius: 6px;
        font-size: 12px;
        font-weight: bold;
        color: white;
        z-index: 1000;
        cursor: pointer;
        transition: all 0.3s ease;
      `;
      document.body.appendChild(statusEl);
    }

    // Update status based on WSL state
    if (!wslStatus.available) {
      statusEl.textContent = '❌ WSL Not Installed';
      statusEl.style.backgroundColor = '#ff4757';
      statusEl.title = 'WSL is not installed. Click for installation instructions.';
    } else if (!wslStatus.hasDistros) {
      statusEl.textContent = '⚠️ WSL No Distributions';
      statusEl.style.backgroundColor = '#ffa502';
      statusEl.title = 'WSL is installed but no Linux distributions are available. Click for setup instructions.';
    } else {
      statusEl.textContent = '✅ WSL Ready';
      statusEl.style.backgroundColor = '#2ed573';
      statusEl.title = 'WSL is ready and available for CTrace';
      
      // Auto-hide the indicator after 3 seconds if everything is working
      setTimeout(() => {
        if (statusEl && statusEl.textContent.includes('✅')) {
          statusEl.style.opacity = '0.3';
        }
      }, 3000);
    }

    // Add click handler for help
    statusEl.onclick = () => {
      if (!wslStatus.available || !wslStatus.hasDistros) {
        this.showWSLSetupDialog(wslStatus);
      }
    };
  }

  /**
   * Show WSL setup dialog with detailed instructions
   * @param {Object} wslStatus - Current WSL status
   */
  showWSLSetupDialog(wslStatus) {
    const modal = document.createElement('div');
    modal.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0, 0, 0, 0.7);
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 10000;
    `;

    const dialog = document.createElement('div');
    dialog.style.cssText = `
      background: white;
      padding: 30px;
      border-radius: 10px;
      max-width: 600px;
      max-height: 80vh;
      overflow-y: auto;
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
    `;

    let instructions = '';
    if (!wslStatus.available) {
      instructions = `
        <h3>🔧 Install WSL (Windows Subsystem for Linux)</h3>
        <p>CTrace requires WSL to run on Windows. Follow these steps:</p>
        <ol>
          <li><strong>Open PowerShell as Administrator</strong>
            <br><small>Right-click Start button → "Windows PowerShell (Admin)"</small>
          </li>
          <li><strong>Run the installation command:</strong>
            <br><code style="background: #f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: monospace;">wsl --install</code>
          </li>
          <li><strong>Restart your computer</strong> when prompted</li>
          <li><strong>Follow the Ubuntu setup</strong> (create username/password)</li>
          <li><strong>Restart this application</strong> to use CTrace</li>
        </ol>
      `;
    } else {
      instructions = `
        <h3>📦 Install a Linux Distribution</h3>
        <p>WSL is installed but you need a Linux distribution to run CTrace:</p>
        <ol>
          <li><strong>Open PowerShell</strong> (no need for Admin)</li>
          <li><strong>List available distributions:</strong>
            <br><code style="background: #f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: monospace;">wsl --list --online</code>
          </li>
          <li><strong>Install Ubuntu (recommended):</strong>
            <br><code style="background: #f0f0f0; padding: 4px 8px; border-radius: 3px; font-family: monospace;">wsl --install Ubuntu</code>
          </li>
          <li><strong>Follow the setup instructions</strong> (create username/password)</li>
          <li><strong>Restart this application</strong> to use CTrace</li>
        </ol>
      `;
    }

    dialog.innerHTML = `
      ${instructions}
      <div style="margin-top: 20px; text-align: right;">
        ${!wslStatus.available ? `
          <button id="auto-install-wsl" style="
            padding: 10px 20px;
            background: #28a745;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            margin-right: 10px;
          ">Install Automatically</button>
        ` : wslStatus.available && !wslStatus.hasDistros ? `
          <button id="install-ubuntu" style="
            padding: 10px 20px;
            background: #28a745;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            margin-right: 10px;
          ">Install Ubuntu</button>
        ` : ''}
        <button id="close-wsl-dialog" style="
          padding: 10px 20px;
          background: #007acc;
          color: white;
          border: none;
          border-radius: 5px;
          cursor: pointer;
          font-size: 14px;
        ">Got it!</button>
      </div>
    `;

    modal.appendChild(dialog);
    document.body.appendChild(modal);

    // Close dialog handlers
    const closeDialog = () => {
      document.body.removeChild(modal);
    };

    document.getElementById('close-wsl-dialog').onclick = closeDialog;
    modal.onclick = (e) => {
      if (e.target === modal) closeDialog();
    };

    // Installation button handlers
    const autoInstallBtn = document.getElementById('auto-install-wsl');
    if (autoInstallBtn) {
      autoInstallBtn.onclick = () => {
        window.ipcRenderer.send('install-wsl');
        closeDialog();
        this.notificationManager.showInfo('WSL installation started. Please follow any prompts that appear.');
      };
    }

    const installUbuntuBtn = document.getElementById('install-ubuntu');
    if (installUbuntuBtn) {
      installUbuntuBtn.onclick = () => {
        window.ipcRenderer.send('install-wsl-distro', 'Ubuntu');
        closeDialog();
        this.notificationManager.showInfo('Ubuntu installation started. Please follow the setup instructions.');
      };
    }
  }

  /**
   * Setup custom title bar controls for frameless window.
   * 
   * This method handles the custom window controls (minimize, maximize, close)
   * and window state management for the frameless window.
   * 
   * @memberof UIController
   * @private
   */
  setupTitleBarControls() {
    const { remote } = require('electron');
    const currentWindow = remote ? remote.getCurrentWindow() : require('electron').remote?.getCurrentWindow();
    
    // If remote is not available, use IPC
    if (!currentWindow) {
      // Setup IPC-based window controls
      const minimizeBtn = document.getElementById('minimize-btn');
      const maximizeBtn = document.getElementById('maximize-btn');
      const closeBtn = document.getElementById('close-btn');
      
      if (minimizeBtn) {
        minimizeBtn.addEventListener('click', () => {
          window.ipcRenderer.send('window-minimize');
        });
      }
      
      if (maximizeBtn) {
        maximizeBtn.addEventListener('click', () => {
          window.ipcRenderer.send('window-maximize-toggle');
        });
      }
      
      if (closeBtn) {
        closeBtn.addEventListener('click', () => {
          window.ipcRenderer.send('window-close');
        });
      }
      
      // Listen for window state changes
      window.ipcRenderer.on('window-maximized', (event, isMaximized) => {
        document.body.classList.toggle('window-maximized', isMaximized);
      });
      
      return;
    }
    
    // Direct window control (if remote is available)
    const minimizeBtn = document.getElementById('minimize-btn');
    const maximizeBtn = document.getElementById('maximize-btn');
    const closeBtn = document.getElementById('close-btn');
    
    if (minimizeBtn) {
      minimizeBtn.addEventListener('click', () => {
        currentWindow.minimize();
      });
    }
    
    if (maximizeBtn) {
      maximizeBtn.addEventListener('click', () => {
        if (currentWindow.isMaximized()) {
          currentWindow.unmaximize();
        } else {
          currentWindow.maximize();
        }
      });
    }
    
    if (closeBtn) {
      closeBtn.addEventListener('click', () => {
        currentWindow.close();
      });
    }
    
    // Update maximize button icon based on window state
    const updateMaximizeButton = () => {
      document.body.classList.toggle('window-maximized', currentWindow.isMaximized());
    };
    
    currentWindow.on('maximize', updateMaximizeButton);
    currentWindow.on('unmaximize', updateMaximizeButton);
    
    // Initial state
    updateMaximizeButton();
  }

  /**
   * Connect managers and set up inter-manager communication
   */
  connectManagers() {
    // Set up tab manager callbacks
    this.tabManager.onLoadFullFile = (filePath) => {
      this.fileOpsManager.loadFullFile(filePath);
    };

    // Set up search manager callbacks
    this.searchManager.openSearchResult = async (filePath, lineNumber) => {
      await this.openSearchResult(filePath, lineNumber);
    };

    // Update search manager with workspace path when workspace changes
    this.searchManager.setWorkspacePath(this.fileOpsManager.getCurrentWorkspacePath());

    // Set up editor content change tracking
    this.editorManager.editor.addEventListener('input', () => {
      if (this.tabManager.activeTabId) {
        const newContent = this.editorManager.getContent();
        this.tabManager.handleContentChange(this.tabManager.activeTabId, newContent);
      }
    });
  }

  /**
   * Setup event listeners for UI components
   */
  setupEventListeners() {
    // Close dialogs when clicking outside
    document.addEventListener('click', (e) => {
      const searchWidget = document.getElementById('search-widget');
      const gotoDialog = document.getElementById('goto-dialog');
      
      if (searchWidget.classList.contains('visible') && 
          !searchWidget.contains(e.target)) {
        this.searchManager.closeSearchWidget();
      }
      
      if (gotoDialog.classList.contains('visible') && 
          !gotoDialog.contains(e.target)) {
        this.searchManager.closeGoToLineDialog();
      }
    });

    // Close menus when clicking outside
    document.addEventListener('click', (e) => {
      if (!e.target.closest('.menu-item')) {
        this.hideAllMenus();
      }
    });
  }

  /**
   * Setup keyboard shortcuts
   */
  setupKeyboardShortcuts() {
    document.addEventListener('keydown', (e) => {
      const searchWidget = document.getElementById('search-widget');
      const gotoDialog = document.getElementById('goto-dialog');
      const isSearchVisible = searchWidget.classList.contains('visible');
      const isGotoVisible = gotoDialog.classList.contains('visible');

      // Handle Enter key
      if (e.key === 'Enter') {
        if (isSearchVisible) {
          e.preventDefault();
          this.searchManager.searchNext();
        } else if (isGotoVisible) {
          e.preventDefault();
          this.searchManager.performGoToLine();
        }
      }
      
      // Handle Escape key
      if (e.key === 'Escape') {
        if (isSearchVisible) {
          e.preventDefault();
          this.searchManager.closeSearchWidget();
        } else if (isGotoVisible) {
          e.preventDefault();
          this.searchManager.closeGoToLineDialog();
        }
      }

      // Handle F3/Shift+F3 for search navigation
      if (e.key === 'F3' && isSearchVisible) {
        e.preventDefault();
        if (e.shiftKey) {
          this.searchManager.searchPrev();
        } else {
          this.searchManager.searchNext();
        }
      }

      // File operations
      if (e.ctrlKey && e.key === 's') {
        e.preventDefault();
        this.fileOpsManager.saveFile();
      }
      
      if (e.ctrlKey && e.key === 'o') {
        e.preventDefault();
        this.fileOpsManager.openFile();
      }
      
      if (e.ctrlKey && e.shiftKey && e.key === 'O') {
        e.preventDefault();
        this.fileOpsManager.openWorkspace();
      }
      
      if (e.ctrlKey && e.key === 'n') {
        e.preventDefault();
        this.tabManager.createNewFile();
      }
      
      if (e.ctrlKey && e.shiftKey && e.key === 'S') {
        e.preventDefault();
        this.fileOpsManager.saveAsFile();
      }
      
      // UI navigation
      if (e.ctrlKey && e.key === 'b') {
        e.preventDefault();
        this.toggleSidebar();
      }
      
      if (e.altKey && e.key === 'z') {
        e.preventDefault();
        this.editorManager.toggleWordWrap();
      }
      
      if (e.shiftKey && e.altKey && e.key === 'F') {
        e.preventDefault();
        const formatted = this.editorManager.formatCode();
        if (this.tabManager.activeTabId) {
          this.tabManager.handleContentChange(this.tabManager.activeTabId, formatted);
        }
      }
      
      if (e.ctrlKey && e.key === 'w') {
        e.preventDefault();
        if (this.tabManager.activeTabId) {
          this.tabManager.closeTab(e, this.tabManager.activeTabId);
        }
      }
      
      if (e.ctrlKey && e.key === 'f') {
        e.preventDefault();
        this.searchManager.showFindDialog();
      }
      
      if (e.ctrlKey && e.key === 'g') {
        e.preventDefault();
        this.searchManager.showGoToLineDialog();
      }
      
      if (e.ctrlKey && e.key === 'Tab') {
        e.preventDefault();
        this.tabManager.switchToNextTab();
      }
      
      if (e.ctrlKey && e.key === '`') {
        e.preventDefault();
        this.toggleToolsPanel();
      }
      
      if (e.ctrlKey && e.shiftKey && e.key === 'F') {
        e.preventDefault();
        this.showSearch();
      }
      
      if (e.ctrlKey && e.shiftKey && e.key === 'E') {
        e.preventDefault();
        this.showExplorer();
      }
    });
  }

  /**
   * Setup resizing functionality
   */
  setupResizing() {
    const sidebar = document.getElementById('sidebar');
    const toolsPanel = document.getElementById('toolsPanel');

    const startResize = (e, type) => {
      this.isResizing = true;
      this.resizeType = type;
      
      if (type === 'sidebar') {
        sidebar.style.transition = 'none';
      } else if (type === 'toolsPanel') {
        toolsPanel.style.transition = 'none';
      }
      
      document.addEventListener('mousemove', this.doResize.bind(this));
      document.addEventListener('mouseup', this.stopResize.bind(this));
      e.preventDefault();
      
      document.body.style.userSelect = 'none';
    };

    window.initSidebarResize = (e) => startResize(e, 'sidebar');
    window.initToolsPanelResize = (e) => startResize(e, 'toolsPanel');
  }

  doResize(e) {
    if (!this.isResizing) return;
    
    const sidebar = document.getElementById('sidebar');
    const toolsPanel = document.getElementById('toolsPanel');
    
    requestAnimationFrame(() => {
      if (this.resizeType === 'sidebar') {
        const containerRect = sidebar.parentElement.getBoundingClientRect();
        const newWidth = e.clientX - containerRect.left;
        const minWidth = 180;
        const maxWidth = window.innerWidth * 0.5;
        
        if (newWidth >= minWidth && newWidth <= maxWidth) {
          sidebar.style.width = newWidth + 'px';
        }
      } else if (this.resizeType === 'toolsPanel') {
        const containerRect = toolsPanel.parentElement.getBoundingClientRect();
        const newWidth = containerRect.right - e.clientX;
        const minWidth = 200;
        const maxWidth = window.innerWidth * 0.6;
        
        if (newWidth >= minWidth && newWidth <= maxWidth) {
          toolsPanel.style.width = newWidth + 'px';
        }
      }
    });
  }

  stopResize() {
    this.isResizing = false;
    
    const sidebar = document.getElementById('sidebar');
    const toolsPanel = document.getElementById('toolsPanel');
    
    if (this.resizeType === 'sidebar') {
      sidebar.style.transition = '';
    } else if (this.resizeType === 'toolsPanel') {
      toolsPanel.style.transition = '';
    }
    
    document.body.style.userSelect = '';
    
    document.removeEventListener('mousemove', this.doResize.bind(this));
    document.removeEventListener('mouseup', this.stopResize.bind(this));
    
    this.resizeType = null;
  }

  /**
   * Setup menu functionality
   */
  setupMenus() {
    window.toggleMenu = (menuId) => {
      const menu = document.getElementById(menuId);
      const dropdown = menu.querySelector('.dropdown-menu');
      
      if (this.activeMenu && this.activeMenu !== menuId) {
        this.hideAllMenus();
      }
      
      if (dropdown.classList.contains('show')) {
        this.hideAllMenus();
      } else {
        dropdown.classList.add('show');
        menu.classList.add('active');
        this.activeMenu = menuId;
      }
    };

    window.hideAllMenus = () => this.hideAllMenus();
    
    // Add click handlers to all dropdown items to stop propagation
    document.querySelectorAll('.dropdown-item').forEach(item => {
      item.addEventListener('click', (e) => {
        e.stopPropagation();
        // hideAllMenus will be called by the onclick handler in HTML
      });
    });
  }

  hideAllMenus() {
    document.querySelectorAll('.dropdown-menu').forEach(menu => {
      menu.classList.remove('show');
    });
    document.querySelectorAll('.menu-item').forEach(item => {
      item.classList.remove('active');
    });
    this.activeMenu = null;
  }

  /**
   * Setup UI component functions
   */
  setupUIComponents() {
    // Activity bar functions
    window.showExplorer = () => this.showExplorer();
    window.showSearch = () => this.showSearch();

    // File operations
    window.createNewFile = () => this.tabManager.createNewFile();
    window.openFile = () => this.fileOpsManager.openFile();
    window.openWorkspace = () => this.openWorkspace();
    window.saveFile = () => this.fileOpsManager.saveFile();
    window.saveAsFile = () => this.fileOpsManager.saveAsFile();
    window.closeCurrentTab = () => {
      if (this.tabManager.activeTabId) {
        this.tabManager.closeTab(new Event('click'), this.tabManager.activeTabId);
      }
    };

    // Editor operations
    window.formatCode = () => {
      const formatted = this.editorManager.formatCode();
      if (this.tabManager.activeTabId) {
        this.tabManager.handleContentChange(this.tabManager.activeTabId, formatted);
      }
    };
    window.toggleWordWrap = () => this.editorManager.toggleWordWrap();

    // Search operations
    window.showFindDialog = () => this.searchManager.showFindDialog();
    window.closeSearchWidget = () => this.searchManager.closeSearchWidget();
    window.searchNext = () => this.searchManager.searchNext();
    window.searchPrev = () => this.searchManager.searchPrev();
    window.showGoToLineDialog = () => this.searchManager.showGoToLineDialog();
    window.closeGoToLineDialog = () => this.searchManager.closeGoToLineDialog();
    window.performGoToLine = () => this.searchManager.performGoToLine();

    // UI navigation
    window.toggleSidebar = () => this.toggleSidebar();
    window.toggleToolsPanel = () => this.toggleToolsPanel();
    window.showToolsPanel = () => this.showToolsPanel();
    window.hideToolsPanel = () => this.hideToolsPanel();
  window.openAssistantPanel = () => this.openAssistantPanel();

    // Visualyzer operations
    window.toggleVisualyzerPanel = () => this.toggleVisualyzerPanel();

    // CTrace helpers
    const stripAnsi = (input) => {
      if (!input || typeof input !== 'string') return input;
      const ansiRegex = /\x1b\[[0-9;]*m/g;
      return input.replace(ansiRegex, '');
    };

    window.runCTrace = async () => {
      const outEl = document.getElementById('ctrace-output');
      this.showToolsPanel();
      if (!outEl) {
        this.notificationManager.showError('CTrace output panel not found');
        return;
      }

      const active = this.tabManager.getActiveTab();
      const currentFilePath = active && active.filePath ? active.filePath : null;
      if (!currentFilePath) {
        outEl.textContent = 'No active file to analyze. Open a file first.';
        this.notificationManager.showWarning('Open a file to analyze with CTrace');
        return;
      }

      outEl.textContent = `Running ctrace on: ${currentFilePath}`;
      try {
        let args = [];
        args.push(`--input=${currentFilePath}`);
        args.push("--static");
        args.push("--sarif-format");
        const result = await window.ipcRenderer.invoke('run-ctrace', args);
        if (result && result.success) {
          outEl.textContent = stripAnsi(result.output || '(no output)');
          this.notificationManager.showSuccess('CTrace completed successfully');
        } else {
          const details = (result && (result.stderr || result.output || result.error)) || 'Unknown error';
          
          // Check if this is a WSL setup error and provide helpful UI
          if (details.includes('WSL') && details.includes('distributions')) {
            outEl.innerHTML = `
              <div style="color: #ff6b6b; font-weight: bold; margin-bottom: 10px;">⚠️ WSL Setup Required</div>
              <div style="white-space: pre-wrap; font-family: monospace; font-size: 12px; line-height: 1.4;">${stripAnsi(details)}</div>
              <div style="margin-top: 15px; padding: 10px; background: #f0f8ff; border: 1px solid #007acc; border-radius: 4px;">
                <div style="font-weight: bold; color: #007acc; margin-bottom: 5px;">Quick Setup:</div>
                <div style="font-size: 12px; color: #333;">
                  1. Open PowerShell as Administrator<br>
                  2. Run: <code style="background: #e6e6e6; padding: 2px 4px; border-radius: 2px;">wsl --install Ubuntu</code><br>
                  3. Restart when prompted and follow setup instructions<br>
                  4. Restart this application
                </div>
              </div>
            `;
            this.notificationManager.showWarning('WSL setup required - see output panel for instructions');
          } else {
            outEl.textContent = `Error running ctrace:\n${stripAnsi(details)}`;
            this.notificationManager.showError('Failed to run CTrace');
          }
        }
        // Auto-scroll to bottom
        outEl.scrollTop = outEl.scrollHeight;
      } catch (err) {
        outEl.textContent = `Exception: ${err.message}`;
        this.notificationManager.showError('Error invoking CTrace');
      }
    };

    window.clearCTraceOutput = () => {
      const outEl = document.getElementById('ctrace-output');
      if (outEl) outEl.textContent = '';
    };

    window.copyCTraceOutput = () => {
      const outEl = document.getElementById('ctrace-output');
      if (!outEl) return;
      const text = outEl.textContent || '';
      try {
        // Prefer Electron clipboard if available via require
        const { clipboard } = require('electron');
        clipboard.writeText(text);
        this.notificationManager.showSuccess('Output copied to clipboard');
      } catch (_) {
        if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
          navigator.clipboard.writeText(text)
            .then(() => this.notificationManager.showSuccess('Output copied to clipboard'))
            .catch(() => this.notificationManager.showError('Failed to copy output'));
        } else {
          this.notificationManager.showError('Clipboard not available');
        }
      }
    };

    // Tab manager reference for global access
    window.tabManager = this.tabManager;
    window.searchManager = this.searchManager;
  }

  /**
   * Activity bar management
   */
  setActiveActivity(activityId) {
    document.querySelectorAll('.activity-item').forEach(item => {
      item.classList.remove('active');
    });
    const element = document.getElementById(activityId);
    if (element) {
      element.classList.add('active');
    }
  }

  showExplorer() {
    this.setActiveActivity('explorer-activity');
    const sidebarTitle = document.getElementById('sidebar-title');
    const explorerView = document.getElementById('explorer-view');
    const searchView = document.getElementById('search-view');
    const sidebar = document.getElementById('sidebar');
    
    if (sidebarTitle) sidebarTitle.textContent = 'Explorer';
    if (explorerView) explorerView.style.display = 'block';
    if (searchView) searchView.style.display = 'none';
    if (sidebar && sidebar.style.display === 'none') {
      sidebar.style.display = 'flex';
    }
  }

  showSearch() {
    this.setActiveActivity('search-activity');
    const sidebarTitle = document.getElementById('sidebar-title');
    const explorerView = document.getElementById('explorer-view');
    const searchView = document.getElementById('search-view');
    const sidebar = document.getElementById('sidebar');
    const searchInput = document.getElementById('sidebar-search-input');
    
    if (sidebarTitle) sidebarTitle.textContent = 'Search';
    if (explorerView) explorerView.style.display = 'none';
    if (searchView) searchView.style.display = 'block';
    if (sidebar && sidebar.style.display === 'none') {
      sidebar.style.display = 'flex';
    }
    setTimeout(() => searchInput && searchInput.focus(), 100);
  }

  /**
   * Sidebar toggle
   */
  toggleSidebar() {
    const sidebar = document.getElementById('sidebar');
    if (!sidebar) return;

    if (sidebar.style.width === '0px' || sidebar.style.display === 'none') {
      sidebar.style.width = '280px';
      sidebar.style.display = 'flex';
    } else {
      sidebar.style.width = '0px';
      setTimeout(() => {
        sidebar.style.display = 'none';
      }, 200);
    }
  }

  /**
   * Tools panel management
   */
  showToolsPanel() {
    const toolsPanel = document.getElementById('toolsPanel');
    if (toolsPanel) {
      toolsPanel.style.display = 'flex';
      toolsPanel.offsetHeight; // Force reflow
      toolsPanel.classList.add('active');
    }
  }

  hideToolsPanel() {
    const toolsPanel = document.getElementById('toolsPanel');
    if (toolsPanel) {
      toolsPanel.classList.remove('active');
      setTimeout(() => {
        toolsPanel.style.display = 'none';
      }, 200);
    }
  }

  toggleToolsPanel() {
    const toolsPanel = document.getElementById('toolsPanel');
    if (toolsPanel) {
      if (toolsPanel.style.display === 'none' || !toolsPanel.classList.contains('active')) {
        this.showToolsPanel();
      } else {
        this.hideToolsPanel();
      }
    }
  }

  /**
   * Visualyzer panel management
   */
  toggleVisualyzerPanel() {
    // Open visualyzer in a separate window
    window.ipcRenderer.send('open-visualyzer');
  }

  closeVisualyzer() {
    // This method is no longer needed since visualyzer is in separate window
    // Kept for backward compatibility
  }

  toggleToolsPanel() {
    const toolsPanel = document.getElementById('toolsPanel');
    if (toolsPanel) {
      if (toolsPanel.classList.contains('active')) {
        this.hideToolsPanel();
      } else {
        this.showToolsPanel();
      }
    }
  }

  openAssistantPanel() {
    const toolsPanel = document.getElementById('toolsPanel');
    // Ensure assistant is configured at least once before opening
    const ensure = this.ensureAssistantConfigured();
    Promise.resolve(ensure).then(() => {
      if (toolsPanel) {
        this.showToolsPanel();
        // Inject assistant chat UI into tools panel
        this.renderAssistantUI();
      }
    });
  }

  /**
   * Inject a simple chat UI into the tools panel (like VSCode Copilot sidebar)
   */
  renderAssistantUI() {
    const toolsPanel = document.getElementById('toolsPanel');
    if (!toolsPanel) return;

    // Save original content so we can restore it later
    if (!this._toolsPanelOriginal) {
      const header = toolsPanel.querySelector('.tools-panel-header');
      const content = toolsPanel.querySelector('.tools-panel-content');
      this._toolsPanelOriginal = {
        headerHTML: header ? header.innerHTML : null,
        contentHTML: content ? content.innerHTML : null
      };
    }

    const header = toolsPanel.querySelector('.tools-panel-header');
    const content = toolsPanel.querySelector('.tools-panel-content');
    if (!header || !content) return;

    // Update header title
    const titleSpan = header.querySelector('span');
    if (titleSpan) titleSpan.textContent = 'Assistant';

    // Build assistant UI
    const cfg = this.getAssistantConfig() || { provider: 'none' };
    
    // Get display name for the assistant
    let displayName = 'Not configured';
    if (cfg.provider === 'local' && cfg.localModelPath) {
      // Extract filename from path and remove .gguf extension
      const pathParts = cfg.localModelPath.replace(/\\/g, '/').split('/');
      const filename = pathParts[pathParts.length - 1];
      displayName = filename.replace(/\.gguf$/i, '');
    } else if (cfg.provider === 'external') {
      displayName = cfg.externalProvider || 'External';
    } else if (cfg.provider === 'ollama') {
      displayName = 'Ollama';
    } else if (cfg.provider !== 'none') {
      displayName = cfg.provider;
    }

    content.innerHTML = `
      <div style="display:flex; flex-direction:column; height:100%;">
        <div style="padding:8px 12px; border-bottom:1px solid rgba(255,255,255,0.03); display:flex; align-items:center; justify-content:space-between">
          <div style="font-size:13px; color:#c9d1d9">Assistant — ${displayName}</div>
          <div style="display:flex; gap:8px; align-items:center">
            <button id="assistant-settings" style="padding:6px 8px; background:#21262d; border:1px solid #30363d; color:#f0f6fc; border-radius:6px; cursor:pointer; font-size:12px">Settings</button>
          </div>
        </div>
        <div id="assistant-messages" style="flex:1; padding:12px; overflow:auto; background:linear-gradient(#0b0f14, #051018);">
          <!-- messages go here -->
        </div>
        <div style="padding:10px; border-top:1px solid rgba(255,255,255,0.03);">
          <div id="context-indicator" style="display:none; padding:6px 8px; margin-bottom:8px; background:#1a1f2e; border:1px solid #2b3036; border-radius:4px; font-size:11px; color:#8b949e; font-family:monospace;">
            <span id="context-text"></span>
            <button id="context-clear" style="margin-left:8px; padding:2px 6px; background:transparent; border:1px solid #30363d; color:#8b949e; border-radius:3px; cursor:pointer; font-size:10px;">✕</button>
          </div>
          <div style="display:flex; gap:8px; align-items:flex-end">
            <textarea id="assistant-input" placeholder="Ask the assistant..." style="flex:1; min-height:44px; max-height:120px; resize:none; padding:8px; border-radius:6px; border:1px solid #2b3036; background:#0d1117; color:#fff"></textarea>
            <button id="assistant-send" style="padding:10px; background:transparent; color:#8b949e; border:none; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:color 0.2s;" title="Send message (Enter)" onmouseover="this.style.color='#c9d1d9'" onmouseout="this.style.color='#8b949e'">
              <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M2 21L23 12L2 3V10L17 12L2 14V21Z" fill="currentColor"/>
              </svg>
            </button>
          </div>
        </div>
      </div>
    `;

    // Helper to append messages with typing effect
    const addMessage = (who, text, options = {}) => {
      const container = document.getElementById('assistant-messages');
      if (!container) return;
      const wrap = document.createElement('div');
      wrap.style.marginBottom = '12px';
      const bubble = document.createElement('div');
      bubble.style.padding = '10px 12px';
      bubble.style.borderRadius = '8px';
      bubble.style.maxWidth = '90%';
      bubble.style.lineHeight = '1.4';
      
      if (who === 'user') {
        bubble.style.background = '#0b5fff';
        bubble.style.color = '#fff';
        bubble.style.marginLeft = 'auto';
        bubble.style.whiteSpace = 'pre-wrap';
        bubble.textContent = text;
        wrap.appendChild(bubble);
        container.appendChild(wrap);
        container.scrollTop = container.scrollHeight;
      } else {
        bubble.style.background = '#111319';
        bubble.style.color = '#e6edf3';
        bubble.style.marginRight = 'auto';
        wrap.appendChild(bubble);
        container.appendChild(wrap);
        
        // Return the bubble element for typing effects
        if (options.typing) {
          return bubble;
        }
        
        // Render markdown for assistant messages
        bubble.innerHTML = renderMarkdown(text);
        container.scrollTop = container.scrollHeight;
      }
    };

    // Typing effect for assistant messages
    const typeMessage = async (bubble, text, speed = 0.1) => { // Changed from 20 to 10ms (faster). Increase for slower, decrease for faster
      const container = document.getElementById('assistant-messages');
      let currentText = '';
      
      for (let i = 0; i < text.length; i++) {
        currentText += text[i];
        bubble.innerHTML = renderMarkdown(currentText);
        if (container) container.scrollTop = container.scrollHeight;
        await new Promise(resolve => setTimeout(resolve, speed));
      }
    };

    // Animated thinking indicator
    const addThinkingMessage = () => {
      const bubble = addMessage('assistant', '', { typing: true });
      if (!bubble) return null;
      
      let dotCount = 0;
      const thinkingInterval = setInterval(() => {
        dotCount = (dotCount % 3) + 1;
        bubble.textContent = 'Thinking' + '.'.repeat(dotCount);
        const container = document.getElementById('assistant-messages');
        if (container) container.scrollTop = container.scrollHeight;
      }, 400);
      
      return { bubble, interval: thinkingInterval };
    };

    // Remove thinking message
    const removeThinkingMessage = (thinkingData) => {
      if (!thinkingData) return;
      clearInterval(thinkingData.interval);
      const container = document.getElementById('assistant-messages');
      if (container && thinkingData.bubble && thinkingData.bubble.parentElement) {
        container.removeChild(thinkingData.bubble.parentElement);
      }
    };

    // Simple markdown renderer
    const renderMarkdown = (text) => {
      // Trim leading/trailing whitespace to avoid extra newlines
      text = text.trim();
      
      // Store original code blocks before any processing
      const codeBlocks = [];
      let codeIndex = 0;
      
      // Extract and store original code blocks with language info
      const textWithPlaceholders = text.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
        const trimmedCode = code.trim();
        codeBlocks.push({ code: trimmedCode, lang: lang || '' });
        return `__CODE_BLOCK_${codeIndex++}__`;
      });
      
      // Escape HTML
      let html = textWithPlaceholders.replace(/&/g, '&amp;')
                                     .replace(/</g, '&lt;')
                                     .replace(/>/g, '&gt;');
      
      // Replace code block placeholders with rendered HTML
      codeIndex = 0;
      html = html.replace(/__CODE_BLOCK_(\d+)__/g, (match, index) => {
        const blockData = codeBlocks[parseInt(index)];
        const originalCode = blockData.code;
        const lang = blockData.lang.toLowerCase();
        
        // Store original code in base64 to avoid any escaping issues
        const base64Code = btoa(unescape(encodeURIComponent(originalCode)));
        
        // Apply syntax highlighting for C/C++ code
        let displayCode;
        if (lang === 'c' || lang === 'cpp' || lang === 'c++') {
          // Use syntax highlighter
          const { applySyntaxHighlight } = window.syntaxHighlighter;
          const fileType = lang === 'c' ? 'C' : 'C++';
          displayCode = applySyntaxHighlight(originalCode, fileType);
        } else {
          // No highlighting - just escape HTML
          displayCode = originalCode.replace(/&/g, '&amp;')
                                     .replace(/</g, '&lt;')
                                     .replace(/>/g, '&gt;');
        }
        
        const langLabel = lang ? `<span style="position:absolute; top:8px; left:12px; font-size:10px; color:#7d8590; text-transform:uppercase; font-weight:600;">${lang}</span>` : '';
        
        return `<div style="position:relative; margin:8px 0;">
          ${langLabel}
          <div style="position:absolute; top:8px; right:8px; display:flex; gap:6px;">
            <button class="code-copy-btn" data-code-b64="${base64Code}" style="padding:4px 8px; background:#21262d; border:1px solid #30363d; color:#f0f6fc; border-radius:4px; cursor:pointer; font-size:11px;">Copy</button>
            <button class="code-replace-btn" data-code-b64="${base64Code}" style="padding:4px 8px; background:#238636; border:1px solid #2ea043; color:#fff; border-radius:4px; cursor:pointer; font-size:11px;">Replace</button>
          </div>
          <pre style="background:#0d1117; padding:12px; border-radius:6px; overflow-x:auto; padding-top:${lang ? '28px' : '12px'};"><code style="font-family:Consolas,Monaco,'Courier New',monospace; font-size:13px; color:#c9d1d9;">${displayCode}</code></pre>
        </div>`;
      });
      
      // Inline code (`code`)
      html = html.replace(/`([^`]+)`/g, '<code style="background:#21262d; padding:2px 6px; border-radius:3px; font-family:Consolas,Monaco,monospace; font-size:13px; color:#f0f6fc;">$1</code>');
      
      // Bold (**text** or __text__)
      html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
      html = html.replace(/__([^_]+)__/g, '<strong>$1</strong>');
      
      // Italic (*text* or _text_)
      html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
      html = html.replace(/_([^_]+)_/g, '<em>$1</em>');
      
      // Headers (### text)
      html = html.replace(/^### (.+)$/gm, '<h3 style="margin:12px 0 8px 0; font-size:16px; font-weight:600; color:#f0f6fc;">$1</h3>');
      html = html.replace(/^## (.+)$/gm, '<h2 style="margin:14px 0 10px 0; font-size:18px; font-weight:600; color:#f0f6fc;">$1</h2>');
      html = html.replace(/^# (.+)$/gm, '<h1 style="margin:16px 0 12px 0; font-size:20px; font-weight:600; color:#f0f6fc;">$1</h1>');
      
      // Lists (- item or * item or 1. item)
      html = html.replace(/^- (.+)$/gm, '<li style="margin-left:20px;">$1</li>');
      html = html.replace(/^\* (.+)$/gm, '<li style="margin-left:20px;">$1</li>');
      html = html.replace(/^\d+\. (.+)$/gm, '<li style="margin-left:20px; list-style-type:decimal;">$1</li>');
      
      // Wrap consecutive <li> in <ul>
      html = html.replace(/(<li[^>]*>.*<\/li>\n?)+/g, (match) => {
        return `<ul style="margin:8px 0; padding-left:0;">${match}</ul>`;
      });
      
      // Line breaks (preserve double newlines as paragraphs)
      html = html.replace(/\n\n/g, '<br><br>');
      
      return html;
    };

    // Prefill a small welcome message with typing effect (only first time)
    const providerName = cfg.provider === 'external' ? (cfg.externalProvider || 'External') : cfg.provider;
    const welcomeText = `Hi — I'm your assistant. Using: ${providerName}. Ask me something or open Settings to change providers.`;
    
    // Check if welcome message has been shown before
    const hasShownWelcome = sessionStorage.getItem('assistantWelcomeShown');
    
    if (!hasShownWelcome) {
      // First time - show typing effect (faster speed: 8ms per character)
      const welcomeBubble = addMessage('assistant', '', { typing: true });
      if (welcomeBubble) {
        typeMessage(welcomeBubble, welcomeText, 8);
      }
      sessionStorage.setItem('assistantWelcomeShown', 'true');
    } else {
      // Already shown - display instantly
      addMessage('assistant', welcomeText);
    }

    // Wire up send button
    const sendBtn = document.getElementById('assistant-send');
    const inputEl = document.getElementById('assistant-input');
    const settingsBtn = document.getElementById('assistant-settings');
    const contextIndicator = document.getElementById('context-indicator');
    const contextText = document.getElementById('context-text');
    const contextClearBtn = document.getElementById('context-clear');

    // Capture selection before it's lost when user clicks on input
    let capturedSelection = '';
    let capturedLineInfo = '';
    
    inputEl.addEventListener('focus', () => {
      const editor = this.editorManager.editor;
      const start = editor.selectionStart;
      const end = editor.selectionEnd;
      const selection = editor.value.substring(start, end);
      
      if (selection) {
        capturedSelection = selection;
        
        // Calculate line numbers
        const textBeforeStart = editor.value.substring(0, start);
        const textBeforeEnd = editor.value.substring(0, end);
        const startLine = (textBeforeStart.match(/\n/g) || []).length + 1;
        const endLine = (textBeforeEnd.match(/\n/g) || []).length + 1;
        
        // Get current file name
        const activeTab = this.tabManager.getActiveTab();
        const fileName = activeTab && activeTab.fileName ? activeTab.fileName : 'Untitled';
        
        // Format context info
        if (startLine === endLine) {
          capturedLineInfo = `${fileName}: ${startLine}`;
        } else {
          capturedLineInfo = `${fileName}: ${startLine}-${endLine}`;
        }
        
        // Show context indicator
        contextText.textContent = capturedLineInfo;
        contextIndicator.style.display = 'block';
      }
    });

    // Handle Enter key to send message (Shift+Enter for new line)
    inputEl.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' && !e.shiftKey) {
        e.preventDefault();
        sendBtn.click();
      }
    });

    // Clear captured selection when input loses focus without sending
    inputEl.addEventListener('blur', () => {
      // Small delay to allow send button click to process
      setTimeout(() => {
        if (!inputEl.value.trim()) {
          capturedSelection = '';
          capturedLineInfo = '';
          contextIndicator.style.display = 'none';
        }
      }, 200);
    });

    // Clear button handler
    contextClearBtn.onclick = () => {
      capturedSelection = '';
      capturedLineInfo = '';
      contextIndicator.style.display = 'none';
    };

    sendBtn.onclick = async () => {
      const text = (inputEl.value || '').trim();
      if (!text) return;
      
      // Use captured selection as context
      let context = '';
      if (capturedSelection) {
        context = `\n\n[Context - Selected Code]:\n\`\`\`\n${capturedSelection}\n\`\`\`\n\n`;
      }
      
      // Clear captured selection and hide indicator after using it
      capturedSelection = '';
      capturedLineInfo = '';
      contextIndicator.style.display = 'none';
      
      // Combine user message with context
      const fullMessage = context ? context + text : text;
      
      // Display only user's text in UI
      addMessage('user', text);
      inputEl.value = '';

      // Get current assistant config
      const cfg = this.getAssistantConfig();
      if (!cfg || cfg.provider === 'none' || cfg.skipped) {
        const bubble = addMessage('assistant', '', { typing: true });
        if (bubble) {
          await typeMessage(bubble, 'Assistant not configured. Please click the settings icon ⚙️ to set up your provider.', 15);
        }
        return;
      }

      // Show animated thinking indicator
      const thinkingData = addThinkingMessage();

      try {
        // All providers go through IPC (main process)
        const result = await window.ipcRenderer.invoke('assistant-chat', {
          provider: cfg.provider,
          message: fullMessage,
          config: cfg
        });

        // Remove the thinking message
        removeThinkingMessage(thinkingData);

        if (result && result.success) {
          const bubble = addMessage('assistant', '', { typing: true });
          if (bubble) {
            await typeMessage(bubble, result.reply, 20);
            
            // Attach event listeners to code action buttons after rendering
            setTimeout(() => {
              attachCodeActionListeners();
            }, 100);
          }
        } else {
          const errorMsg = result && result.error ? result.error : 'Unknown error occurred';
          const bubble = addMessage('assistant', '', { typing: true });
          if (bubble) {
            await typeMessage(bubble, `❌ Error: ${errorMsg}`, 15);
          }
        }
      } catch (err) {
        // Remove thinking message
        removeThinkingMessage(thinkingData);
        
        console.error('Assistant chat error:', err);
        const bubble = addMessage('assistant', '', { typing: true });
        if (bubble) {
          await typeMessage(bubble, `❌ Error: ${err.message || 'Failed to communicate with assistant'}`, 15);
        }
      }
    };

    // Helper to attach event listeners to code action buttons
    const attachCodeActionListeners = () => {
      const container = document.getElementById('assistant-messages');
      if (!container) return;
      
      // Copy button handlers
      container.querySelectorAll('.code-copy-btn').forEach(btn => {
        btn.onclick = () => {
          const base64Code = btn.getAttribute('data-code-b64');
          const code = decodeURIComponent(escape(atob(base64Code)));
          
          try {
            const { clipboard } = require('electron');
            clipboard.writeText(code);
            btn.textContent = 'Copied!';
            setTimeout(() => btn.textContent = 'Copy', 2000);
          } catch (_) {
            if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
              navigator.clipboard.writeText(code)
                .then(() => {
                  btn.textContent = 'Copied!';
                  setTimeout(() => btn.textContent = 'Copy', 2000);
                })
                .catch(() => alert('Failed to copy code'));
            }
          }
        };
      });
      
      // Replace button handlers
      container.querySelectorAll('.code-replace-btn').forEach(btn => {
        btn.onclick = () => {
          const base64Code = btn.getAttribute('data-code-b64');
          const code = decodeURIComponent(escape(atob(base64Code)));
          
          const editor = this.editorManager.editor;
          const start = editor.selectionStart;
          const end = editor.selectionEnd;
          
          if (start !== end) {
            // Replace selected text
            const before = editor.value.substring(0, start);
            const after = editor.value.substring(end);
            editor.value = before + code + after;
            
            // Update tab state
            if (this.tabManager.activeTabId) {
              this.tabManager.handleContentChange(this.tabManager.activeTabId, editor.value);
            }
            
            btn.textContent = 'Replaced!';
            setTimeout(() => btn.textContent = 'Replace', 2000);
          } else {
            // Insert at cursor position
            const before = editor.value.substring(0, start);
            const after = editor.value.substring(start);
            editor.value = before + code + after;
            
            // Update tab state
            if (this.tabManager.activeTabId) {
              this.tabManager.handleContentChange(this.tabManager.activeTabId, editor.value);
            }
            
            btn.textContent = 'Inserted!';
            setTimeout(() => btn.textContent = 'Replace', 2000);
          }
          
          // Focus editor
          editor.focus();
        };
      });
    };

    settingsBtn.onclick = () => {
      // Open the assistant setup modal for reconfiguration
      this.showAssistantSetupGuide((cfg) => {
        // Re-render assistant UI to reflect changes
        this.renderAssistantUI();
      });
    };
  }

  /**
   * Retrieve assistant configuration from localStorage
   * @returns {Object|null}
   */
  getAssistantConfig() {
    try {
      const raw = localStorage.getItem('assistantConfig');
      if (!raw) return null;
      return JSON.parse(raw);
    } catch (err) {
      console.error('Failed to parse assistantConfig from localStorage', err);
      return null;
    }
  }

  /**
   * Save assistant configuration to localStorage
   * @param {Object} cfg
   */
  saveAssistantConfig(cfg) {
    try {
      localStorage.setItem('assistantConfig', JSON.stringify(cfg));
      this.notificationManager.showSuccess('Assistant settings saved');
    } catch (err) {
      console.error('Failed to save assistantConfig', err);
      this.notificationManager.showError('Failed to save assistant settings');
    }
  }

  /**
   * Ensure assistant is configured; if not, show guided setup modal
   */
  async ensureAssistantConfigured() {
    const cfg = this.getAssistantConfig();
    if (cfg && cfg.provider) return cfg;
    // Show setup guide modal and wait for user to complete or cancel
    return new Promise((resolve) => {
      this.showAssistantSetupGuide(resolve);
    });
  }

  /**
   * Render a first-time setup modal for Assistant configuration.
   * Calls the done callback with saved config or null if cancelled.
   */
  showAssistantSetupGuide(done) {
    // Load existing config to pre-fill form
    const existingConfig = this.getAssistantConfig() || {};
    
    // Modal overlay
    const modal = document.createElement('div');
    modal.style.cssText = `
      position: fixed; top:0; left:0; width:100%; height:100%;
      background: rgba(0,0,0,0.6); display:flex; align-items:center; justify-content:center; z-index:10001;
    `;

    const dialog = document.createElement('div');
    dialog.style.cssText = `
      width: 720px; max-width: 95%; background: #fff; border-radius:8px; padding:20px; box-shadow:0 8px 30px rgba(0,0,0,0.3);
      font-family: sans-serif; color: #222;
    `;

    dialog.innerHTML = `
      <h2 style="margin-top:0">Assistant setup</h2>
      <p>Choose how you'd like to connect the Assistant. You can use Ollama, an external API (ChatGPT5, Deepseek, or other), or point to a local GGUF model on your machine.</p>
      <div style="display:flex; gap:12px; margin-top:12px;">
        <label style="flex:1; border:1px solid #e2e2e2; padding:12px; border-radius:6px; cursor:pointer;" id="assist-opt-ollama">
          <input type="radio" name="assist-provider" value="ollama" style="margin-right:8px"> Ollama (local or remote)
          <div style="font-size:12px; color:#555; margin-top:6px">Connect to an Ollama server (default: http://localhost:11434)</div>
        </label>
        <label style="flex:1; border:1px solid #e2e2e2; padding:12px; border-radius:6px; cursor:pointer;" id="assist-opt-external">
          <input type="radio" name="assist-provider" value="external" style="margin-right:8px"> External API
          <div style="font-size:12px; color:#555; margin-top:6px">Use ChatGPT5, Deepseek or other hosted APIs (requires API key)</div>
        </label>
        <label style="flex:1; border:1px solid #e2e2e2; padding:12px; border-radius:6px; cursor:pointer;" id="assist-opt-local">
          <input type="radio" name="assist-provider" value="local" style="margin-right:8px"> Local GGUF model
          <div style="font-size:12px; color:#555; margin-top:6px">Point to a GGUF model file on your computer</div>
        </label>
      </div>

      <div id="assist-extra" style="margin-top:16px"></div>

      <div style="display:flex; justify-content:flex-end; gap:8px; margin-top:18px;">
        <button id="assist-skip" style="padding:8px 12px; background:transparent; border:1px solid #cfcfcf; border-radius:6px; cursor:pointer">Skip for now</button>
        <button id="assist-cancel" style="padding:8px 12px; background:#ddd; border:none; border-radius:6px; cursor:pointer">Cancel</button>
        <button id="assist-save" style="padding:8px 12px; background:#007acc; color:white; border:none; border-radius:6px; cursor:pointer">Save</button>
      </div>
    `;

    modal.appendChild(dialog);
    document.body.appendChild(modal);

    const extra = dialog.querySelector('#assist-extra');

    const clearExtra = () => { extra.innerHTML = ''; };

    const makeInputRow = (labelText, inputId, placeholder = '') => {
      const row = document.createElement('div');
      row.style.cssText = 'display:flex; flex-direction:column; gap:6px; margin-top:8px;';
      row.innerHTML = `
        <label style="font-size:13px; color:#333">${labelText}</label>
        <input id="${inputId}" style="padding:8px; border:1px solid #e2e2e2; border-radius:4px; font-size:13px" placeholder="${placeholder}">
      `;
      return row;
    };

    const providerRadios = dialog.querySelectorAll('input[name="assist-provider"]');
    const optOllama = dialog.querySelector('#assist-opt-ollama');
    const optExternal = dialog.querySelector('#assist-opt-external');
    const optLocal = dialog.querySelector('#assist-opt-local');

    const selectProvider = (value) => {
      providerRadios.forEach(r => r.checked = (r.value === value));
      optOllama.style.borderColor = value === 'ollama' ? '#007acc' : '#e2e2e2';
      optExternal.style.borderColor = value === 'external' ? '#007acc' : '#e2e2e2';
      optLocal.style.borderColor = value === 'local' ? '#007acc' : '#e2e2e2';

      clearExtra();
      if (value === 'ollama') {
        extra.appendChild(makeInputRow('Ollama host (include protocol)', 'ollama-host', 'http://localhost:11434'));
        extra.appendChild(makeInputRow('System prompt (optional)', 'system-prompt', 'You are a helpful assistant...'));
        
        // Pre-fill with saved values
        setTimeout(() => {
          const hostInput = document.getElementById('ollama-host');
          const systemInput = document.getElementById('system-prompt');
          if (hostInput && existingConfig.ollamaHost) {
            hostInput.value = existingConfig.ollamaHost;
          }
          if (systemInput && existingConfig.systemPrompt) {
            systemInput.value = existingConfig.systemPrompt;
          }
        }, 0);
      } else if (value === 'external') {
        const selRow = document.createElement('div');
        selRow.style.cssText = 'display:flex; gap:8px; align-items:center; margin-top:8px;';
        selRow.innerHTML = `
          <label style="font-size:13px">Provider</label>
          <select id="external-provider" style="padding:6px; border:1px solid #e2e2e2; border-radius:4px">
            <option value="ChatGPT5">ChatGPT5</option>
            <option value="Deepseek">Deepseek</option>
            <option value="Other">Other</option>
          </select>
        `;
        extra.appendChild(selRow);
        extra.appendChild(makeInputRow('API Key', 'external-api-key', 'sk-...'));
        extra.appendChild(makeInputRow('System prompt (optional)', 'system-prompt', 'You are a helpful assistant...'));
        
        // Pre-fill with saved values
        setTimeout(() => {
          const providerSelect = document.getElementById('external-provider');
          const apiKeyInput = document.getElementById('external-api-key');
          const systemInput = document.getElementById('system-prompt');
          if (providerSelect && existingConfig.externalProvider) {
            providerSelect.value = existingConfig.externalProvider;
          }
          if (apiKeyInput && existingConfig.apiKey) {
            apiKeyInput.value = existingConfig.apiKey;
          }
          if (systemInput && existingConfig.systemPrompt) {
            systemInput.value = existingConfig.systemPrompt;
          }
        }, 0);
      } else if (value === 'local') {
        const row = document.createElement('div');
        row.style.cssText = 'display:flex; gap:8px; align-items:center; margin-top:8px;';
        row.innerHTML = `
          <input id="local-model-path" placeholder="Select GGUF model file..." style="flex:1; padding:8px; border:1px solid #e2e2e2; border-radius:4px" readonly>
          <button id="local-browse" style="padding:8px 10px; border-radius:4px; border:none; background:#007acc; color:white; cursor:pointer">Browse</button>
        `;
        extra.appendChild(row);

        // Browse handler using IPC
        setTimeout(() => {
          const browseBtn = document.getElementById('local-browse');
          const pathInput = document.getElementById('local-model-path');
          
          // Pre-fill with saved value
          if (pathInput && existingConfig.localModelPath) {
            pathInput.value = existingConfig.localModelPath;
          }
          
          if (browseBtn) {
            browseBtn.onclick = async () => {
              try {
                const result = await window.ipcRenderer.invoke('select-llm-file');
                if (result && result.filePath) {
                  pathInput.value = result.filePath;
                }
              } catch (err) {
                console.error('Error selecting model file', err);
                this.notificationManager.showError('Unable to open file selector');
              }
            };
          }
        }, 0);
        
        // Add context size configuration
        const contextRow = document.createElement('div');
        contextRow.style.cssText = 'margin-top:12px;';
        contextRow.innerHTML = `
          <label style="display:block; margin-bottom:4px; color:#333; font-size:13px">Context Size (tokens):</label>
          <input id="context-size" type="number" placeholder="8192" style="width:100%; padding:8px; border:1px solid #e2e2e2; border-radius:4px" value="8192" min="512" max="32768">
          <div style="margin-top:4px; font-size:11px; color:#666">Lower values use less VRAM. Recommended: 2048-8192. Default model max: 40960</div>
        `;
        extra.appendChild(contextRow);
        
        // Add GPU layers configuration
        const gpuRow = document.createElement('div');
        gpuRow.style.cssText = 'margin-top:12px;';
        gpuRow.innerHTML = `
          <label style="display:block; margin-bottom:4px; color:#333; font-size:13px">GPU Layers (0 = CPU only, -1 = all layers):</label>
          <input id="gpu-layers" type="number" placeholder="0" style="width:100%; padding:8px; border:1px solid #e2e2e2; border-radius:4px" value="0">
          <div style="margin-top:4px; font-size:11px; color:#666">Higher values offload more layers to GPU for faster inference. Use -1 to offload all layers.</div>
        `;
        extra.appendChild(gpuRow);
        
        // Add system prompt field for local models too
        extra.appendChild(makeInputRow('System prompt (optional)', 'system-prompt', 'You are a helpful assistant...'));
        
        // Pre-fill all settings
        setTimeout(() => {
          const systemInput = document.getElementById('system-prompt');
          const gpuLayersInput = document.getElementById('gpu-layers');
          const contextSizeInput = document.getElementById('context-size');
          
          if (systemInput && existingConfig.systemPrompt) {
            systemInput.value = existingConfig.systemPrompt;
          }
          if (gpuLayersInput && existingConfig.gpuLayers !== undefined) {
            gpuLayersInput.value = existingConfig.gpuLayers;
          }
          if (contextSizeInput && existingConfig.contextSize !== undefined) {
            contextSizeInput.value = existingConfig.contextSize;
          }
        }, 0);
      }
    };

    // Click handlers for the option cards as well
    optOllama.onclick = () => selectProvider('ollama');
    optExternal.onclick = () => selectProvider('external');
    optLocal.onclick = () => selectProvider('local');

    // Pre-select provider based on saved config, or default to external
    const savedProvider = existingConfig.provider && existingConfig.provider !== 'none' 
      ? existingConfig.provider 
      : 'external';
    selectProvider(savedProvider);

    // Buttons
    const btnSave = dialog.querySelector('#assist-save');
    const btnCancel = dialog.querySelector('#assist-cancel');
    const btnSkip = dialog.querySelector('#assist-skip');

    const closeModal = (result) => {
      try { document.body.removeChild(modal); } catch (_) {}
      if (done) done(result);
    };

    btnCancel.onclick = () => closeModal(null);
    btnSkip.onclick = () => {
      // Save a lightweight config indicating user skipped
      const cfg = { provider: 'none', skipped: true };
      this.saveAssistantConfig(cfg);
      closeModal(cfg);
    };

    btnSave.onclick = () => {
      const selected = Array.from(providerRadios).find(r => r.checked);
      if (!selected) {
        this.notificationManager.showError('Please select a provider');
        return;
      }
      const provider = selected.value;
      const cfg = { provider };
      
      // Get system prompt if it exists
      const systemPromptEl = document.getElementById('system-prompt');
      if (systemPromptEl && systemPromptEl.value.trim()) {
        cfg.systemPrompt = systemPromptEl.value.trim();
      }
      
      if (provider === 'ollama') {
        const hostEl = document.getElementById('ollama-host');
        cfg.ollamaHost = hostEl && hostEl.value ? hostEl.value.trim() : 'http://localhost:11434';
      } else if (provider === 'external') {
        const prov = document.getElementById('external-provider');
        const key = document.getElementById('external-api-key');
        cfg.externalProvider = prov ? prov.value : 'ChatGPT5';
        cfg.apiKey = key ? key.value.trim() : '';
        if (!cfg.apiKey) {
          this.notificationManager.showError('Please enter an API key for the external provider');
          return;
        }
      } else if (provider === 'local') {
        const pathEl = document.getElementById('local-model-path');
        const gpuLayersEl = document.getElementById('gpu-layers');
        const contextSizeEl = document.getElementById('context-size');
        
        cfg.localModelPath = pathEl ? pathEl.value : '';
        if (!cfg.localModelPath) {
          this.notificationManager.showError('Please choose a local GGUF model file');
          return;
        }
        
        // Save GPU layers setting (default to 0 if not specified)
        cfg.gpuLayers = gpuLayersEl && gpuLayersEl.value !== '' ? parseInt(gpuLayersEl.value, 10) : 0;
        
        // Save context size setting (default to 8192 if not specified)
        cfg.contextSize = contextSizeEl && contextSizeEl.value !== '' ? parseInt(contextSizeEl.value, 10) : 8192;
      }

      // Persist and close
      this.saveAssistantConfig(cfg);
      // Notify main process in case it needs to warm things up
      try { window.ipcRenderer.send('assistant-config-updated', cfg); } catch (_) {}
      closeModal(cfg);
    };

    // Dismiss modal when clicking outside the dialog
    modal.onclick = (e) => { if (e.target === modal) closeModal(null); };
  }
  /**
   * Open workspace and update search manager
   */
  async openWorkspace() {
    const result = await this.fileOpsManager.openWorkspace();
    if (result && result.success) {
      this.searchManager.setWorkspacePath(result.folderPath);
    }
  }

  /**
   * Open search result with proper line navigation
   * @param {string} filePath - File path
   * @param {number} lineNumber - Line number
   */
  async openSearchResult(filePath, lineNumber) {
    console.log('Opening search result:', filePath, 'at line', lineNumber);
    
    try {
      const normalizedPath = filePath.replace(/\\\\/g, '\\');
      
      const result = await window.ipcRenderer.invoke('read-file', normalizedPath);
      console.log('Search result read result:', result);
      
      if (result.success) {
        let tabId;
        
        if (result.warning === 'encoding') {
          console.log('Search result: Encoding warning detected, showing dialog...');
          const userChoice = await this.notificationManager.showEncodingWarningDialog();
          console.log('Search result: User choice:', userChoice);
          
          if (userChoice === 'no') {
            console.log('Search result: User chose not to open file');
            return;
          } else if (userChoice === 'yes') {
            console.log('Search result: User chose to open file anyway');
            const forceResult = await window.ipcRenderer.invoke('force-open-file', normalizedPath);
            console.log('Search result: Force open result:', forceResult);
            
            if (forceResult.success) {
              tabId = this.fileOpsManager.openFileInTab(normalizedPath, forceResult.content, forceResult.fileName, {
                isPartial: forceResult.isPartial,
                totalSize: forceResult.totalSize,
                loadedSize: forceResult.loadedSize,
                encodingWarning: forceResult.encodingWarning
              });
              
              this.notificationManager.showWarning(`Opened ${forceResult.fileName} at line ${lineNumber} with encoding warnings`);
            } else {
              this.notificationManager.showError('Failed to open file: ' + forceResult.error);
              return;
            }
          }
        } else {
          console.log('Search result: No warnings, opening file normally');
          tabId = this.fileOpsManager.openFileInTab(normalizedPath, result.content, result.fileName, {
            isPartial: result.isPartial,
            totalSize: result.totalSize,
            loadedSize: result.loadedSize,
            encodingWarning: result.encodingWarning
          });
          
          this.notificationManager.showSuccess(`Opened ${result.fileName} at line ${lineNumber}`);
        }
        
        // Switch to explorer view and wait for editor to be ready
        this.showExplorer();
        
        // Wait for the tab to switch and editor to update, then jump to line
        setTimeout(() => {
          this.editorManager.jumpToLine(lineNumber);
        }, 200);
        
      } else {
        this.notificationManager.showError('Error opening file: ' + (result.error || 'Unknown error'));
      }
    } catch (error) {
      console.error('Error opening search result:', error);
      this.notificationManager.showError('Error opening search result: ' + error.message);
    }
  }
}

// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
  window.uiController = new UIController();
});

module.exports = UIController;