/**
 * VLCode Lite — Electron main process
 * Runs one backend child process per window so each workspace stays isolated.
 */
const fs = require('fs');
const http = require('http');
const path = require('path');
const net = require('net');
const { fork } = require('child_process');
const { app, BrowserWindow, Menu, dialog, ipcMain, shell } = require('electron');

const DEFAULT_PORT = 4000;
const START_TIMEOUT_MS = 30000;
const FORCE_KILL_DELAY_MS = 3000;
const UNEXPECTED_EXIT_RETRY_WINDOW_MS = 60000;

const windowSessions = new Map();
const workspaceWindows = new Map();
const launchPromises = new Map();
let lastFocusedWindowId = null;

function normalizeWorkDir(dirPath) {
  return dirPath ? path.resolve(dirPath) : '';
}

function isExistingDirectory(dirPath) {
  if (!dirPath || !fs.existsSync(dirPath)) return false;
  try {
    return fs.statSync(dirPath).isDirectory();
  } catch {
    return false;
  }
}

function getAppRoot() {
  return app.getAppPath();
}

function getServerEntry() {
  if (app.isPackaged) {
    const packagedEntry = path.join(getAppRoot(), 'bin', 'vlcode-lite.js');
    if (fs.existsSync(packagedEntry)) {
      return packagedEntry;
    }
    const unpackedEntry = path.join(process.resourcesPath, 'app.asar.unpacked', 'bin', 'vlcode-lite.js');
    if (fs.existsSync(unpackedEntry)) {
      return unpackedEntry;
    }
  }
  return path.join(getAppRoot(), 'bin', 'vlcode-lite.js');
}

function getPreloadPath() {
  return path.join(__dirname, 'preload.cjs');
}

function getChildCwd() {
  return app.isPackaged ? process.resourcesPath : getAppRoot();
}

function getWritableSourceRoot() {
  return app.isPackaged
    ? path.join(process.resourcesPath, 'app.asar.unpacked')
    : getAppRoot();
}

function getLogDir() {
  const candidates = [];
  try {
    candidates.push(path.join(app.getPath('userData'), 'logs'));
  } catch {}
  candidates.push(path.join(process.cwd(), '.vlcode-electron-logs'));
  for (const candidate of candidates) {
    try {
      fs.mkdirSync(candidate, { recursive: true });
      return candidate;
    } catch {}
  }
  return '';
}

function appendLog(fileName, message) {
  const logDir = getLogDir();
  if (!logDir) return;
  try {
    fs.appendFileSync(path.join(logDir, fileName), `[${new Date().toISOString()}] ${message}\n`);
  } catch {}
}

function safeWriteToStream(stream, text) {
  if (!text) return;
  try {
    if (stream?.writable) {
      stream.write(text);
    }
  } catch (err) {
    appendLog('electron-main.log', `stream write failed: ${err.stack || err.message}`);
  }
}

function buildWindowUrl(port) {
  return `http://127.0.0.1:${port}`;
}

function buildChildEnv() {
  const childEnv = {
    ...process.env,
    ELECTRON_RUN: '1',
    ELECTRON_RUN_AS_NODE: '1',
    NODE_ENV: app.isPackaged ? 'production' : 'development',
    VLCODE_IS_PACKAGED: app.isPackaged ? '1' : '0',
    VLCODE_APP_ROOT: getAppRoot(),
    VLCODE_APP_VERSION: app.getVersion(),
    VLCODE_UNPACKED_DIR: getWritableSourceRoot(),
    VLCODE_LOG_DIR: getLogDir(),
  };
  if (app.isPackaged) {
    const bundledBrowsers = path.join(process.resourcesPath, 'ms-playwright');
    if (fs.existsSync(bundledBrowsers)) {
      childEnv.PLAYWRIGHT_BROWSERS_PATH = bundledBrowsers;
    }
  }
  return childEnv;
}

function extractRequestedWorkDir(argv) {
  for (let i = 0; i < argv.length; i++) {
    const arg = argv[i];
    if (arg === '--dir' || arg === '-d') {
      const next = argv[i + 1];
      return next ? normalizeWorkDir(next) : '';
    }
    if (arg === '--empty-window') {
      return '';
    }
  }
  return null;
}

function isPortFree(port) {
  return new Promise((resolve) => {
    const server = net.createServer();
    server.once('error', () => resolve(false));
    server.once('listening', () => {
      server.close(() => resolve(true));
    });
    server.listen({ port, exclusive: true });
  });
}

async function findFreePort(startPort = DEFAULT_PORT, maxPort = 4099) {
  for (let port = startPort; port <= maxPort; port++) {
    if (await isPortFree(port)) return port;
  }
  throw new Error(`No free port found in range ${startPort}-${maxPort}`);
}

