/*   
   pfodWebDebug.js
 * (c)2025 Forward Computing and Control Pty. Ltd.
 * NSW Australia, www.forward.com.au
 * This code is not warranted to be fit for any purpose. You may only use it at your own risk.
 * This generated code may be freely used for both private and commercial use
 * provided this copyright is maintained.
 */

// Canvas Drawing Application
// Dependencies are loaded as global variables via script tags
// MergeAndRedraw and DrawingManager are available on window object


// JavaScript version constant loaded globally from version.js
// JS_VERSION is available as a global variable

// DrawingViewer class to encapsulate all viewer functionality
class DrawingViewer {
  constructor(options = {}) {
    console.log('[PFODWEB_DEBUG] DrawingViewer constructor called - NEW INSTANCE CREATED');
    console.log('[PFODWEB_DEBUG] URL:', window.location.href);
    console.log('[PFODWEB_DEBUG] Referrer:', document.referrer);
    console.log('[PFODWEB_DEBUG] Constructor options:', options);

    // Check if we have a pre-connected ConnectionManager from connectWithPrompt
    if (window.pfodConnectionManager) {
      console.log('[PFODWEB_DEBUG] Using pre-connected ConnectionManager from connectWithPrompt');
      // Use the existing ConnectionManager directly - it already has all protocol info and connection details
      this.connectionManager = window.pfodConnectionManager;
      this.protocol = this.connectionManager.protocol;
      this.targetIP = this.connectionManager.config?.targetIP || null;
      this.baudRate = this.connectionManager.config?.baudRate || 115200;
      console.log('[PFODWEB_DEBUG] Set protocol from ConnectionManager:', this.protocol);
      // Keep the global for error messages - will be cleared on page reload
    } else {
      // Extract protocol, target IP, and baud rate from URL parameters (fallback for HTTP with targetIP in URL)
      this.protocol = this.extractProtocol();
      this.targetIP = this.extractTargetIP();
      this.baudRate = this.extractBaudRate();
      console.log('[PFODWEB_DEBUG] Protocol:', this.protocol);
      console.log('[PFODWEB_DEBUG] Target IP:', this.targetIP);
      console.log('[PFODWEB_DEBUG] Baud Rate:', this.baudRate);

      // Initialize ConnectionManager with selected protocol
      this.connectionManager = new ConnectionManager({
        protocol: this.protocol,
        targetIP: this.targetIP,
        baudRate: this.baudRate
      });
    }
    console.log('[PFODWEB_DEBUG] ConnectionManager initialized with protocol:', this.protocol);

    // DOM Elements
    this.canvas = document.getElementById('drawing-canvas');
    this.ctx = this.canvas.getContext('2d');
    this.canvasContainer = document.getElementById('canvas-container');

    // Application State - each viewer has its own isolated state
    this.updateTimer = null;
    this.isUpdating = false; // Start with updates disabled until first load completes
    this.js_ver = window.JS_VERSION; // Client JavaScript version

    // Request queue system - isolated per viewer
    this.requestQueue = [];
    // Use simple boolean for queue processing state (single-threaded JavaScript environment)
    this._isProcessingQueue = false;
    console.log(`[SENTREQUEST] CLEARED: on creation`);
    this.sentRequest = null; // Currently in-flight request
    this.currentRetryCount = 0;
    // MAX_RETRIES will be set based on connection manager's protocol
    // It's accessed dynamically via this.connectionManager.getMaxRetries()

    // Request tracking for touch vs insertDwg - isolated per viewer
    this.requestTracker = {
      touchRequests: new Set(), // Track touch-triggered requests
      insertDwgRequests: new Set() // Track insertDwg-triggered requests
    };

    // Unified shadow processing system for all request types (always active)
    this.shadowProcessing = {
      responses: new Map(), // drawingName -> response data
      requestType: null, // 'main', 'refresh', 'touch', 'insertDwg', etc.
      shadowDrawingManager: new DrawingManager() // shadow copy of drawing manager
    };

    // Transformation state for push/pop operations - used during JSON processing
    this.transformStack = []; // Stack to store transformation states

    // Map to store all active touchZones by command - now managed by DrawingManager
    // this.touchZonesByCmd = {}; // Format: {cmd: touchZone} - DEPRECATED

    // Window dimension tracking for change detection and saving
    this.lastLogicalWidth = null;
    this.lastLogicalHeight = null;
    this.lastWindowWidth = null;
    this.lastWindowHeight = null;

    // Load previous window dimensions from storage to pass to redraw
    const initialDimensions = this.loadPreviousDimensions();

    // Initialize our tracking with loaded dimensions
    if (initialDimensions) {
      this.lastLogicalWidth = initialDimensions.logicalWidth;
      this.lastLogicalHeight = initialDimensions.logicalHeight;
      this.lastWindowWidth = initialDimensions.windowWidth;
      this.lastWindowHeight = initialDimensions.windowHeight;
    }

    // Touch state for handling mouse/touch events - instance-specific
    this.touchState = {
      isDown: false,
      wasDown: false,
      startX: 0,
      startY: 0,
      lastX: 0,
      lastY: 0,
      startTime: 0,
      longPressTimer: null,
      targetTouchZone: null,
      hasEnteredZones: new Set(),
      hasDragged: false,
      lastSentTouchType: null
    };

    // Current identifier for touchZone requests - defaults to 'pfodWeb'
    this.currentIdentifier = 'pfodWeb';

    // Command stack for back navigation - stores previous commands that opened displays
    this.commandStack = []; // Stack of commands that opened new displays
    this.currentRefreshCmd = null; // Command to resend when reload button is clicked
    this.currentRefreshCmdType = null; // Type of the current refresh command (main, mainMenu, etc)
    this.currentlyDisplayingDwg = false; // Set to true when {+ received, false when non-dwgUpdate received
    this.rawDataScrollLocked = false; // Track if raw data scroll is locked
    this.rawDataPollingInterval = null; // Interval handle for raw data polling

    // Chart display state
    this.currentChart = null; // Current chart instance
    this.currentChartLabels = null; // Current chart field labels
    this.currentChartFieldCount = null; // Number of fields in current chart
    this.currentChartLimit = null; // Data point limit for current chart

    // Queue for holding responses while mouse is down (to prevent flashing)
    this.pendingResponseQueue = [];

    // Update collection for atomic refresh processing
    // updateCollection removed - using unified shadow processing system

    // Text input dialog state
    this.textInputDialog = null;

    // Transformation state for push/pop operations - used during JSON processing
    this.currentTransform = {
      x: 0,
      y: 0,
      scale: 1.0
    }; // Current transformation (initial state)

    // Create isolated MergeAndRedraw instance for this viewer
    // Create Redraw instance with canvas and context - uses its own local data
    this.redraw = new window.Redraw(this.canvas, this.ctx, initialDimensions);

    // Create DrawingDataProcessor instance for this viewer
    this.drawingDataProcessor = new window.DrawingDataProcessor(this);

    // Initialize Message Collector and Viewer
    this.initializeMessageViewer();

    // Set up event listeners using pfodWebMouse.js
    this.setupEventListeners();

    // Set initial CSS mode to canvas display
    document.body.className = 'canvas-mode';
    console.log('[DRAWING_VIEWER] Canvas mode CSS enabled');
  }

  /**
   * Initialize message collector and raw message viewer
   */
  initializeMessageViewer() {
    try {
      // Skip message viewer and collector initialization if designer parameter is present
      const urlParams = new URLSearchParams(window.location.search);
      if (urlParams.has('designer')) {
        console.log('[PFODWEB_DEBUG] Designer mode detected - skipping message viewer and collector initialization');
        return;
      }

      // Create message collector if not already created
      if (!window.messageCollector) {
        window.messageCollector = new MessageCollector(500);
        ConnectionManager.setMessageCollector(window.messageCollector);
        console.log('[PFODWEB_DEBUG] Message collector created and set on ConnectionManager');
      }

      // Create CSV collector if not already created
      if (!window.csvCollector) {
        window.csvCollector = new CSVCollector();
        ConnectionManager.setCSVCollector(window.csvCollector);
        console.log('[PFODWEB_DEBUG] CSV collector created and set on ConnectionManager');
      }

      // Create raw data collector if not already created
      console.log('[PFODWEB_DEBUG] Checking rawDataCollector - exists?', !!window.rawDataCollector, 'RawDataCollector class exists?', typeof RawDataCollector);
      if (!window.rawDataCollector) {
        try {
          console.log('[PFODWEB_DEBUG] Creating RawDataCollector instance...');
          window.rawDataCollector = new RawDataCollector();
          console.log('[PFODWEB_DEBUG] RawDataCollector instance created successfully');
          ConnectionManager.setRawDataCollector(window.rawDataCollector);
          console.log('[PFODWEB_DEBUG] Raw data collector created and set on ConnectionManager');
        } catch (e) {
          console.error('[PFODWEB_DEBUG] Error creating RawDataCollector:', e);
        }
      } else {
        console.log('[PFODWEB_DEBUG] RawDataCollector already exists, not creating new instance');
      }

      // Create chart display if not already created
      if (!window.chartDisplay) {
        try {
          console.log('[PFODWEB_DEBUG] Creating ChartDisplay instance...');
          window.chartDisplay = new ChartDisplay();
          console.log('[PFODWEB_DEBUG] ChartDisplay instance created successfully');
        } catch (e) {
          console.error('[PFODWEB_DEBUG] Error creating ChartDisplay:', e);
        }
      } else {
        console.log('[PFODWEB_DEBUG] ChartDisplay already exists, not creating new instance');
      }

      // Create raw message viewer
      window.rawMessageViewer = new RawMessageViewer(window.messageCollector, 'raw-message-viewer');
      window.rawMessageViewer.initialize();
      console.log('[PFODWEB_DEBUG] Raw message viewer initialized');

      // Keyboard shortcut disabled - now only accessible via toolbar menu
      // // Add a keyboard shortcut to toggle the viewer (Ctrl+Shift+M)
      // document.addEventListener('keydown', (event) => {
      //   if (event.ctrlKey && event.shiftKey && event.key === 'M') {
      //     event.preventDefault();
      //     if (window.rawMessageViewer) {
      //       window.rawMessageViewer.toggle();
      //     }
      //   }
      // });
      console.log('[PFODWEB_DEBUG] Keyboard shortcut Ctrl+Shift+M disabled - access via toolbar menu');
    } catch (error) {
      console.error('[PFODWEB_DEBUG] Error initializing message viewer:', error);
    }
  }

  // Extract protocol from URL parameters
  extractProtocol() {
    console.log(`[PROTOCOL] Extracting protocol from URL parameters`);
    console.log(`[PROTOCOL] window.location.search: ${window.location.search}`);

    // Infer protocol from parameter presence
    const urlParams = new URLSearchParams(window.location.search);

    // Check for protocol-specific parameters
    if (urlParams.has('serial')) {
      console.log(`[PROTOCOL] Found 'serial' parameter - using Serial protocol`);
      return 'serial';
    } else if (urlParams.has('ble')) {
      console.log(`[PROTOCOL] Found 'ble' parameter - using BLE protocol`);
      return 'ble';
    } else if (urlParams.has('targetIP')) {
      console.log(`[PROTOCOL] Found 'targetIP' parameter - using HTTP protocol`);
      return 'http';
    }

    // Default to HTTP if not specified
    console.log(`[PROTOCOL] No protocol-specific parameters found, defaulting to 'http'`);
    return 'http';
  }

  // Extract target IP address from URL parameters or global variable
  extractTargetIP() {
    console.log(`[TARGET_IP] Extracting target IP from URL or global variable`);
    console.log(`[TARGET_IP] window.PFOD_TARGET_IP: ${window.PFOD_TARGET_IP}`);
    console.log(`[TARGET_IP] window.location.search: ${window.location.search}`);

    // First check if global variable was set by index.html
    if (window.PFOD_TARGET_IP) {
      console.log(`[TARGET_IP] Using global variable: ${window.PFOD_TARGET_IP}`);
      return window.PFOD_TARGET_IP;
    }

    // Extract from URL parameters (e.g., ?targetIP=192.168.1.100)
    const urlParams = new URLSearchParams(window.location.search);
    const targetIP = urlParams.get('targetIP');
    console.log(`[TARGET_IP] URL parameter targetIP: ${targetIP}`);

    if (targetIP) {
      // Validate IP address format
      const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
      if (ipRegex.test(targetIP)) {
        const parts = targetIP.split('.');
        // Validate IP address ranges
        const isValidIP = parts.every(part => {
          const num = parseInt(part, 10);
          return num >= 0 && num <= 255;
        });
        if (isValidIP) {
          console.log(`[TARGET_IP] Valid IP found: ${targetIP}`);
          return targetIP;
        } else {
          console.log(`[TARGET_IP] Invalid IP ranges in: ${targetIP}`);
        }
      } else {
        console.log(`[TARGET_IP] Invalid IP format: ${targetIP}`);
      }
    }

    console.log(`[TARGET_IP] No valid target IP found, returning null`);
    return null;
  }

  // Extract baud rate from URL parameters
  extractBaudRate() {
    console.log(`[BAUD_RATE] Extracting baud rate from URL parameters`);
    console.log(`[BAUD_RATE] window.location.search: ${window.location.search}`);

    // Extract from URL parameters - can be ?serial=115200 or standalone ?baudRate=115200
    const urlParams = new URLSearchParams(window.location.search);

    // First check if serial parameter has a value (e.g., ?serial=115200)
    const serialValue = urlParams.get('serial');
    console.log(`[BAUD_RATE] URL parameter serial: ${serialValue}`);

    if (serialValue && serialValue !== '') {
      // Parse and validate baud rate from serial parameter value
      const parsedBaudRate = parseInt(serialValue, 10);
      const validBaudRates = [9600, 19200, 38400, 57600, 74880, 115200];

      if (validBaudRates.includes(parsedBaudRate)) {
        console.log(`[BAUD_RATE] Valid baud rate found in serial parameter: ${parsedBaudRate}`);
        return parsedBaudRate;
      } else {
        console.log(`[BAUD_RATE] Invalid baud rate in serial parameter: ${serialValue}, defaulting to 115200`);
      }
    }

    // Fallback to baudRate parameter (for backwards compatibility)
    const baudRate = urlParams.get('baudRate');
    console.log(`[BAUD_RATE] URL parameter baudRate: ${baudRate}`);

    if (baudRate) {
      // Parse and validate baud rate
      const parsedBaudRate = parseInt(baudRate, 10);
      const validBaudRates = [9600, 19200, 38400, 57600, 74880, 115200];

      if (validBaudRates.includes(parsedBaudRate)) {
        console.log(`[BAUD_RATE] Valid baud rate found: ${parsedBaudRate}`);
        return parsedBaudRate;
      } else {
        console.log(`[BAUD_RATE] Invalid baud rate: ${baudRate}, defaulting to 9600`);
      }
    }

    // Default to 115200 if not specified or invalid
    console.log(`[BAUD_RATE] No valid baud rate found, defaulting to 115200`);
    return 115200;
  }

  // Build endpoint URL with target IP
  buildEndpoint(path) {
    console.log(`[ENDPOINT] buildEndpoint called with path: ${path}, targetIP: ${this.targetIP}`);
    if (this.targetIP) {
      const fullEndpoint = `http://${this.targetIP}${path}`;
      console.log(`[ENDPOINT] Built full endpoint: ${fullEndpoint}`);
      return fullEndpoint;
    }
    console.log(`[ENDPOINT] No targetIP, returning relative path: ${path}`);
    return path; // Fallback to relative URL
  }

  // Build fetch options with appropriate CORS settings
  buildFetchOptions(additionalHeaders = {}) {
    return {
      headers: {
        'Accept': 'application/json',
        'X-Requested-With': 'XMLHttpRequest',
        ...additionalHeaders
      },
      mode: this.targetIP ? 'cors' : 'same-origin',
      credentials: this.targetIP ? 'omit' : 'same-origin',
      cache: 'no-cache'
    };
  }

