import { app, BrowserWindow, desktopCapturer, ipcMain, screen, session, shell } from 'electron';
import path from 'node:path';
import started from 'electron-squirrel-startup';
import { updateElectronApp, UpdateSourceType } from 'update-electron-app';

import { appConfig } from './config/env';
import { logError } from './lib/errors';
import { safeHandle } from './main/ipc-utils';
import { initSentryMain } from './main/sentry';

initSentryMain();

if (app.isPackaged) {
  updateElectronApp({
    updateSource: {
      type: UpdateSourceType.StaticStorage,
      baseUrl: `https://storage.googleapis.com/flowy-releases/llink/${process.platform}/${process.arch}`
    }
  });
}

// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (started) {
  app.quit();
}

// Set the dock icon for development mode on macOS.
if (process.platform === 'darwin' && !app.isPackaged) {
  app.dock?.setIcon(path.join(__dirname, '../../assets/icon.png'));
}

// In dev, `LLINK_PROFILE=foo yarn start` spins up a second instance with an
// isolated userData dir so it can coexist with the default one (separate auth,
// cookies, leveldb locks).
const devProfile = !app.isPackaged ? process.env.LLINK_PROFILE : undefined;
if (devProfile) {
  app.setPath('userData', `${app.getPath('userData')}-${devProfile}`);
}

// Single-instance lock: on Windows/Linux, clicking a llink:// URL launches a new
// process. The lock makes the losing instance quit and fires `second-instance` on
// the primary, so we focus the existing window instead of spawning a duplicate.
// macOS uses `open-url` instead and doesn't need this, but the lock is harmless.
// Skip the lock when running a named dev profile — those instances are meant to
// run alongside the default one.
if (!devProfile && !app.requestSingleInstanceLock()) {
  app.quit();
}

// Register llink:// as the default handler for this app. macOS also gets this
// declaratively via CFBundleURLTypes (forge.config.ts `protocols`); Windows relies
// on this runtime call (Squirrel doesn't register protocols on install).
if (!app.isDefaultProtocolClient('llink')) {
  app.setAsDefaultProtocolClient('llink');
}

let mainWindow: BrowserWindow | null = null;
let autoplayWindow: BrowserWindow | null = null;
let huddleWindow: BrowserWindow | null = null;
let screenRecordWindow: BrowserWindow | null = null;

function hardenWindow(win: BrowserWindow) {
  const wc = win.webContents;

  // Disable pinch / Cmd+scroll zoom; our layouts assume DPR=1.
  wc.setVisualZoomLevelLimits(1, 1);

  // Block devtools, reload, and zoom keyboard shortcuts in packaged builds.
  wc.on('before-input-event', (event, input) => {
    if (input.type !== 'keyDown') return;
    const key = input.key.toLowerCase();
    const cmdOrCtrl = input.meta || input.control;

    const isDevtools =
      key === 'f12' ||
      (cmdOrCtrl && input.alt && key === 'i') ||
      (cmdOrCtrl && input.shift && key === 'i') ||
      (cmdOrCtrl && input.shift && key === 'c') ||
      (cmdOrCtrl && input.shift && key === 'j');
    const isReload = cmdOrCtrl && (key === 'r' || key === 'f5');
    const isZoom = cmdOrCtrl && (key === '=' || key === '+' || key === '-' || key === '0');

    if (app.isPackaged && (isDevtools || isReload)) {
      event.preventDefault();
    }
    if (isZoom) {
      event.preventDefault();
    }
  });

  // Route window.open / target=_blank through the OS browser instead of a new BrowserWindow.
  wc.setWindowOpenHandler(({ url }) => {
    if (url.startsWith('http://') || url.startsWith('https://')) {
      shell.openExternal(url);
    }
    return { action: 'deny' };
  });

  // Freeze the window to its initial origin — block any full-page navigation.
  // In-app routing uses pushState / hash which don't trigger will-navigate.
  wc.on('will-navigate', (event, url) => {
    const currentUrl = wc.getURL();
    if (!currentUrl) return;
    try {
      if (new URL(url).origin !== new URL(currentUrl).origin) {
        event.preventDefault();
        if (url.startsWith('http://') || url.startsWith('https://')) {
          shell.openExternal(url);
        }
      }
    } catch {
      event.preventDefault();
    }
  });
}

