// =============================================================================
// STEAM DECK / STEAMOS CONTROLLER INPUT FIX
// =============================================================================
// These environment variables MUST be set before Electron/Chromium initializes.
// They signal to Steam's input layer that this application handles its own
// controller input and should NOT have mouse/keyboard emulation applied.
//
// Without these, Steam assumes the app needs Desktop mouse emulation when running
// in Game Mode, which overrides the app's native gamepad support.
// =============================================================================

// Tell SDL (and by extension Steam Input) that this app uses the gamepad API
// SDL_GAMECONTROLLERCONFIG is used by SDL to know about controllers
process.env.SDL_GAMECONTROLLERCONFIG = process.env.SDL_GAMECONTROLLERCONFIG || '';

// Signal that this app handles gamepad input natively
// This prevents Steam from applying mouse emulation in Game Mode
// IMPORTANT: set to 0 to avoid Steam's virtual gamepad layer when possible.
// Forcing this to 1 can keep Steam virtualization/emulation active.
process.env.SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD =
  process.env.SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD ?? '0';

// Prevent Steam from remapping the controller to keyboard/mouse
// Setting to '1' tells Steam we want raw controller access
process.env.SDL_GAMECONTROLLER_IGNORE_DEVICES = '';

// Disable Steam's overlay input hooks for this process if possible
process.env.SteamNoOverlayUIDrawing = process.env.SteamNoOverlayUIDrawing || '0';

// Tell Steam Input we're a native controller app
// When STEAM_INPUT_ENABLE_VIRTUAL_GAMEPAD is 0, Steam won't virtualize the gamepad
process.env.STEAM_INPUT_ENABLE_VIRTUAL_GAMEPAD =
  process.env.STEAM_INPUT_ENABLE_VIRTUAL_GAMEPAD ?? '0';

// Hint that this is a game/controller-focused app
process.env.SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS = '1';

// =============================================================================
// STEAMWORKS API INTEGRATION
// =============================================================================
// Initialize Steam API to properly signal to Steam that this app handles
// controller input natively. This is more reliable than environment variables
// alone for disabling Steam Input's mouse/keyboard emulation.
//
// NOTE: Since Nebula is categorized as Software (not a Game), we can't configure
// Steam Input settings in the Steamworks dashboard. Instead, we initialize the
// Steam Input API directly to signal native controller handling.
// =============================================================================

let steamworksClient = null;
let steamworksInitialized = false;
let steamInput = null;
let steamworksModule = null;
let steamCallbacksInterval = null;

function initializeSteamworks() {
  try {
    const steamworks = require('steamworks.js');
    steamworksModule = steamworks;
    
    // Initialize with Nebula's Steam App ID
    steamworksClient = steamworks.init(4290110);
    steamworksInitialized = true;
    
    // Log successful initialization
    const playerName = steamworksClient.localplayer.getName();
    console.log(`[Steamworks] Initialized successfully for user: ${playerName}`);
    
    // Initialize Steam Input API - this tells Steam we handle controllers natively
    // and should prevent mouse/keyboard emulation in Game Mode
    try {
      steamInput = steamworksClient.input;
      if (steamInput) {
        console.log('[Steamworks] Steam Input API available - native controller mode enabled');

        // Explicitly initialize Steam Input.
        // Also ensure Steam callbacks are pumped; Steamworks features (including input)
        // depend on runCallbacks being called regularly.
        try {
          if (typeof steamInput.init === 'function') {
            steamInput.init();
          }
        } catch (initErr) {
          console.log('[Steamworks] Steam Input init failed:', initErr.message);
        }

        if (!steamCallbacksInterval && typeof steamworks.runCallbacks === 'function') {
          steamCallbacksInterval = setInterval(() => {
            try {
              steamworks.runCallbacks();
            } catch {
              // Ignore callback pump errors to avoid crashing the app.
            }
          }, 100);
        }
        
        // Try to get connected controllers to verify input is working
        try {
          const controllers = steamInput.getControllers();
          if (controllers && controllers.length > 0) {
            console.log(`[Steamworks] Found ${controllers.length} connected controller(s)`);
          }
        } catch (inputErr) {
          // Controller enumeration may not be available, that's OK
        }
      }
    } catch (inputErr) {
      console.log('[Steamworks] Steam Input API not fully available:', inputErr.message);
    }
    
    return true;
  } catch (e) {
    // Not running through Steam, or steamworks.js not available
    // This is fine - app works without Steam API
    if (e.code === 'MODULE_NOT_FOUND') {
      console.log('[Steamworks] steamworks.js not installed - running without Steam API');
    } else if (e.message && e.message.includes('Steam client')) {
      console.log('[Steamworks] Steam client not running - running without Steam API');
    } else {
      console.log('[Steamworks] Failed to initialize:', e.message || e);
    }
    return false;
  }
}

// Initialize Steamworks early (before app.ready)
// This is critical for Steam Input to recognize native controller support
initializeSteamworks();

const { app, BrowserWindow, BrowserView, ipcMain, session, screen, shell, dialog, Menu, clipboard, webContents } = require('electron');

// Cleanup Steam callback pump on exit
app.once('before-quit', () => {
  if (steamCallbacksInterval) {
    clearInterval(steamCallbacksInterval);
    steamCallbacksInterval = null;
  }
  try {
    steamInput?.shutdown?.();
  } catch {}
});
const { autoUpdater } = require('electron-updater');
const { pathToFileURL } = require('url');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { spawn } = require('child_process');
const PerformanceMonitor = require('./performance-monitor');
const GPUFallback = require('./gpu-fallback');
const GPUConfig = require('./gpu-config');
const PluginManager = require('./plugin-manager');
const portableData = require('./portable-data');

// Initialize performance monitoring and GPU management
const perfMonitor = new PerformanceMonitor();
const gpuFallback = new GPUFallback();
const gpuConfig = new GPUConfig();
const pluginManager = new PluginManager();

// =============================================================================
// DESKTOP MODE: BrowserView tab management
// =============================================================================
const desktopViewStateByWindowId = new Map();
const desktopViewByWebContentsId = new Map();
const menuPopupByWindowId = new Map();
const MENU_POPUP_SIZE = { width: 240, height: 240 };

const SCROLL_NORMALIZATION_CSS = `
  *, *::before, *::after { scroll-behavior: auto !important; }
  html, body { scroll-behavior: auto !important; }
`;

const SCROLL_NORMALIZATION_JS = `
(function() {
  if (window.__nebulaScrollNormalized) return;
  window.__nebulaScrollNormalized = true;
  const SCROLL_SPEED = 100;
  document.addEventListener('wheel', function(e) {
    if (e.ctrlKey || e.metaKey || e.altKey) return;
    let target = e.target;
    let scrollable = null;
    while (target && target !== document.body && target !== document.documentElement) {
      const style = window.getComputedStyle(target);
      const overflowY = style.overflowY;
      const overflowX = style.overflowX;
      if ((overflowY === 'auto' || overflowY === 'scroll') && target.scrollHeight > target.clientHeight) { scrollable = target; break; }
      if ((overflowX === 'auto' || overflowX === 'scroll') && target.scrollWidth > target.clientWidth && e.shiftKey) { scrollable = target; break; }
      target = target.parentElement;
    }
    if (!scrollable) scrollable = document.scrollingElement || document.documentElement || document.body;
    let deltaY = e.deltaY;
    let deltaX = e.deltaX;
    if (e.deltaMode === 1) {
      deltaY *= SCROLL_SPEED; deltaX *= SCROLL_SPEED;
    } else if (e.deltaMode === 2) {
      deltaY *= window.innerHeight; deltaX *= window.innerWidth;
    } else {
      const sign = deltaY > 0 ? 1 : -1;
      deltaY = sign * Math.min(Math.abs(deltaY), SCROLL_SPEED * 3);
      const signX = deltaX > 0 ? 1 : -1;
      deltaX = signX * Math.min(Math.abs(deltaX), SCROLL_SPEED * 3);
    }
    e.preventDefault();
    scrollable.scrollBy({ top: deltaY, left: e.shiftKey ? deltaX : 0, behavior: 'auto' });
  }, { passive: false, capture: true });
})();
`;

function getDesktopViewState(win) {
  if (!win) return null;
  let state = desktopViewStateByWindowId.get(win.id);
  if (!state) {
    state = {
      views: new Map(), // tabId -> BrowserView
      activeTabId: null,
      bounds: null
    };
    desktopViewStateByWindowId.set(win.id, state);
  }
  return state;
}

function createMenuPopupWindow(parentWin) {
  const menuWin = new BrowserWindow({
    parent: parentWin,
    modal: false,
    frame: false,
    transparent: true,
    resizable: false,
    show: false,
    alwaysOnTop: true,
    skipTaskbar: true,
    focusable: true,
    hasShadow: true,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true,
      sandbox: false,
      partition: 'persist:main'
    }
  });

  menuWin.setMenu(null);
  try { menuWin.setAlwaysOnTop(true, 'pop-up-menu'); } catch {}

  const hideMenu = () => {
    if (!menuWin.isDestroyed()) menuWin.hide();
  };

  menuWin.on('blur', hideMenu);
  parentWin.on('move', hideMenu);
  parentWin.on('resize', hideMenu);

  menuWin.on('closed', () => {
    try { menuPopupByWindowId.delete(parentWin.id); } catch {}
    try { parentWin.removeListener('move', hideMenu); } catch {}
    try { parentWin.removeListener('resize', hideMenu); } catch {}
  });

  menuWin.loadFile(path.join(__dirname, 'renderer', 'menu-popup.html'));
  return menuWin;
}

function positionMenuPopup(parentWin, menuWin, anchorRect) {
  if (!parentWin || !menuWin || !anchorRect) return;
  const contentBounds = parentWin.getContentBounds();
  const display = screen.getDisplayMatching(contentBounds);
  const workArea = display?.workArea || contentBounds;

  const width = MENU_POPUP_SIZE.width;
  const height = MENU_POPUP_SIZE.height;
  let x = Math.round(contentBounds.x + anchorRect.x + anchorRect.width - width);
  let y = Math.round(contentBounds.y + anchorRect.y + anchorRect.height + 6);

  if (x < workArea.x) x = workArea.x;
  if (y < workArea.y) y = workArea.y;
  if (x + width > workArea.x + workArea.width) x = workArea.x + workArea.width - width;
  if (y + height > workArea.y + workArea.height) y = workArea.y + workArea.height - height;

  menuWin.setBounds({ x, y, width, height }, false);
}

function getOwnerWindowForContents(contents) {
  if (!contents) return null;
  try {
    if (contents.hostWebContents) {
      return BrowserWindow.fromWebContents(contents.hostWebContents);
    }
  } catch {}
  try {
    const maybeWin = BrowserWindow.fromWebContents(contents);
    if (maybeWin) return maybeWin;
  } catch {}
  const mapped = desktopViewByWebContentsId.get(contents.id);
  return mapped?.win || null;
}

function getActiveDesktopViewWebContents(win) {
  const state = getDesktopViewState(win);
  if (!state || !state.activeTabId) return null;
  const view = state.views.get(state.activeTabId);
  return view?.webContents || null;
}

function sendBrowserViewEvent(win, payload) {
  try {
    if (win && !win.isDestroyed()) {
      win.webContents.send('browserview-event', payload);
    }
  } catch {}
}

