import {
  app,
  BrowserWindow,
  desktopCapturer,
  Menu,
  session,
  shell,
  Tray
} from 'electron';
import * as fs from 'fs';
import * as path from 'path';
import { DESKTOP_APP_DISPLAY_NAME } from '../app/desktop-branding.rules';
import { readDesktopSettings } from '../desktop-settings';

let mainWindow: BrowserWindow | null = null;
let tray: Tray | null = null;
let closeToTrayEnabled = true;
let appQuitting = false;
let youtubeRequestHeadersConfigured = false;

const WINDOW_STATE_CHANGED_CHANNEL = 'window-state-changed';
const YOUTUBE_EMBED_REFERRER = 'https://toju.app/';

function ensureYoutubeEmbedRequestHeaders(): void {
  if (youtubeRequestHeadersConfigured || !app.isPackaged) {
    return;
  }

  youtubeRequestHeadersConfigured = true;

  session.defaultSession.webRequest.onBeforeSendHeaders(
    {
      urls: [
        'https://www.youtube-nocookie.com/*',
        'https://www.youtube.com/*',
        'https://*.youtube.com/*',
        'https://*.googlevideo.com/*',
        'https://*.ytimg.com/*'
      ]
    },
    (details, callback) => {
      const requestHeaders = { ...details.requestHeaders };

      requestHeaders['Referer'] ??= YOUTUBE_EMBED_REFERRER;

      callback({ requestHeaders });
    }
  );
}

function getAssetPath(...segments: string[]): string {
  const basePath = app.isPackaged
    ? path.join(process.resourcesPath, 'images')
    : path.join(__dirname, '..', '..', '..', 'images');

  return path.join(basePath, ...segments);
}

function getExistingAssetPath(...segments: string[]): string | undefined {
  const assetPath = getAssetPath(...segments);

  return fs.existsSync(assetPath) ? assetPath : undefined;
}

function getWindowIconPath(): string | undefined {
  if (process.platform === 'win32')
    return getExistingAssetPath('windows', 'icon.ico');

  if (process.platform === 'linux')
    return getExistingAssetPath('icon.png');

  return undefined;
}

export function getDockIconPath(): string | undefined {
  return getExistingAssetPath('macos', '1024x1024.png');
}

function getTrayIconPath(): string | undefined {
  if (process.platform === 'win32')
    return getExistingAssetPath('windows', 'icon.ico');

  return getExistingAssetPath('icon.png');
}

export { getWindowIconPath };

export function getMainWindow(): BrowserWindow | null {
  return mainWindow;
}

function destroyTray(): void {
  if (!tray) {
    return;
  }

  tray.destroy();
  tray = null;
}

function requestAppQuit(): void {
  prepareWindowForAppQuit();
  app.quit();
}

function ensureTray(): void {
  if (tray) {
    return;
  }

  const trayIconPath = getTrayIconPath();

  if (!trayIconPath) {
    return;
  }

  tray = new Tray(trayIconPath);
  tray.setToolTip('Toju');
  tray.setContextMenu(
    Menu.buildFromTemplate([
      {
        label: 'Open Toju',
        click: () => {
          void showMainWindow();
        }
      },
      {
        type: 'separator'
      },
      {
        label: 'Close Toju',
        click: () => {
          requestAppQuit();
        }
      }
    ])
  );

  tray.on('click', () => {
    void showMainWindow();
  });
}

function hideWindowToTray(): void {
  if (!mainWindow || mainWindow.isDestroyed()) {
    return;
  }

  mainWindow.hide();
  emitWindowState();
}

export function updateCloseToTraySetting(enabled: boolean): void {
  closeToTrayEnabled = enabled;
}

export function prepareWindowForAppQuit(): void {
  appQuitting = true;
  destroyTray();
}

export async function showMainWindow(): Promise<void> {
  if (!mainWindow || mainWindow.isDestroyed()) {
    await createWindow();
    return;
  }

  if (mainWindow.isMinimized()) {
    mainWindow.restore();
  }

  if (!mainWindow.isVisible()) {
    mainWindow.show();
  }

  mainWindow.focus();
  emitWindowState();
}

function emitWindowState(): void {
  if (!mainWindow || mainWindow.isDestroyed()) {
    return;
  }

  mainWindow.webContents.send(WINDOW_STATE_CHANGED_CHANNEL, {
    isFocused: mainWindow.isFocused(),
    isMinimized: mainWindow.isMinimized()
  });
}