  // Get context-specific storage key based on referrer and current URL
  getDimensionStorageKey() {
    const isIframe = window.self !== window.top;
    const referrer = document.referrer;

    if (isIframe && referrer) {
      // Extract page name from referrer for iframe context
      const referrerPath = new URL(referrer).pathname;
      const pageName = referrerPath.split('/').pop().split('.')[0] || 'unknown';
      return `pfodWeb_dimensions_iframe_${pageName}`;
    } else {
      // Main window context
      return 'pfodWeb_dimensions_main';
    }
  }

  // Load previous dimensions from localStorage to pass to redraw
  loadPreviousDimensions() {
    try {
      const storageKey = this.getDimensionStorageKey();
      const saved = localStorage.getItem(storageKey);
      if (saved) {
        const dims = JSON.parse(saved);
        console.log(`[DIMENSIONS] Loaded previous dimensions from ${storageKey}: logical=${dims.logicalWidth}x${dims.logicalHeight}, window=${dims.windowWidth}x${dims.windowHeight}`);
        return dims;
      } else {
        console.log(`[DIMENSIONS] No previous dimensions found for ${storageKey}`);
        return null;
      }
    } catch (e) {
      console.log('[DIMENSIONS] Error loading dimensions:', e);
      return null;
    }
  }

  // Handle resize with dimension change detection and saving
  handleResize() {
    console.log('[RESIZE] handleResize() called, isInChartMode:', window.isInChartMode, 'chartDisplay exists:', !!window.chartDisplay);

    // Check if in chart mode - use different resize handling
    if (window.isInChartMode && window.chartDisplay) {
      console.log('[RESIZE] In chart mode - delegating to ChartDisplay.handleResize()');
      window.chartDisplay.handleResize(this.canvas);
      return;
    }

    console.log('[RESIZE] Not in chart mode - using drawing resize logic');

    // Skip resize if raw data is displayed (canvas is hidden)
    if (this.canvas.style.display === 'none') {
      console.log('[RESIZE] Canvas is hidden (raw data display) - skipping resize');
      return;
    }

    // Get current drawing data to determine logical dimensions
    const logicalDrawingData = this.redraw.redrawDrawingManager.getCurrentDrawingData();
    if (!logicalDrawingData) {
      console.warn('No drawing data available for resize handling');
      this.redraw.resizeCanvas(this.touchState);
      return;
    }

    // Get current dimensions
    const logicalWidth = Math.min(Math.max(logicalDrawingData.x, 1), 255);
    const logicalHeight = Math.min(Math.max(logicalDrawingData.y, 1), 255);
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight - 40; // Subtract 40px for button bar to keep it visible

    // Check if dimensions have changed
    const dimensionsChanged = (
      this.lastLogicalWidth !== logicalWidth ||
      this.lastLogicalHeight !== logicalHeight ||
      this.lastWindowWidth !== windowWidth ||
      this.lastWindowHeight !== windowHeight
    );

    // Update tracking and save if dimensions changed
    if (dimensionsChanged) {
      console.log(`[DIMENSIONS] Dimensions changed - saving: logical=${logicalWidth}x${logicalHeight}, window=${windowWidth}x${windowHeight}`);

      this.lastLogicalWidth = logicalWidth;
      this.lastLogicalHeight = logicalHeight;
      this.lastWindowWidth = windowWidth;
      this.lastWindowHeight = windowHeight;

      // Save to localStorage
      this.saveDimensions(logicalWidth, logicalHeight, windowWidth, windowHeight);
    }

    // Call redraw to handle the actual resizing and redraw elements
    this.redraw.resizeCanvas(this.touchState);
    this.redraw.performRedraw();
  }

  // Save current dimensions to localStorage for future reloads
  saveDimensions(logicalWidth, logicalHeight, windowWidth, windowHeight) {
    try {
      const dims = {
        logicalWidth: logicalWidth,
        logicalHeight: logicalHeight,
        windowWidth: windowWidth,
        windowHeight: windowHeight
      };
      const storageKey = this.getDimensionStorageKey();
      localStorage.setItem(storageKey, JSON.stringify(dims));
      console.log(`[DIMENSIONS] Saved dimensions to ${storageKey}: logical=${logicalWidth}x${logicalHeight}, window=${windowWidth}x${windowHeight}`);
    } catch (e) {
      console.log('[DIMENSIONS] Error saving dimensions:', e);
    }
  }

  // Set up event listeners for the canvas - delegates to pfodWebMouse.js
  setupEventListeners() {
    // Mouse and touch event handling is now in pfodWebMouse.js
    if (typeof window.pfodWebMouse !== 'undefined') {
      window.pfodWebMouse.setupEventListeners(this);
    } else {
      console.error('pfodWebMouse.js not loaded - mouse events will not work');
    }

    // Setup toolbar button listeners
    this.setupToolbarButtons();

    // Context menu disabled - raw data now only accessible via toolbar menu
    // this.setupContextMenu();
  }

  /**
   * Setup toolbar button listeners
   */
  setupToolbarButtons() {
    // Skip toolbar setup if designer parameter is present
    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.has('designer')) {
      console.log('[TOOLBAR] Designer mode detected - skipping toolbar button setup');
      return;
    }

    // Left arrow button - pop previous command from stack or request main menu if stack is empty
    const btnLeftArrow = document.getElementById('btn-left-arrow');
    if (btnLeftArrow) {
      btnLeftArrow.addEventListener('click', () => {
        console.log('[TOOLBAR] Left arrow clicked - going back one menu');

        // If in chart mode, stop polling immediately (regardless of back target)
        if (window.isInChartMode) {
          console.log('[TOOLBAR] In chart mode - stopping chart polling immediately');
          this.exitChartDisplay();
        }

        let cmdToSend;
        if (this.commandStack.length > 0) {
          cmdToSend = this.commandStack.pop();
          console.log('[TOOLBAR] Popped command from stack:', cmdToSend);
        } else {
          cmdToSend = '{.}';
          console.log('[TOOLBAR] Command stack is empty - using main menu command');
        }
        this.currentRefreshCmd = cmdToSend;
        this.currentRefreshCmdType = 'back';  // Back navigation command
        this.clearPendingQueue();
        this.addToRequestQueue(null, cmdToSend, null, null, 'back');
        console.log('[TOOLBAR] Back navigation request queued');
      });
    }

    // Middle button (reload) - resend last command
    const btnReload = document.getElementById('btn-reload');
    if (btnReload) {
      btnReload.addEventListener('click', () => {
        console.log('[TOOLBAR] Reload button clicked - resending current refresh command');
        console.log('[TOOLBAR] currentRefreshCmd value:', this.currentRefreshCmd);
        console.log('[TOOLBAR] currentRefreshCmd type:', typeof this.currentRefreshCmd);
        console.log('[TOOLBAR] currentRefreshCmd is null:', this.currentRefreshCmd === null);
        this.clearPendingQueue();
        this.addToRequestQueue(null, this.currentRefreshCmd, null, null, 'refresh');
      });
    }

    // Right button (three dots) - show toolbar menu
    const btnMenu = document.getElementById('btn-menu');
    if (btnMenu) {
      btnMenu.addEventListener('click', (event) => {
        console.log('[TOOLBAR] Menu button clicked');
        this.showToolbarMenu(event);
      });
    }

    console.log('[TOOLBAR] Toolbar button listeners setup complete');
  }

  /**
   * Show toolbar menu with options
   */
  showToolbarMenu(event) {
    console.log('[TOOLBAR_MENU] showToolbarMenu called');

    const btnMenu = document.getElementById('btn-menu');
    if (!btnMenu) {
      console.error('[TOOLBAR_MENU] Menu button not found');
      return;
    }

    // Remove any existing menu
    const existing = document.getElementById('toolbar-menu');
    if (existing) {
      existing.remove();
    }

    // Create menu
    const menu = document.createElement('div');
    menu.id = 'toolbar-menu';
    menu.style.cssText = `
      position: fixed;
      background-color: white;
      border: 2px solid #333;
      border-radius: 4px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
      z-index: 999999;
      min-width: 160px;
      padding: 4px 0;
      visibility: hidden;
    `;

    // Add menu item for raw data
    const item = document.createElement('div');
    item.style.cssText = `
      padding: 10px 16px;
      cursor: pointer;
      user-select: none;
      font-family: Arial, sans-serif;
      font-size: 14px;
      color: #000;
      white-space: nowrap;
    `;
    item.textContent = 'Show Raw Data';
    item.addEventListener('click', () => {
      console.log('[TOOLBAR_MENU] Show Raw Data clicked');
      if (window.rawMessageViewer) {
        window.rawMessageViewer.show();
        console.log('[TOOLBAR_MENU] Opened raw data viewer');
      }
      menu.remove();
    });
    item.addEventListener('mouseover', () => {
      item.style.backgroundColor = '#e8e8e8';
    });
    item.addEventListener('mouseout', () => {
      item.style.backgroundColor = 'transparent';
    });

    menu.appendChild(item);
    document.body.appendChild(menu);

    // Get button position AFTER menu is in DOM
    const rect = btnMenu.getBoundingClientRect();
    console.log('[TOOLBAR_MENU] Button rect:', { left: rect.left, right: rect.right, top: rect.top, bottom: rect.bottom, width: rect.width });

    // Get menu dimensions
    const menuHeight = menu.offsetHeight;
    const menuWidth = menu.offsetWidth;
    console.log('[TOOLBAR_MENU] Menu dimensions:', { width: menuWidth, height: menuHeight });

    // Position menu ABOVE button
    // - Right edge of menu aligned with right edge of button
    // - Bottom of menu just above button top
    const menuLeft = rect.right - menuWidth;
    const menuTop = rect.top - menuHeight - 2;

    menu.style.left = Math.max(0, menuLeft) + 'px';  // Don't go off-screen left
    menu.style.top = Math.max(0, menuTop) + 'px';    // Don't go off-screen top
    menu.style.visibility = 'visible';

    console.log('[TOOLBAR_MENU] Menu positioned at left=' + Math.max(0, menuLeft) + 'px, top=' + Math.max(0, menuTop) + 'px');
    console.log('[TOOLBAR_MENU] Menu z-index: 999999');

    // Close menu on outside click
    const closeMenu = (e) => {
      if (!e.target.closest('#toolbar-menu') && e.target !== btnMenu) {
        console.log('[TOOLBAR_MENU] Closing menu (outside click)');
        menu.remove();
        document.removeEventListener('click', closeMenu);
      }
    };

    // Use slight delay to ensure event listeners are ready
    setTimeout(() => {
      document.addEventListener('click', closeMenu);
      console.log('[TOOLBAR_MENU] Close listener attached');
    }, 50);
  }

  /**
   * Setup right-click context menu
   */
  setupContextMenu() {
    // Skip context menu setup if designer parameter is present
    const urlParams = new URLSearchParams(window.location.search);
    if (urlParams.has('designer')) {
      console.log('[PFODWEB_DEBUG] Designer mode detected - skipping context menu setup');
      return;
    }

    const canvas = document.getElementById('drawing-canvas');
    if (!canvas) {
      console.warn('[CONTEXT_MENU] Canvas not found');
      return;
    }

    // Create context menu container
    const contextMenu = document.createElement('div');
    contextMenu.id = 'pfod-context-menu';
    contextMenu.style.cssText = `
      display: none;
      position: fixed;
      background-color: #2d2d30;
      border: 1px solid #555;
      border-radius: 4px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.3);
      z-index: 10000;
      min-width: 200px;
      font-family: 'Segoe UI', Arial, sans-serif;
      font-size: 12px;
    `;

    contextMenu.innerHTML = `
      <div class="pfod-context-menu-item" data-action="show-messages">
        <span style="color: #ce9178; margin-right: 8px;">📊</span>
        Show Raw Messages
        <span style="color: #858585; margin-left: auto; margin-left: 20px; font-size: 10px;">Ctrl+Shift+M</span>
      </div>
    `;

    // Add styles for menu items
    const style = document.createElement('style');
    style.textContent = `
      .pfod-context-menu-item {
        padding: 8px 12px;
        color: #d4d4d4;
        cursor: pointer;
        display: flex;
        align-items: center;
        user-select: none;
        transition: background-color 0.15s;
      }

      .pfod-context-menu-item:hover {
        background-color: #3e3e42;
      }

      .pfod-context-menu-item:active {
        background-color: #454545;
      }

      .pfod-context-menu-divider {
        height: 1px;
        background-color: #3e3e42;
        margin: 4px 0;
      }
    `;
    document.head.appendChild(style);
    document.body.appendChild(contextMenu);

    // Right-click context menu disabled - now only accessible via toolbar menu
    // canvas.addEventListener('contextmenu', (event) => {
    //   event.preventDefault();
    //   this.showContextMenu(event.clientX, event.clientY, contextMenu);
    // });

    // Close menu on document click
    document.addEventListener('click', (event) => {
      if (event.target.closest('#pfod-context-menu')) {
        return; // Don't close if clicking menu
      }
      contextMenu.style.display = 'none';
    });

    // Handle menu item clicks
    contextMenu.addEventListener('click', (event) => {
      const item = event.target.closest('.pfod-context-menu-item');
      if (!item) return;

      const action = item.dataset.action;
      contextMenu.style.display = 'none';

      switch (action) {
        case 'show-messages':
          if (window.rawMessageViewer) {
            window.rawMessageViewer.show();
            console.log('[CONTEXT_MENU] Opened message viewer');
          }
          break;
        case 'clear-messages':
          if (window.messageCollector) {
            window.messageCollector.clear();
            console.log('[CONTEXT_MENU] Cleared all messages');
          }
          break;
        case 'export-json':
          if (window.rawMessageViewer) {
            window.rawMessageViewer.exportJSON();
            console.log('[CONTEXT_MENU] Exported messages as JSON');
          }
          break;
        case 'export-csv':
          if (window.rawMessageViewer) {
            window.rawMessageViewer.exportCSV();
            console.log('[CONTEXT_MENU] Exported messages as CSV');
          }
          break;
      }
    });

    console.log('[CONTEXT_MENU] Context menu setup complete');
  }

  /**
   * Show context menu at specified position
   */
  showContextMenu(x, y, menu) {
    menu.style.display = 'block';
    menu.style.left = x + 'px';
    menu.style.top = y + 'px';

    // Adjust if menu goes off-screen
    const rect = menu.getBoundingClientRect();
    if (rect.right > window.innerWidth) {
      menu.style.left = (window.innerWidth - rect.width - 10) + 'px';
    }
    if (rect.bottom > window.innerHeight) {
      menu.style.top = (window.innerHeight - rect.height - 10) + 'px';
    }

    console.log('[CONTEXT_MENU] Showing at', x, y);
  }

  // Queue initial request using existing request queue system
  queueInitialRequest() {
    const startupCmd = '{.}';

    console.log('Sending {.} request without version to get drawing name from server via session context');
    console.log(`Queueing initial request with command: ${startupCmd}`);

    // Add to request queue with mainMenu type - not a drawing request
    const requestType = 'mainMenu';
    this.addToRequestQueue(null, startupCmd, null, null, requestType);
  }

  // Update page title to include main drawing name