const createWindow = () => {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    resizable: true,
    fullscreenable: true,
    frame: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      backgroundThrottling: false,
    },
  });
  hardenWindow(mainWindow);

  mainWindow.on('maximize', () => {
    mainWindow?.webContents.send('window:maximize-changed', true);
  });
  mainWindow.on('unmaximize', () => {
    mainWindow?.webContents.send('window:maximize-changed', false);
  });

  if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
    mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
    mainWindow.webContents.openDevTools();
  } else {
    mainWindow.loadFile(
      path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`),
    );
  }

  mainWindow.on('closed', () => {
    mainWindow = null;
    app.quit();
  });
};

const createAutoplayWindow = () => {
  if (autoplayWindow) return;

  autoplayWindow = new BrowserWindow({
    width: 320,
    height: 88,
    resizable: false,
    frame: false,
    alwaysOnTop: true,
    skipTaskbar: true,
    focusable: false,
    show: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  });
  autoplayWindow.setVisibleOnAllWorkspaces(true);
  hardenWindow(autoplayWindow);

  if (AUTOPLAY_WINDOW_VITE_DEV_SERVER_URL) {
    autoplayWindow.loadURL(AUTOPLAY_WINDOW_VITE_DEV_SERVER_URL);
  } else {
    autoplayWindow.loadFile(
      path.join(__dirname, `../renderer/${AUTOPLAY_WINDOW_VITE_NAME}/index.html`),
    );
  }

  autoplayWindow.on('closed', () => {
    autoplayWindow = null;
  });
};

const createHuddleWindow = () => {
  if (huddleWindow) return;

  huddleWindow = new BrowserWindow({
    width: 1024,
    height: 768,
    resizable: true,
    fullscreenable: true,
    frame: true,
    title: 'Huddle',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  });
  hardenWindow(huddleWindow);

  huddleWindow.on('closed', () => {
    huddleWindow = null;
  });
};

// Connection data is passed via URL hash so it's available synchronously on
// renderer mount — avoids the IPC race where `huddle:connect` could be sent
// before React attached its listener.
const loadHuddleWindow = (data: { token: string; serverUrl: string }) => {
  if (!huddleWindow) return;
  const params = new URLSearchParams({ token: data.token, serverUrl: data.serverUrl });
  const hash = params.toString();
  if (HUDDLE_WINDOW_VITE_DEV_SERVER_URL) {
    huddleWindow.loadURL(`${HUDDLE_WINDOW_VITE_DEV_SERVER_URL}#${hash}`);
  } else {
    huddleWindow.loadFile(
      path.join(__dirname, `../renderer/${HUDDLE_WINDOW_VITE_NAME}/index.html`),
      { hash },
    );
  }
};

function positionAutoplayWindow() {
  if (!autoplayWindow) return;
  const { width } = screen.getPrimaryDisplay().workAreaSize;
  const [winW] = autoplayWindow.getSize();
  autoplayWindow.setPosition(width - winW - 16, 16);
}

const createScreenRecordWindow = () => {
  if (screenRecordWindow) return;

  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
  const winW = 240;
  const winH = 48;

  screenRecordWindow = new BrowserWindow({
    width: winW,
    height: winH,
    x: Math.round((width - winW) / 2),
    y: height - winH - 32,
    resizable: false,
    frame: false,
    alwaysOnTop: true,
    skipTaskbar: true,
    focusable: true,
    show: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
    },
  });
  screenRecordWindow.setVisibleOnAllWorkspaces(true);
  hardenWindow(screenRecordWindow);

  if (SCREEN_RECORD_WINDOW_VITE_DEV_SERVER_URL) {
    screenRecordWindow.loadURL(SCREEN_RECORD_WINDOW_VITE_DEV_SERVER_URL);
  } else {
    screenRecordWindow.loadFile(
      path.join(__dirname, `../renderer/${SCREEN_RECORD_WINDOW_VITE_NAME}/index.html`),
    );
  }

  screenRecordWindow.on('closed', () => {
    screenRecordWindow = null;
  });
};

// Window control IPC handlers
ipcMain.on('window:minimize', (event) => {
  BrowserWindow.fromWebContents(event.sender)?.minimize();
});
ipcMain.on('window:maximize', (event) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  if (win?.isMaximized()) {
    win.unmaximize();
  } else {
    win?.maximize();
  }
});
ipcMain.on('window:close', (event) => {
  BrowserWindow.fromWebContents(event.sender)?.close();
});
ipcMain.on('window:fullscreen', (event) => {
  const win = BrowserWindow.fromWebContents(event.sender);
  if (win) win.setFullScreen(!win.isFullScreen());
});

// Secondary window IPC handlers
ipcMain.on('window:open-huddle', (_event, data: { token: string; serverUrl: string }) => {
  createHuddleWindow();
  loadHuddleWindow(data);
  huddleWindow?.focus();
});
ipcMain.on('window:close-huddle', () => {
  huddleWindow?.close();
});

safeHandle('screen:get-sources', async () => {
  const sources = await desktopCapturer.getSources({
    types: ['screen', 'window'],
    thumbnailSize: { width: 320, height: 180 },
  });
  return sources.map((source) => ({
    id: source.id,
    name: source.name,
    thumbnailDataUrl: source.thumbnail.toDataURL(),
    appIconDataUrl: source.appIcon?.toDataURL() ?? null,
  }));
});