export async function createWindow(): Promise<void> {
  const windowIconPath = getWindowIconPath();

  closeToTrayEnabled = readDesktopSettings().closeToTray;
  ensureTray();
  ensureYoutubeEmbedRequestHeaders();

  mainWindow = new BrowserWindow({
    width: 1400,
    height: 900,
    minWidth: 800,
    minHeight: 600,
    frame: false,
    title: DESKTOP_APP_DISPLAY_NAME,
    titleBarStyle: 'hidden',
    backgroundColor: '#0a0a0f',
    ...(windowIconPath ? { icon: windowIconPath } : {}),
    webPreferences: {
      backgroundThrottling: false,
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, '..', 'preload.js'),
      webSecurity: true
    }
  });

  if (process.platform === 'linux') {
    session.defaultSession.setDisplayMediaRequestHandler(
      async (_request, respond) => {
        // On Linux/Wayland the system picker (useSystemPicker: true) handles
        // the portal. This handler is only reached if the system picker is
        // unavailable (e.g. X11 without a portal). Fall back to
        // desktopCapturer so the user still gets something.
        try {
          const sources = await desktopCapturer.getSources({
            types: ['window', 'screen'],
            thumbnailSize: { width: 150, height: 150 }
          });
          const firstSource = sources[0];

          if (firstSource) {
            respond({ video: firstSource });
            return;
          }
        } catch {
          // desktopCapturer also unavailable
        }

        respond({});
      },
      { useSystemPicker: true }
    );
  }

  if (process.platform === 'win32') {
    session.defaultSession.setDisplayMediaRequestHandler(
      async (request, respond) => {
        // On Windows the system picker (useSystemPicker: true) is preferred.
        // This handler is only reached when the system picker is unavailable.
        // Include loopback audio when the renderer requested it so that
        // getDisplayMedia receives an audio track and the renderer-side
        // restrictOwnAudio constraint can keep the app's own voice playback
        // out of the captured stream.
        try {
          const sources = await desktopCapturer.getSources({
            types: ['window', 'screen'],
            thumbnailSize: { width: 150, height: 150 }
          });
          const firstSource = sources[0];

          if (firstSource) {
            respond({
              video: firstSource,
              ...(request.audioRequested ? { audio: 'loopback' } : {})
            });

            return;
          }
        } catch {
          // desktopCapturer also unavailable
        }

        respond({});
      },
      { useSystemPicker: true }
    );
  }

  if (process.env['NODE_ENV'] === 'development') {
    const devUrl = process.env['SSL'] === 'true'
      ? 'https://localhost:4200'
      : 'http://localhost:4200';

    await mainWindow.loadURL(devUrl);

    if (process.env['DEBUG_DEVTOOLS'] === '1') {
      mainWindow.webContents.openDevTools();
    }
  } else {
    await mainWindow.loadFile(path.join(__dirname, '..', '..', 'client', 'browser', 'index.html'));
  }

  mainWindow.on('close', (event) => {
    if (appQuitting || !closeToTrayEnabled) {
      return;
    }

    event.preventDefault();
    hideWindowToTray();
  });

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

  mainWindow.on('focus', () => {
    mainWindow?.flashFrame(false);
    emitWindowState();
  });

  mainWindow.on('blur', () => {
    emitWindowState();
  });

  mainWindow.on('minimize', () => {
    emitWindowState();
  });

  mainWindow.on('restore', () => {
    emitWindowState();
  });

  mainWindow.on('show', () => {
    emitWindowState();
  });

  mainWindow.on('hide', () => {
    emitWindowState();
  });

  emitWindowState();

  mainWindow.webContents.on('context-menu', (_event, params) => {
    mainWindow?.webContents.send('show-context-menu', {
      posX: params.x,
      posY: params.y,
      isEditable: params.isEditable,
      selectionText: params.selectionText,
      linkURL: params.linkURL,
      mediaType: params.mediaType,
      srcURL: params.srcURL,
      editFlags: {
        canCut: params.editFlags.canCut,
        canCopy: params.editFlags.canCopy,
        canPaste: params.editFlags.canPaste,
        canSelectAll: params.editFlags.canSelectAll
      }
    });
  });

  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
    shell.openExternal(url);
    return { action: 'deny' };
  });

  mainWindow.webContents.on('will-navigate', (event, url) => {
    const currentUrl = mainWindow?.webContents.getURL();
    const isSameOrigin = new URL(url).origin === new URL(currentUrl || '').origin;

    if (!isSameOrigin) {
      event.preventDefault();
      shell.openExternal(url);
    }
  });
}