function createBrowserViewForTab(win, tabId, url) {
  const state = getDesktopViewState(win);
  if (!state) return null;
  if (state.views.has(tabId)) return state.views.get(tabId);

  const view = new BrowserView({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false,
      contextIsolation: true,
      partition: 'persist:main',
      sandbox: false,
      webSecurity: true,
      allowRunningInsecureContent: false,
      nativeWindowOpen: false,
      additionalArguments: [`--nebula-tab-id=${tabId}`]
    }
  });

  try {
    if (!process.env.NEBULA_DEBUG_ELECTRON_UA) {
      view.webContents.setUserAgent(app.userAgentFallback || computeBaseUA());
    }
  } catch {}

  state.views.set(tabId, view);
  desktopViewByWebContentsId.set(view.webContents.id, { win, tabId, view });

  view.webContents.on('page-title-updated', (_e, title) => {
    sendBrowserViewEvent(win, { tabId, type: 'page-title-updated', title });
  });

  view.webContents.on('destroyed', () => {
    try { desktopViewByWebContentsId.delete(view.webContents.id); } catch {}
    try { state.views.delete(tabId); } catch {}
    if (state.activeTabId === tabId) state.activeTabId = null;
  });

  view.webContents.on('page-favicon-updated', (_e, favicons) => {
    sendBrowserViewEvent(win, { tabId, type: 'page-favicon-updated', favicons });
  });

  view.webContents.on('did-navigate', (_e, url) => {
    sendBrowserViewEvent(win, { tabId, type: 'did-navigate', url });
  });

  view.webContents.on('did-navigate-in-page', (_e, url) => {
    sendBrowserViewEvent(win, { tabId, type: 'did-navigate-in-page', url });
  });

  view.webContents.on('did-finish-load', () => {
    sendBrowserViewEvent(win, { tabId, type: 'did-finish-load' });
  });

  view.webContents.on('did-fail-load', (_e, errorCode, errorDescription, validatedURL, isMainFrame) => {
    sendBrowserViewEvent(win, {
      tabId,
      type: 'did-fail-load',
      errorCode,
      errorDescription,
      validatedURL,
      isMainFrame
    });
  });

  view.webContents.on('dom-ready', () => {
    try { view.webContents.insertCSS(SCROLL_NORMALIZATION_CSS); } catch {}
    try { view.webContents.executeJavaScript(SCROLL_NORMALIZATION_JS, true); } catch {}
    sendBrowserViewEvent(win, { tabId, type: 'dom-ready' });
  });

  view.webContents.on('focus', () => {
    sendBrowserViewEvent(win, { tabId, type: 'focus' });
  });

  // Route window.open() calls to tabs unless OAuth allowlist matched
  view.webContents.setWindowOpenHandler((details) => {
    const { url: targetUrl } = details;
    if (!/^https?:\/\//i.test(targetUrl)) return { action: 'deny' };
    const oauthDomains = [
      'accounts.google.com',
      'login.microsoftonline.com',
      'appleid.apple.com',
      'github.com/login',
      'auth0.com',
      'okta.com',
      'login.live.com',
      'facebook.com/dialog',
      'api.twitter.com/oauth',
      'discord.com/oauth2'
    ];
    const isOAuthDomain = oauthDomains.some(domain => targetUrl.toLowerCase().includes(domain.toLowerCase()));
    if (isOAuthDomain) return { action: 'allow' };
    try { win.webContents.send('open-url-new-tab', targetUrl); } catch {}
    return { action: 'deny' };
  });

  if (url) {
    try { view.webContents.loadURL(url); } catch {}
  }

  return view;
}

function setActiveBrowserView(win, tabId) {
  const state = getDesktopViewState(win);
  if (!state) return null;
  const view = state.views.get(tabId);
  if (!view) return null;

  state.activeTabId = tabId;
  try {
    win.setBrowserView(view);
    if (state.bounds) {
      view.setBounds(state.bounds);
    }
    view.setAutoResize({ width: true, height: true });
    view.webContents.focus();
  } catch {}
  return view;
}

function destroyBrowserView(win, tabId) {
  const state = getDesktopViewState(win);
  if (!state) return false;
  const view = state.views.get(tabId);
  if (!view) return false;
  try {
    if (state.activeTabId === tabId) {
      try { win.setBrowserView(null); } catch {}
      state.activeTabId = null;
    }
    state.views.delete(tabId);
    desktopViewByWebContentsId.delete(view.webContents.id);
    try { view.webContents.destroy(); } catch {}
  } catch {}
  return true;
}

function getZoomTargetForEvent(event) {
  const win = BrowserWindow.fromWebContents(event.sender);
  if (!win) return null;
  if (win.__nebulaMode === 'desktop') {
    return getActiveDesktopViewWebContents(win) || win.webContents;
  }
  return win.webContents;
}

// Initialize portable data paths BEFORE app.ready (must be done early)
// This enables portable mode on all platforms (Windows, macOS, Linux)
// Data is stored in 'user-data' folder within the application directory
portableData.initialize();

/**
 * Get the path for a user data file (bookmarks, history, etc.)
 * Uses portable path when in portable mode, otherwise uses __dirname
 * @param {string} filename - The filename (e.g., 'bookmarks.json')
 * @returns {string} The full path to the file
 */
function getDataFilePath(filename) {
  const portablePath = portableData.getDataFilePath(filename);
  if (portablePath) {
    return portablePath;
  }
  return path.join(__dirname, filename);
}

/**
 * Get the directory path for user data files
 * Uses portable path when in portable mode, otherwise uses __dirname
 * @returns {string} The directory path
 */
function getDataDirPath() {
  if (portableData.isPortableMode()) {
    const portablePath = portableData.getPortableDataPath();
    if (portablePath) {
      return portablePath;
    }
  }
  return __dirname;
}

// Try to enable WebAuthn/platform authenticator features early.
// This helps Chromium expose platform authenticators (Touch ID / built-in) where supported.
try {
  app.commandLine.appendSwitch('enable-experimental-web-platform-features');
  // Add common WebAuthn-related feature flags. These are safe attempts to enable platform
  // authenticators and related WebAuthn plumbing in embedded Chromium builds.
  app.commandLine.appendSwitch('enable-features', 'WebAuthn,WebAuthnNestedAssertions,WebAuthnCable');
} catch (e) {
  // Non-fatal: some environments may not allow commandLine changes at this time.
}

// =============================================================================
// GAMEPAD / CONTROLLER CHROMIUM FLAGS
// =============================================================================
// Enable native gamepad support in Chromium - helps with Steam Deck compatibility
try {
  // Enable raw gamepad access (bypasses Steam's virtualization when possible)
  app.commandLine.appendSwitch('enable-gamepad-extensions');
  
  // Ensure the Gamepad API is enabled and working
  app.commandLine.appendSwitch('enable-blink-features', 'GamepadExtensions');
  
  // On Linux/Steam Deck, this can help with gamepad detection
  if (process.platform === 'linux') {
    // Disable Chromium's sandbox for gamepad access if having issues
    // (Only needed in some SteamOS configurations)
    // app.commandLine.appendSwitch('no-sandbox');
    
    // Use the system's gamepad config rather than Chromium's built-in
    app.commandLine.appendSwitch('enable-features', 'WebGamepad');
  }
} catch (e) {
  console.warn('[Gamepad] Failed to set Chromium gamepad flags:', e.message);
}

// Configure GPU settings before app is ready
gpuConfig.configure();

// Set a custom application name
app.setName('Nebula');

// --- Custom User Agent (hide Electron token & brand as Nebula) ---
// Many sites rely on UA sniffing. Default Electron UA contains 'Electron/x.y.z' which
// makes detection sites label the app as an Electron application. We construct a
// Chrome‑compatible UA string without the Electron token, appending a Nebula marker.
// NOTE: Keep the Chrome and Safari tokens for maximum compatibility.
// If you ever need to temporarily reveal Electron for debugging, set NEBULA_DEBUG_ELECTRON_UA=1.
const chromeVersion = process.versions.chrome; // matches bundled Chromium
const nebulaVersion = app.getVersion();
function computeBaseUA() {
  let platformPart;
  if (process.platform === 'win32') {
    // Use generic Windows 10 token; detailed build numbers rarely needed and can cause UA entropy issues.
    platformPart = 'Windows NT 10.0; Win64; x64';
  } else if (process.platform === 'darwin') {
    // A neutral modern macOS token; avoid exposing real minor version for stability.
    platformPart = 'Macintosh; Intel Mac OS X 10_15_7';
  } else {
    platformPart = 'X11; Linux x86_64';
  }
  return `Mozilla/5.0 (${platformPart}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion} Safari/537.36 Nebula/${nebulaVersion}`;
}

if (!process.env.NEBULA_DEBUG_ELECTRON_UA) {
  // Set a fallback UA so any new sessions inherit it automatically.
  try { app.userAgentFallback = computeBaseUA(); } catch {}
}

// Setup GPU crash handling
gpuFallback.setupCrashHandling();

// --- clear any prior registrations to prevent duplicate‐handler errors ---
ipcMain.removeHandler('window-minimize');
ipcMain.removeHandler('window-maximize');
ipcMain.removeHandler('window-close');

// =============================================================================
// BIG PICTURE MODE - Steam Deck / Console UI
// =============================================================================

function envTruthy(value) {
  if (value === undefined || value === null) return false;
  const s = String(value).trim().toLowerCase();
  return s === '1' || s === 'true' || s === 'yes' || s === 'on';
}

function argvHasFlag(flag) {
  return process.argv.includes(flag);
}

/**
 * Heuristic: detect Steam Deck / SteamOS Gaming Mode (gamescope) launches.
 *
 * This is intentionally conservative and only used for picking the *default*
 * startup UI. Users can override via CLI/env.
 */
function isGameModeEnvironment() {
  const env = process.env;

  // Common Steam tenfoot / gamepad UI markers
  if (envTruthy(env.STEAM_GAMEPADUI)) return true;
  if (envTruthy(env.SteamTenfoot)) return true;
  if (envTruthy(env.STEAM_TENFOOT)) return true;

  // SteamOS / gamescope compositor markers
  const currentDesktop = String(env.XDG_CURRENT_DESKTOP || '').toLowerCase();
  const sessionDesktop = String(env.XDG_SESSION_DESKTOP || '').toLowerCase();
  if (currentDesktop.includes('gamescope') || sessionDesktop.includes('gamescope')) return true;

  if (env.GAMESCOPE_WSI || env.GAMESCOPE_SESSION || env.GAMESCOPE_FOCUSED_APP) return true;

  return false;
}

function shouldStartInBigPictureMode() {
  // Explicit CLI overrides first
  if (argvHasFlag('--no-big-picture') || argvHasFlag('--no-bigpicture')) return false;
  if (argvHasFlag('--big-picture') || argvHasFlag('--bigpicture') || argvHasFlag('--tenfoot') || argvHasFlag('--game-mode')) return true;

  // Explicit env overrides
  if (envTruthy(process.env.NEBULA_NO_BIG_PICTURE) || envTruthy(process.env.NEBULA_NO_BIGPICTURE)) return false;
  if (envTruthy(process.env.NEBULA_BIG_PICTURE) || envTruthy(process.env.NEBULA_BIGPICTURE) || envTruthy(process.env.NEBULA_GAME_MODE)) return true;

  // Auto-detect SteamOS Gaming Mode
  return isGameModeEnvironment();
}

// Steam Deck screen dimensions: 1280x800
const STEAM_DECK_WIDTH = 1280;
const STEAM_DECK_HEIGHT = 800;
const HANDHELD_THRESHOLD = 1366; // Consider screens smaller than this as "handheld"

// Track if main window is currently in Big Picture Mode (no separate window anymore)
let isInBigPictureMode = false;

