// Dynamic imports to avoid bundling Expo dependencies when not needed
function loadExpoDependencies() {
  try {
    const {
      withInfoPlist,
      withAndroidManifest,
      withDangerousMod,
      createRunOncePlugin,
      ConfigPlugin,
    } = require('@expo/config-plugins') as any;
    const { withBuildProperties } = require('expo-build-properties') as any;
    return {
      withInfoPlist,
      withAndroidManifest,
      withDangerousMod,
      createRunOncePlugin,
      ConfigPlugin,
      withBuildProperties,
    };
  } catch {
    throw new Error(`
Ditto Expo plugin requires additional dependencies:
npm install @expo/config-plugins expo-build-properties

These are only needed when using Expo with Ditto.
    `);
  }
}

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: any = (expoConfig: any, props: DittoConfig) => {
  const { withInfoPlist } = loadExpoDependencies();
  return withInfoPlist(expoConfig, (config: any) => {
    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: any = (expoConfig: any) => {
  const { withBuildProperties, withAndroidManifest } = loadExpoDependencies();
  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: string) =>
          libraries.map((lib) => `lib/${arch}/${lib}`)
        ),
      },
    },
  });
  expoConfig = withAndroidManifest(expoConfig, async (config: any) => {
    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;
};

// Workaround for fmt 11.x consteval incompatibility with Xcode 26.4 clang.
// Compile the fmt pod as C++17 so __cpp_consteval is not defined, which forces
// fmt to skip the consteval FMT_STRING code path entirely and fall back to
// runtime format-string validation. Scoped to the fmt target only — RCT-Folly,
// React-Core, and the rest of RN keep their C++20 settings.
// See: https://github.com/fmtlib/fmt/issues/4740 and SDKS-3242.
// Remove when React Native upgrades to a build that bundles fmt >= 11.1.
const FMT_CONSTEVAL_SNIPPET = `
    # Ditto: fmt consteval workaround for Xcode 26.4 (SDKS-3242).
    installer.pods_project.targets.each do |target|
      if target.name == 'fmt'
        target.build_configurations.each do |cfg|
          cfg.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
        end
      end
    end
`;

// Idempotency marker. The comment text is stable — it comes from
// FMT_CONSTEVAL_SNIPPET and we own it — so a formatter reflowing whitespace
// around it won't cause a duplicate injection.
const FMT_CONSTEVAL_MARKER =
  '# Ditto: fmt consteval workaround for Xcode 26.4 (SDKS-3242).';

// Match `react_native_post_install(...)` for both the single-line form
// (`react_native_post_install(installer)`) and the multi-line form with
// trailing arguments. Handles one level of balanced parens in the arg list,
// which is more than any real Podfile template requires.
const REACT_NATIVE_POST_INSTALL_RE =
  /react_native_post_install\((?:[^()]|\([^()]*\))*\)\s*/;

const withDittoFmtConstevalFix: any = (expoConfig: any) => {
  const { withDangerousMod } = loadExpoDependencies();
  return withDangerousMod(expoConfig, [
    'ios',
    async (config: any) => {
      const fs = require('node:fs');
      const path = require('node:path');
      const podfilePath = path.join(
        config.modRequest.platformProjectRoot,
        'Podfile'
      );
      if (!fs.existsSync(podfilePath)) return config;
      let podfile: string = fs.readFileSync(podfilePath, 'utf8');
      if (podfile.includes(FMT_CONSTEVAL_MARKER)) return config;
      if (!REACT_NATIVE_POST_INSTALL_RE.test(podfile)) return config;
      podfile = podfile.replace(
        REACT_NATIVE_POST_INSTALL_RE,
        (match: string) => match + FMT_CONSTEVAL_SNIPPET
      );
      fs.writeFileSync(podfilePath, podfile);
      return config;
    },
  ]);
};

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

export default function dittoExpoPlugin(...args: any[]) {
  const { createRunOncePlugin } = loadExpoDependencies();
  return createRunOncePlugin(withDitto, 'ditto_expo')(...args);
}
