import {
  ConfigPlugin,
  createRunOncePlugin,
  withAndroidManifest,
  withInfoPlist,
} from '@expo/config-plugins';
import { withBuildProperties } from 'expo-build-properties';

const BLE_USAGE = 'Uses Bluetooth to connect and sync with nearby devices.';
const LAN_USAGE = 'Uses WiFi to connect and sync with nearby devices.';

type DittoConfig = {
  /**
   * The iOS Bluetooth prompt's description. Defaults to "Uses Bluetooth to
   * connect and sync with nearby devices."
   */
  bluetoothUsageDescription?: string;

  /**
   * The iOS LAN prompt's description. Defaults to "Uses WiFi to connect and
   * sync with nearby devices."
   */
  localNetworkUsageDescription?: string;
};

const withDittoIOS: ConfigPlugin<DittoConfig> = (expoConfig, props) =>
  withInfoPlist(expoConfig, (config) => {
    const infoPlist = config.modResults;

    infoPlist.NSBluetoothAlwaysUsageDescription =
      props.bluetoothUsageDescription ??
      infoPlist.NSBluetoothAlwaysUsageDescription ??
      BLE_USAGE;

    infoPlist.NSLocalNetworkUsageDescription =
      props.localNetworkUsageDescription ??
      infoPlist.NSLocalNetworkUsageDescription ??
      LAN_USAGE;

    if (!Array.isArray(infoPlist.NSBonjourServices)) {
      infoPlist.NSBonjourServices = [];
    }

    if (!infoPlist.NSBonjourServices.includes('_http-alt._tcp.')) {
      infoPlist.NSBonjourServices.push('_http-alt._tcp.');
    }

    if (!Array.isArray(infoPlist.UIBackgroundModes)) {
      infoPlist.UIBackgroundModes = [];
    }

    if (!infoPlist.UIBackgroundModes.includes('bluetooth-central')) {
      infoPlist.UIBackgroundModes.push('bluetooth-central');
    }

    if (!infoPlist.UIBackgroundModes.includes('bluetooth-peripheral')) {
      infoPlist.UIBackgroundModes.push('bluetooth-peripheral');
    }

    return config;
  });

const withDittoAndroid: ConfigPlugin<DittoConfig> = (expoConfig) => {
  const architectures = ['x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'];
  const libraries = [
    'libjsi.so',
    'libdittoffi.so',
    'libreact_nativemodule_core.so',
    'libturbomodulejsijni.so',
    'libreactnative.so',
  ];

  expoConfig = withBuildProperties(expoConfig, {
    android: {
      packagingOptions: {
        pickFirst: architectures.flatMap((arch) =>
          libraries.map((lib) => `lib/${arch}/${lib}`)
        ),
      },
    },
  });
  expoConfig = withAndroidManifest(expoConfig, async (config) => {
    const androidManifest = config.modResults;
    const permissions = androidManifest.manifest['uses-permission'] || [];

    const permissionsToAdd = [
      { name: 'android.permission.BLUETOOTH' },
      { name: 'android.permission.BLUETOOTH_ADMIN' },
      { name: 'android.permission.BLUETOOTH_ADVERTISE' },
      { name: 'android.permission.BLUETOOTH_CONNECT' },
      {
        name: 'android.permission.BLUETOOTH_SCAN',
        attributes: { 'android:usesPermissionFlags': 'neverForLocation' },
      },
      { name: 'android.permission.ACCESS_FINE_LOCATION' },
      { name: 'android.permission.ACCESS_COARSE_LOCATION' },
      { name: 'android.permission.INTERNET' },
      { name: 'android.permission.ACCESS_WIFI_STATE' },
      { name: 'android.permission.ACCESS_NETWORK_STATE' },
      { name: 'android.permission.CHANGE_NETWORK_STATE' },
      { name: 'android.permission.CHANGE_WIFI_MULTICAST_STATE' },
      { name: 'android.permission.CHANGE_WIFI_STATE' },
      {
        name: 'android.permission.NEARBY_WIFI_DEVICES',
        attributes: { 'android:usesPermissionFlags': 'neverForLocation' },
      },
    ];

    permissionsToAdd.forEach((permission) => {
      function isPermissionAlreadyRequested(
        permissionName: string,
        manifestPermissions: any[]
      ): boolean {
        return manifestPermissions.some(
          (e) => e.$['android:name'] === permissionName
        );
      }
      if (!isPermissionAlreadyRequested(permission.name, permissions)) {
        const permissionObject = {
          $: {
            'android:name': permission.name,
            ...permission.attributes,
          },
        };
        permissions.push(permissionObject);
      }
    });

    androidManifest.manifest['uses-permission'] = permissions;
    return config;
  });
  return expoConfig;
};

const withDitto: ConfigPlugin<DittoConfig> = (config, props = {}) => {
  config = withDittoIOS(config, props);
  config = withDittoAndroid(config, props);
  return config;
};

export default createRunOncePlugin(withDitto, 'ditto_expo');