/**
 * Check if the current display is likely a Steam Deck or similar handheld
 */
function isSteamDeckDisplay() {
  const primaryDisplay = screen.getPrimaryDisplay();
  const { width, height } = primaryDisplay.size;
  
  // Check for Steam Deck resolution or similar small screens
  const isSteamDeckRes = width === STEAM_DECK_WIDTH && height === STEAM_DECK_HEIGHT;
  const isSmallScreen = width <= HANDHELD_THRESHOLD;
  
  // Also check for certain aspect ratios common in handhelds (16:10, 16:9)
  const aspectRatio = width / height;
  const isHandheldAspect = aspectRatio >= 1.5 && aspectRatio <= 1.8;
  
  return isSteamDeckRes || (isSmallScreen && isHandheldAspect);
}

/**
 * Get screen info for UI decisions
 */
function getScreenInfo() {
  const primaryDisplay = screen.getPrimaryDisplay();
  const { width, height } = primaryDisplay.size;
  const { scaleFactor } = primaryDisplay;
  
  return {
    width,
    height,
    scaleFactor,
    isSteamDeck: width === STEAM_DECK_WIDTH && height === STEAM_DECK_HEIGHT,
    isSmallScreen: width <= HANDHELD_THRESHOLD,
    aspectRatio: width / height,
    suggestBigPicture: isSteamDeckDisplay()
  };
}

/**
 * Launch Big Picture Mode in the main window (no separate window)
 * This keeps resources low and prevents SteamOS from creating desktop mode alongside.
 */
function launchBigPictureMode() {
  const windows = BrowserWindow.getAllWindows();
  // Prefer the top-level app window (menu popup is a child window)
  const mainWindow = windows.find(w => {
    try { return w && !w.isDestroyed() && !w.getParentWindow(); } catch { return false; }
  });
  
  if (!mainWindow || mainWindow.isDestroyed()) {
    console.warn('[BigPicture] No main window available');
    return null;
  }
  
  if (isInBigPictureMode) {
    console.log('[BigPicture] Already in Big Picture Mode');
    mainWindow.focus();
    return mainWindow;
  }
  
  isInBigPictureMode = true;

  // Switch mode and ensure any active BrowserView is detached so it can't cover the UI.
  try { mainWindow.__nebulaMode = 'bigpicture'; } catch {}
  try { mainWindow.setBrowserView(null); } catch {}
  
  // Enter fullscreen for Big Picture experience
  mainWindow.setFullScreen(true);
  mainWindow.setTitle('Nebula - Big Picture Mode');
  
  // Navigate to Big Picture UI
  mainWindow.loadFile('renderer/bigpicture.html');
  
  console.log('[BigPicture] Launched in main window');
  return mainWindow;
}

/**
 * Exit Big Picture Mode and return to desktop UI in the same window
 */
function exitBigPictureMode() {
  const windows = BrowserWindow.getAllWindows();
  // Prefer the top-level app window (menu popup is a child window)
  const mainWindow = windows.find(w => {
    try { return w && !w.isDestroyed() && !w.getParentWindow(); } catch { return false; }
  });
  
  if (!mainWindow || mainWindow.isDestroyed()) {
    console.warn('[BigPicture] No main window to exit from');
    return;
  }
  
  if (!isInBigPictureMode) {
    console.log('[BigPicture] Not in Big Picture Mode');
    return;
  }
  
  isInBigPictureMode = false;

  // Restore desktop mode and (after the UI reloads) reattach the active BrowserView.
  try { mainWindow.__nebulaMode = 'desktop'; } catch {}
  
  // Exit fullscreen and restore normal window
  mainWindow.setFullScreen(false);
  mainWindow.setTitle('Nebula');
  
  // Navigate back to desktop UI
  mainWindow.loadFile('renderer/index.html');

  try {
    mainWindow.webContents.once('did-finish-load', () => {
      try {
        const state = getDesktopViewState(mainWindow);
        const tabId = state?.activeTabId;
        const view = tabId ? state?.views?.get(tabId) : null;
        if (view) {
          try { mainWindow.setBrowserView(view); } catch {}
          try { if (state.bounds) view.setBounds(state.bounds); } catch {}
          try { view.setAutoResize({ width: true, height: true }); } catch {}
        }
      } catch {}
    });
  } catch {}
  
  // Maximize on Windows after exiting fullscreen
  if (process.platform === 'win32') {
    setTimeout(() => {
      try { mainWindow.maximize(); } catch {}
    }, 100);
  }
  
  console.log('[BigPicture] Exited to desktop mode');
}

// IPC handlers for Big Picture Mode
ipcMain.handle('get-screen-info', () => getScreenInfo());

ipcMain.handle('launch-bigpicture', () => {
  launchBigPictureMode();
  return { success: true };
});

ipcMain.handle('exit-bigpicture', () => {
  exitBigPictureMode();
  return { success: true };
});

ipcMain.handle('is-bigpicture-suggested', () => {
  return isSteamDeckDisplay();
});

// Check if currently in Big Picture Mode
ipcMain.handle('is-in-bigpicture', () => {
  return isInBigPictureMode;
});

ipcMain.on('exit-bigpicture', () => {
  exitBigPictureMode();
});

// IPC handler for sending mouse input events to webviews (used by Big Picture Mode)
ipcMain.handle('webview-send-input-event', async (event, { webContentsId, inputEvent }) => {
  try {
    const { webContents: webContentsModule } = require('electron');
    const targetWebContents = webContentsModule.fromId(webContentsId);
    if (targetWebContents && !targetWebContents.isDestroyed()) {
      targetWebContents.sendInputEvent(inputEvent);
      return { success: true };
    }
    return { success: false, error: 'WebContents not found' };
  } catch (err) {
    console.error('[Main] webview-send-input-event error:', err);
    return { success: false, error: err.message };
  }
});

// =============================================================================