// Screen recording IPC handlers
ipcMain.on('screen-record:start', () => {
  createScreenRecordWindow();
  if (!screenRecordWindow) return;

  const winW = 240;
  const winH = 48;
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;
  screenRecordWindow.setSize(winW, winH);
  screenRecordWindow.setPosition(
    Math.round((width - winW) / 2),
    height - winH - 32,
  );

  const send = () => {
    screenRecordWindow?.webContents.send('screen-record:init');
    screenRecordWindow?.showInactive();
  };

  if (screenRecordWindow.webContents.isLoading()) {
    screenRecordWindow.webContents.once('did-finish-load', send);
  } else {
    send();
  }
});
ipcMain.on('screen-record:stop', () => {
  mainWindow?.webContents.send('screen-record:stopped');
  screenRecordWindow?.close();
  mainWindow?.focus();
});
ipcMain.on('screen-record:cancel', () => {
  screenRecordWindow?.close();
});

// Autoplay IPC handlers
ipcMain.on('autoplay:play', (_event, payload) => {
  if (!autoplayWindow) createAutoplayWindow();
  if (!autoplayWindow) return;

  const isVideo = typeof payload?.mimeType === 'string' && payload.mimeType.startsWith('video/');
  autoplayWindow.setSize(320, isVideo ? 260 : 88);
  positionAutoplayWindow();

  autoplayWindow.webContents.send('autoplay:play', payload);
  autoplayWindow.showInactive();
});

ipcMain.on('autoplay:dismiss', () => {
  autoplayWindow?.webContents.send('autoplay:stop');
  autoplayWindow?.hide();
});

ipcMain.on('autoplay:navigate', (_event, data) => {
  autoplayWindow?.webContents.send('autoplay:stop');
  autoplayWindow?.hide();
  if (mainWindow) {
    mainWindow.webContents.send('autoplay:navigate', data);
    mainWindow.focus();
  }
});

// --- Dock badge ---

ipcMain.on('app:set-dock-badge', (_event, count: number) => {
  if (process.platform === 'darwin') {
    app.dock?.setBadge(count > 0 ? String(count) : '');
  }
});

safeHandle('app:get-version', () => app.getVersion());

safeHandle('link:open-external', async (_event, url) => {
  if (typeof url !== 'string') return;
  // Only allow http(s) URLs for security
  if (!url.startsWith('http://') && !url.startsWith('https://')) return;
  await shell.openExternal(url);
});

// --- Attachment download ---
// Triggers a native download with save-as dialog. Cross-origin safe — unlike
// the web `<a download>` hack, which is ignored for cross-origin URLs.
ipcMain.on(
  'attachment:download',
  (event, payload: { url: string; filename?: string }) => {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return;
    const { url, filename } = payload ?? {};
    if (typeof url !== 'string') return;
    if (!url.startsWith('http://') && !url.startsWith('https://')) return;

    const dlSession = win.webContents.session;
    const onWillDownload = (_e: Electron.Event, item: Electron.DownloadItem) => {
      if (filename) item.setSaveDialogOptions({ defaultPath: filename });
      dlSession.removeListener('will-download', onWillDownload);
    };
    dlSession.on('will-download', onWillDownload);
    win.webContents.downloadURL(url);
  },
);

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', () => {
  // Allow CORS for API requests from the renderer process.
  // The server doesn't handle OPTIONS preflight, so we intercept at the
  // Electron network layer: inject CORS headers and return 200 for preflight.
  session.defaultSession.webRequest.onHeadersReceived(
    { urls: [`${appConfig.orionUrl}/*`, 'https://storage.googleapis.com/*'] },
    (details, callback) => {
      const headers = { ...details.responseHeaders };
      headers['access-control-allow-origin'] = ['*'];
      headers['access-control-allow-headers'] = ['Content-Type', 'Authorization'];
      headers['access-control-allow-methods'] = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'];

      if (details.method === 'OPTIONS') {
        callback({ responseHeaders: headers, statusLine: 'HTTP/1.1 200 OK' });
      } else {
        callback({ responseHeaders: headers });
      }
    },
  );

  createWindow();
  createAutoplayWindow();
});

app.on('window-all-closed', () => {
  app.quit();
});

app.on('activate', () => {
  if (!mainWindow) {
    createWindow();
  } else {
    mainWindow.show();
    mainWindow.focus();
  }
});

function focusMainWindow() {
  if (!mainWindow) return;
  if (mainWindow.isMinimized()) mainWindow.restore();
  if (!mainWindow.isVisible()) mainWindow.show();
  mainWindow.focus();
}

// macOS delivers llink:// URLs via this event, both when the app is already
// running and on cold start (after `ready`). We prevent the default to silence
// Electron's warning and focus the window — OS-level focus alone won't restore
// a hidden or minimized window. Cold start is handled by createWindow().
app.on('open-url', (event) => {
  event.preventDefault();
  focusMainWindow();
});

// Windows/Linux: the OS launches a second process with the URL in argv; the
// single-instance lock diverts it here on the primary instance.
app.on('second-instance', () => {
  focusMainWindow();
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
