/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 */

import path from 'path';
import {
  _NuxManagerContext,
  _createNuxManager,
  _setGlobalInteractionReporter,
  _LoggerContext,
} from 'flipper-plugin';
// eslint-disable-next-line no-restricted-imports,flipper/no-electron-remote-imports
import {ipcRenderer, SaveDialogReturnValue, clipboard, shell} from 'electron';
import fs from 'fs';
import {setupMenuBarTracking} from './setupMenuBar';
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
import type {Icon, RenderHost} from 'flipper-ui-core';
import {getLocalIconUrl} from '../utils/icons';
import {getCPUUsage} from 'process';
import {ElectronIpcClientRenderer} from '../electronIpc';

export async function initializeElectron(
  flipperServer: FlipperServer,
  flipperServerConfig: FlipperServerConfig,
  electronIpcClient: ElectronIpcClientRenderer,
) {
  const [electronProcess, electronTheme] = await Promise.all([
    electronIpcClient.send('getProcess'),
    electronIpcClient.send('getNativeTheme'),
  ]);

  const execPath = process.execPath || electronProcess.execPath;
  const isProduction = !/node_modules[\\/]electron[\\/]/.test(execPath);

  setupMenuBarTracking(electronIpcClient);

  function restart(update: boolean = false) {
    if (isProduction) {
      if (update) {
        const options = {
          args: process.argv
            .splice(0, 1)
            .filter((arg) => arg !== '--no-launcher' && arg !== '--no-updater'),
        };
        electronIpcClient.send('relaunch', options);
      } else {
        electronIpcClient.send('relaunch');
      }
      electronIpcClient.send('exit');
    } else {
      // Relaunching the process with the standard way doesn't work in dev mode.
      // So instead we're sending a signal to dev server to kill the current instance of electron and launch new.
      fetch(`${flipperServerConfig.env.DEV_SERVER_URL}/_restartElectron`, {
        method: 'POST',
      });
    }
  }

  FlipperRenderHostInstance = {
    processId: electronProcess.pid,
    isProduction,
    readTextFromClipboard() {
      return Promise.resolve(clipboard.readText());
    },
    writeTextToClipboard(text: string) {
      clipboard.writeText(text);
    },
    async showSaveDialog(options) {
      return (await electronIpcClient.send('showSaveDialog', options))
        ?.filePath;
    },
    async showOpenDialog({filter, defaultPath}) {
      const result = await electronIpcClient.send('showOpenDialog', {
        defaultPath,
        properties: ['openFile'],
        filters: filter ? [filter] : undefined,
      });
      return result.filePaths?.[0];
    },
    showSelectDirectoryDialog(defaultPath = path.resolve('/')) {
      return electronIpcClient
        .send('showOpenDialog', {
          properties: ['openDirectory'],
          defaultPath,
        })
        .then((result: SaveDialogReturnValue & {filePaths: string[]}) => {
          if (result.filePath) {
            return result.filePath.toString();
          }
          // Electron typings seem of here, just in case,
          // (can be tested with settings dialog)
          // handle both situations
          if (result.filePaths) {
            return result.filePaths[0];
          }
          return undefined;
        });
    },
    importFile: (async ({
      defaultPath,
      extensions,
      title,
      encoding = 'utf-8',
      multi,
    } = {}) => {
      let {filePaths} = await electronIpcClient.send('showOpenDialog', {
        defaultPath,
        properties: [
          'openFile',
          ...(multi ? (['multiSelections'] as const) : []),
        ],
        filters: extensions ? [{extensions, name: ''}] : undefined,
        title,
      });

      if (!filePaths.length) {
        return;
      }

      if (!multi) {
        filePaths = [filePaths[0]];
      }

      const descriptors = await Promise.all(
        filePaths.map(async (filePath) => {
          const fileName = path.basename(filePath);

          const data = await fs.promises.readFile(filePath, {encoding});
          return {
            data,
            name: fileName,
          };
        }),
      );

      return multi ? descriptors : descriptors[0];
    }) as RenderHost['importFile'],
    async exportFile(data, {defaultPath, encoding = 'utf-8'} = {}) {
      const {filePath} = await electronIpcClient.send('showSaveDialog', {
        defaultPath,
      });

      if (!filePath) {
        return;
      }

      await fs.promises.writeFile(filePath, data, {encoding});
      return filePath;
    },
    async exportFileBinary(data, {defaultPath} = {}) {
      const {filePath} = await electronIpcClient.send('showSaveDialog', {
        defaultPath,
      });

      if (!filePath) {
        return;
      }

      await fs.promises.writeFile(filePath, data, {encoding: 'binary'});
      return filePath;
    },
    openLink(url: string) {
      shell.openExternal(url);
    },
    hasFocus() {
      // eslint-disable-next-line node/no-sync
      return electronIpcClient.sendSync('getCurrentWindowState').isFocused;
    },
    onIpcEvent(event, callback) {
      ipcRenderer.on(event, (_ev, ...args: any[]) => {
        callback(...(args as any));
      });
    },
    sendIpcEvent(event, ...args: any[]) {
      ipcRenderer.send(event, ...args);
    },
    shouldUseDarkColors() {
      return electronTheme.shouldUseDarkColors;
    },
    restartFlipper(update: boolean = false) {
      restart(update);
    },
    serverConfig: flipperServerConfig,
    GK(gatekeeper) {
      return flipperServerConfig.gatekeepers[gatekeeper] ?? false;
    },
    flipperServer,
    async requirePlugin(path): Promise<{plugin: any; css?: string}> {
      const plugin = electronRequire(path);
      /**
       * Check if the plugin includes a bundled css. If so,
       * load its content too.
       */
      const idx = path.lastIndexOf('.');
      const cssPath = path.substring(0, idx < 0 ? path.length : idx) + '.css';
      try {
        await fs.promises.access(cssPath);

        const buffer = await fs.promises.readFile(cssPath, {encoding: 'utf-8'});
        const css = buffer.toString();

        return {plugin, css};
      } catch (e) {}

      return {plugin};
    },
    getStaticResourceUrl(relativePath): string {
      return (
        'file://' +
        path.resolve(flipperServerConfig.paths.staticPath, relativePath)
      );
    },
    getLocalIconUrl(icon: Icon, url: string): string {
      return getLocalIconUrl(
        icon,
        url,
        flipperServerConfig.paths.staticPath,
        !flipperServerConfig.environmentInfo.isProduction,
      );
    },
    unloadModule(path: string) {
      const resolvedPath = electronRequire.resolve(path);
      if (!resolvedPath || !electronRequire.cache[resolvedPath]) {
        return;
      }
      delete electronRequire.cache[resolvedPath];
    },
    getPercentCPUUsage() {
      return getCPUUsage().percentCPUUsage;
    },
  } as RenderHost;
}