function createWindow(startUrl, bigPictureMode = false) {
  // Capture high‑resolution startup timing markers
  const perfMarks = { createWindow_called: performance.now() };

  // Track Big Picture Mode state if starting in that mode
  if (bigPictureMode) {
    isInBigPictureMode = true;
  }

  // Get the available screen size (avoid full workArea allocation jank by starting slightly smaller then maximizing later if desired)
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
  const initialWidth = Math.min(width, Math.round(width * 0.9));
  const initialHeight = Math.min(height, Math.round(height * 0.9));

  // Window is created hidden; we only show after first meaningful paint to avoid OS‑level pointer jank while Chromium initializes
  let windowOptions = {
    width: bigPictureMode ? width : initialWidth,
    height: bigPictureMode ? height : initialHeight,
    show: false,
    useContentSize: true,
    backgroundColor: bigPictureMode ? '#0a0a0f' : '#121212', // Big Picture uses darker bg
    resizable: true,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      nodeIntegration: false, // Security & performance improvement
      contextIsolation: true,
      webviewTag: true,
      enableRemoteModule: false, // Deprecated and slow
      nodeIntegrationInSubFrames: false, // Security & performance
      nativeWindowOpen: false,
      spellcheck: false,
      webSecurity: true,
      allowRunningInsecureContent: false,
      experimentalFeatures: false,
      offscreen: false,
      enableWebSQL: false,
      plugins: false,
      backgroundThrottling: false, // keep UI responsive during early load
      // OAuth compatibility settings
      partition: 'persist:main',
      sandbox: false
    },
    fullscreen: bigPictureMode, // Start in fullscreen for Big Picture Mode
    autoHideMenuBar: true,
    icon: process.platform === 'darwin'
      ? path.join(__dirname, 'assets/images/Logos/Nebula-Favicon.icns')
      : path.join(__dirname, 'assets/images/Logos/Nebula-favicon.png'),
    title: 'Nebula'
  };

  if (process.platform === 'darwin') {
    // Use a hidden/transparent title bar on macOS so we can render a
    // custom, sleeker header in the renderer while still supporting
    // native traffic-light placement. The renderer will expose a
    // draggable region via CSS (-webkit-app-region: drag).
    Object.assign(windowOptions, {
      frame: true,
      titleBarStyle: 'hiddenInset',
      trafficLightPosition: { x: 15, y: 20 },
      // Transparent background so renderer chrome blends with content.
      backgroundColor: '#00000000',
      transparent: true,
    });
  } else if (process.platform === 'win32') {
    // Use frameless window on Windows with custom title bar controls
    // rendered in the tab strip area (Firefox-style).
    Object.assign(windowOptions, {
      frame: false,
      backgroundColor: '#0b0d10',
    });
  } else {
    windowOptions.frame = true;
  }

  const win = new BrowserWindow(windowOptions);
  win.__nebulaMode = bigPictureMode ? 'bigpicture' : 'desktop';
  win.on('closed', () => {
    const state = desktopViewStateByWindowId.get(win.id);
    if (state) {
      for (const view of state.views.values()) {
        try { desktopViewByWebContentsId.delete(view.webContents.id); } catch {}
        try { view.webContents.destroy(); } catch {}
      }
      desktopViewStateByWindowId.delete(win.id);
    }
  });
  perfMarks.browserWindow_instantiated = performance.now();

  // Intercept window.open() requests and route them into the existing window as a new tab
  // instead of spawning separate BrowserWindows. We allow a small list of specific OAuth
  // domains to open real popups if the flow depends on window.opener relationships.
  // Everything else becomes a new tab.
  win.webContents.setWindowOpenHandler((details) => {
    const { url } = details;
    if (!/^https?:\/\//i.test(url)) return { action: 'deny' };
    // OAuth / SSO allowlist - only allow specific authentication provider domains
    // Be restrictive to prevent normal links from opening in new windows
    const oauthDomains = [
      'accounts.google.com',
      'login.microsoftonline.com',
      'appleid.apple.com',
      'github.com/login',
      'auth0.com',
      'okta.com',
      'login.live.com',
      'facebook.com/dialog',
      'api.twitter.com/oauth',
      'discord.com/oauth2'
    ];
    const isOAuthDomain = oauthDomains.some(domain => url.toLowerCase().includes(domain.toLowerCase()));
    if (isOAuthDomain) {
      return { action: 'allow' }; // preserve popup semantics for complex auth flows
    }
    // Forward to renderer to open as tab
    try { win.webContents.send('open-url-new-tab', url); } catch {}
    return { action: 'deny' };
  });

  // IMPORTANT: Do NOT intercept 'will-navigate' with preventDefault() because
  // that strips POST bodies (turning logins into GET requests). Let Chromium
  // perform the navigation normally. If you need to observe navigations, add
  // a listener without calling preventDefault().
  // (Previous code here was causing login forms to fail.)

  // Remove deprecated 'new-window' handler that forcibly loaded targets in the
  // same window; this also broke some auth popup flows. setWindowOpenHandler
  // above now governs popup behavior.

  // ensure all embedded <webview> tags behave predictably without heavy injections
  win.webContents.on('did-attach-webview', (event, webviewContents) => {
    // Route <webview> window.open() calls to tabs unless OAuth allowlist matched
    webviewContents.setWindowOpenHandler((details) => {
      const { url } = details;
      if (!/^https?:\/\//i.test(url)) return { action: 'deny' };
      // OAuth / SSO allowlist - only allow specific authentication provider domains
      const oauthDomains = [
        'accounts.google.com',
        'login.microsoftonline.com',
        'appleid.apple.com',
        'github.com/login',
        'auth0.com',
        'okta.com',
        'login.live.com',
        'facebook.com/dialog',
        'api.twitter.com/oauth',
        'discord.com/oauth2'
      ];
      const isOAuthDomain = oauthDomains.some(domain => url.toLowerCase().includes(domain.toLowerCase()));
      if (isOAuthDomain) {
        return { action: 'allow' }; // keep popup for auth
      }
      // Send to main window's webContents to open a new tab
      try {
        win.webContents.send('open-url-new-tab', url);
      } catch {}
      return { action: 'deny' };
    });
  });

  // Load appropriate UI based on mode (Big Picture or Desktop)
  if (bigPictureMode) {
    win.loadFile('renderer/bigpicture.html');
    win.setTitle('Nebula - Big Picture Mode');
  } else {
    win.loadFile('renderer/index.html');
  }
  perfMarks.loadFile_issued = performance.now();

  // if caller passed in a URL, forward it to the renderer after load
  if (startUrl) {
    win.webContents.once('did-finish-load', () => {
      win.webContents.send('open-url', startUrl);
    });
  }

  // Set default zoom to 100%
  const zoomFactor = 1.0;
  const loadStartTime = Date.now();
  // Show window ASAP after first paint for perceived performance
  let shown = false;
  const showNow = (reason) => {
    if (shown) return;
    shown = true;
    win.show();
    if (process.platform === 'win32') {
      // Defer maximize to next frame to avoid large-surface first paint cost
      setTimeout(() => {
        try { win.maximize(); } catch {}
      }, 16);
    }
    console.log(`[Startup] Window shown (${reason}) in ${(performance.now() - perfMarks.createWindow_called).toFixed(1)}ms`);
  };

  win.webContents.once('ready-to-show', () => showNow('ready-to-show'));
  // Fallback in case ready-to-show is delayed
  setTimeout(() => showNow('timeout-fallback'), 4000);

  win.webContents.on('did-finish-load', () => {
    win.webContents.setZoomFactor(zoomFactor);
    const loadTime = Date.now() - loadStartTime;
    perfMonitor.trackLoadTime(win.webContents.getURL(), loadTime);
    perfMarks.did_finish_load = performance.now();

    // Defer heavier, non‑critical tasks to next idle slice to keep UI smooth
    setTimeout(() => {
      // Kick off GPU status check here (was earlier) to avoid competing with first paint
      gpuConfig.checkGPUStatus()
        .then(gpuStatus => {
          console.log('[Deferred] GPU Configuration Results:');
          console.log('- GPU Status:', gpuStatus);
          console.log('- Recommendations:', gpuConfig.getRecommendations());
        })
        .catch(err => console.error('[Deferred] GPU status check failed:', err));

      // Start performance monitoring after initial load
      perfMonitor.start();
    }, 300);
    // Diagnostic: check WebAuthn / platform authenticator availability in renderer
    try {
      win.webContents.executeJavaScript(`(async function(){
        const out = { hasNavigator: !!window.navigator, hasCredentials: !!navigator.credentials, hasCreate: !!(navigator.credentials && navigator.credentials.create), hasGet: !!(navigator.credentials && navigator.credentials.get) };
        try {
          if (window.PublicKeyCredential) {
            out.PublicKeyCredential = true;
            out.isUserVerifyingPlatformAuthenticatorAvailable = typeof PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable === 'function' ? await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() : 'unknown';
          } else {
            out.PublicKeyCredential = false;
          }
        } catch (e) { out.webauthnError = String(e); }
        return out;
      })()`)
      .then(result => {
        console.log('[WebAuthn Diagnostic] renderer report:', result);
      }).catch(err => {
        console.error('[WebAuthn Diagnostic] executeJavaScript failed:', err);
      });
    } catch (e) {
      console.warn('WebAuthn diagnostic injection skipped:', e);
    }

  // After the first load, let plugins know a window exists
  try { pluginManager.emit('window-created', win); } catch {}
  });

  // Renderer manages history; no main-process recording here
}

// This method will be called when Electron has finished initialization
// Configure sessions asynchronously (non-blocking for window creation)
function configureSessionsAsync() {
  const sessionsToConfigure = [session.fromPartition('persist:main'), session.defaultSession];
  try {
    for (const ses of sessionsToConfigure) {
      if (!ses) continue;
      ses.setPermissionRequestHandler((webContents, permission, callback) => {
        if (['notifications', 'geolocation', 'camera', 'microphone'].includes(permission)) {
          callback(false);
        } else {
          callback(true);
        }
      });
      try {
        let realUA = ses.getUserAgent();
        // If Electron token present and we're not in debug mode, recompute using base builder.
        if (!process.env.NEBULA_DEBUG_ELECTRON_UA) {
          const hasElectron = /Electron\//i.test(realUA);
          if (hasElectron || !/Nebula\//.test(realUA)) {
            realUA = app.userAgentFallback || computeBaseUA();
            ses.setUserAgent(realUA);
          }
        } else {
          // Debug mode: just append Nebula tag if missing (keeps Electron segment visible)
            if (realUA && !/Nebula\//.test(realUA)) {
              ses.setUserAgent(realUA + ' Nebula/' + app.getVersion());
            }
        }
      } catch (e) {
        console.warn('Failed to read real user agent, keeping default:', e);
      }
      ses.cookies.on('changed', (event, cookie, cause, removed) => {
        if (cookie.domain && (cookie.domain.includes('google') || cookie.domain.includes('accounts'))) {
          console.log(`Cookie ${removed ? 'removed' : 'added'}: ${cookie.name} for ${cookie.domain}`);
        }
      });
      ses.webRequest.onBeforeSendHeaders((details, callback) => {
        const headers = details.requestHeaders;
        if (details.url.includes('accounts.google.com') || details.url.includes('oauth')) {
          headers['Referrer-Policy'] = 'strict-origin-when-cross-origin';
          headers['Accept'] = headers['Accept'] || 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8';
        }
        if (!headers['Accept-Language'] && !headers['accept-language']) {
          headers['Accept-Language'] = 'en-US,en;q=0.9';
        }
        callback({ requestHeaders: headers });
      });
    }
    console.log('Session configured successfully for OAuth compatibility');
  } catch (err) {
    console.error('Session setup error:', err);
  }
}

app.whenReady().then(() => {
  const t0 = performance.now();

  // If launched via SteamOS Gaming Mode / gamepad UI, default to Big Picture Mode.
  // Desktop launches remain unchanged. Big Picture now opens in main window to keep resources low.
  const startInBigPicture = shouldStartInBigPictureMode();
  if (startInBigPicture) {
    console.log('[Startup] Detected game mode launch; starting in Big Picture Mode (in main window)');
    createWindow(null, true); // Pass bigPictureMode flag
  } else {
    createWindow();
  }

  // Initialize user plugins after app ready
  try {
    pluginManager.ensureUserPluginsDir();
    pluginManager.loadAll();
    pluginManager.emit('app-ready');
  } catch (e) {
    console.error('[Plugins] initialization error:', e);
  }
  console.log('[Startup] initial window created (', startInBigPicture ? 'bigpicture' : 'desktop', ') in', (performance.now() - t0).toFixed(1), 'ms after app.whenReady');

  // Handle GPU process crashes (still register early)
  app.on('gpu-process-crashed', (event, killed) => {
    console.warn('GPU process crashed, killed:', killed);
    if (!killed) {
      console.log('Attempting to recover GPU process...');
    }
  });

  // Defer session configuration to microtask/next tick (already inexpensive) – keep explicit
  setImmediate(configureSessionsAsync);

  // Register download handlers for common sessions
  try {
    const mainSes = session.fromPartition('persist:main');
    const defSes = session.defaultSession;
    if (mainSes) registerDownloadHandling(mainSes);
    if (defSes && defSes !== mainSes) registerDownloadHandling(defSes);
  // Allow plugins to attach webRequest hooks
  if (mainSes) pluginManager.applyWebRequestHandlers(mainSes);
  if (defSes) pluginManager.applyWebRequestHandlers(defSes);
  pluginManager.emit('session-configured', { mainSes, defSes });
  } catch (e) {
    console.warn('Failed to register download handlers:', e);
  }

  if (process.platform === 'darwin') {
    app.dock.setIcon(path.join(__dirname, 'assets/images/Logos/Nebula-Icon.icns'));
  }
  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });

  // --- Auto-Updater Setup ---
  // Configure auto-updater logging
  try {
    autoUpdater.logger = require('electron-updater').autoUpdater.logger;
    if (autoUpdater.logger && autoUpdater.logger.transports && autoUpdater.logger.transports.file) {
      autoUpdater.logger.transports.file.level = 'info';
    }
  } catch (err) {
    console.log('[AutoUpdater] Could not configure logger:', err.message);
  }

  // Check for updates after a short delay to not block startup
  setTimeout(() => {
    autoUpdater.checkForUpdatesAndNotify().catch(err => {
      console.log('[AutoUpdater] Update check failed:', err.message);
    });
  }, 3000);

  // Auto-updater event handlers
  autoUpdater.on('checking-for-update', () => {
    console.log('[AutoUpdater] Checking for updates...');
    broadcastToAll('update-status', { status: 'checking' });
  });

  autoUpdater.on('update-available', (info) => {
    console.log('[AutoUpdater] Update available:', info.version);
    broadcastToAll('update-status', { status: 'available', version: info.version });
  });

  autoUpdater.on('update-not-available', (info) => {
    console.log('[AutoUpdater] No update available. Current version:', app.getVersion());
    broadcastToAll('update-status', { status: 'not-available', currentVersion: app.getVersion() });
  });

  autoUpdater.on('download-progress', (progress) => {
    console.log(`[AutoUpdater] Download progress: ${progress.percent.toFixed(1)}%`);
    broadcastToAll('update-status', { status: 'downloading', progress: progress.percent });
  });

  autoUpdater.on('update-downloaded', (info) => {
    console.log('[AutoUpdater] Update downloaded:', info.version);
    broadcastToAll('update-status', { status: 'downloaded', version: info.version });
    // Optionally prompt user to restart
    dialog.showMessageBox({
      type: 'info',
      title: 'Update Ready',
      message: `Nebula ${info.version} has been downloaded.`,
      detail: 'The update will be installed when you restart the app.',
      buttons: ['Restart Now', 'Later']
    }).then(result => {
      if (result.response === 0) {
        autoUpdater.quitAndInstall();
      }
    });
  });

  autoUpdater.on('error', (err) => {
    console.error('[AutoUpdater] Error:', err.message);
    broadcastToAll('update-status', { status: 'error', message: err.message });
  });
});

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

// ipcMain handlers

// --- Auto-Update IPC handlers ---
ipcMain.handle('check-for-updates', async () => {
  try {
    const result = await autoUpdater.checkForUpdates();
    return { success: true, updateInfo: result?.updateInfo };
  } catch (err) {
    return { success: false, error: err.message };
  }
});

ipcMain.handle('download-update', async () => {
  try {
    await autoUpdater.downloadUpdate();
    return { success: true };
  } catch (err) {
    return { success: false, error: err.message };
  }
});

ipcMain.handle('install-update', () => {
  autoUpdater.quitAndInstall();
});

ipcMain.handle('get-app-version', () => {
  return app.getVersion();
});

ipcMain.handle('get-app-info', () => {
  return {
    version: app.getVersion(),
    electron: process.versions.electron,
    chrome: process.versions.chrome,
    node: process.versions.node,
    v8: process.versions.v8,
    platform: process.platform,
    arch: process.arch,
    isPackaged: app.isPackaged,
    isDevelopment: !app.isPackaged
  };
});