//  updatePageTitle(drawingName) {
//    if (drawingName) {
//      document.title = `pfodWeb ${drawingName}`;
//    }
//  }

  // Load drawing data from the server
  async loadDrawing() {
    // Main drawing is always the first in the array
    const currentDrawingName = this.redraw.redrawDrawingManager.getCurrentDrawingName();
    if (!currentDrawingName) {
      console.error('No drawing name specified');
      return;
    }

    try {
      // Disable updates during loading
      this.isUpdating = false;
      // Clear any existing timer
      if (this.updateTimer) {
        clearTimeout(this.updateTimer);
        this.updateTimer = null;
      }

      // Check if we have a saved version
      const savedVersion = localStorage.getItem(`${currentDrawingName}_version`);
      const savedData = localStorage.getItem(`${currentDrawingName}_data`);

      let cmd;
      if (savedVersion) {
        cmd = '{' + savedVersion+ ':'+ currentDrawingName + '}';
        console.log(`Using saved version: ${savedVersion}`);
      } else {
        console.log('No valid saved version+data pair - requesting fresh data (dwg:start)');
        cmd = '{' + currentDrawingName + '}';
      }

      console.log(`Requesting drawing with command: ${cmd}`);

      // Add main drawing request to the queue
      this.addToRequestQueue(currentDrawingName, cmd, null, null, 'main');
    } catch (error) {
      console.error('Failed to load drawing:', error);
      this.isUpdating = true; // Re-enable updates even if loading failed
    }
  }

  // Schedule the next update request
  scheduleNextUpdate() {
    const mainDrawingName = this.redraw.getCurrentDrawingName();
    console.log(`[SCHEDULE_NEXT_UPDATE] ${mainDrawingName}`);
    // Clear any existing timer first
    if (this.updateTimer) {
      clearTimeout(this.updateTimer);
      this.updateTimer = null;
    }

    // Get the current main drawing data from redraw system (where data is actually stored after processing)
    const currentDrawingData = this.redraw.redrawDrawingManager.drawingsData[mainDrawingName]?.data;

    // Check if a touchActionInput dialog is currently open
    const hasOpenDialog = window.pfodWebMouse.touchActionInputOpen;

    // Only schedule an update if refresh is greater than 0, mouse is not down, queue is empty, and no request in flight
    // This ensures that a refresh value of 0 properly disables automatic updates
    // and prevents updates during mouse interactions or ongoing queue processing
    // Also check that a drawing is currently being displayed (not raw data or menu)
    if (this.isUpdating && currentDrawingData && currentDrawingData.refresh > 0 && !this.touchState.isDown &&
        this.requestQueue.length === 0 && !this.sentRequest && !hasOpenDialog && this.currentlyDisplayingDwg) {
      console.log(`[REFRESH] Scheduling next update in ${currentDrawingData.refresh}ms for drawing "${this.redraw.getCurrentDrawingName()}"`);
      this.updateTimer = setTimeout(() => this.fetchRefresh(), currentDrawingData.refresh);
      // Also schedule updates for inserted drawings
      if (this.redraw.redrawDrawingManager.drawings.length > 1) {
        console.log(`Will fetch updates for ${this.redraw.redrawDrawingManager.drawings.length - 1} inserted drawings during next update cycle`);
      }
    } else if (currentDrawingData && currentDrawingData.refresh === 0) {
      console.log(`[REFRESH] Automatic updates disabled (refresh=0) for drawing "${this.redraw.getCurrentDrawingName()}"`);
    } else if (!currentDrawingData) {
      console.log('[REFRESH] No drawing data available, cannot schedule updates');
    } else if (!this.isUpdating) {
      console.log('[REFRESH] Updates currently paused');
    } else if (this.touchState.isDown) {
      console.log('[REFRESH] Skipping update scheduling - mouse is down');
    } else if (this.requestQueue.length > 0) {
      console.log(`[REFRESH] Skipping update scheduling - queue not empty (${this.requestQueue.length} requests)`);
    } else if (this.sentRequest) {
      console.log(`[REFRESH] Skipping update scheduling - request in flight for "${this.sentRequest.drawingName}"`);
    } else if (hasOpenDialog) {
      console.log('[REFRESH] Skipping update scheduling - touchActionInput dialog is open');
    } else if (!this.currentlyDisplayingDwg) {
      console.log('[REFRESH] Not currently displaying dwg - skipping timer reschedule');
    }

  }

  // Fetch refreshes from the server
  async fetchRefresh() {
    console.log(`[REFRESH] Refresh timer fired - starting update cycle for drawing "${this.redraw.getCurrentDrawingName()}" at ${new Date().toISOString()}`);

    // Check if a touchActionInput dialog is currently open
    if (window.pfodWebMouse.touchActionInputOpen) {
      console.log(`[REFRESH] Blocking refresh cycle - touchActionInput dialog is open`);
      this.scheduleNextUpdate(); // Reschedule for later
      return;
    }

    // Block update requests if user activity is present
    if (this.touchState.isDown) {
      console.log(`[REFRESH] Blocking update cycle - mouse is down`);
      this.scheduleNextUpdate(); // Reschedule for later
      return;
    }

    // Check if queue has user requests (non-refresh/non-refresh-related requestType)
    // Protect refresh-related requests (refresh, refresh-insertDwg, insertDwg) from being blocked
    const hasUserRequests = this.requestQueue.some(req =>
      req.requestType !== 'refresh' && req.requestType !== 'refresh-insertDwg' && req.requestType !== 'insertDwg'
    );
    if (hasUserRequests) {
      console.log(`[REFRESH] Blocking refresh cycle - user requests in queue`);
      this.scheduleNextUpdate(); // Reschedule for later
      return;
    }

    // Check if user request is in flight (non-refresh/non-refresh-related requestType)
    if (this.sentRequest && (this.sentRequest.requestType !== 'refresh' &&
        this.sentRequest.requestType !== 'refresh-insertDwg' &&
        this.sentRequest.requestType !== 'insertDwg')) {
      console.log(`[REFRESH] Blocking refresh cycle - user request in flight (${this.sentRequest.requestType})`);
      this.scheduleNextUpdate(); // Reschedule for later
      return;
    }

    // Check if shadow processing is already active
    if (this.shadowProcessing.requestType) {
      console.log(`[REFRESH] Shadow processing already active (${this.shadowProcessing.requestType}), waiting for completion`);
      // Don't reschedule - let the current processing complete and schedule naturally
      return;
    }

    try {
      // Get main drawing name from redraw manager (same as scheduleNextUpdate)
      const mainDrawingName = this.redraw.redrawDrawingManager.getMainDrawingName();
      const currentDrawingData = this.redraw.redrawDrawingManager.drawingsData[mainDrawingName]?.data;

      if (!currentDrawingData || !mainDrawingName) {
        throw new Error('No active drawing');
      }

      // Set flag to indicate we're currently updating
      this.isUpdating = false;

      console.log(`[UPDATE] Starting update cycle at ${new Date().toISOString()}`);

      // TODO: Need to introduce concept of current drawing different from drawings[0]
      // For now, using drawings[0] as current drawing but this needs architectural change
      const currentDrawingName = this.redraw.redrawDrawingManager.drawings.length > 0 ? this.redraw.redrawDrawingManager.drawings[0] : '';
      console.log(`[UPDATE] Current drawing: "${currentDrawingName}", inserted drawings: ${this.redraw.redrawDrawingManager.drawings.length - 1}`);
      console.log(`[UPDATE] All drawings array: [${this.redraw.redrawDrawingManager.drawings.join(', ')}]`);

      // Debug: log if any drawing name is null
      if (currentDrawingName === null || currentDrawingName === undefined) {
        console.warn(`[UPDATE] WARNING: currentDrawingName is ${currentDrawingName}, drawings array:`, this.redraw.redrawDrawingManager.drawings);
      }

      // Update collection removed - using unified shadow processing system

      // Check if designer mode is active
      const urlParams = new URLSearchParams(window.location.search);
      const isDesignerMode = urlParams.has('designer');

      if (isDesignerMode) {
        // In designer mode, only queue the main drawing
        console.log(`[UPDATE] Designer mode detected - queueing only main drawing "${currentDrawingName}"`);
        await this.queueDrawingUpdate(currentDrawingName);
      } else {
        // In normal mode, queue main drawing and all inserted drawings with their versions
        const allDrawings = this.redraw.redrawDrawingManager.drawings;
        console.log(`[UPDATE] Normal mode - queueing main drawing and ${allDrawings.length - 1} inserted drawings`);

        for (const drawingName of allDrawings) {
          console.log(`[UPDATE] Queueing update for drawing "${drawingName}"`);
          await this.queueDrawingUpdate(drawingName);
        }
      }

      // Re-enable updates
      this.isUpdating = true;
      this.scheduleNextUpdate();
      console.log(`[UPDATE] Update cycle queued at ${new Date().toISOString()}`);
    } catch (error) {
      console.error('[UPDATE] Failed to update drawing:', error);
      // Re-enable updates even if this one failed
      this.isUpdating = true;
      this.scheduleNextUpdate();
    }
  }

  // Add a request to the queue
  addToRequestQueue(drawingName, cmd, options, touchZoneInfo, requestType = 'unknown') {
    console.warn(`[QUEUE] Adding request for "${drawingName}" to queue (type: ${requestType})`);
    console.log(`[QUEUE] Command "${cmd}"`);
    console.log(`[QUEUE] Current shadow processing type: ${this.shadowProcessing.requestType}`);
    console.log(`[QUEUE] Queue length before add: ${this.requestQueue.length}, sentRequest: ${this.sentRequest ? this.sentRequest.drawingName + '(' + this.sentRequest.requestType + ')' : 'null'}`);

    // Log stack trace if this is a null drawing name request
    if (drawingName === null) {
      console.warn(`[QUEUE] NULL DRAWING NAME REQUEST - Stack trace:`);
      console.trace('[QUEUE] NULL REQUEST STACK');
    }
    if (requestType == 'unknown') {
      console.error(`[QUEUE] Error: Unknown requestType`);
      return;
    }

    // If this is a non-refresh request (touch, etc), clean up any existing refresh requests
    // BUT: protect refresh-insertDwg items which are part of the refresh block
    if (requestType !== 'refresh' && requestType !== 'refresh-insertDwg' && requestType !== 'insertDwg') {
      // Remove all refresh requests from queue
      const refreshRequestsInQueue = this.requestQueue.filter(req => req.requestType === 'refresh' || req.requestType === 'refresh-insertDwg');
      if (refreshRequestsInQueue.length > 0) {
        this.requestQueue = this.requestQueue.filter(req => req.requestType !== 'refresh' && req.requestType !== 'refresh-insertDwg');
        console.log(`[QUEUE] Removed ${refreshRequestsInQueue.length} refresh/refresh-insertDwg requests from queue due to user activity (${requestType})`);
      }

      // Mark sent refresh request for discard (including refresh-insertDwg being processed)
      if (this.sentRequest && (this.sentRequest.requestType === 'refresh' || this.sentRequest.requestType === 'refresh-insertDwg')) {
        this.sentRequest.discardResponse = true;
        console.log(`[QUEUE] Marked sent ${this.sentRequest.requestType} request for "${this.sentRequest.drawingName}" to be discarded`);
      }
    }

    this.setProcessingQueue(true);

    // Track the request type
    if (requestType === 'touch') {
      this.requestTracker.touchRequests.add(drawingName);
      console.log(`[QUEUE] Tracking touch request for "${drawingName}"`);
    } else if (requestType === 'insertDwg') {
      this.requestTracker.insertDwgRequests.add(drawingName);
      console.log(`[QUEUE] Tracking insertDwg request for "${drawingName}"`);
    }

    // Check if this is a drag request and remove any existing drag requests from the same touchZone cmd
    if (touchZoneInfo && touchZoneInfo.filter === TouchZoneFilters.DRAG) {
      const cmd = touchZoneInfo.cmd;
      console.log(`[QUEUE] Removing existing DRAG requests for cmd="${cmd}" to minimize network traffic`);

      // Remove existing drag requests from the same cmd
      this.requestQueue = this.requestQueue.filter(request => {
        const isDragRequest = request.touchZoneInfo &&
          request.touchZoneInfo.filter === TouchZoneFilters.DRAG &&
          request.touchZoneInfo.cmd === cmd;
        if (isDragRequest) {
          console.log(`[QUEUE] Removed duplicate DRAG request for cmd="${cmd}"`);
        }
        return !isDragRequest;
      });
    }

    this.requestQueue.push({
      drawingName: drawingName,
      cmd: cmd,
      touchZoneInfo: touchZoneInfo,
      requestType: requestType
    });
    console.warn(`[QUEUE] addToRequestQueue current queue is:`, JSON.stringify(this.requestQueue, null, 2));
    // Process the queue if not already processing
    this.processRequestQueue();
  }

  // process response of type {,..|+A} and {; ,,|+A~dwgName}
  processMenuResponse(data, request) {
    let cmd;
    if (data.cmd) {
      cmd = data.cmd;
    } else {
      console.log('[QUEUE] No cmd field in server response ', JSON.stringify(data));
      return false;
    }
    let msgType = cmd[0];
    if (!(msgType.startsWith("{,") || msgType.startsWith("{;"))) {
      console.log('[QUEUE] Not a menu response ', JSON.stringify(data));
      return false;
    }

    let result = translateMenuResponse(cmd);
    if (result.pfodDrawing == 'error') {
      this.handleDrawingError(result);
      return false;
    }

    // result has form
    //    const result = {
    //  pfodDrawing: 'menu',
    //  drawingName: ${drawingName}', << may be empty
    //  identifier: ${identifier}
    //});
    this.currentIdentifier = result.identifier;
    let drawingName;
    if (result.drawingName.trim() !== '') {
      drawingName = result.drawingName; // update it
    } else {
      drawingName = this.shadowProcessing.shadowDrawingManager.getCurrentDrawingName(); // assume we are updating main dwg from menu
    }
    console.log(`[processMenuResponse] Updated dwgName and currentDrawingName "${drawingName}"`);
    // Update page title with drawing name
   // this.updatePageTitle(drawingName);

    // Add the drawing as the first drawing in the array if not already present
    if (!this.shadowProcessing.shadowDrawingManager.drawings.includes(drawingName)) {
      this.shadowProcessing.shadowDrawingManager.drawings.unshift(drawingName);
    }

    // Check if server response includes version information
    let serverVersion = data.version;
    let storedVersion = null;
    let hasStoredData = false;

    // Get stored version for this drawing using DrawingManager
    if (this.shadowProcessing.shadowDrawingManager) {
      storedVersion = this.shadowProcessing.shadowDrawingManager.getStoredVersion(drawingName);
      // Check if we actually have saved data for this drawing (not just the version)
      hasStoredData = this.shadowProcessing.shadowDrawingManager.drawingsData[drawingName]?.data ? true : false;
    }

    // For updates without version info (like {;), assume version hasn't changed
    // Use stored version as fallback ONLY if we have actual stored data
    if (!serverVersion && storedVersion && hasStoredData) {
      serverVersion = storedVersion;
      console.log(`[QUEUE] No version in update - using stored version: ${storedVersion}`);
    }

    // Build the command string for the drawing request
    let drawingCmd;
    // Include version in command only if:
    // 1. We have actual stored data for this drawing
    // 2. We have a stored version that matches server version
    if (hasStoredData && storedVersion && serverVersion && storedVersion === serverVersion) {
      drawingCmd = '{' + storedVersion + ':' + drawingName + '}';
      console.log(`[QUEUE] Version match with stored data: Including version ${storedVersion} in {${drawingName}} request`);
    } else {
      drawingCmd = '{' + drawingName + '}';
      if (!hasStoredData) {
        console.log(`[QUEUE] No stored data for ${drawingName} - requesting initial data without version`);
      } else {
        console.log(`[QUEUE] Version mismatch or no stored version: Sending {${drawingName}} without version (stored: ${storedVersion}, server: ${serverVersion})`);
      }
    }

    // Queue the actual drawing request from main menu - add as first drawing and use 'main' requestType
    console.log(`[QUEUE] Main menu requesting drawing "${drawingName}" - adding to drawings[0] and using requestType: main`);
    // Add this drawing as the first drawing since it's from main menu
    if (!this.shadowProcessing.shadowDrawingManager.drawings.includes(drawingName)) {
      this.shadowProcessing.shadowDrawingManager.drawings.unshift(drawingName);
    }
    this.addToRequestQueue(drawingName, drawingCmd, request.options, null, 'main');
    console.log(`[QUEUE] Processed drawing menu item ${cmd}`);
    return true;
  }

  isEmptyCmd(cmd) {
    if (!cmd) {
      return false
    }
    if (cmd.length < 2) {
      return false;
    }
    let cmd0 = cmd[0].trim();
    let cmd1 = cmd[1].trim();
    if ((cmd0 == '{') && (cmd1 == '}')) {
      console.log(`[DRAWING_DATA] Received empty cmd response `);
      return true; // Successfully handled - no drawing data to process
    }
    return false;
  }

  // Atomic helper methods for queue processing state
  isProcessingQueue() {
    return this._isProcessingQueue;
  }

  setProcessingQueue(value) {
    const oldValue = this._isProcessingQueue;
    this._isProcessingQueue = value;
    console.log(`[QUEUE_STATE] setProcessingQueue(${value}) - oldValue: ${oldValue}, newValue: ${value}`);
    return value;
  }

  trySetProcessingQueue(expectedValue, newValue) {
    if (this._isProcessingQueue === expectedValue) {
      this._isProcessingQueue = newValue;
      console.log(`[QUEUE_STATE] trySetProcessingQueue(${expectedValue}, ${newValue}) - success: true`);
      return true;
    } else {
      console.log(`[QUEUE_STATE] trySetProcessingQueue(${expectedValue}, ${newValue}) - success: false, current: ${this._isProcessingQueue}`);
      return false;
    }
  }

  redrawCanvas() {
              // Update the MergeAndRedraw module with the latest state
      console.warn(`[QUEUE] redrawCanvas isDown: ${this.touchState.isDown}`);
              if (!this.touchState.isDown) {
                if (this.touchState.wasDown) {
                  this.touchState.wasDown = this.touchState.isDown;
               }
          // Redraw no longer needs access to drawingManager or requestQueue
          // Data is managed locally in redraw
              }

          // Redraw the canvas with what we have
          // Note: TouchAction redraws are now handled directly by pfodWebMouse calling redraw.redrawForTouchAction()
          // This method only handles normal redraws
          this.handleResize();
   }

  /**
   * Display raw data text in place of canvas
   * Creates a scrolling text display for {=} response data
   * Keeps button panel visible at bottom
   * Continuously appends incoming data and auto-scrolls (unless scroll is locked)
   */
  displayRawDataText(chartTitle, rawData) {
    // console.log('[RAW_DATA] displayRawDataText called - title:', chartTitle, 'data length:', rawData.length);

    // Switch to raw data display CSS mode
    document.body.className = 'rawdata-mode';
    // console.log('[RAW_DATA] Switched to rawdata-mode CSS');

    // Get canvas wrapper (contains just the canvas)
    const canvasWrapper = document.getElementById('canvas-wrapper');
    if (!canvasWrapper) {
      console.error('[RAW_DATA] Canvas wrapper not found');
      return;
    }

    // Create or get raw data display element
    let rawDataDisplay = document.getElementById('raw-data-text-display');
    if (!rawDataDisplay) {
      // console.log('[RAW_DATA] Creating new raw data display');
      rawDataDisplay = document.createElement('div');
      rawDataDisplay.id = 'raw-data-text-display';
      rawDataDisplay.style.cssText = `
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        background-color: white;
        overflow: hidden;
        box-sizing: border-box;
      `;

      // Create title bar with lock scroll button
      const titleBar = document.createElement('div');
      titleBar.id = 'raw-data-title-bar';
      titleBar.style.cssText = `
        background-color: #333;
        color: white;
        padding: 8px 10px;
        font-weight: bold;
        flex-shrink: 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
        height: 32px;
        box-sizing: border-box;
      `;

      const titleText = document.createElement('span');
      titleText.textContent = chartTitle || 'Raw Data';

      const lockButton = document.createElement('button');
      lockButton.id = 'raw-data-lock-scroll-btn';
      lockButton.textContent = '🔓 Scroll';
      lockButton.style.cssText = `
        background-color: #555;
        color: white;
        border: 1px solid #777;
        padding: 4px 8px;
        border-radius: 3px;
        cursor: pointer;
        font-size: 12px;
      `;

      let scrollLocked = false;
      lockButton.addEventListener('click', () => {
        scrollLocked = !scrollLocked;
        lockButton.textContent = scrollLocked ? '🔒 Locked' : '🔓 Scroll';
        lockButton.style.backgroundColor = scrollLocked ? '#a00' : '#555';
        this.rawDataScrollLocked = scrollLocked;
      });

      const saveButton = document.createElement('button');
      saveButton.id = 'raw-data-save-btn';
      saveButton.textContent = '💾 Save';
      saveButton.style.cssText = `
        background-color: #0066cc;
        color: white;
        border: 1px solid #0044aa;
        padding: 4px 8px;
        margin-left: 8px;
        border-radius: 3px;
        cursor: pointer;
        font-size: 12px;
      `;

      saveButton.addEventListener('click', () => {
        // Get all raw data
        const textContent = document.getElementById('raw-data-text-content');
        if (!textContent) {
          console.error('[RAW_DATA] Text content element not found for save');
          return;
        }

        const rawDataText = textContent.textContent;

        // Create blob and download
        const blob = new Blob([rawDataText], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `rawdata_${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);

        console.log('[RAW_DATA] Saved raw data to file:', link.download);
      });

      titleBar.appendChild(titleText);
      titleBar.appendChild(lockButton);
      titleBar.appendChild(saveButton);

      // Create scrolling text area - THIS is where scrollbar appears
      const textArea = document.createElement('div');
      textArea.id = 'raw-data-text-content';
      textArea.style.cssText = `
        flex: 1;
        overflow-y: auto;
        overflow-x: auto;
        padding: 10px;
        font-family: monospace;
        white-space: pre;
        background-color: white;
        color: #333;
        box-sizing: border-box;
        min-height: 0;
      `;

      rawDataDisplay.appendChild(titleBar);
      rawDataDisplay.appendChild(textArea);

      // console.log('[RAW_DATA] Raw data display structure created - layout: titleBar + scrollable textArea');

      // Initialize scroll lock state
      this.rawDataScrollLocked = false;
    } else {
      // Display already exists - update the title
      const titleBar = rawDataDisplay.querySelector('#raw-data-title-bar');
      if (titleBar) {
        const titleText = titleBar.querySelector('span');
        if (titleText) {
          titleText.textContent = chartTitle || 'Raw Data';
        }
      }
    }

    // Hide canvas and show raw data display
    this.canvas.style.display = 'none';
    if (rawDataDisplay.parentNode !== canvasWrapper) {
      canvasWrapper.innerHTML = '';
      canvasWrapper.appendChild(rawDataDisplay);
    }

    // Append new data to text content (not replace)
    const textContent = document.getElementById('raw-data-text-content');
    if (textContent) {
      // console.log('[RAW_DATA] Found text content element, appending', rawData.length, 'chars');
      // Append new data directly without adding separator newline
      textContent.textContent += rawData;
      // console.log('[RAW_DATA] Data appended, new length:', textContent.textContent.length);

      // Mark that we've displayed data up to this point
      if (window.rawDataCollector) {
        window.rawDataCollector.markDisplayedUpTo();
      }

      // Auto-scroll to bottom unless scroll is locked
      if (!this.rawDataScrollLocked) {
        textContent.scrollTop = textContent.scrollHeight;
        // console.log('[RAW_DATA] Auto-scrolled to bottom');
      } else {
        // console.log('[RAW_DATA] Scroll is locked, not auto-scrolling');
      }

      // Start polling for new data to append continuously
      this.startRawDataPolling();
    } else {
      console.error('[RAW_DATA] Text content element not found!');
    }
  }

  /**
   * Exit raw data display and restore canvas
   */
  exitRawDataDisplay() {
    // console.log('[RAW_DATA] Exiting raw data display');

    // Switch back to canvas display CSS mode
    document.body.className = 'canvas-mode';
    // console.log('[RAW_DATA] Switched back to canvas-mode CSS');

    const canvasWrapper = document.getElementById('canvas-wrapper');
    if (!canvasWrapper) {
      console.error('[RAW_DATA] Canvas wrapper not found in exitRawDataDisplay');
      return;
    }

    const rawDataDisplay = document.getElementById('raw-data-text-display');
    if (rawDataDisplay) {
      // console.log('[RAW_DATA] Removing raw data display element');
      canvasWrapper.innerHTML = '';
      canvasWrapper.appendChild(this.canvas);
    }

    this.canvas.style.display = 'block';
    // console.log('[RAW_DATA] Canvas restored');
    // Do NOT redraw here - drawing data not ready yet
    // The drawing will be redrawn when the queued drawing request response arrives

    // Stop polling for new data
    this.stopRawDataPolling();

    // Don't clear raw data collector - it must continue collecting independently
  }

  /**
   * Start polling for new raw data and appending to display
   */
  startRawDataPolling() {
    // Stop any existing polling
    this.stopRawDataPolling();

    // console.log('[RAW_DATA] Starting data polling');
    this.rawDataPollingInterval = setInterval(() => {
      // Check if raw data display still exists
      const textContent = document.getElementById('raw-data-text-content');
      if (!textContent) {
        // console.log('[RAW_DATA] Raw data display no longer exists, stopping polling');
        this.stopRawDataPolling();
        return;
      }

      // Get new data from collector
      if (window.rawDataCollector) {
        const newData = window.rawDataCollector.getNewData();
        if (newData.length > 0) {
          // console.log('[RAW_DATA] Polling found', newData.length, 'new chars, appending to display');
          textContent.textContent += newData;

          // Mark that we've displayed this data
          window.rawDataCollector.markDisplayedUpTo();

          // Auto-scroll to bottom unless scroll is locked
          if (!this.rawDataScrollLocked) {
            textContent.scrollTop = textContent.scrollHeight;
          }
        }
      }
    }, 100); // Poll every 100ms for new data
  }

  /**
   * Stop polling for new raw data
   */
  stopRawDataPolling() {
    if (this.rawDataPollingInterval) {
      clearInterval(this.rawDataPollingInterval);
      this.rawDataPollingInterval = null;
      // console.log('[RAW_DATA] Stopped data polling');
    }
  }

  /**
   * Display chart from CSV data with specified labels and limit
   * @param {string} title - Chart title
   * @param {array} labels - Field labels [xField, yField1, yField2, ...]
   * @param {number} limit - Maximum number of CSV lines to display (default 500)
   */
  displayChart(title, labels, limit = 500) {
    console.log('[CHART] displayChart called - title:', title, 'labels:', labels, 'limit:', limit);

    if (!window.chartDisplay) {
      console.error('[CHART] ChartDisplay not available');
      return;
    }

    // Set global flag to indicate chart mode is active
    window.isInChartMode = true;
    console.log('[CHART] Set window.isInChartMode = true');

    // Switch to chart display CSS mode
    document.body.className = 'chart-mode';
    console.log('[CHART] Switched to chart-mode CSS');

    const canvasWrapper = document.getElementById('canvas-wrapper');
    if (!canvasWrapper) {
      console.error('[CHART] Canvas wrapper not found');
      return;
    }

    try {
      // Get field count from number of labels
      const fieldCount = labels.length;

      // Load CSV data from collector
      const csvLines = window.chartDisplay.loadCSVData(fieldCount);
      console.log('[CHART] Loaded', csvLines.length, 'CSV lines for', fieldCount, 'fields');

      // Note: If no CSV data yet, chart will still open and will be populated as data arrives via polling
      if (csvLines.length === 0) {
        console.log('[CHART] No CSV data available yet, will open empty chart and populate as data arrives');
      }

      // Parse CSV into dataset (will be null or empty if no CSV data)
      const dataset = window.chartDisplay.parseCSVToDataset(csvLines, labels, limit);
      console.log('[CHART] Dataset created:', dataset ? 'success' : 'empty/null');
      // Continue even if dataset is null/empty - chart will populate as data arrives

      // Create and display chart
      const chart = window.chartDisplay.createAndDisplayChart(title, dataset, labels, this.canvas);
      if (!chart) {
        console.error('[CHART] Failed to create chart');
        return;
      }

      // Store reference to chart for updates
      this.currentChart = chart;
      this.currentChartLabels = labels;
      this.currentChartFieldCount = fieldCount;
      this.currentChartLimit = limit;

      // Start polling for chart updates
      window.chartDisplay.startUpdatePolling(chart, fieldCount, labels, limit, this.canvas);

      console.log('[CHART] Chart display complete');
    } catch (error) {
      console.error('[CHART] Error displaying chart:', error);
    }
  }

  /**
   * Exit chart display and restore canvas
   */
  exitChartDisplay() {
    console.log('[CHART] Exiting chart display');

    // Clear global chart mode flag
    window.isInChartMode = false;
    console.log('[CHART] Set window.isInChartMode = false');

    // Stop chart polling
    if (window.chartDisplay) {
      window.chartDisplay.stopUpdatePolling();
      window.chartDisplay.clear();
    }

    // Clear chart references
    this.currentChart = null;
    this.currentChartLabels = null;
    this.currentChartFieldCount = null;
    this.currentChartLimit = null;

    // Switch back to canvas display CSS mode
    document.body.className = 'canvas-mode';
    console.log('[CHART] Switched back to canvas-mode CSS');

    const canvasWrapper = document.getElementById('canvas-wrapper');
    if (canvasWrapper) {
      canvasWrapper.innerHTML = '';
      canvasWrapper.appendChild(this.canvas);
    }

    this.canvas.style.display = 'block';
    console.log('[CHART] Canvas restored');
  }

  /**
   * Handle valid dwg update responses ({}, {+...}, or partial updates)
   * Sets the currentlyDisplayingDwg flag for {+ responses
   * Processes through shadow system for drawing updates
   */
  handleDwgResponse(data, request) {
    console.log('[QUEUE] Handling dwg response');

    // CHECK for {+ BEFORE processDrawingData modifies the cmd structure
    // Important: processDrawingData restructures the cmd array, so we must check before calling it
    // Also check for pfodDrawing: 'start' (direct drawing format)
    const isFullDwgUpdate = (data.cmd && data.cmd.length > 0 && data.cmd[0].startsWith('{+')) || (data.pfodDrawing === 'start');

    // Check if this DRAG response should be discarded due to newer drag requests in queue
    if (request.touchZoneInfo && request.touchZoneInfo.filter === TouchZoneFilters.DRAG) {
      const cmd = request.touchZoneInfo.cmd;
      const hasNewerDragRequest = this.requestQueue.some(queuedRequest =>
        queuedRequest.touchZoneInfo &&
        queuedRequest.touchZoneInfo.filter === TouchZoneFilters.DRAG &&
        queuedRequest.touchZoneInfo.cmd === cmd
      );

      if (hasNewerDragRequest) {
        console.log(`[QUEUE] Discarding DRAG response for cmd="${cmd}" - newer request exists in queue`);
        return false; // Discard this response
      }
    }

    // All responses are processed through unified shadow system (always active)
    try {
      console.log(`[SHADOW] Processing ${request.requestType} response for "${request.drawingName}"`);

      // Insert name property from request since responses no longer include it
      // For touch requests (touchAction/touchActionInput), don't assign drawing name to update merged data only
      if (request.requestType === 'touch') {
        console.log(`[QUEUE] Touch request - updating merged data only, no individual drawing updates`);
        data.name = null; // No drawing name = update merged data only
      } else {
        data.name = request.drawingName;
      }

      this.processDrawingData(data, null, request.requestType);

      // Set flag indicating we're now displaying a dwg (only for {+ full dwg updates, not {})
      // Note: We check this BEFORE processDrawingData because that function modifies data.cmd
      if (isFullDwgUpdate) {
        this.currentlyDisplayingDwg = true;
        console.log('[QUEUE] Set currentlyDisplayingDwg = true (full dwg update {+ received)');
      } else {
        console.log('[QUEUE] Not a full dwg update - currentlyDisplayingDwg remains false');
      }

      // Store the processed response
      this.shadowProcessing.responses.set(request.drawingName, { data, request });

      // Note: checkAndApplyShadowUpdates will be called by caller after sentRequest is cleared
      // This allows the shadow system to know that this request is complete
      return true;
    } catch (error) {
      console.error(`[SHADOW] Error in shadow processing:`, error);
      console.error(`[QUEUE] Error stack:`, error.stack);

      // Additional diagnostics for debugging
      const dwgName = request.drawingName || " ";
      console.log(`[QUEUE] Debugging state for "${dwgName}":`);
      console.log(`- Main drawing name: ${this.shadowProcessing.shadowDrawingManager.getCurrentDrawingName()}`);
      console.log(`- Drawing in drawings array: ${this.shadowProcessing.shadowDrawingManager.drawings.includes(request.drawingName)}`);
      console.log(`- Drawing in drawingsData: ${this.shadowProcessing.shadowDrawingManager.drawingsData[request.drawingName] ? 'yes' : 'no'}`);
      console.log(`- unindexedItems collection exists: ${this.shadowProcessing.shadowDrawingManager.unindexedItems[request.drawingName] ? 'yes' : 'no'}`);
      console.log(`- indexedItems collection exists: ${this.shadowProcessing.shadowDrawingManager.indexedItems[request.drawingName] ? 'yes' : 'no'}`);
      console.log(`- touchZonesByCmd collection exists: ${this.shadowProcessing.shadowDrawingManager.touchZonesByCmd[request.drawingName] ? 'yes' : 'no'}`);

      // Try to fix any missing collections
      if (!this.shadowProcessing.shadowDrawingManager.unindexedItems[request.drawingName] || !this.shadowProcessing.shadowDrawingManager.indexedItems[request.drawingName]) {
        console.log(`[QUEUE] Attempting to fix missing collections for "${dwgName}"`);
        this.shadowProcessing.shadowDrawingManager.ensureItemCollections(request.drawingName);
      }

      // Check if this is main drawing or initial connection
      const currentDrawingName = this.shadowProcessing.shadowDrawingManager.getCurrentDrawingName();
      const isMainOrInitial = (request.drawingName === null || request.drawingName === currentDrawingName);

      if (isMainOrInitial) {
        // Show alert dialog with Close button that reloads page
        console.log(`[ALERT] Triggering No Connection alert for "${request.drawingName}" (main or initial connection)`);
        console.log(`[ALERT] Error message: ${error.message}`);
        console.log(`[ALERT] Error name: ${error.name}`);
        this.showNoConnectionAlert();
      } else {
        // For inserted drawings, just log the error but continue processing
        console.warn(`[QUEUE] ERROR: Failed to load inserted drawing "${request.drawingName}" - continuing without it`);
      }

      // Clean up shadow processing on error
      this.cleanupShadowProcessing();
      return false;
    }
  }

  /**
   * Handle non-dwg update responses (responses that are not {}, {+...}, or partial updates)
   * Only restores from backup if currently displaying dwg, then clears flag
   * Processes based on response type
   */
  handleNonDwgResponse(data, request, requestType) {
    console.log('[QUEUE] Handling non-dwg response');

    // Only restore from backup if currently displaying a dwg
    if (this.currentlyDisplayingDwg) {
      console.log('[QUEUE] Currently displaying dwg - restoring from backup and redrawing');
      // Clear flag - we're no longer displaying a dwg
      this.currentlyDisplayingDwg = false;
      console.log('[QUEUE] Set currentlyDisplayingDwg = false (non-dwg update)');

      // Cancel refresh timer since we're no longer displaying a drawing
      if (this.updateTimer) {
        clearTimeout(this.updateTimer);
        this.updateTimer = null;
        console.log('[REFRESH] Cancelled refresh timer - no longer displaying dwg');
      }

      // Restore from backup to clear touchAction overlays
      if (window.pfodWebMouse && window.pfodWebMouse.touchActionBackups) {
        console.log('[QUEUE] Restoring from touchAction backup');
        for (const idx in window.pfodWebMouse.touchActionBackups.allIndexedItemsByNumber) {
          const backupItem = window.pfodWebMouse.touchActionBackups.allIndexedItemsByNumber[idx];
          this.shadowProcessing.shadowDrawingManager.allIndexedItemsByNumber[idx] = backupItem;
        }
      }
      // Redraw canvas to show restored state
      this.redrawCanvas();
    } else {
      console.log('[QUEUE] Not currently displaying dwg - skipping backup restore');
    }

    // Handle menu responses
    if (data.cmd && data.cmd[0] && (data.cmd[0].startsWith('{,') || data.cmd[0].startsWith('{;'))) {
      console.log('[QUEUE] Processing menu response');

      // If we're in raw data display mode, exit it first
      if (document.body.className === 'rawdata-mode') {
        console.log('[QUEUE] Exiting raw data display before processing menu response');
        this.exitRawDataDisplay();
      }

      var result = this.processMenuResponse(data, request);
      if (result) {
        // Menu responses represent navigation to a new display
        // updateNavigationStack will skip refresh, refresh-insertDwg, and back requests internally
        this.updateNavigationStack(this.sentRequest);
        return; // menu was handled
      }
    }

    // Handle specific response types
    if (data.cmd && data.cmd[0] && data.cmd[0].startsWith('{=')) {
      console.log('[QUEUE] Processing response - checking for chart vs raw data');
      console.log('[QUEUE] Full data.cmd array:', data.cmd);
      console.log('[QUEUE] window.chartDisplay exists:', !!window.chartDisplay);

      // Try to parse as chart format (with pipe-delimited labels)
      let chartInfo = null;
      if (window.chartDisplay) {
        console.log('[QUEUE] Calling parseChartLabels with entire cmd array');
        chartInfo = window.chartDisplay.parseChartLabels(data.cmd);
        console.log('[QUEUE] parseChartLabels returned:', chartInfo);
      } else {
        console.log('[QUEUE] WARNING: window.chartDisplay is not defined! Type:', typeof window.chartDisplay);
      }

      if (chartInfo) {
        // This is a chart response
        console.log('[QUEUE] Processing chart response:', chartInfo);
        this.updateNavigationStack(this.sentRequest);
        this.displayChart(chartInfo.title, chartInfo.labels, chartInfo.limit);
      } else {
        // This is a raw data response (no pipe-delimited labels or old format)
        console.log('[QUEUE] Processing raw data response');

        // Extract title for raw data display (from first element only)
        let chartTitle = '';
        const msgType = data.cmd[0];
        const startIdx = msgType.indexOf('=');
        let endIdx = msgType.indexOf('|');
        if (endIdx === -1) {
          endIdx = msgType.length;
        }
        if (startIdx !== -1) {
          chartTitle = msgType.substring(startIdx + 1, endIdx).trim();
        }

        // Get collected raw data
        let rawData = '';
        if (window.rawDataCollector) {
          rawData = window.rawDataCollector.getRawDataWithoutClearing();
        }

        if (rawData.length > 0 || chartTitle) {
          // Raw data responses represent navigation to a new display
          // updateNavigationStack will skip refresh, refresh-insertDwg, and back requests internally
          this.updateNavigationStack(this.sentRequest);
          // Display raw data
          this.displayRawDataText(chartTitle, rawData);
        }
      }
      return true;
    }

    // Add handlers for other non-dwg response types here as needed

    return false;
  }

  // Update navigation stack when response represents a navigation to new display
  // Skips updates for requests that manage their own stack (refresh, back)
  updateNavigationStack(request) {
    // Skip stack updates for refresh and back navigation requests
    if (request.requestType === 'refresh' || request.requestType === 'refresh-insertDwg' || request.requestType === 'back') {
      console.log('[TOOLBAR] Skipping stack update for request type:', request.requestType);
      return;
    }

    // Push current command to stack if different from top
    if (this.currentRefreshCmd) {
      if (this.commandStack.length === 0 || this.currentRefreshCmd !== this.commandStack[this.commandStack.length - 1]) {
        this.commandStack.push(this.currentRefreshCmd);
        console.log('[TOOLBAR] Pushed to stack (navigation):', this.currentRefreshCmd);
      }
    }
    // Update current command to the new display
    this.currentRefreshCmd = request.cmd;
    this.currentRefreshCmdType = request.requestType;
    console.log('[TOOLBAR] Updated currentRefreshCmd (navigation):', this.currentRefreshCmd, 'type:', request.requestType);
  }

  // Clear all pending requests from queue (keeps sentRequest intact)
  clearPendingQueue() {
    const queueLength = this.requestQueue.length;
    this.requestQueue = [];
    console.log(`[QUEUE] Cleared ${queueLength} pending requests, sentRequest still: ${this.sentRequest ? this.sentRequest.drawingName + '(' + this.sentRequest.requestType + ')' : 'null'}`);
  }

  // Process the request queue
  async processRequestQueue() {
    // Safety check: ensure requestQueue is initialized
    if (!this.requestQueue) {
      console.error('[QUEUE] Error: requestQueue is undefined. Aborting queue processing.');
      return;
    }
    //if (this.sentRequest) {
    //  console.log(`[QUEUE] processRequestQueue have sentRequest, queue length: ${this.requestQueue.length}`);
    //} else {
    //  console.log(`[QUEUE] processRequestQueue no sentRequest, queue length: ${this.requestQueue.length}`);
    //}       
    // Try to atomically set processing state from false to true
//    if (!this.trySetProcessingQueue(false, true)) {
//      console.log(`[QUEUE] Already processing queue - skipping`);
//      return;
//    }

    // Return early if there's already a request in flight or queue is empty
    if (this.sentRequest || this.requestQueue.length === 0) {
      if (this.sentRequest) {
        //console.log(`[QUEUE] Request already in flight for "${this.sentRequest.drawingName}" - waiting`);
      }
      // Reset processing state before returning
      if (this.sentRequest) {
        this.setProcessingQueue(true);
      } else {
        //console.log(`[QUEUE] NO sentRequest and queue empty`);
        this.setProcessingQueue(false);

        // Only redraw if shadow processing is not active - check inUse flag
        if (!this.shadowProcessing.shadowDrawingManager.inUse) {
            //console.log(`[QUEUE] No shadow processing active - calling redrawCanvas for final display`);
            setTimeout(() => {
                this.redrawCanvas();
                this.scheduleNextUpdate();
            }, 10);
        } else {
            //console.log(`[QUEUE] Shadow processing active - skipping premature redraw`);
            // Resume update scheduling after a brief delay to allow shadow processing to complete
            setTimeout(() => {
                this.scheduleNextUpdate();
            }, 10);
        }
      }
      return;
    }

    console.log(`[QUEUE] processRequestQueue current queue is:`, JSON.stringify(this.requestQueue, null, 2));

 //    this.setProcessingQueue(true); // have non-zero queue length
    // Remove the request from queue and move it to sentRequest
    const request = this.requestQueue.shift();
    console.warn(`[QUEUE] PROCESSING: "${request.drawingName}" (${request.requestType}) - moved from queue to sentRequest`);
    console.warn(`[QUEUE] after setting sentRequest the current queue is:`, JSON.stringify(this.requestQueue, null, 2));
    this.sentRequest = request;
    console.log(`[SENTREQUEST] ASSIGNED: "${request.drawingName}" (${request.requestType}) at ${new Date().toISOString()}`);
    console.warn(`[QUEUE] sentRequest is:`, JSON.stringify(this.sentRequest, null, 2));

    try {
      // Initialize shadow processing for session-starting requests only
      // insertDwg and refresh-insertDwg requests are part of existing sessions
      if (['mainMenu', 'main', 'touch', 'refresh'].includes(request.requestType)) {
        try {
          this.initializeShadowProcessing(request);
        } catch (error) {
          console.error(`[SHADOW] Error initializing shadow processing:`, error);
          alert(`Shadow processing initialization error: ${error.message}`);
          return; // Stop processing this request
        }
      }

      // Track the touchZone filter and cmd for this request being sent
      if (request.touchZoneInfo) {
        if (!this.sentRequests) {
          this.sentRequests = [];
        }
        this.sentRequests.push({
          drawingName: request.drawingName,
          cmd: request.touchZoneInfo.cmd,
          filter: request.touchZoneInfo.filter,
          timestamp: Date.now()
        });
        console.log(`[QUEUE] Tracking sent request: cmd="${request.touchZoneInfo.cmd}", filter="${request.touchZoneInfo.filter}"`);
      }
      // Use ConnectionManager to send command
      console.log(`[QUEUE] Sending command: ${request.cmd}`);

      const responseText = await this.connectionManager.send(request.cmd);

      // Create response-like object for compatibility with existing code
      const response = {
        ok: true,
        status: 200,
        text: async () => responseText
      };

      console.warn(`[QUEUE] Received response for "${request.drawingName}": status ${response.status}, queue length: ${this.requestQueue.length}`);

      if (!response.ok) {
        throw new Error(`Server returned ${response.status} for drawing "${request.drawingName}"`);
      }

      // Log the raw JSON that we already have
      console.log(`[QUEUE] Received raw JSON data for "${request.drawingName}":`);
      console.log(responseText);

      // Check if response should be discarded
      if (request.discardResponse) {
        //console.log(`[QUEUE] Discarding response for "${request.drawingName}" - marked for discard due to user activity`);
        // Clear the sent request and continue processing
        //console.log(`[SENTREQUEST] CLEARED: "${request.drawingName}" (${request.requestType}) at ${new Date().toISOString()}`);
        this.sentRequest = null;
        setTimeout(() => {
           this.processRequestQueue();
        }, 10);
        return;
      }

     // Don't clear sentRequest here - will be cleared after processing is complete

      // Track request type for logging purposes
      let lastRequest = request.requestType;

      /***
      // Prefilter JSON to fix newlines in strings before parsing
      // prehaps add this back later to catch all control chars
      function prefilterJSON(jsonString) {
        let result = '';
        let inString = false;
        let escaping = false;
        
        for (let i = 0; i < jsonString.length; i++) {
          const char = jsonString[i];
          
          if (escaping) {
            result += char;
            escaping = false;
            continue;
          }
          
          if (char === '\\') {
            result += char;
            escaping = true;
            continue;
          }
          
          if (char === '"') {
            inString = !inString;
            result += char;
            continue;
          }
          
          if (inString && char === '\n') {
            result += '\\n';  // Replace literal newline with escaped newline
          } else {
            result += char;
          }
        }
        
        return result;
      }
      
      // Parse the JSON for processing
      const cleanedResponseText = prefilterJSON(responseText);
      const data = JSON.parse(cleanedResponseText);
      console.log('[QUEUE] parsedText ', JSON.stringify(data,null,2));
      **/
      const data = JSON.parse(responseText);

      // Cache response if it has a version (disabled in designer mode)
      const urlParams = new URLSearchParams(window.location.search);
      if (!urlParams.has('designer') && typeof cacheResponse === 'function') {
        cacheResponse(data, request, this.connectionManager);
      }

      // Handle the response data
      if (this.touchState.isDown) {
        // Mouse is down - queue the response to prevent flashing
        console.log(`[QUEUE] Mouse is down (touchState.isDown=${this.touchState.isDown}) - queuing response for "${request.drawingName}" to prevent flashing`);
        // Remove the processed request from the queue first
//         this.sentRequest = null;
//         this.requestQueue.shift();
         console.warn(`[QUEUE] after isDown sentRequest the current queue is:`, JSON.stringify(this.requestQueue, null, 2));


        // For DRAG responses, keep only the latest one
        if (request.touchZoneInfo && request.touchZoneInfo.filter === TouchZoneFilters.DRAG) {
          const cmd = request.touchZoneInfo.cmd;
          // Remove any existing DRAG response for the same cmd
          this.pendingResponseQueue = this.pendingResponseQueue.filter(pendingResponse =>
            !(pendingResponse.request.touchZoneInfo &&
              pendingResponse.request.touchZoneInfo.filter === TouchZoneFilters.DRAG &&
              pendingResponse.request.touchZoneInfo.cmd === cmd)
          );
          console.log(`[QUEUE] Keeping only latest DRAG response for cmd="${cmd}"`);
        }

        // Add this response to the pending queue
        this.pendingResponseQueue.push({
          request: request,
          data: data
        });
        console.log(`[QUEUE] Added to pending queue. Total pending responses: ${this.pendingResponseQueue.length}`);
      } else {
        // Mouse is up - process immediately

        console.log(`[QUEUE] Processing data for drawing "${request.drawingName}" (type: ${request.requestType})`);

        // Detect response type for logging
        if (data.pfodDrawing === 'start' || data.pfodDrawing === 'update') {
          lastRequest = 'dwgUpdate';
        } else if (data.cmd && data.cmd[0]) {
          if (data.cmd[0].startsWith('{,') || data.cmd[0].startsWith('{;')) {
            lastRequest = 'mainMenu';
          } else if (data.cmd[0].startsWith('{=')) {
            lastRequest = 'rawData';
          } else if (data.cmd[0].startsWith('{+')) {
            lastRequest = 'dwgUpdate';
          } else if (this.isEmptyCmd(data.cmd)) {
            lastRequest = 'empty';
          }
        }

        // Check if this is a valid dwg update:
        // 1. {+ response (full or partial dwg update), OR
        // 2. pfodDrawing: 'start' or 'update' (direct drawing format), OR
        // 3. {} response AND currently displaying a dwg (update to current dwg)
        const isFullOrPartialDwgUpdate = (data.cmd && data.cmd.length > 0 && data.cmd[0].startsWith('{+')) || (data.pfodDrawing === 'start') || (data.pfodDrawing === 'update');
        const isEmptyResponse = this.isEmptyCmd(data.cmd);
        const isDwgUpdate = isFullOrPartialDwgUpdate || (this.currentlyDisplayingDwg && isEmptyResponse);

        // If not a valid dwg update, handle as non-dwg response (menu, raw data, etc.)
        if (!isDwgUpdate) {
          console.log(`[QUEUE] Response is NOT a valid dwg update (${lastRequest}) - handling as non-dwg response (isFullOrPartial=${isFullOrPartialDwgUpdate}, isEmpty=${isEmptyResponse}, currentlyDwg=${this.currentlyDisplayingDwg})`);
          this.handleNonDwgResponse(data, request, request.requestType);
          // Clear the sent request and continue processing
          console.log(`[QUEUE] COMPLETED: ${lastRequest} response - clearing sentRequest`);
          this.sentRequest = null;
          this.processRequestQueue();
          return;
        }

        // Handle valid dwg response through dedicated method
        if (this.handleDwgResponse(data, request)) {
          // Success - clear the sent request and continue processing
          console.log(`[QUEUE] COMPLETED: ${lastRequest} response - clearing sentRequest`);
          this.sentRequest = null;
          // Now check if we should apply collected shadow updates (after sentRequest is cleared)
          this.checkAndApplyShadowUpdates();
          this.processRequestQueue();
          return;
        } else {
          // Error was already handled in handleDwgResponse
          // Clear the failed request and continue processing
          console.log(`[SENTREQUEST] CLEARED: "${request.drawingName}" (${request.requestType}) at ${new Date().toISOString()}`);
          this.sentRequest = null;

          // For inserted drawings, if we're at the end of the queue, proceed with redraw
          if (this.requestQueue.length === 0 && !this.sentRequest) {
            console.log(`[QUEUE] Queue empty after failed request. Drawing with available data.`);
            this.setProcessingQueue(false);
            this.redrawCanvas();
            // Resume update scheduling after failed request cleanup
            this.scheduleNextUpdate();
          }

          // Continue processing queue
          setTimeout(() => {
            if (this.sentRequest || this.requestQueue.length !== 0) {
              this.processRequestQueue();
            }
          }, 10);
          return;
        }
      }

      // Legacy queue completion logic removed - now handled by shadow processing atomic updates

    } catch (error) {
      // Catch any errors from JSON parsing or other non-handler logic
      let dwgName = " ";
      if  (request.drawingName !== undefined && request.drawingName !== null) {
        dwgName = request.drawingName;
      }
      console.error(`[QUEUE] Error processing request for "${dwgName}":`, error);
      console.error(`[QUEUE] Error stack:`, error.stack);

      // Clear the failed request
      console.log(`[SENTREQUEST] CLEARED: "${request.drawingName}" (${request.requestType}) at ${new Date().toISOString()}`);
      this.sentRequest = null;

      // Continue processing queue
      setTimeout(() => {
        if (this.sentRequest || this.requestQueue.length !== 0) {
          this.processRequestQueue();
        }
      }, 10);
    }
  }


  // Queue an update for any drawing (main or inserted)
  async queueDrawingUpdate(drawingName) {
    try {
      console.log(`[QUEUE_DWG] Preparing fetch for drawing "${drawingName}" at ${new Date().toISOString()}`);

      // Warn if drawingName is null/undefined
      if (!drawingName) {
        console.warn(`[QUEUE_DWG] WARNING: drawingName is null or undefined!`);
        console.trace('[QUEUE_DWG] Stack trace for null drawingName');
      }

      const savedVersion = localStorage.getItem(`${drawingName}_version`);
      const savedData = localStorage.getItem(`${drawingName}_data`);
      let cmd;
      // Add version to command only if we have both version and data
      if (savedVersion) { // && savedData) {
        cmd = '{' + savedVersion + ':' + drawingName + '}';
        console.log(`Using saved version: ${savedVersion}`);
      } else {
        console.log('No valid saved version+data pair - requesting fresh data (dwg:start)');
        cmd = '{' + drawingName + '}';
      }

      console.log(`[QUEUE_DWG] Constructed command: "${cmd}"`);

      /**
      // Use /pfodWeb endpoint with cmd parameter in {drawingName} format
      let endpoint = `/pfodWeb?cmd=${encodeURIComponent('{' + drawingName + '}')}`;

      // Add version query parameter if available and valid AND there's corresponding data
      if (savedVersion !== null && savedData) {
        endpoint += `&version=${encodeURIComponent(savedVersion)}`;
        console.log(`[QUEUE_DWG] Using saved version "${savedVersion}" for "${drawingName}"`);
      } else {
        if (savedVersion !== null && !savedData) {
          console.log(`[QUEUE_DWG] Found valid version "${savedVersion}" without data for "${drawingName}" - keeping version but requesting full drawing data`);
          // Don't remove the version - it's valid (including empty string), just request fresh data
        } else {
          console.log(`[QUEUE_DWG] No saved version for "${drawingName}", requesting full drawing data`);
        }
      }
      **/
      // Add to the request queue
      this.addToRequestQueue(drawingName, cmd, null, null, 'refresh');
      console.log(`[QUEUE_DWG] Added "${drawingName}" to request queue`);
    } catch (error) {
      console.error(`[QUEUE_DWG] Failed to queue drawing "${drawingName}":`, error);
    }
  }

  // Process all pending responses that were queued while mouse was down
  processPendingResponses() {
    if (this.pendingResponseQueue.length === 0) {
      console.log(`[QUEUE] No pending responses to process - ensuring refresh timer is restarted`);
      this.scheduleNextUpdate();
      return;
    }

    console.log(`[QUEUE] Processing ${this.pendingResponseQueue.length} pending responses after mouse release`);
    const hadPendingResponses = this.pendingResponseQueue.length > 0;

    // Process responses in order of receipt
    while (this.pendingResponseQueue.length > 0) {
      const pendingResponse = this.pendingResponseQueue.shift();
      const request = pendingResponse.request;
      const data = pendingResponse.data;

      console.log(`[QUEUE] Processing queued response for "${request.drawingName}"`);

      // Detect response type for logging
      let responseType = request.requestType;
      if (data.pfodDrawing === 'start' || data.pfodDrawing === 'update') {
        responseType = 'dwgUpdate';
      } else if (data.cmd && data.cmd[0]) {
        if (data.cmd[0].startsWith('{,') || data.cmd[0].startsWith('{;')) {
          responseType = 'mainMenu';
        } else if (data.cmd[0].startsWith('{=')) {
          responseType = 'rawData';
        } else if (data.cmd[0].startsWith('{+')) {
          responseType = 'dwgUpdate';
        } else if (this.isEmptyCmd(data.cmd)) {
          responseType = 'empty';
        }
      }

      // Check if this is a valid dwg update:
      // 1. {+ response (full or partial dwg update), OR
      // 2. pfodDrawing: 'start' or 'update' (direct drawing format), OR
      // 3. {} response AND currently displaying a dwg (update to current dwg)
      const isFullOrPartialDwgUpdate = (data.cmd && data.cmd.length > 0 && data.cmd[0].startsWith('{+')) || (data.pfodDrawing === 'start') || (data.pfodDrawing === 'update');
      const isEmptyResponse = this.isEmptyCmd(data.cmd);
      const isDwgUpdate = isFullOrPartialDwgUpdate || (this.currentlyDisplayingDwg && isEmptyResponse);

      if (!isDwgUpdate) {
        console.log(`[QUEUE] Pending response is NOT a current dwg update (${responseType}) - handling as non-dwg response (isFullOrPartial=${isFullOrPartialDwgUpdate}, isEmpty=${isEmptyResponse}, currentlyDwg=${this.currentlyDisplayingDwg})`);
        // Handle the non-dwg response (checks flag, restores backup, redraws, and processes based on type)
        this.handleNonDwgResponse(data, request, request.requestType);
        // Skip normal processing for non-dwg responses
        continue;
      }

      // Handle valid dwg response through dedicated method
      if (this.handleDwgResponse(data, request)) {
        console.log(`[QUEUE] Successfully processed dwg response from pending queue`);
      } else {
        // Error was already logged in handleDwgResponse
        console.error(`[QUEUE] Failed to process dwg response from pending queue`);
      }
    }

    console.log(`[QUEUE] Finished processing all pending responses`);

    // Apply shadow updates to redraw manager and redraw after processing all responses
    if (hadPendingResponses) {
      console.log(`[QUEUE] Finished processing pending responses - checking if shadow updates should be applied`);
      if (!this.touchState.isDown) {
        // Clear sentRequest if still set so queue can continue processing insertDwg requests
        if (this.sentRequest) {
          console.log(`[QUEUE] Clearing sentRequest "${this.sentRequest.drawingName}" to allow queue processing`);
          console.log(`[SENTREQUEST] CLEARED: "${this.sentRequest.drawingName}" (${this.sentRequest.requestType}) - after processing pending responses`);
          this.sentRequest = null;
        }

        // Check if we should apply shadow updates now or wait for more related requests
        // Do this after sentRequest is cleared so the shadow system knows requests are complete
        console.log(`[QUEUE] Checking for more related requests before applying shadow updates`);
        this.checkAndApplyShadowUpdates();
      }
    }
    setTimeout(() => {
         this.processRequestQueue();
         // Ensure rescheduling after mouse up if queue is empty and no request in flight
         if (this.requestQueue.length === 0 && !this.sentRequest) {
           this.scheduleNextUpdate();
         }
    }, 10);
  }

  // Check if all responses are collected and apply them atomically
  checkAndApplyShadowUpdates() {
    console.log(`[SHADOW] === checkAndApplyShadowUpdates() called ===`);
    console.log(`[SHADOW] Current responses collected: ${this.shadowProcessing.responses.size}`);
    console.log(`[SHADOW] Response drawings: [${Array.from(this.shadowProcessing.responses.keys()).join(', ')}]`);

    // Skip if no active session
    if (!this.shadowProcessing.requestType) {
      console.log(`[SHADOW] No active session - skipping`);
      return;
    }

    // Check abandonment conditions based on request type
    if (this.shouldAbandonShadowProcessing()) {
      console.log(`[SHADOW] Abandoning ${this.shadowProcessing.requestType} processing - ${this.shadowProcessing.responses.size} responses discarded`);
      this.cleanupShadowProcessing();
      return;
    }

    // All responses have already been processed individually as they were received

    // Check if there are more related requests in queue - wait for them
    const hasMoreRelated = this.hasMoreRelatedRequests();
    const hasRelatedInFlight = this.hasRelatedRequestInFlight();

    console.log(`[SHADOW] Check results - hasMoreRelated: ${hasMoreRelated}, hasRelatedInFlight: ${hasRelatedInFlight}`);

    if (hasMoreRelated || hasRelatedInFlight) {
      console.log(`[SHADOW] Waiting for more ${this.shadowProcessing.requestType} responses - queue: ${hasMoreRelated}, in-flight: ${hasRelatedInFlight}`);
      return;
    }

    // All responses collected - apply them atomically
    console.log(`[SHADOW] All responses collected - applying shadow updates atomically`);
    this.applyShadowUpdates();
  }

  // Apply collected shadow updates atomically
  applyShadowUpdates() {
    console.log(`[SHADOW] Applying shadow updates atomically - copying processed data to redraw`);

    try {
      // Get the main drawing name from shadow manager (always has drawings array populated)
      const mainDrawingName = this.shadowProcessing.shadowDrawingManager.drawings.length > 0 ?
        this.shadowProcessing.shadowDrawingManager.drawings[0] : '';
      console.log(`[REFRESH] Using shadow drawing name: ${mainDrawingName}`);

      // Get the current refresh rate BEFORE updating (may be 0, very long, or undefined)
      const oldRefreshData = this.redraw.redrawDrawingManager.drawingsData[mainDrawingName]?.data;
      const oldRefreshRate = oldRefreshData?.refresh;
      console.log(`[REFRESH] Before shadow update - oldRefreshRate: ${oldRefreshRate}`);

      // Copy processed shadow data to isolated redraw drawing manager
      // processDrawingData has already been called and processed data in shadow copy
      console.log(`[SHADOW] Updating redraw with processed shadow data`);

      // Check if any responses are touch requests - if so, skip merge
      const isTouchRequest = Array.from(this.shadowProcessing.responses.values())
        .some(response => response.request.requestType === 'touch');

      if (isTouchRequest) {
        console.log(`[SHADOW] Touch request detected - skipping merge operation, using shadow data as-is`);
      } else {
        // Create merged collections using DrawingMerger after all individual drawings are processed
        console.log(`[SHADOW] Normal request - performing merge operation`);
        const drawingMerger = new window.DrawingMerger(this.shadowProcessing.shadowDrawingManager);
        drawingMerger.mergeAllDrawings();
      }

      // Atomically update redraw drawing manager with processed shadow copy (triggers redraw)
      this.redraw.updateFromShadow(this.shadowProcessing.shadowDrawingManager);

      // Get the new refresh rate AFTER updating
      const newRefreshData = this.redraw.redrawDrawingManager.drawingsData[mainDrawingName]?.data;
      const newRefreshRate = newRefreshData?.refresh;
      console.log(`[REFRESH] After shadow update - newRefreshRate: ${newRefreshRate}`);

      // If refresh rate changed, explicitly reschedule the timer with the new rate
      if (oldRefreshRate !== newRefreshRate) {
        console.log(`[REFRESH] Refresh rate changed: ${oldRefreshRate}ms → ${newRefreshRate}ms`);

        // Cancel old timer if it exists
        if (this.updateTimer) {
          clearTimeout(this.updateTimer);
          this.updateTimer = null;
          console.log(`[REFRESH] Cancelled old refresh timer (rate was ${oldRefreshRate}ms)`);
        }

        // Schedule new timer with new rate
        if (newRefreshRate && newRefreshRate > 0) {
          console.log(`[REFRESH] Scheduling new refresh timer with ${newRefreshRate}ms interval`);
          this.updateTimer = setTimeout(() => this.fetchRefresh(), newRefreshRate);
        } else {
          console.log(`[REFRESH] New refresh rate is ${newRefreshRate} - automatic updates disabled`);
        }
      } else {
        console.log(`[REFRESH] Refresh rate unchanged (${oldRefreshRate}ms)`);
        // Resume update scheduling with existing rate
        this.scheduleNextUpdate();
      }

      // Clean up shadow processing session before resuming updates
      this.cleanupShadowProcessing();

    } catch (error) {
      console.error(`[SHADOW] Error applying shadow updates:`, error);
      alert(`Error applying shadow updates: ${error.message}`);
      // Clean up on error
      this.cleanupShadowProcessing();
    }
  }

  // Initialize shadow processing for a new session
  initializeShadowProcessing(request) {
    // Skip if already processing same request type
    if (this.shadowProcessing.requestType === request.requestType) {
      console.log(`[SHADOW] Shadow processing already active for ${request.requestType}`);
      return;
    }

    // Clean up any previous shadow processing
    if (this.shadowProcessing.requestType) {
      console.log(`[SHADOW] Cleaning up previous ${this.shadowProcessing.requestType} session before starting ${request.requestType}`);
      this.cleanupShadowProcessing();
    }

    // Initialize new shadow processing session
    this.shadowProcessing.requestType = request.requestType;
    this.shadowProcessing.responses.clear();

    // Create shadow copy of current drawing data
    this.createShadowCopy();

    console.log(`[SHADOW] Starting shadow processing session for ${request.requestType}`);
  }

  // Create shadow copy of current drawing data
  createShadowCopy() {
    try {
      // Create new shadow DrawingManager instance
      this.shadowProcessing.shadowDrawingManager = new window.DrawingManager();

      // Copy ALL redraw drawing manager data to shadow for processing
      this.redraw.copyToShadow(this.shadowProcessing.shadowDrawingManager);

    } catch (error) {
      console.error(`[SHADOW] Failed to create shadow copy:`, error);
      // Fall back to empty shadow manager
      this.shadowProcessing.shadowDrawingManager = new window.DrawingManager();
    }
  }

  // Clean up shadow processing session
  cleanupShadowProcessing() {
    // Reset shadow manager to new instance instead of null to avoid null access errors
    this.shadowProcessing.shadowDrawingManager = new window.DrawingManager();
    this.shadowProcessing.responses.clear();
    this.shadowProcessing.requestType = null; // This is the flag for active/inactive
    console.log(`[SHADOW] Shadow processing session cleaned up`);
  }

  // Check if shadow processing should be abandoned based on request type priorities
  shouldAbandonShadowProcessing() {
    const requestType = this.shadowProcessing.requestType;

    // Priority order: mainMenu > main > touch > refresh
    // Higher priority requests abandon lower priority ones

    // Refresh requests (lowest priority) - abandoned by mouse down or any higher priority request
    if (requestType === 'refresh') {
      return this.touchState.isDown || this.requestQueue.some(req => ['mainMenu', 'main', 'touch'].includes(req.requestType));
    }

    // Touch requests - abandoned by main or mainMenu requests
    if (requestType === 'touch') {
      return this.requestQueue.some(req => ['mainMenu', 'main'].includes(req.requestType));
    }

    // Main requests - abandoned only by mainMenu requests
    if (requestType === 'main') {
      return this.requestQueue.some(req => req.requestType === 'mainMenu');
    }

    // MainMenu requests (highest priority) - never abandoned
    if (requestType === 'mainMenu') {
      return false;
    }

    // Default - abandon unknown request types
    console.warn(`[SHADOW] Unknown request type for abandonment check: ${requestType}`);
    return true;
  }

  // Check if there are more requests related to current shadow processing
  hasMoreRelatedRequests() {
    const requestType = this.shadowProcessing.requestType;
    const relatedRequests = this.requestQueue.filter(req =>
      req.requestType === requestType ||
      req.requestType === 'insertDwg' ||
      (requestType === 'refresh' && req.requestType === 'refresh-insertDwg'));

    console.log(`[SHADOW_CHECK] hasMoreRelatedRequests() - shadow type: ${requestType}`);
    console.log(`[SHADOW_CHECK] Queue length: ${this.requestQueue.length}, related requests: ${relatedRequests.length}`);
    console.log(`[SHADOW_CHECK] All queue request types: [${this.requestQueue.map(req => req.drawingName + '(' + req.requestType + ')').join(', ')}]`);
    console.log(`[SHADOW_CHECK] Related request types: [${relatedRequests.map(req => req.drawingName + '(' + req.requestType + ')').join(', ')}]`);

    return relatedRequests.length > 0;
  }

  // Check if there's a related request in flight
  hasRelatedRequestInFlight() {
    const requestType = this.shadowProcessing.requestType;
    const hasRelated = this.sentRequest && (
      this.sentRequest.requestType === requestType ||
      this.sentRequest.requestType === 'insertDwg' ||
      (requestType === 'refresh' && this.sentRequest.requestType === 'refresh-insertDwg'));

    console.log(`[SHADOW_CHECK] hasRelatedRequestInFlight() - shadow type: ${requestType}`);
    console.log(`[SHADOW_CHECK] sentRequest: ${this.sentRequest ? this.sentRequest.drawingName + '(' + this.sentRequest.requestType + ')' : 'null'}`);
    console.log(`[SHADOW_CHECK] hasRelated: ${hasRelated}`);

    return hasRelated;
  }

  // Update collection shadow processors removed - using unified shadow processing system

  // Process drawing data (converted from global function)
  // touchZones are processed by adding current transform and then storing in touchZonesByCmd[dwgName]
  // in merge all touchZones are merged together into allTouchZonesByCmd
  // in redraw all the touchZones are drawn after unindexed and indexed items, if in debug mode
  processDrawingData(data, savedData, requestType = 'unknown') {
    // DrawingDataProcessor ALWAYS works on shadowDrawingManager only
    // Pass shadow drawing manager locally to avoid changing global references
    return this.drawingDataProcessor.processDrawingData(data, this.shadowProcessing.shadowDrawingManager, savedData, requestType);
  }


  // Handle insertDwg items by adding them to the request queue
  handleInsertDwg(item) {
    const drawingName = item.drawingName;
    const xOffset = parseFloat(item.xOffset || 0);
    const yOffset = parseFloat(item.yOffset || 0);

    console.log(`[INSERT_DWG] Handling insertDwg for drawing "${drawingName}" with offset (${xOffset}, ${yOffset})`);

    // Verify this is a valid insertDwg item
    if (!item.type || (item.type !== 'insertDwg' && item.type.toLowerCase() !== 'insertdwg')) {
      console.error(`[INSERT_DWG] Invalid item type: ${item.type}. Expected 'insertDwg'`);
      console.log(`[INSERT_DWG] Full item:`, JSON.stringify(item));
    }

    // Ensure the target drawing has its item collections properly initialized
    this.shadowProcessing.shadowDrawingManager.ensureItemCollections(drawingName);

    if (!drawingName) {
      console.error('[INSERT_DWG] InsertDwg item missing drawingName:', item);
      return {
        error: 'Missing drawing name',
        item: item
      };
    }

    // Check if we're trying to insert the current drawing (prevent infinite recursion)
    const mainDrawingName = this.shadowProcessing.shadowDrawingManager.drawings.length > 0 ? this.shadowProcessing.shadowDrawingManager.drawings[0] : '';
    if (drawingName === mainDrawingName) {
      console.warn(`[INSERT_DWG] Error: Cannot insert drawing "${drawingName}" into itself`);
      return {
        error: 'Self-insertion not allowed',
        drawingName: mainDrawingName
      };
    }

    // Check if this drawing is already in the drawings array
    if (this.shadowProcessing.shadowDrawingManager.drawings.includes(drawingName)) {
      console.log(`[INSERT_DWG] Drawing "${drawingName}" is already in drawings list.`);

      // Even if drawing is already in the drawings list, explicitly check if we need to request it
      if (!this.shadowProcessing.shadowDrawingManager.drawingsData[drawingName] || !this.shadowProcessing.shadowDrawingManager.drawingsData[drawingName].data) {
        console.log(`[INSERT_DWG] Drawing "${drawingName}" in list but data missing - will request it`);
        // Add to the request queue if not already in queue
        if (!this.requestQueue.some(req => req.drawingName === drawingName)) {
          const cmd = '{' + drawingName + '}';

          console.warn(`[INSERT_DWG] Adding "${drawingName}" to request queue (already in drawings)`);
          this.addToRequestQueue(drawingName, cmd, null, null, 'insertDwg');
        } else {
          console.log(`[INSERT_DWG] "${drawingName}" already in request queue`);
        }
      }

      return {
        drawingName: drawingName,
        dataAvailable: this.shadowProcessing.shadowDrawingManager.drawingsData[drawingName] && this.shadowProcessing.shadowDrawingManager.drawingsData[drawingName].data ? true : false,
        alreadyInList: true
      };
    }

    // Ensure collections exist for this drawing
    this.shadowProcessing.shadowDrawingManager.ensureItemCollections(drawingName);

    // Add this drawing to the DrawingManager
    this.shadowProcessing.shadowDrawingManager.addInsertedDrawing(
      drawingName,
      xOffset,
      yOffset,
      item.transform || {
        x: 0,
        y: 0,
        scale: 1.0
      },
      mainDrawingName // Parent drawing name
    );

    console.log(`[INSERT_DWG] Created entry for drawing "${drawingName}" in drawingsData`);
    console.log(`[INSERT_DWG] Request timestamp: ${new Date().toISOString()}`);

    // Add to the request queue
    if (!this.requestQueue.some(req => req.drawingName === drawingName)) {
      const cmd = '{' + drawingName + '}';

      // Determine the appropriate request type based on current shadow processing context
      let requestType = 'insertDwg';
      if (this.shadowProcessing.requestType === 'refresh') {
        requestType = 'refresh-insertDwg';
        console.warn(`[INSERT_DWG] Adding "${drawingName}" to request queue (refresh-triggered insert)`);
      } else {
        console.warn(`[INSERT_DWG] Adding "${drawingName}" to request queue (new insert)`);
      }

      this.addToRequestQueue(drawingName, cmd, null, null, requestType);
    } else {
      console.log(`[INSERT_DWG] "${drawingName}" already in request queue`);
    }

    // Return immediately so that the placeholder can be drawn
    return {
      drawingName: drawingName,
      dataAvailable: false,
      newlyAdded: true
    };
  }



  // Remove an inserted drawing and its touchZones, plus any child drawings
  removeInsertedDrawing(drawingName) {
    if (!drawingName) {
      console.error('No drawing name provided to removeInsertedDrawing');
      return;
    }

    console.log(`[REMOVE_DWG] Removing inserted drawing: ${drawingName}`);

    // Remove any pending requests for this drawing from the queue
    const initialQueueLength = this.requestQueue.length;
    this.requestQueue = this.requestQueue.filter(request => request.drawingName !== drawingName);
    let removedCount = initialQueueLength - this.requestQueue.length;

    // Also check and clear if the currently sent request is for this drawing
    if (this.sentRequest && this.sentRequest.drawingName === drawingName) {
      console.log(`[REMOVE_DWG] Clearing in-flight request for ${drawingName}`);
      console.log(`[SENTREQUEST] CLEARED: "${request.drawingName}" (${request.requestType}) at ${new Date().toISOString()}`);
      this.sentRequest = null;
      removedCount++;
    }

    if (removedCount > 0) {
      console.log(`[REMOVE_DWG] Removed ${removedCount} request(s) for ${drawingName} (${initialQueueLength - this.requestQueue.length} from queue, ${this.sentRequest ? 0 : (removedCount - (initialQueueLength - this.requestQueue.length))} in-flight)`);
    }

    // First identify any child drawings that have this drawing as their parent
    const childDrawings = this.shadowProcessing.shadowDrawingManager.getChildDrawings(drawingName);

    // Recursively remove all child drawings first
    childDrawings.forEach(childName => {
      console.log(`[REMOVE_DWG] Removing child drawing ${childName} of ${drawingName}`);
      this.removeInsertedDrawing(childName);
    });

    // Remove associated touchZones (if touchZonesByCmd is available)
    if (typeof this.touchZonesByCmd !== 'undefined') {
      this.removeTouchZonesByDrawing(drawingName);
    }

    // Remove the drawing using the manager
    this.shadowProcessing.shadowDrawingManager.removeInsertedDrawing(drawingName);

    console.log(`[REMOVE_DWG] Completed removal of inserted drawing: ${drawingName}`);
  }

  // Remove touchZones associated with a specific drawing
  removeTouchZonesByDrawing(drawingName) {
    if (!drawingName) {
      console.error('No drawing name provided to removeTouchZonesByDrawing');
      return;
    }

    console.log(`Removing touchZones for drawing: ${drawingName}`);

    // Create a new array of keys to remove
    const keysToRemove = [];

    // Find all touchZones belonging to this drawing
    for (const cmd in this.touchZonesByCmd) {
      const touchZone = this.touchZonesByCmd[cmd];
      if (touchZone.parentDrawingName === drawingName) {
        keysToRemove.push(cmd);
        console.log(`Marked touchZone for removal: cmd=${cmd}, drawing=${drawingName}`);
      }
    }

    // Remove identified touchZones
    keysToRemove.forEach(cmd => {
      delete this.touchZonesByCmd[cmd];
      console.log(`Removed touchZone: cmd=${cmd}`);
    });

    console.log(`Removed ${keysToRemove.length} touchZones for drawing: ${drawingName}`);
  }

  // Build connection info message
  getConnectionInfoMessage() {
    let connectionInfo = '';

    console.log('[CONNECTION_INFO] Protocol:', this.protocol);
    console.log('[CONNECTION_INFO] Adapter:', this.connectionManager?.adapter);

    if (this.protocol === 'http' && this.targetIP) {
      connectionInfo = `HTTP to ${this.targetIP}`;
    } else if (this.protocol === 'serial') {
      const portName = this.connectionManager?.adapter?.portName || 'COM?';
      connectionInfo = `Serial: ${portName} @ ${this.baudRate} baud`;
    } else if (this.protocol === 'ble') {
      // Try multiple ways to get device name
      let deviceName = this.connectionManager?.adapter?.device?.name;
      if (!deviceName) {
        deviceName = this.connectionManager?.adapter?.deviceName;
      }
      if (!deviceName) {
        deviceName = 'Unknown Device';
      }
      connectionInfo = `BLE: ${deviceName}`;
    } else {
      connectionInfo = 'Unknown Connection';
    }

    console.log('[CONNECTION_INFO] Final message:', connectionInfo);
    return connectionInfo;
  }

  // Show "No Connection" alert dialog after max retries
  showNoConnectionAlert() {
    console.log('[ALERT] Showing No Connection alert after max retry attempts');

    // Create overlay
    const overlay = document.createElement('div');
    overlay.id = 'no-connection-overlay';
    overlay.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.5);
      z-index: 999998;
      display: flex;
      justify-content: center;
      align-items: center;
    `;

    // Create alert dialog
    const alertBox = document.createElement('div');
    alertBox.style.cssText = `
      background-color: white;
      padding: 30px 40px;
      border-radius: 10px;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
      text-align: center;
      max-width: 500px;
      font-family: Arial, sans-serif;
    `;

    // Create message
    const message = document.createElement('p');
    message.textContent = 'No Connection';
    message.style.cssText = `
      font-size: 24px;
      font-weight: bold;
      margin: 0 0 15px 0;
      color: #d32f2f;
    `;

    // Create connection details
    const details = document.createElement('p');
    const connectionInfo = this.getConnectionInfoMessage();
    details.textContent = `Failed to connect to: ${connectionInfo}`;
    details.style.cssText = `
      font-size: 14px;
      margin: 0 0 20px 0;
      color: #666;
      font-family: monospace;
    `;

    // Create Close button
    const closeButton = document.createElement('button');
    closeButton.textContent = 'Close';
    closeButton.style.cssText = `
      background-color: #2196F3;
      color: white;
      border: none;
      padding: 10px 30px;
      font-size: 16px;
      border-radius: 5px;
      cursor: pointer;
      outline: none;
    `;

    // Make button respond to hover
    closeButton.onmouseover = () => {
      closeButton.style.backgroundColor = '#1976D2';
    };
    closeButton.onmouseout = () => {
      closeButton.style.backgroundColor = '#2196F3';
    };

    // Close button handler - reload page
    const reloadPage = () => {
      console.log('[ALERT] Close button clicked - reloading page');
      window.location.reload();
    };

    closeButton.onclick = reloadPage;

    // Handle Enter key
    const handleKeyPress = (event) => {
      if (event.key === 'Enter') {
        event.preventDefault();
        reloadPage();
      }
    };
    document.addEventListener('keydown', handleKeyPress);

    // Focus the button so Enter works immediately
    setTimeout(() => {
      closeButton.focus();
    }, 100);

    // Assemble dialog
    alertBox.appendChild(message);
    alertBox.appendChild(details);
    alertBox.appendChild(closeButton);
    overlay.appendChild(alertBox);
    document.body.appendChild(overlay);
  }

  // Handle drawing error (not found, etc) - instance method for multi-viewer support
  handleDrawingError(errorData) {
    console.error(`Drawing error: ${errorData.error} - ${errorData.message}`);

    // Completely remove any canvas container that might interfere
    if (this.canvasContainer) {
      this.canvasContainer.style.display = 'none';
    }

    // Create a brand new error message div directly in the body
    // First, remove any existing error message
    const existingError = document.getElementById('error-message');
    if (existingError) {
      document.body.removeChild(existingError);
    }

    // Create the new error element
    const errorMessageElement = document.createElement('div');
    errorMessageElement.id = 'error-message';

    // Apply inline styles directly
    errorMessageElement.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: white;
            z-index: 999999;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            padding: 20px;
            box-sizing: border-box;
            font-family: Arial, sans-serif;
            color: #333;
            text-align: center;
        `;

    // Set the HTML content
    errorMessageElement.innerHTML = `
            <div style="
                background-color: white;
                padding: 30px;
                border-radius: 10px;
                box-shadow: 0 4px 8px rgba(0,0,0,0.1);
                max-width: 80%;
                margin: 0 auto;
                text-align: center;
            ">
                <h2 style="
                    color: #d32f2f;
                    margin-bottom: 20px;
                    font-size: 28px;
                    font-weight: bold;
                ">Drawing Error</h2>
                <p style="
                    font-size: 20px;
                    margin-bottom: 20px;
                    color: #333;
                ">${errorData.message}</p>
                <p style="
                    font-size: 18px;
                    margin-bottom: 30px;
                    color: #666;
                ">Please check the drawing name and try again.</p>
            </div>
        `;

    // Add to the document body
    document.body.appendChild(errorMessageElement);

    // For debugging
    console.log('Error message created and added to body');

    // Remove any canvas, just to be sure
    if (this.canvas) {
      this.canvas.style.display = 'none';
    }

    // Disable updates
    this.isUpdating = false;
    if (this.updateTimer) {
      clearTimeout(this.updateTimer);
      this.updateTimer = null;
    }

    // Log to console
    console.warn("ERROR DISPLAYED TO USER:", errorData.message);

    // Try to adjust the page title to indicate the error
    document.title = "Error: Drawing Not Found";
  }


}


// TouchZone special values - these remain global as they're constants
const TouchZoneSpecialValues = {
  TOUCHED_COL: 65534, // Only used in touchZone actions to specify touched col value
  TOUCHED_ROW: 65532, // Only used in touchZone actions to specify touched row value
};

// Dynamic script loader
function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = src;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
}

// Load all dependencies in order
async function loadDependencies() {
  const dependencies = [
    './version.js',
    './connectionManager.js',
    './csvCollector.js',
    './rawDataCollector.js',
    // JSFreeChart files - must load in dependency order
    './jsfreechart/src/JSFreeChart.js',
    './jsfreechart/src/Module.js',
    './jsfreechart/src/Args.js',
    './jsfreechart/src/Utils.js',
    './jsfreechart/src/graphics/Color.js',
    './jsfreechart/src/Colors.js',
    './jsfreechart/src/graphics/Point2D.js',
    './jsfreechart/src/graphics/Rectangle.js',
    './jsfreechart/src/graphics/Dimension.js',
    './jsfreechart/src/graphics/HAlign.js',
    './jsfreechart/src/graphics/RectangleEdge.js',
    './jsfreechart/src/graphics/Insets.js',
    './jsfreechart/src/graphics/Offset2D.js',
    './jsfreechart/src/graphics/Scale2D.js',
    './jsfreechart/src/graphics/Fit2D.js',
    './jsfreechart/src/graphics/Stroke.js',
    './jsfreechart/src/graphics/TextAnchor.js',
    './jsfreechart/src/graphics/Font.js',
    './jsfreechart/src/graphics/LineCap.js',
    './jsfreechart/src/graphics/LineJoin.js',
    './jsfreechart/src/graphics/RefPt2D.js',
    './jsfreechart/src/graphics/Anchor2D.js',
    './jsfreechart/src/graphics/BaseContext2D.js',
    './jsfreechart/src/graphics/CanvasContext2D.js',
    './jsfreechart/src/data/Map.js',
    './jsfreechart/src/data/Range.js',
    './jsfreechart/src/data/StandardXYDataset.js',
    './jsfreechart/src/data/XYDatasetUtils.js',
    './jsfreechart/src/data/KeyedValues2DDataset.js',
    './jsfreechart/src/table/BaseElement.js',
    './jsfreechart/src/table/TableElement.js',
    './jsfreechart/src/table/TextElement.js',
    './jsfreechart/src/table/StandardRectanglePainter.js',
    './jsfreechart/src/table/FlowElement.js',
    './jsfreechart/src/table/RectangleElement.js',
    './jsfreechart/src/table/GridElement.js',
    './jsfreechart/src/renderer/ColorSource.js',
    './jsfreechart/src/renderer/StrokeSource.js',
    './jsfreechart/src/renderer/BaseXYRenderer.js',
    './jsfreechart/src/renderer/ScatterRenderer.js',
    './jsfreechart/src/renderer/XYLineRenderer.js',
    './jsfreechart/src/util/Format.js',
    './jsfreechart/src/util/NumberFormat.js',
    './jsfreechart/src/axis/AxisSpace.js',
    './jsfreechart/src/axis/LabelOrientation.js',
    './jsfreechart/src/axis/TickMark.js',
    './jsfreechart/src/axis/NumberTickSelector.js',
    './jsfreechart/src/axis/ValueAxis.js',
    './jsfreechart/src/axis/BaseValueAxis.js',
    './jsfreechart/src/axis/LinearAxis.js',
    './jsfreechart/src/labels/StandardXYLabelGenerator.js',
    './jsfreechart/src/legend/LegendBuilder.js',
    './jsfreechart/src/legend/LegendItemInfo.js',
    './jsfreechart/src/legend/StandardLegendBuilder.js',
    './jsfreechart/src/plot/XYPlot.js',
    './jsfreechart/src/Chart.js',
    './jsfreechart/src/Charts.js',
    // Chart display and utility files
    './chartDisplay.js',
    './caching.js',
    './messageViewer.js',
    // Drawing and core files
    './DrawingManager.js',
    './displayTextUtils.js',
    './redraw.js',
    './drawingMerger.js',
    './webTranslator.js',
    './drawingDataProcessor.js',
    './pfodWebMouse.js'
  ];

  for (const dep of dependencies) {
    await loadScript(dep);
  }

  // Make JS_VERSION available globally after dependencies are loaded
  if (typeof JS_VERSION !== 'undefined') {
    window.JS_VERSION = JS_VERSION;
    console.log('[PFODWEB_DEBUG] JS_VERSION loaded and made globally available:', JS_VERSION);
  } else {
    console.warn('[PFODWEB_DEBUG] Warning: JS_VERSION not defined after loading dependencies');
  }
}

// Global viewer instance
let drawingViewer = null;

// Global flag to indicate if chart mode is currently active
// Used by handleResize() to determine which resize method to use
window.isInChartMode = false;

// Event Listeners
window.addEventListener('DOMContentLoaded', async () => {
  console.log('[PFODWEB_DEBUG] DOMContentLoaded event fired');

  console.log('[PFODWEB_DEBUG] URL when DOMContentLoaded:', window.location.href);
  console.log('[PFODWEB_DEBUG] Referrer when DOMContentLoaded:', document.referrer);
  await loadDependencies();
  await initializeApp();
});
window.addEventListener('resize', () => {
  console.log('[WINDOW_RESIZE] Resize event fired, drawingViewer exists:', !!drawingViewer, 'isInChartMode:', window.isInChartMode);
  if (drawingViewer) {
    drawingViewer.handleResize();
  } else {
    console.log('[WINDOW_RESIZE] drawingViewer not ready yet, ignoring resize');
  }
});


// Touch and mouse event handlers - now handled in DrawingViewer.setupEventListeners()

// Touch state is now handled as instance properties in DrawingViewer class
// See this.touchState in DrawingViewer constructor

// Handle browser refresh button and navigation away
window.addEventListener('beforeunload', async function(event) {
  // Store the current URL pattern
  localStorage.setItem('lastUrlPattern', window.location.pathname);

  // Clean up connection if it exists
  if (drawingViewer && drawingViewer.connectionManager) {
    console.log('[CLEANUP] Disconnecting before page unload...');
    try {
      await drawingViewer.connectionManager.disconnect();
    } catch (error) {
      console.error('[CLEANUP] Error during disconnect:', error);
    }
  }
});

// Handle returning from browser refresh
window.addEventListener('DOMContentLoaded', function() {
  const lastUrlPattern = localStorage.getItem('lastUrlPattern');
  if (lastUrlPattern && lastUrlPattern.includes('/update')) {
    // If we were on an update URL, make sure we load the drawing correctly
    const pathSegments = lastUrlPattern.split('/').filter(segment => segment.length > 0);
    if (pathSegments.length > 0) {
      const currentDrawingName = pathSegments[0];
      // Make sure the drawing is the first in the array
      if (!this.shadowProcessing.shadowDrawingManager.drawings.includes(currentDrawingName)) {
        this.shadowProcessing.shadowDrawingManager.drawings.unshift(currentDrawingName);
      }
    }
  }
});

// Continue initialization after connection prompt
function continueInitialization() {
  console.log('[PFODWEB_DEBUG] continueInitialization() called after connection prompt');

  // Always clean up any existing drawingViewer when navigating to this page
  // This handles the case where user goes back from this page and then navigates here again
  if (drawingViewer) {
    console.log('[PFODWEB_DEBUG] Cleaning up existing DrawingViewer from previous session');
    if (drawingViewer.connectionManager) {
      drawingViewer.connectionManager.disconnect().catch(err => {
        console.error('[PFODWEB_DEBUG] Error disconnecting previous connection:', err);
      });
    }
    drawingViewer = null;
  }

  // Create the DrawingViewer instance
  drawingViewer = new DrawingViewer();

  // Make drawingViewer globally accessible for pfodWebMouse
  window.drawingViewer = drawingViewer;

  try {
    // Initialize the viewer - queue initial request to get drawing name from server
    drawingViewer.queueInitialRequest();

    // Redraw instance already created with canvas and context - no init needed
    // Data is managed locally in redraw

    // The drawing name will be extracted and drawing loaded via the request queue
  } catch (error) {
    console.error('Failed to initialize application:', error);
    // Show error to user
    document.body.innerHTML = `<div style="padding: 20px; text-align: center; font-family: Arial;">
            <h2>Error Loading Drawing</h2>
            <p>Failed to get drawing name from server: ${error.message}</p>
        </div>`;
  }
}

// Helper function to validate IP address
function isValidIPAddress(ip) {
  if (!ip || typeof ip !== 'string') return false;
  const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
  if (!ipRegex.test(ip)) return false;
  const parts = ip.split('.');
  return parts.every(part => {
    const num = parseInt(part, 10);
    return num >= 0 && num <= 255;
  });
}

// Initialize the application
async function initializeApp() {
  console.log('[PFODWEB_DEBUG] initializeApp() called');
  console.log('[PFODWEB_DEBUG] Current URL:', window.location.href);
  console.log('[PFODWEB_DEBUG] Referrer:', document.referrer);
  console.log('[PFODWEB_DEBUG] Document ready state:', document.readyState);
  console.log('Initializing canvas drawing viewer');

  // Check if connection parameters are provided
  const urlParams = new URLSearchParams(window.location.search);

  // Check for protocol-specific parameters
  const hasTargetIP = urlParams.has('targetIP');
  const hasSerial = urlParams.has('serial');
  const hasBLE = urlParams.has('ble');

  // If targetIP is provided, validate it
  if (hasTargetIP) {
    const targetIP = urlParams.get('targetIP');

    // Check if targetIP is empty or invalid
    if (!targetIP || targetIP.trim() === '' || !isValidIPAddress(targetIP)) {
      console.log('[PFODWEB_DEBUG] targetIP parameter provided but empty or invalid:', targetIP);
      console.log('[PFODWEB_DEBUG] Showing HTTP connection prompt for user to enter valid IP');

      // Pre-select HTTP radio button
      document.getElementById('prompt-protocol-http').checked = true;
      // Update UI to show HTTP settings
      if (typeof updatePromptUI === 'function') {
        updatePromptUI();
      }
      // Clear any existing IP value and focus on the input field
      const ipInput = document.getElementById('prompt-ip');
      if (ipInput) {
        ipInput.value = '';
        ipInput.focus();
      }
      // Validate the connect button state
      if (typeof validateConnectButton === 'function') {
        validateConnectButton();
      }
      document.getElementById('connection-prompt').style.display = 'flex';
      return;
    }

    // Valid targetIP provided, use it directly and continue
    console.log('[PFODWEB_DEBUG] Valid HTTP connection with targetIP=' + targetIP + ' - proceeding directly');
    continueInitialization();
    return;
  }

  // If serial parameter is in URL, show connection prompt with Serial section pre-selected
  if (hasSerial) {
    console.log('[PFODWEB_DEBUG] Serial parameter found - showing Serial connection prompt');
    // Pre-select Serial radio button
    document.getElementById('prompt-protocol-serial').checked = true;
    // Update UI to show Serial settings
    if (typeof updatePromptUI === 'function') {
      updatePromptUI();
    }
    // If serial has a value, try to pre-fill the baud rate
    const baudRate = urlParams.get('serial');
    if (baudRate && baudRate !== '') {
      const baudSelect = document.getElementById('prompt-baud');
      // Try to set the value - if invalid, it won't match any option
      baudSelect.value = baudRate;
      // If the value didn't match any option, the select will have an empty value
      console.log('[PFODWEB_DEBUG] Set baud rate to:', baudRate, 'Actual value:', baudSelect.value);
    }
    // Validate the connect button state after pre-filling
    if (typeof validateConnectButton === 'function') {
      validateConnectButton();
    }
    document.getElementById('connection-prompt').style.display = 'flex';
    return;
  }

  // If ble parameter is in URL, show connection prompt with BLE section pre-selected
  if (hasBLE) {
    console.log('[PFODWEB_DEBUG] BLE parameter found - showing BLE connection prompt');
    // Pre-select BLE radio button
    document.getElementById('prompt-protocol-ble').checked = true;
    // Update UI to show BLE settings
    if (typeof updatePromptUI === 'function') {
      updatePromptUI();
    }
    document.getElementById('connection-prompt').style.display = 'flex';
    return;
  }

  // No parameters - check if designer mode
  const hasDesigner = urlParams.has('designer');

  if (hasDesigner) {
    // Designer mode - use default server connection without prompt
    console.log('[PFODWEB_DEBUG] Designer mode - using default server connection');
    continueInitialization();
  } else {
    // Show connection prompt with HTTP pre-selected (default)
    console.log('[PFODWEB_DEBUG] No connection parameters - showing connection prompt');
    // Validate the connect button state (HTTP is selected by default, so button should be enabled)
    if (typeof validateConnectButton === 'function') {
      validateConnectButton();
    }
    document.getElementById('connection-prompt').style.display = 'flex';
    // Focus on IP address field for immediate input
    // Use setTimeout to ensure focus happens after display is set
    setTimeout(() => {
      document.getElementById('prompt-ip').focus();
    }, 0);
  }
}

// Make continueInitialization available globally so connection prompt can call it
window.continueInitialization = continueInitialization;


// Global touch event handling functions moved to pfodWebMouse.js

// Make DrawingViewer available globally for browser use
window.DrawingViewer = DrawingViewer;