function probeHealth(port) {
  return new Promise((resolve) => {
    const req = http.get({
      host: '127.0.0.1',
      port,
      path: '/api/health',
      timeout: 1500,
    }, (res) => {
      res.resume();
      resolve(res.statusCode === 200);
    });
    req.on('error', () => resolve(false));
    req.on('timeout', () => {
      req.destroy();
      resolve(false);
    });
  });
}

function waitForServerReady(port, child, timeoutMs = START_TIMEOUT_MS) {
  return new Promise((resolve, reject) => {
    let settled = false;
    const finish = (err) => {
      if (settled) return;
      settled = true;
      clearTimeout(timeout);
      clearInterval(poller);
      child.off('exit', onExit);
      if (err) reject(err);
      else resolve();
    };
    const onExit = (code, signal) => {
      finish(new Error(`Backend exited before ready (code: ${code ?? 'null'}, signal: ${signal ?? 'none'})`));
    };
    const poller = setInterval(async () => {
      try {
        if (await probeHealth(port)) finish();
      } catch {}
    }, 250);
    const timeout = setTimeout(() => {
      finish(new Error(`Backend start timeout (${timeoutMs}ms)`));
    }, timeoutMs);
    child.once('exit', onExit);
  });
}

function pipeChildLogs(child, port) {
  child.stdout?.on('data', (chunk) => {
    const text = chunk.toString();
    appendLog(`backend-${port}.log`, text.trimEnd());
    safeWriteToStream(process.stdout, `[vlcode:${port}] ${text}`);
  });
  child.stderr?.on('data', (chunk) => {
    const text = chunk.toString();
    appendLog(`backend-${port}.log`, text.trimEnd());
    safeWriteToStream(process.stderr, `[vlcode:${port}] ${text}`);
  });
}

async function startServer(workDir = '') {
  const port = await findFreePort(DEFAULT_PORT, 4099);
  const args = ['--web', '--port', String(port)];
  if (workDir) args.push('--dir', workDir);
  else args.push('--empty-window');

  const child = fork(getServerEntry(), args, {
    cwd: getChildCwd(),
    env: buildChildEnv(),
    stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
  });

  appendLog('electron-main.log', `starting backend on port ${port} for ${workDir || '[empty window]'}`);
  pipeChildLogs(child, port);
  await waitForServerReady(port, child);
  return { child, port };
}

function getSessionByWindow(win) {
  return win ? windowSessions.get(win.id) || null : null;
}

function focusWindow(win) {
  if (!win || win.isDestroyed()) return;
  if (win.isMinimized()) win.restore();
  win.show();
  win.focus();
}

function cleanupSession(session) {
  if (!session) return;
  windowSessions.delete(session.window.id);
  if (session.workDir && workspaceWindows.get(session.workDir) === session.window.id) {
    workspaceWindows.delete(session.workDir);
  }
  if (lastFocusedWindowId === session.window.id) {
    lastFocusedWindowId = null;
  }
}

function stopServer(session) {
  const child = session?.serverProcess;
  if (!child) return;
  session.serverProcess = null;
  try {
    child.kill('SIGTERM');
  } catch {}
  setTimeout(() => {
    if (child.exitCode === null && child.signalCode === null) {
      try { child.kill('SIGKILL'); } catch {}
    }
  }, FORCE_KILL_DELAY_MS);
}

async function restartSession(session, reason = 'restart') {
  if (!session || session.closing || session.restarting) return;
  session.restarting = true;
  try {
    stopServer(session);
    const started = await startServer(session.workDir);
    session.serverProcess = started.child;
    session.port = started.port;
    session.lastStartAt = Date.now();
    bindSessionLifecycle(session);
    if (session.window && !session.window.isDestroyed()) {
      await session.window.loadURL(buildWindowUrl(session.port));
    }
  } catch (err) {
    dialog.showErrorBox(
      'VLCode Lite — Backend Restart Failed',
      `Reason: ${reason}\n\n${err.message}`
    );
    session.window?.close();
  } finally {
    session.restarting = false;
  }
}

function bindSessionLifecycle(session) {
  const child = session.serverProcess;
  if (!child) return;

  child.on('message', (msg) => {
    if (msg?.type === 'restart') {
      restartSession(session, 'server requested restart');
    }
  });

  child.on('exit', (code, signal) => {
    appendLog(
      'electron-main.log',
      `backend exited for ${session.workDir || '[empty window]'} (code=${code ?? 'null'}, signal=${signal ?? 'none'})`
    );
    if (session.closing || session.restarting) return;
    if (code === 0 && session.window && !session.window.isDestroyed()) {
      restartSession(session, 'backend exited cleanly');
      return;
    }
    const ranLongEnough = (Date.now() - (session.lastStartAt || 0)) > UNEXPECTED_EXIT_RETRY_WINDOW_MS;
    if (ranLongEnough) {
      session.unexpectedExitCount = 0;
    }
    if ((session.unexpectedExitCount || 0) < 1) {
      session.unexpectedExitCount = (session.unexpectedExitCount || 0) + 1;
      restartSession(session, `unexpected backend exit (${code ?? 'null'})`);
      return;
    }
    dialog.showErrorBox(
      'VLCode Lite — Backend Stopped',
      `Workspace: ${session.workDir || '[empty window]'}\nCode: ${code ?? 'null'}\nSignal: ${signal ?? 'none'}\nLogs: ${getLogDir() || '[unavailable]'}`
    );
    session.window?.close();
  });
}