// --- window control handlers (only registered once now)
ipcMain.handle('window-minimize', event => {
  BrowserWindow.fromWebContents(event.sender).minimize();
});
ipcMain.handle('window-maximize', event => {
  const w = BrowserWindow.fromWebContents(event.sender);
  w.isMaximized() ? w.unmaximize() : w.maximize();
});
ipcMain.handle('window-close', event => {
  BrowserWindow.fromWebContents(event.sender).close();
});
ipcMain.handle('window-is-maximized', event => {
  return BrowserWindow.fromWebContents(event.sender).isMaximized();
});

// Add site and search history IPC handlers
// Site history is now handled via localStorage in the renderer
// But keep these handlers for compatibility and potential future use
ipcMain.handle('load-site-history', async () => {
  const filePath = getDataFilePath('site-history.json');
  try {
    const data = await fs.promises.readFile(filePath, 'utf-8');
    return JSON.parse(data);
  } catch (err) {
    return [];
  }
});

ipcMain.handle('save-site-history', async (event, history) => {
  const filePath = getDataFilePath('site-history.json');
  try {
    if (portableData.isPortableMode()) {
      await portableData.writeSecureFileAsync(filePath, JSON.stringify(history, null, 2));
    } else {
      await fs.promises.writeFile(filePath, JSON.stringify(history, null, 2));
    }
    return true;
  } catch (err) {
    return false;
  }
});

ipcMain.handle('clear-site-history', async () => {
  const filePath = getDataFilePath('site-history.json');
  try {
    if (portableData.isPortableMode()) {
      await portableData.writeSecureFileAsync(filePath, JSON.stringify([], null, 2));
    } else {
      await fs.promises.writeFile(filePath, JSON.stringify([], null, 2));
    }
    return true;
  } catch (err) {
    return false;
  }
});

ipcMain.handle('load-search-history', async () => {
  const filePath = getDataFilePath('search-history.json');
  try {
    const data = await fs.promises.readFile(filePath, 'utf-8');
    return JSON.parse(data);
  } catch (err) {
    return [];
  }
});

ipcMain.handle('save-search-history', async (event, history) => {
  const filePath = getDataFilePath('search-history.json');
  try {
    if (portableData.isPortableMode()) {
      await portableData.writeSecureFileAsync(filePath, JSON.stringify(history, null, 2));
    } else {
      await fs.promises.writeFile(filePath, JSON.stringify(history, null, 2));
    }
    return true;
  } catch (err) {
    return false;
  }
});

// debug: log default‐homepage changes from renderer
ipcMain.on('homepage-changed', (event, url) => {
  console.log('[MAIN] homepage-changed →', url);
});

// Handle theme changes - broadcast to all windows
ipcMain.on('theme-changed', (event, theme) => {
  console.log('[MAIN] theme-changed →', theme?.name || 'unknown');
  // Broadcast theme change to all browser windows
  BrowserWindow.getAllWindows().forEach(win => {
    if (win.webContents && win.webContents.id !== event.sender.id) {
      win.webContents.send('theme-changed', theme);
    }
  });
});

// Handle display scale changes
ipcMain.on('set-display-scale', (event, scale) => {
  console.log('[MAIN] set-display-scale →', scale);
  try {
    // Get the webcontents from the event (will be bigPictureWindow)
    const wc = event.sender;
    if (wc && typeof wc.setZoomFactor === 'function') {
      const zoomFactor = Math.max(0.5, Math.min(3, scale / 100));
      wc.setZoomFactor(zoomFactor);
      console.log(`[MAIN] Applied zoom factor: ${zoomFactor} for scale ${scale}%`);
    }
  } catch (err) {
    console.warn('[MAIN] Failed to apply display scale:', err);
  }
});

// Bookmark management
ipcMain.handle('load-bookmarks', async () => {
  try {
    const bookmarksPath = getDataFilePath('bookmarks.json');
    try {
      await fs.promises.access(bookmarksPath);
    } catch {
      console.log('No bookmarks file found, starting with empty array');
      return [];
    }
    const data = await fs.promises.readFile(bookmarksPath, 'utf8');
    const bookmarks = JSON.parse(data);
    console.log(`Loaded ${bookmarks.length} bookmarks from file`);
    return bookmarks;
  } catch (error) {
    console.error('Error loading bookmarks:', error);
    // Try to create a backup if the file is corrupted
    const bookmarksPath = getDataFilePath('bookmarks.json');
    const backupPath = getDataFilePath(`bookmarks.backup.${Date.now()}.json`);
    try {
      await fs.promises.copyFile(bookmarksPath, backupPath);
      console.log(`Corrupted bookmarks file backed up to: ${backupPath}`);
    } catch (backupError) {
      console.error('Failed to create backup:', backupError);
    }
    return [];
  }
});

ipcMain.handle('save-bookmarks', async (event, bookmarks) => {
  try {
    const bookmarksPath = getDataFilePath('bookmarks.json');
    try {
      await fs.promises.access(bookmarksPath);
      const backupPath = getDataFilePath('bookmarks.backup.json');
      await fs.promises.copyFile(bookmarksPath, backupPath);
    } catch {}
    // Use secure file writing in portable mode
    if (portableData.isPortableMode()) {
      await portableData.writeSecureFileAsync(bookmarksPath, JSON.stringify(bookmarks, null, 2));
    } else {
      await fs.promises.writeFile(bookmarksPath, JSON.stringify(bookmarks, null, 2));
    }
    console.log(`Saved ${bookmarks.length} bookmarks to file`);
    return true;
  } catch (error) {
    console.error('Error saving bookmarks:', error);
    return false;
  }
});

ipcMain.handle('clear-browser-data', async () => {
  try {
    const sessionsToClear = [session.defaultSession, session.fromPartition('persist:main')];

    for (const ses of sessionsToClear) {
      if (!ses) continue;
      // Clear all common site storage types
      await ses.clearStorageData({
        storages: [
          'cookies',
          'localstorage',
          'indexdb',
          'filesystem',
          'websql',
          'serviceworkers',
          'caches',
          'shadercache',
          'appcache'
        ],
      });
      // Clear caches and auth
      await ses.clearCache();
      await ses.clearAuthCache();
    }

    // Also reset on-disk history JSON files managed by the app
    const siteHistoryPath = getDataFilePath('site-history.json');
    const searchHistoryPath = getDataFilePath('search-history.json');
    try { 
      if (portableData.isPortableMode()) {
        await portableData.writeSecureFileAsync(siteHistoryPath, JSON.stringify([], null, 2));
      } else {
        await fs.promises.writeFile(siteHistoryPath, JSON.stringify([], null, 2));
      }
    } catch {}
    try { 
      if (portableData.isPortableMode()) {
        await portableData.writeSecureFileAsync(searchHistoryPath, JSON.stringify([], null, 2));
      } else {
        await fs.promises.writeFile(searchHistoryPath, JSON.stringify([], null, 2));
      }
    } catch {}

    return true; // Indicate success
  } catch (error) {
    console.error('Failed to clear browser data:', error);
    return false; // Indicate failure
  }
});

// Optional: standalone clear for search history JSON
ipcMain.handle('clear-search-history', async () => {
  const filePath = getDataFilePath('search-history.json');
  try {
    if (portableData.isPortableMode()) {
      await portableData.writeSecureFileAsync(filePath, JSON.stringify([], null, 2));
    } else {
      await fs.promises.writeFile(filePath, JSON.stringify([], null, 2));
    }
    return true;
  } catch (err) {
    return false;
  }
});

ipcMain.handle('get-zoom-factor', event => {
  const wc = getZoomTargetForEvent(event);
  return wc ? wc.getZoomFactor() : 1.0;
});

ipcMain.handle('zoom-in', event => {
  const wc = getZoomTargetForEvent(event);
  if (!wc) return 1.0;
  const current = wc.getZoomFactor();
  const z = Math.min(current + 0.1, 3);
  wc.setZoomFactor(z);
  return z;
});


ipcMain.handle('zoom-out', event => {
  const wc = getZoomTargetForEvent(event);
  if (!wc) return 1.0;
  const current = wc.getZoomFactor();
  const z = Math.max(current - 0.1, 0.25);
  wc.setZoomFactor(z);
  return z;
});

ipcMain.handle('get-display-scale', async (event) => {
  // Try to read from localStorage data (user data path)
  const userDataPath = app.getPath('userData');
  const storageFile = path.join(userDataPath, 'localStorage');
  
  try {
    // Try to get from electron store or persistent storage
    // For now, we'll just return a default and let the app set it
    // The display scale is stored in localStorage on the client side
    return 100; // Default to 100%
  } catch (err) {
    return 100; // Default to 100%
  }
});

ipcMain.handle('set-zoom-factor', (event, zoomFactor) => {
  const wc = getZoomTargetForEvent(event);
  if (wc && typeof wc.setZoomFactor === 'function') {
    wc.setZoomFactor(zoomFactor);
    return true;
  }
  return false;
});

// allow renderer to pop a tab into its own window
ipcMain.handle('open-tab-in-new-window', (event, url) => {
  createWindow(url);
});

ipcMain.handle('save-site-history-entry', async (event, url) => {
  const filePath = getDataFilePath('site-history.json');
  try {
    let data = [];
    try {
      const raw = await fs.promises.readFile(filePath, 'utf8');
      data = JSON.parse(raw);
    } catch {}
    // Remove if already exists to avoid duplicates
    data = data.filter(item => item !== url);
    // Add to beginning and clamp size
    data.unshift(url);
    if (data.length > 100) data = data.slice(0, 100);
    if (portableData.isPortableMode()) {
      await portableData.writeSecureFileAsync(filePath, JSON.stringify(data, null, 2));
    } else {
      await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2));
    }
    return true;
  } catch (err) {
    console.error('[MAIN] Error saving site history entry:', err);
    return false;
  }
});

// Add performance monitoring IPC handlers
ipcMain.handle('get-performance-report', () => {
  return perfMonitor.getReport();
});

ipcMain.handle('force-gc', () => {
  perfMonitor.forceGC();
  return true;
});

// GPU diagnostics handler
ipcMain.handle('get-gpu-info', async () => {
  try {
    const gpuStatus = await gpuConfig.checkGPUStatus();
    const fallbackStatus = gpuFallback.getStatus();
    const recommendations = gpuConfig.getRecommendations();
    
    return {
      ...gpuStatus,
      fallbackStatus: fallbackStatus,
      recommendations: recommendations,
      isOptimized: gpuStatus.isSupported && !fallbackStatus.fallbackLevel
    };
  } catch (err) {
    console.error('Error getting GPU info:', err);
    return { error: err.message, isSupported: false };
  }
});

// Force GPU fallback handler
ipcMain.handle('apply-gpu-fallback', (event, level) => {
  try {
    gpuFallback.applyFallback(level);
    return { success: true, level: level };
  } catch (err) {
    console.error('Error applying GPU fallback:', err);
    return { error: err.message };
  }
});

// About/info handler
ipcMain.handle('get-about-info', () => {
  try {
    return {
      appName: app.getName(),
      appVersion: app.getVersion(),
      isPackaged: app.isPackaged,
      appPath: app.getAppPath(),
      userDataPath: app.getPath('userData'),
      electronVersion: process.versions.electron,
      chromeVersion: process.versions.chrome,
      nodeVersion: process.versions.node,
      v8Version: process.versions.v8,
      platform: process.platform,
      arch: process.arch,
      osType: os.type(),
      osRelease: os.release(),
      cpu: os.cpus()?.[0]?.model || 'Unknown CPU',
      totalMemGB: Math.round((os.totalmem() / (1024 ** 3)) * 10) / 10,
    };
  } catch (err) {
    console.error('Error building about info:', err);
    return { error: err.message };
  }
});

