/**
 * Copyright (c) Facebook, Inc. and its 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 os from 'os';
import isProduction, {isTest} from './isProduction';
import fs from 'fs-extra';
import {getStaticPath} from './pathUtils';
import type {State, Store} from '../reducers/index';
import {deconstructClientId} from './clientUtils';
import {sideEffect} from './sideEffect';
import {Logger} from '../fb-interfaces/Logger';

type PlatformInfo = {
  arch: string;
  platform: string;
  unixname: string;
  versions: {
    [key: string]: string | undefined;
  };
};

export type SelectionInfo = {
  plugin: string | null;
  pluginName: string | null;
  pluginVersion: string | null;
  app: string | null;
  os: string | null;
  device: string | null;
  deviceName: string | null;
  deviceSerial: string | null;
  deviceType: string | null;
  archived: boolean | null;
};

export type Info = PlatformInfo & {
  selection: SelectionInfo;
};

let platformInfo: PlatformInfo | undefined;
let selection: SelectionInfo = {
  plugin: null,
  pluginName: null,
  pluginVersion: null,
  app: null,
  os: null,
  device: null,
  deviceName: null,
  deviceSerial: null,
  deviceType: null,
  archived: null,
};

export default (store: Store, _logger: Logger) => {
  return sideEffect(
    store,
    {
      name: 'recomputeSelectionInfo',
      throttleMs: 0,
      noTimeBudgetWarns: true,
      runSynchronously: true,
      fireImmediately: true,
    },
    (state) => ({
      connections: state.connections,
      loadedPlugins: state.plugins.loadedPlugins,
    }),
    (state, _store) => {
      selection = getSelectionInfo(state.connections, state.loadedPlugins);
    },
  );
};

/**
 * This method builds up some metadata about the users environment that we send
 * on bug reports, analytic events, errors etc.
 */
export function getInfo(): Info {
  if (!platformInfo) {
    platformInfo = {
      arch: process.arch,
      platform: process.platform,
      unixname: os.userInfo().username,
      versions: {
        electron: process.versions.electron,
        node: process.versions.node,
        platform: os.release(),
      },
    };
  }
  return {
    ...platformInfo,
    selection,
  };
}

let APP_VERSION: string | undefined;
export function getAppVersion(): string {
  return (APP_VERSION =
    APP_VERSION ??
    process.env.FLIPPER_FORCE_VERSION ??
    (isTest()
      ? '0.0.0'
      : (isProduction()
          ? fs.readJsonSync(getStaticPath('package.json'), {
              throws: false,
            })?.version
          : require('../../package.json').version) ?? '0.0.0'));
}

export function stringifyInfo(info: Info): string {
  const lines = [
    `Platform: ${info.platform} ${info.arch}`,
    `Unixname: ${info.unixname}`,
    `Versions:`,
  ];

  for (const key in info.versions) {
    lines.push(`  ${key}: ${String(info.versions[key])}`);
  }

  return lines.join('\n');
}

export function getSelectionInfo(
  connections: State['connections'],
  loadedPlugins: State['plugins']['loadedPlugins'],
): SelectionInfo {
  const selectedApp = connections.selectedApp;
  const clientIdParts = selectedApp ? deconstructClientId(selectedApp) : null;
  const loadedPlugin = connections.selectedPlugin
    ? loadedPlugins.get(connections.selectedPlugin)
    : null;
  return {
    plugin: connections.selectedPlugin || null,
    pluginName: loadedPlugin?.name || null,
    pluginVersion: loadedPlugin?.version || null,
    app: clientIdParts?.app || null,
    device: connections.selectedDevice?.title || null,
    deviceName: clientIdParts?.device || null,
    deviceSerial: connections.selectedDevice?.serial || null,
    deviceType: connections.selectedDevice?.deviceType || null,
    os: connections.selectedDevice?.os || null,
    archived: connections.selectedDevice?.isArchived || false,
  };
}