function createAppWindow(port) {
  const windowOptions = {
    width: 1440,
    height: 900,
    minWidth: 1024,
    minHeight: 680,
    title: 'VLCode Lite',
    show: false,
    titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default',
    backgroundColor: '#1e1e2e',
    webPreferences: {
      preload: getPreloadPath(),
      nodeIntegration: false,
      contextIsolation: true,
      spellcheck: false,
    },
  };
  if (process.platform === 'darwin') {
    windowOptions.trafficLightPosition = { x: 12, y: 12 };
  }

  const win = new BrowserWindow(windowOptions);

  win.once('ready-to-show', () => win.show());
  win.on('focus', () => {
    lastFocusedWindowId = win.id;
  });
  win.on('closed', () => {
    const session = getSessionByWindow(win);
    if (!session) return;
    session.closing = true;
    stopServer(session);
    cleanupSession(session);
  });

  win.webContents.setWindowOpenHandler(({ url }) => {
    if (url.startsWith('http') && !url.startsWith(buildWindowUrl(port))) {
      shell.openExternal(url);
      return { action: 'deny' };
    }
    return { action: 'allow' };
  });

  if (!app.isPackaged) {
    win.webContents.on('before-input-event', (_event, input) => {
      if (input.meta && input.alt && input.key === 'i') {
        win.webContents.toggleDevTools();
      }
    });
  }

  win.loadURL(buildWindowUrl(port));
  return win;
}

async function openWorkspacePicker(targetWindow) {
  const session = getSessionByWindow(targetWindow);
  const result = await dialog.showOpenDialog(targetWindow || undefined, {
    title: 'Open Folder',
    defaultPath: resolvePickerDefaultPath('', session?.workDir || ''),
    properties: ['openDirectory', 'createDirectory'],
  });
  return result.canceled ? '' : normalizeWorkDir(result.filePaths?.[0] || '');
}

async function openWorkspaceWindow(dirPath = '', options = {}) {
  const workDir = normalizeWorkDir(dirPath);
  const forceNew = !!options.forceNew;
  if (workDir && !isExistingDirectory(workDir)) {
    throw new Error(`Directory not found: ${workDir}`);
  }
  const existingId = !forceNew ? workspaceWindows.get(workDir) : null;
  if (existingId) {
    const existing = windowSessions.get(existingId);
    if (existing?.window && !existing.window.isDestroyed()) {
      focusWindow(existing.window);
      return { ok: true, reused: true, port: existing.port, windowId: existing.window.id };
    }
    workspaceWindows.delete(workDir);
  }

  const launchKey = forceNew && !workDir
    ? `empty:${Date.now()}:${Math.random().toString(36).slice(2, 8)}`
    : workDir;

  if (launchPromises.has(launchKey)) {
    return launchPromises.get(launchKey);
  }

  const launch = (async () => {
    const started = await startServer(workDir);
    const win = createAppWindow(started.port);
    const session = {
      window: win,
      serverProcess: started.child,
      port: started.port,
      workDir,
      closing: false,
      restarting: false,
      lastStartAt: Date.now(),
      unexpectedExitCount: 0,
    };
    windowSessions.set(win.id, session);
    if (workDir) {
      workspaceWindows.set(workDir, win.id);
    }
    lastFocusedWindowId = win.id;
    bindSessionLifecycle(session);
    return { ok: true, reused: false, port: session.port, windowId: win.id };
  })().finally(() => {
    launchPromises.delete(launchKey);
  });

  launchPromises.set(launchKey, launch);
  return launch;
}