// Toggle DevTools for the requesting window (main window webContents)
ipcMain.handle('open-devtools', (event) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  if (!win) return false;
  const contents = win.__nebulaMode === 'desktop'
    ? (getActiveDesktopViewWebContents(win) || win.webContents)
    : win.webContents;
  if (contents.isDevToolsOpened()) {
    contents.closeDevTools();
  } else {
  // Open docked inside the main window (bottom). Other options: 'right', 'undocked', 'detach'
  contents.openDevTools({ mode: 'bottom' });
  }
  return contents.isDevToolsOpened();
});

// =============================================================================
// BrowserView IPC (desktop mode tabs)
// =============================================================================
ipcMain.handle('browserview-create', (event, { tabId, url }) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return { success: false, error: 'no-window' };
    if (win.__nebulaMode !== 'desktop') return { success: false, error: 'not-desktop' };
    const view = createBrowserViewForTab(win, tabId, url);
    return { success: !!view };
  } catch (err) {
    return { success: false, error: err.message };
  }
});

ipcMain.handle('browserview-set-active', (event, { tabId }) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return false;
    if (win.__nebulaMode !== 'desktop') return false;
    return !!setActiveBrowserView(win, tabId);
  } catch {
    return false;
  }
});

ipcMain.handle('browserview-destroy', (event, { tabId }) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return false;
    if (win.__nebulaMode !== 'desktop') return false;
    return destroyBrowserView(win, tabId);
  } catch {
    return false;
  }
});

ipcMain.handle('browserview-load-url', (event, { tabId, url }) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return false;
    if (win.__nebulaMode !== 'desktop') return false;
    const state = getDesktopViewState(win);
    const view = state?.views.get(tabId);
    if (!view) return false;
    view.webContents.loadURL(url);
    return true;
  } catch {
    return false;
  }
});

ipcMain.handle('browserview-reload', (event, { tabId, ignoreCache }) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return false;
    if (win.__nebulaMode !== 'desktop') return false;
    const state = getDesktopViewState(win);
    const view = state?.views.get(tabId);
    if (!view) return false;
    if (ignoreCache && typeof view.webContents.reloadIgnoringCache === 'function') {
      view.webContents.reloadIgnoringCache();
    } else {
      view.webContents.reload();
    }
    return true;
  } catch {
    return false;
  }
});

ipcMain.handle('browserview-get-url', (event, { tabId }) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return null;
    if (win.__nebulaMode !== 'desktop') return null;
    const state = getDesktopViewState(win);
    const view = state?.views.get(tabId);
    return view?.webContents.getURL() || null;
  } catch {
    return null;
  }
});

ipcMain.handle('browserview-execute-js', async (event, { tabId, code }) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return null;
    if (win.__nebulaMode !== 'desktop') return null;
    const state = getDesktopViewState(win);
    const view = state?.views.get(tabId);
    if (!view) return null;
    return await view.webContents.executeJavaScript(code, true);
  } catch {
    return null;
  }
});

ipcMain.handle('browserview-set-bounds', (event, bounds) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return false;
    if (win.__nebulaMode !== 'desktop') return false;
    const state = getDesktopViewState(win);
    if (!state) return false;
    const safeBounds = {
      x: Math.max(0, Math.round(bounds?.x || 0)),
      y: Math.max(0, Math.round(bounds?.y || 0)),
      width: Math.max(0, Math.round(bounds?.width || 0)),
      height: Math.max(0, Math.round(bounds?.height || 0))
    };
    state.bounds = safeBounds;
    if (state.activeTabId) {
      const view = state.views.get(state.activeTabId);
      if (view) {
        view.setBounds(safeBounds);
      }
    }
    return true;
  } catch {
    return false;
  }
});

// Overlay menu (to sit above BrowserView)
ipcMain.on('menu-popup-toggle', (event, payload = {}) => {
  try {
    const parentWin = BrowserWindow.fromWebContents(event.sender);
    if (!parentWin) return;

    let menuWin = menuPopupByWindowId.get(parentWin.id);
    if (!menuWin || menuWin.isDestroyed()) {
      menuWin = createMenuPopupWindow(parentWin);
      menuPopupByWindowId.set(parentWin.id, menuWin);
    }

    if (menuWin.isVisible()) {
      menuWin.hide();
      return;
    }

    positionMenuPopup(parentWin, menuWin, payload.anchorRect);

    const initPayload = { theme: payload.theme || null };
    const sendInit = () => {
      try { menuWin.webContents.send('menu-popup-init', initPayload); } catch {}
    };
    try {
      if (menuWin.webContents.isLoadingMainFrame()) {
        menuWin.webContents.once('did-finish-load', sendInit);
      } else {
        sendInit();
      }
    } catch {}

    menuWin.show();
    menuWin.focus();
  } catch {}
});

ipcMain.on('menu-popup-hide', (event) => {
  try {
    const parentWin = BrowserWindow.fromWebContents(event.sender);
    if (!parentWin) return;
    const menuWin = menuPopupByWindowId.get(parentWin.id);
    if (menuWin && !menuWin.isDestroyed()) menuWin.hide();
  } catch {}
});

ipcMain.on('menu-popup-command', (event, payload = {}) => {
  try {
    const menuWin = BrowserWindow.fromWebContents(event.sender);
    const parentWin = menuWin?.getParentWindow();
    if (menuWin && !menuWin.isDestroyed()) menuWin.hide();
    if (!parentWin || parentWin.isDestroyed()) return;
    if (!payload?.cmd || payload.cmd === 'close') return;
    parentWin.webContents.send('menu-command', payload);
  } catch {}
});

ipcMain.on('browserview-broadcast', (event, { channel, args }) => {
  try {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return;
    if (win.__nebulaMode !== 'desktop') return;
    const state = getDesktopViewState(win);
    if (!state) return;
    for (const view of state.views.values()) {
      try { view.webContents.send(channel, ...(args || [])); } catch {}
    }
  } catch {}
});

ipcMain.on('browserview-host-message', (event, payload = {}) => {
  try {
    const { tabId, channel, args } = payload || {};
    console.log('[IPC Main] browserview-host-message received, tabId:', tabId, 'channel:', channel);
    
    let win = getOwnerWindowForContents(event.sender);
    console.log('[IPC Main] getOwnerWindowForContents returned:', win ? 'window found' : 'null');

    if (!win && tabId) {
      console.log('[IPC Main] Trying to find window by tabId...');
      for (const candidate of BrowserWindow.getAllWindows()) {
        const state = desktopViewStateByWindowId.get(candidate.id);
        console.log('[IPC Main] Checking window', candidate.id, 'state:', state ? 'found' : 'null', 'has tabId:', state?.views?.has(tabId));
        if (state && state.views && state.views.has(tabId)) {
          win = candidate;
          console.log('[IPC Main] Found window by tabId');
          break;
        }
      }
    }

    if (!win || win.isDestroyed()) {
      console.log('[IPC Main] No valid window found, returning');
      return;
    }
    console.log('[IPC Main] Forwarding to renderer');
    win.webContents.send('browserview-host-message', { tabId, channel, args: args || [] });
  } catch (err) {
    console.error('[IPC Main] Error:', err);
  }
});

// Helper function to read package.json version
function getInstalledElectronVersion() {
  try {
    const packageJsonPath = path.join(__dirname, 'package.json');
    const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
    
    // Get the version from devDependencies
    const electronDep = packageJson.devDependencies?.electron;
    const electronNightlyDep = packageJson.devDependencies?.['electron-nightly'];
    
    if (electronDep) {
      return electronDep.replace(/^\D+/, ''); // Remove ^ or ~ or other version specifiers
    }
    if (electronNightlyDep) {
      return electronNightlyDep.replace(/^\D+/, '');
    }
    
    return app.getVersion();
  } catch (err) {
    console.error('Error reading installed electron version:', err);
    return app.getVersion();
  }
}

// Electron version management handlers
ipcMain.handle('get-electron-versions', async (event, buildType = 'stable') => {
  const https = require('https');
  
  return new Promise((resolve) => {
    let url;
    
    if (buildType === 'nightly') {
      // Get latest nightly version from npm
      url = 'https://registry.npmjs.org/electron-nightly/latest';
    } else {
      // Get latest stable version from npm
      url = 'https://registry.npmjs.org/electron/latest';
    }
    
    const request = https.get(url, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        try {
          const packageInfo = JSON.parse(data);
          // Get the actual installed version from package.json, not app.getVersion()
          const installedVersion = getInstalledElectronVersion();
          resolve({
            available: packageInfo.version,
            current: installedVersion,
            buildType: buildType
          });
        } catch (err) {
          console.error('Failed to parse version info:', err);
          resolve({
            available: null,
            current: getInstalledElectronVersion(),
            error: 'Failed to fetch version info'
          });
        }
      });
    });
    
    request.on('error', (err) => {
      console.error('Failed to fetch versions:', err);
      resolve({
        available: null,
        current: getInstalledElectronVersion(),
        error: err.message
      });
    });
    
    request.setTimeout(5000, () => {
      request.destroy();
      resolve({
        available: null,
        current: getInstalledElectronVersion(),
        error: 'Version check timed out'
      });
    });
  });
});

ipcMain.handle('upgrade-electron', async (event, buildType = 'stable') => {
  const https = require('https');
  const { exec } = require('child_process');

  console.log('[ELECTRON-UPGRADE] Checking environment...');
  console.log('[ELECTRON-UPGRADE] app.isPackaged:', app.isPackaged);
  console.log('[ELECTRON-UPGRADE] __dirname:', __dirname);
  console.log('[ELECTRON-UPGRADE] process.resourcesPath:', process.resourcesPath);

  // For packaged apps (like win-unpacked), we can't use npm
  // This feature is only for development with `npm start`
  // Steam users will get updates through Steam

  return new Promise((resolve) => {
    resolve({
      success: false,
      error: 'Electron updates are not available in packaged builds',
      message: 'For Steam users: Updates are delivered through Steam.\n\nFor developers: Use "npm start" to enable Electron updates during development.'
    });
  });

  /* Keeping this code commented for future reference if needed
  const packageName = buildType === 'nightly' ? 'electron-nightly' : 'electron';
  const packageJsonPath = path.join(__dirname, 'package.json');
  const nodeModulesPath = path.join(__dirname, 'node_modules');

  return new Promise((resolve) => {
    // Check if we're in a real development environment
    if (app.isPackaged || !fs.existsSync(packageJsonPath) || !fs.existsSync(nodeModulesPath)) {
      resolve({
        success: false,
        error: 'Electron updates are only available in development mode',
        message: 'Run the app with "npm start" to enable Electron updates.'
      });
      return;
    }

    // Run npm install to upgrade the package
    const command = `npm install --save-dev ${packageName}@latest`;

    console.log('[ELECTRON-UPGRADE] Running command:', command);
    console.log('[ELECTRON-UPGRADE] Working directory:', __dirname);

    exec(command,
      {
        cwd: __dirname,
        maxBuffer: 10 * 1024 * 1024,
        shell: true,
        env: process.env
      },
      (error, stdout, stderr) => {
        if (error) {
          console.error('[ELECTRON-UPGRADE] Upgrade failed:', error);
          console.error('[ELECTRON-UPGRADE] stderr:', stderr);

          let errorMsg = error.message;
          if (errorMsg.includes('ENOENT')) {
            errorMsg = 'npm command not found. Please ensure Node.js and npm are installed.';
          } else if (errorMsg.includes('EACCES')) {
            errorMsg = 'Permission denied. Try running as administrator.';
          }

          resolve({
            success: false,
            error: errorMsg,
            message: 'Failed to upgrade Electron'
          });
        } else {
          console.log('[ELECTRON-UPGRADE] Upgrade output:', stdout);
          if (stderr) console.log('[ELECTRON-UPGRADE] stderr:', stderr);

          // Clean up alternate package
          try {
            const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
            if (buildType === 'nightly' && packageJson.devDependencies?.electron) {
              delete packageJson.devDependencies.electron;
              fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
            } else if (buildType === 'stable' && packageJson.devDependencies?.['electron-nightly']) {
              delete packageJson.devDependencies['electron-nightly'];
              fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
            }
          } catch (err) {
            console.warn('[ELECTRON-UPGRADE] Could not clean up alternate package:', err);
          }

          resolve({
            success: true,
            message: 'Electron upgrade completed. Restarting application...'
          });
        }
      }
    );
  });
  */
});

