import { EventEmitter } from 'events';
import * as fs from 'fs';
import * as path from 'path';

import type * as defaultMenuModule from '@electron/internal/browser/default-menu';
import type * as url from 'url';
import type * as v8 from 'v8';

const Module = require('module') as NodeJS.ModuleInternal;

// We modified the original process.argv to let node.js load the init.js,
// we need to restore it here.
process.argv.splice(1, 1);

// Import common settings.
require('@electron/internal/common/init');

process._linkedBinding('electron_browser_event_emitter').setEventEmitterPrototype(EventEmitter.prototype);

// Don't quit on fatal error.
process.on('uncaughtException', function (error) {
  // Do nothing if the user has a custom uncaught exception handler.
  if (process.listenerCount('uncaughtException') > 1) {
    return;
  }

  // Show error in GUI.
  // We can't import { dialog } at the top of this file as this file is
  // responsible for setting up the require hook for the "electron" module
  // so we import it inside the handler down here
  import('electron')
    .then(({ dialog }) => {
      const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
      const message = 'Uncaught Exception:\n' + stack;
      dialog.showErrorBox('A JavaScript error occurred in the main process', message);
    });
});

// Emit 'exit' event on quit.
const { app } = require('electron');

app.on('quit', (_event, exitCode) => {
  process.emit('exit', exitCode);
});

if (process.platform === 'win32') {
  // If we are a Squirrel.Windows-installed app, set app user model ID
  // so that users don't have to do this.
  //
  // Squirrel packages are always of the form:
  //
  // PACKAGE-NAME
  // - Update.exe
  // - app-VERSION
  //   - OUREXE.exe
  //
  // Squirrel itself will always set the shortcut's App User Model ID to the
  // form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call
  // app.setAppUserModelId with a matching identifier so that renderer processes
  // will inherit this value.
  const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe');

  if (fs.existsSync(updateDotExe)) {
    const packageDir = path.dirname(path.resolve(updateDotExe));
    const packageName = path.basename(packageDir).replaceAll(/\s/g, '');
    const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replaceAll(/\s/g, '');

    app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`);
  }
}

// Map process.exit to app.exit, which quits gracefully.
process.exit = app.exit as () => never;

// Load the RPC server.
require('@electron/internal/browser/rpc-server');

// Load the guest view manager.
require('@electron/internal/browser/guest-view-manager');

// Now we try to load app's package.json.
const v8Util = process._linkedBinding('electron_common_v8_util');
let packagePath = null;
let packageJson = null;
const searchPaths: string[] = v8Util.getHiddenValue(global, 'appSearchPaths');
const searchPathsOnlyLoadASAR: boolean = v8Util.getHiddenValue(global, 'appSearchPathsOnlyLoadASAR');
// Borrow the _getOrCreateArchive asar helper
const getOrCreateArchive = process._getOrCreateArchive;
delete process._getOrCreateArchive;

if (process.resourcesPath) {
  for (packagePath of searchPaths) {
    try {
      packagePath = path.join(process.resourcesPath, packagePath);
      if (searchPathsOnlyLoadASAR) {
        if (!getOrCreateArchive?.(packagePath)) {
          continue;
        }
      }
      packageJson = Module._load(path.join(packagePath, 'package.json'));
      break;
    } catch {
      continue;
    }
  }
}

if (packageJson == null) {
  process.nextTick(function () {
    return process.exit(1);
  });
  throw new Error('Unable to find a valid app');
}

// Set application's version.
if (packageJson.version != null) {
  app.setVersion(packageJson.version);
}

// Set application's name.
if (packageJson.productName != null) {
  app.name = `${packageJson.productName}`.trim();
} else if (packageJson.name != null) {
  app.name = `${packageJson.name}`.trim();
}

// Set application's desktop name.
if (packageJson.desktopName != null) {
  app.setDesktopName(packageJson.desktopName);
} else {
  app.setDesktopName(`${app.name}.desktop`);
}

// Set v8 flags, deliberately lazy load so that apps that do not use this
// feature do not pay the price
if (packageJson.v8Flags != null) {
  (require('v8') as typeof v8).setFlagsFromString(packageJson.v8Flags);
}

app.setAppPath(packagePath);

// Load the chrome devtools support.
require('@electron/internal/browser/devtools');

// Load protocol module to ensure it is populated on app ready
require('@electron/internal/browser/api/protocol');

// Load web-contents module to ensure it is populated on app ready
require('@electron/internal/browser/api/web-contents');

// Load web-frame-main module to ensure it is populated on app ready
require('@electron/internal/browser/api/web-frame-main');

// Set main startup script of the app.
const mainStartupScript = packageJson.main || 'index.js';

const KNOWN_XDG_DESKTOP_VALUES = new Set(['Pantheon', 'Unity:Unity7', 'pop:GNOME']);

function currentPlatformSupportsAppIndicator () {
  if (process.platform !== 'linux') return false;
  const currentDesktop = process.env.XDG_CURRENT_DESKTOP;

  if (!currentDesktop) return false;
  if (KNOWN_XDG_DESKTOP_VALUES.has(currentDesktop)) return true;
  // ubuntu based or derived session (default ubuntu one, communitheme…) supports
  // indicator too.
  if (/ubuntu/ig.test(currentDesktop)) return true;

  return false;
}

// Workaround for electron/electron#5050 and electron/electron#9046
process.env.ORIGINAL_XDG_CURRENT_DESKTOP = process.env.XDG_CURRENT_DESKTOP;
if (currentPlatformSupportsAppIndicator()) {
  process.env.XDG_CURRENT_DESKTOP = 'Unity';
}

// Quit when all windows are closed and no other one is listening to this.
app.on('window-all-closed', () => {
  if (app.listenerCount('window-all-closed') === 1) {
    app.quit();
  }
});

const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu') as typeof defaultMenuModule;

// Create default menu.
//
// The |will-finish-launching| event is emitted before |ready| event, so default
// menu is set before any user window is created.
app.once('will-finish-launching', setDefaultApplicationMenu);

const { appCodeLoaded } = process;
delete process.appCodeLoaded;

if (packagePath) {
  // Finally load app's main.js and transfer control to C++.
  if ((packageJson.type === 'module' && !mainStartupScript.endsWith('.cjs')) || mainStartupScript.endsWith('.mjs')) {
    const { loadESM } = __non_webpack_require__('internal/process/esm_loader');
    const main = (require('url') as typeof url).pathToFileURL(path.join(packagePath, mainStartupScript));
    loadESM(async (esmLoader: any) => {
      try {
        await esmLoader.import(main.toString(), undefined, Object.create(null));
        appCodeLoaded!();
      } catch (err) {
        appCodeLoaded!();
        process.emit('uncaughtException', err as Error);
      }
    });
  } else {
    // Call appCodeLoaded before just for safety, it doesn't matter here as _load is syncronous
    appCodeLoaded!();
    process._firstFileName = Module._resolveFilename(path.join(packagePath, mainStartupScript), null, false);
    Module._load(path.join(packagePath, mainStartupScript), Module, true);
  }
} else {
  console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)');
  console.error('This normally means you\'ve damaged the Electron package somehow');
  appCodeLoaded!();
}