function buildAppMenu() {
  const template = [
    ...(process.platform === 'darwin' ? [{
      label: app.name,
      submenu: [
        { role: 'about' },
        { type: 'separator' },
        { role: 'services' },
        { type: 'separator' },
        { role: 'hide' },
        { role: 'hideOthers' },
        { role: 'unhide' },
        { type: 'separator' },
        { role: 'quit' },
      ],
    }] : []),
    {
      label: 'File',
      submenu: [
        {
          label: 'New Window',
          accelerator: 'CmdOrCtrl+Shift+N',
          click: () => {
            openWorkspaceWindow('', { forceNew: true }).catch((err) => {
              dialog.showErrorBox('VLCode Lite — New Window Failed', err.message);
            });
          },
        },
        {
          label: 'Open Folder...',
          accelerator: 'CmdOrCtrl+O',
          click: async (_item, browserWindow) => {
            try {
              const dirPath = await openWorkspacePicker(browserWindow || BrowserWindow.getFocusedWindow());
              if (dirPath) await openWorkspaceWindow(dirPath);
            } catch (err) {
              dialog.showErrorBox('VLCode Lite — Open Failed', err.message);
            }
          },
        },
        { type: 'separator' },
        { role: 'close' },
      ],
    },
    {
      label: 'Edit',
      submenu: [
        { role: 'undo' },
        { role: 'redo' },
        { type: 'separator' },
        { role: 'cut' },
        { role: 'copy' },
        { role: 'paste' },
        { role: 'selectAll' },
      ],
    },
    {
      label: 'View',
      submenu: [
        { role: 'reload' },
        { role: 'forceReload' },
        { role: 'togglefullscreen' },
        ...(app.isPackaged ? [] : [{ role: 'toggleDevTools' }]),
      ],
    },
    {
      label: 'Window',
      submenu: [
        { role: 'minimize' },
        { role: 'zoom' },
        ...(process.platform === 'darwin'
          ? [{ type: 'separator' }, { role: 'front' }, { role: 'window' }]
          : [{ role: 'close' }]),
      ],
    },
    {
      label: 'Help',
      submenu: [
        {
          label: 'VLCode Lite Docs',
          click: () => shell.openExternal('https://editor.visuallogic.ai/'),
        },
      ],
    },
  ];

  return Menu.buildFromTemplate(template);
}

function focusLastWindow() {
  const preferred = lastFocusedWindowId ? windowSessions.get(lastFocusedWindowId)?.window : null;
  if (preferred && !preferred.isDestroyed()) {
    focusWindow(preferred);
    return;
  }
  const fallback = BrowserWindow.getAllWindows()[0];
  if (fallback) focusWindow(fallback);
}

function resolvePickerDefaultPath(requestedPath, currentWorkDir = '') {
  const candidates = [requestedPath, currentWorkDir, currentWorkDir ? path.dirname(currentWorkDir) : '', app.getPath('documents'), app.getPath('home')];
  for (const candidate of candidates) {
    if (!candidate) continue;
    const resolved = normalizeWorkDir(candidate);
    if (!resolved || !fs.existsSync(resolved)) continue;
    try {
      const stat = fs.statSync(resolved);
      return stat.isDirectory() ? resolved : path.dirname(resolved);
    } catch {}
  }
  return app.getPath('home');
}

function registerIpcHandlers() {
  ipcMain.handle('vlcode:pick-directory', async (event, payload = {}) => {
    const win = BrowserWindow.fromWebContents(event.sender);
    const session = getSessionByWindow(win);
    const result = await dialog.showOpenDialog(win || undefined, {
      title: 'Open Folder',
      defaultPath: resolvePickerDefaultPath(payload.defaultPath, session?.workDir || ''),
      properties: ['openDirectory', 'createDirectory'],
    });
    return {
      canceled: result.canceled,
      path: result.filePaths?.[0] || '',
    };
  });

  ipcMain.handle('vlcode:open-workspace-window', async (_event, payload = {}) => {
    const dirPath = normalizeWorkDir(payload.dirPath);
    if (!dirPath) throw new Error('dirPath is required');
    return openWorkspaceWindow(dirPath, payload);
  });
}

const gotLock = app.requestSingleInstanceLock();
if (!gotLock) {
  app.quit();
} else {
  app.on('second-instance', (_event, commandLine) => {
    const requestedDir = extractRequestedWorkDir(commandLine);
    if (requestedDir !== null) {
      openWorkspaceWindow(requestedDir).catch((err) => {
        dialog.showErrorBox('VLCode Lite — Open Failed', err.message);
      });
      return;
    }
    focusLastWindow();
  });

  app.whenReady().then(async () => {
    registerIpcHandlers();
    Menu.setApplicationMenu(buildAppMenu());
    try {
      const requestedDir = extractRequestedWorkDir(process.argv);
      await openWorkspaceWindow(requestedDir ?? '');
    } catch (err) {
      dialog.showErrorBox(
        'VLCode Lite — Launch Failed',
        `Failed to start the backend server.\n\n${err.message}`
      );
      app.quit();
      return;
    }

    app.on('activate', () => {
      if (BrowserWindow.getAllWindows().length === 0) {
        openWorkspaceWindow('').catch((err) => {
          dialog.showErrorBox('VLCode Lite — Open Failed', err.message);
        });
      }
    });
  });

  app.on('before-quit', () => {
    for (const session of windowSessions.values()) {
      session.closing = true;
      stopServer(session);
    }
  });

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