ipcMain.handle('restart-app', async (event) => {
  // Quit and relaunch the app
  app.relaunch();
  app.quit();
});

// Open local file dialog -> returns file:// URL (or null if cancelled)
ipcMain.handle('show-open-file-dialog', async () => {
  try {
    const result = await dialog.showOpenDialog({
      properties: ['openFile'],
      filters: [
        { name: 'HTML Files', extensions: ['html', 'htm', 'xhtml'] },
        { name: 'All Files', extensions: ['*'] }
      ]
    });
    if (result.canceled || !result.filePaths || !result.filePaths.length) return null;
    const filePath = result.filePaths[0];
    try {
      return pathToFileURL(filePath).href;
    } catch {
      // Fallback manual conversion
      let p = filePath.replace(/\\/g, '/');
      if (!p.startsWith('/')) p = '/' + p; // ensure leading slash for drive letters
      return 'file://' + (p.startsWith('/') ? '/' : '') + p; // double slash safety
    }
  } catch (err) {
    console.error('open-file dialog failed:', err);
    return null;
  }
});

// Helper to build and show a native context menu for a given webContents + params
function buildAndShowContextMenu(sender, params = {}) {
  try {
    const ownerWin = getOwnerWindowForContents(sender);
    const embedder = ownerWin?.webContents || sender.hostWebContents || sender;
    const template = [];

    template.push(
      { label: 'Back', enabled: sender.canGoBack?.(), click: () => { try { sender.goBack(); } catch {} } },
      { label: 'Forward', enabled: sender.canGoForward?.(), click: () => { try { sender.goForward(); } catch {} } },
      { label: 'Reload', click: () => { try { sender.reload(); } catch {} } },
      { type: 'separator' }
    );

    // Link actions
    const linkURL = params.linkURL && params.linkURL.startsWith('http') ? params.linkURL : undefined;
    if (linkURL) {
      template.push(
        { label: 'Open Link in New Tab', click: () => embedder.send('context-menu-command', { cmd: 'open-link-new-tab', url: linkURL }) },
        { label: 'Download Link', click: () => {
            try { (sender.hostWebContents || sender).downloadURL(linkURL); } catch (e) { console.error('downloadURL failed:', e); }
          }
        },
        { label: 'Open Link Externally', click: () => shell.openExternal(linkURL).catch(()=>{}) },
        { label: 'Copy Link Address', click: () => clipboard.writeText(linkURL) },
        { type: 'separator' }
      );
    }

    // Image actions
    const imageURL = (params.mediaType === 'image' && params.srcURL) ? params.srcURL : (params.imgURL || undefined);
    if (imageURL) {
      template.push(
        { label: 'Open Image in New Tab', click: () => embedder.send('context-menu-command', { cmd: 'open-image-new-tab', url: imageURL }) },
        { label: 'Copy Image Address', click: () => clipboard.writeText(imageURL) },
  { label: 'Save Image As...', click: () => embedder.send('context-menu-command', { cmd: 'save-image', url: imageURL, mime: params.mediaType === 'image' ? params.mimeType : undefined }) },
        { type: 'separator' }
      );
    }

    // Text / editable
    if (params.isEditable) {
      template.push(
        { label: 'Undo', role: 'undo' },
        { label: 'Redo', role: 'redo' },
        { type: 'separator' },
        { label: 'Cut', role: 'cut' },
        { label: 'Copy', role: 'copy' },
        { label: 'Paste', role: 'paste' },
        { label: 'Select All', role: 'selectAll' },
        { type: 'separator' }
      );
    } else if (params.selectionText) {
      template.push(
        { label: 'Copy', role: 'copy' },
        { label: 'Select All', role: 'selectAll' },
        { type: 'separator' }
      );
    }

    template.push({
      label: 'Inspect Element',
      click: () => {
        try {
          const inspectTarget = sender;
          const inspectX = params.x ?? params.clientX ?? 0;
          const inspectY = params.y ?? params.clientY ?? 0;
          
          // Open DevTools docked at bottom if not already open
          if (!inspectTarget.isDevToolsOpened()) {
            inspectTarget.openDevTools({ mode: 'bottom' });
          }
          
          // Inspect the element
          setTimeout(() => {
            try {
              inspectTarget.inspectElement(inspectX, inspectY);
            } catch (e) {
              // Fallback: try on original sender
              try { sender.inspectElement(inspectX, inspectY); } catch {}
            }
          }, 50);
        } catch (err) {
          console.error('Inspect Element failed:', err);
        }
      }
    });

  // Allow plugins to customize/append context menu
  try { pluginManager.applyContextMenuContrib(template, params, sender); } catch {}
  const menu = Menu.buildFromTemplate(template);
    const win = ownerWin || BrowserWindow.fromWebContents(embedder);
    if (win) menu.popup({ window: win });
  } catch (err) {
    console.error('Failed to build context menu:', err);
  }
}

// IPC trigger (legacy / renderer-requested)
ipcMain.handle('show-context-menu', (event, params = {}) => {
  buildAndShowContextMenu(event.sender, params);
});

// Plugins: expose renderer preload list
ipcMain.handle('plugins-get-renderer-preloads', () => {
  try { return pluginManager.getRendererPreloads(); } catch { return []; }
});

// Plugins: expose registered internal pages (nebula://<id>)
ipcMain.handle('plugins-get-pages', () => {
  try { return pluginManager.getRendererPages(); } catch { return []; }
});

// Plugins: management IPC for settings UI
ipcMain.handle('plugins-list', () => pluginManager.discoverPlugins());
ipcMain.handle('plugins-set-enabled', async (_e, { id, enabled }) => {
  const ok = await pluginManager.setEnabled(id, enabled);
  // Reload to apply enable/disable (requires app reload for renderer preloads)
  pluginManager.reload();
  return ok;
});
ipcMain.handle('plugins-reload', (_e, { id } = {}) => {
  pluginManager.reload(id);
  return true;
});

// Automatic native context menu for any webContents (windows + webviews)
app.on('web-contents-created', (event, contents) => {
  contents.on('context-menu', (e, params) => {
    buildAndShowContextMenu(contents, params);
  });

  // Emit to plugins
  try { pluginManager.emit('web-contents-created', contents); } catch {}

  // On macOS, when a page (or a <webview>) enters HTML fullscreen (e.g., YouTube video),
  // also toggle the BrowserWindow into simple fullscreen so the content uses the whole
  // screen and macOS traffic lights/titlebar are hidden. Revert when HTML fullscreen exits.
  if (process.platform === 'darwin') {
    const getOwningWindow = () => {
      try {
        const host = contents.hostWebContents || contents;
        return BrowserWindow.fromWebContents(host) || null;
      } catch { return null; }
    };

    contents.on('enter-html-full-screen', () => {
      const win = getOwningWindow();
      if (!win) return;
      win.__htmlFsDepth = (win.__htmlFsDepth || 0) + 1;
      // If the window is already in native fullscreen (green button), don't switch modes
      const alreadyNativeFs = typeof win.isFullScreen === 'function' && win.isFullScreen();
      if (!alreadyNativeFs && !win.isSimpleFullScreen?.()) {
        try { win.setSimpleFullScreen?.(true); win.__htmlFsUsingSimple = true; } catch {}
      }
    });

    contents.on('leave-html-full-screen', () => {
      const win = getOwningWindow();
      if (!win) return;
      win.__htmlFsDepth = Math.max(0, (win.__htmlFsDepth || 1) - 1);
      if (win.__htmlFsDepth === 0 && win.__htmlFsUsingSimple) {
        try { if (win.isSimpleFullScreen?.()) win.setSimpleFullScreen?.(false); } catch {}
        win.__htmlFsUsingSimple = false;
      }
    });
  }
});

// --- Image save handlers ---
ipcMain.handle('save-image-from-dataurl', async (event, { suggestedName = 'image', dataUrl }) => {
  try {
    if (!dataUrl || !dataUrl.startsWith('data:')) return false;
    const match = /^data:(.*?);base64,(.*)$/.exec(dataUrl);
    if (!match) return false;
    const mime = match[1] || 'application/octet-stream';
    const ext = (mime.split('/')[1] || 'png').split(';')[0];
    const buf = Buffer.from(match[2], 'base64');
    const win = BrowserWindow.fromWebContents(event.sender.hostWebContents || event.sender);
    const { canceled, filePath } = await dialog.showSaveDialog(win, { defaultPath: `${suggestedName}.${ext}` });
    if (canceled || !filePath) return false;
    await fs.promises.writeFile(filePath, buf);
    return true;
  } catch (err) {
    console.error('save-image-from-dataurl failed:', err);
    return false;
  }
});

ipcMain.handle('save-image-from-url', async (event, { url }) => {
  if (!url) return false;
  const win = BrowserWindow.fromWebContents(event.sender.hostWebContents || event.sender);
  try {
    let dataBuf;
    if (url.startsWith('http')) {
      const res = await fetch(url);
      if (!res.ok) throw new Error('HTTP '+res.status);
      const arrayBuf = await res.arrayBuffer();
      dataBuf = Buffer.from(arrayBuf);
      const ctype = res.headers.get('content-type') || 'application/octet-stream';
      const ext = (ctype.split('/')[1] || 'png').split(';')[0];
      const { canceled, filePath } = await dialog.showSaveDialog(win, { defaultPath: `image.${ext}` });
      if (canceled || !filePath) return false;
      await fs.promises.writeFile(filePath, dataBuf);
      return true;
    } else if (url.startsWith('data:')) {
      // Forward to dataURL handler path – easier to keep logic single
      return ipcMain.emit('save-image-from-dataurl', event, { dataUrl: url });
    } else if (url.startsWith('file:')) {
      // Copy file to chosen destination
      const filePathSrc = new URL(url).pathname.replace(/^\//, '');
      const base = path.basename(filePathSrc);
      const { canceled, filePath } = await dialog.showSaveDialog(win, { defaultPath: base });
      if (canceled || !filePath) return false;
      await fs.promises.copyFile(filePathSrc, filePath);
      return true;
    } else {
      return false;
    }
  } catch (err) {
    console.error('save-image-from-url failed:', err);
    return false;
  }
});

// =========================
// Download manager plumbing
// =========================

// In-memory download registry
const downloads = new Map(); // id -> { id, url, filename, savePath, totalBytes, receivedBytes, state, startedAt, mime, canResume, paused, scan? }

function broadcastToAll(channel, payload) {
  try {
    for (const wc of webContents.getAllWebContents()) {
      try { wc.send(channel, payload); } catch {}
    }
  } catch (e) {
    // Fallback to windows only
    for (const win of BrowserWindow.getAllWindows()) {
      try { win.webContents.send(channel, payload); } catch {}
    }
  }
}

function registerDownloadHandling(ses) {
  if (!ses || ses.__nebulaDownloadsHooked) return;
  ses.__nebulaDownloadsHooked = true;
  ses.on('will-download', async (event, item, wc) => {
    try {
      // Build an id (prefer stable GUID if available)
      const id = typeof item.getGUID === 'function' ? item.getGUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
      item.__nebulaId = id;
      const filename = item.getFilename();
      const mime = item.getMimeType?.() || 'application/octet-stream';
      const totalBytes = item.getTotalBytes();
      const url = item.getURL();

      // Choose a default save path under user's Downloads, ensure unique to avoid overwrite
      const defaultDir = app.getPath('downloads');
      const uniquePath = await computeUniqueSavePath(defaultDir, filename);
      try { item.setSavePath(uniquePath); } catch {}

      const info = {
        id, url, filename,
        savePath: uniquePath,
        totalBytes,
        receivedBytes: 0,
        state: 'in-progress',
        startedAt: Date.now(),
        mime,
        canResume: false,
        paused: false,
        scan: { status: process.platform === 'win32' ? 'pending' : 'unavailable', engine: process.platform === 'win32' ? 'Windows Defender' : 'none' }
      };
      downloads.set(id, { ...info, item });
  const payload = { ...info };
  broadcastToAll('downloads-started', payload);

      item.on('updated', (e, state) => {
        const d = downloads.get(id);
        if (!d) return;
        d.receivedBytes = item.getReceivedBytes();
        d.canResume = !!item.canResume?.();
        d.paused = !!item.isPaused?.();
        d.state = state === 'interrupted' ? 'interrupted' : 'in-progress';
        downloads.set(id, d);
        broadcastToAll('downloads-updated', {
          id,
          receivedBytes: d.receivedBytes,
          totalBytes: d.totalBytes,
          state: d.state,
          canResume: d.canResume,
          paused: d.paused
        });
      });

      item.once('done', async (e, state) => {
        const d = downloads.get(id) || {};
        const finalState = state === 'completed' ? 'completed' : (state === 'cancelled' ? 'cancelled' : 'interrupted');
        const final = {
          id,
          url,
          filename,
          savePath: item.getSavePath?.() || d.savePath,
          totalBytes: d.totalBytes || item.getTotalBytes?.() || 0,
          receivedBytes: item.getReceivedBytes?.() || d.receivedBytes || 0,
          state: finalState,
          startedAt: d.startedAt || Date.now(),
          endedAt: Date.now(),
          mime,
          scan: d.scan || { status: process.platform === 'win32' ? 'pending' : 'unavailable', engine: process.platform === 'win32' ? 'Windows Defender' : 'none' }
        };
        // Store minimal object; drop live item ref
        downloads.set(id, final);
        broadcastToAll('downloads-done', final);

        // Kick off a malware scan on Windows if the download completed and path exists
        if (finalState === 'completed' && final.savePath && process.platform === 'win32') {
          try {
            // Update to scanning state and broadcast
            const cur = downloads.get(id) || final;
            cur.scan = { ...(cur.scan || {}), status: 'scanning', engine: 'Windows Defender' };
            downloads.set(id, cur);
            broadcastToAll('downloads-scan-started', { id, savePath: final.savePath });

            const result = await scanFileForMalware(final.savePath);
            const updated = downloads.get(id) || cur;
            updated.scan = result;
            downloads.set(id, updated);
            broadcastToAll('downloads-scan-result', { id, scan: result });
          } catch (scanErr) {
            const updated = downloads.get(id) || final;
            updated.scan = { status: 'error', engine: 'Windows Defender', details: String(scanErr && scanErr.message || scanErr) };
            downloads.set(id, updated);
            broadcastToAll('downloads-scan-result', { id, scan: updated.scan });
          }
        }
      });
    } catch (err) {
      console.error('will-download handler error:', err);
    }
  });
}

async function computeUniqueSavePath(dir, baseName) {
  try {
    const target = path.join(dir, baseName);
    try {
      await fs.promises.access(target);
      // Already exists, create a (n) suffix
      const { name, ext } = splitNameExt(baseName);
      for (let i = 1; i < 10000; i++) {
        const candidate = path.join(dir, `${name} (${i})${ext}`);
        try { await fs.promises.access(candidate); } catch { return candidate; }
      }
      // Fallback if too many
      return path.join(dir, `${Date.now()}-${baseName}`);
    } catch {
      return target; // does not exist
    }
  } catch (e) {
    // Fallback to temp directory
    return path.join(app.getPath('downloads'), `${Date.now()}-${baseName}`);
  }
}

function splitNameExt(filename) {
  const ext = path.extname(filename);
  const name = filename.slice(0, filename.length - ext.length);
  return { name, ext };
}

// IPC: list downloads
ipcMain.handle('downloads-get-all', () => {
  return Array.from(downloads.values()).map(d => {
    const { item, ...rest } = d;
    if (item) {
      return {
        ...rest,
        receivedBytes: item.getReceivedBytes?.() ?? rest.receivedBytes ?? 0,
        totalBytes: item.getTotalBytes?.() ?? rest.totalBytes ?? 0,
        state: rest.state || 'in-progress',
        paused: item.isPaused?.() || false,
        canResume: item.canResume?.() || false,
        scan: rest.scan || { status: process.platform === 'win32' ? 'pending' : 'unavailable', engine: process.platform === 'win32' ? 'Windows Defender' : 'none' }
      };
    }
    return rest;
  });
});

// IPC: control a download (pause/resume/cancel/open/show)
ipcMain.handle('downloads-action', async (event, { id, action }) => {
  const d = downloads.get(id);
  if (!d) return false;
  const item = d.item;
  try {
    switch (action) {
      case 'pause':
        if (item && !item.isPaused?.()) item.pause?.();
        return true;
      case 'resume':
        if (item && item.canResume?.()) item.resume?.();
        return true;
      case 'cancel':
        if (item && d.state === 'in-progress') item.cancel?.();
        return true;
      case 'delete-file': {
        if (d.savePath) {
          try {
            await fs.promises.unlink(d.savePath);
            // Mark entry as deleted (custom state) and clear savePath
            const updated = { ...d, state: d.state === 'completed' ? 'deleted' : d.state, savePath: null };
            downloads.set(id, updated);
            broadcastToAll('downloads-updated', { id, state: updated.state, savePath: null });
            return true;
          } catch (e) {
            console.error('Failed to delete file:', e);
            return false;
          }
        }
        return false;
      }
      case 'rescan': {
        if (d.savePath && process.platform === 'win32') {
          try {
            const cur = downloads.get(id) || d;
            cur.scan = { status: 'scanning', engine: 'Windows Defender' };
            downloads.set(id, cur);
            broadcastToAll('downloads-scan-started', { id, savePath: d.savePath });
            const result = await scanFileForMalware(d.savePath);
            const updated = downloads.get(id) || cur;
            updated.scan = result;
            downloads.set(id, updated);
            broadcastToAll('downloads-scan-result', { id, scan: result });
            return true;
          } catch (e) {
            console.error('Rescan failed:', e);
            const updated = downloads.get(id) || d;
            updated.scan = { status: 'error', engine: 'Windows Defender', details: String(e && e.message || e) };
            downloads.set(id, updated);
            broadcastToAll('downloads-scan-result', { id, scan: updated.scan });
            return false;
          }
        }
        return false;
      }
      case 'open-file':
        if (d.savePath) {
          await shell.openPath(d.savePath);
          return true;
        }
        return false;
      case 'show-in-folder':
        if (d.savePath) {
          shell.showItemInFolder(d.savePath);
          return true;
        }
        return false;
      default:
        return false;
    }
  } catch (e) {
    console.error('downloads-action error:', e);
    return false;
  }
});

// IPC: clear completed entries from the registry (keeps in-progress)
ipcMain.handle('downloads-clear-completed', () => {
  for (const [id, d] of downloads.entries()) {
    if (d.state === 'completed' || d.state === 'cancelled' || d.state === 'deleted') downloads.delete(id);
  }
  broadcastToAll('downloads-cleared');
  return true;
});

// ---------------------------
// Malware scan helpers (Windows Defender)
// ---------------------------
async function findDefenderMpCmdRun() {
  if (process.platform !== 'win32') return null;
  const candidates = [];
  const programData = process.env['ProgramData'];
  if (programData) {
    const platformDir = path.join(programData, 'Microsoft', 'Windows Defender', 'Platform');
    try {
      const entries = await fs.promises.readdir(platformDir, { withFileTypes: true });
      const versions = entries.filter(e => e.isDirectory()).map(e => e.name);
      // Sort versions descending (simple lex sort approximates ok as versions are zero-padded; fallback to reverse chronological by stats)
      versions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true, sensitivity: 'base' }));
      for (const v of versions) {
        candidates.push(path.join(platformDir, v, 'MpCmdRun.exe'));
      }
    } catch {}
  }
  const programFiles = process.env['ProgramFiles'] || 'C://Program Files';
  candidates.push(path.join(programFiles, 'Windows Defender', 'MpCmdRun.exe'));
  candidates.push(path.join(programFiles, 'Microsoft Defender', 'MpCmdRun.exe'));
  for (const c of candidates) {
    try {
      await fs.promises.access(c, fs.constants.X_OK | fs.constants.R_OK);
      return c;
    } catch {}
  }
  return null;
}

async function scanFileForMalware(filePath) {
  if (process.platform !== 'win32') {
    return { status: 'unavailable', engine: 'none', details: 'Malware scanning is only available on Windows with Microsoft Defender.' };
  }
  try {
    // Ensure file exists
    await fs.promises.access(filePath, fs.constants.R_OK);
  } catch {
    return { status: 'error', engine: 'Windows Defender', details: 'File not found for scanning.' };
  }
  const exe = await findDefenderMpCmdRun();
  if (!exe) {
    return { status: 'unavailable', engine: 'Windows Defender', details: 'Microsoft Defender command-line scanner not found.' };
  }

  return await new Promise((resolve) => {
    const args = ['-Scan', '-ScanType', '3', '-File', filePath];
    let stdout = '';
    let stderr = '';
    const child = spawn(exe, args, { windowsHide: true });
    child.stdout.on('data', (d) => { stdout += d.toString(); });
    child.stderr.on('data', (d) => { stderr += d.toString(); });
    child.on('error', (err) => {
      resolve({ status: 'error', engine: 'Windows Defender', details: 'Failed to run scanner: ' + String(err && err.message || err) });
    });
    child.on('close', (code) => {
      const out = (stdout + '\n' + stderr).toLowerCase();
      // Heuristics: exit code 2 indicates threats found; also parse output
      const infected = code === 2 || /threat|infected|malware|found\s*:\s*[1-9]/i.test(stdout) || /threat|infected|malware/.test(stderr);
      if (infected) {
        resolve({ status: 'infected', engine: 'Windows Defender', details: stdout || stderr, exitCode: code });
      } else if (code === 0 || /no threats/.test(out) || /found\s*:\s*0/.test(out)) {
        resolve({ status: 'clean', engine: 'Windows Defender', details: stdout || 'No threats found.', exitCode: code });
      } else {
        resolve({ status: 'error', engine: 'Windows Defender', details: (stdout || stderr || 'Unknown scan result') + ` (code ${code})`, exitCode: code });
      }
    });
  });
}
