/* eslint-disable no-bitwise */
import {
  IOSConfig,
  WarningAggregator,
  withAppDelegate,
  withInfoPlist,
  type ConfigPlugin,
  type ExportedConfigWithProps,
} from '@expo/config-plugins';
import eol from 'eol';
import type { ExpoBraintreePluginProps } from './withExpoBraintree';
import type { AppDelegateProjectFile } from '@expo/config-plugins/build/ios/Paths';

export type AppleLanguage = 'objc' | 'objcpp' | 'swift' | 'rb';

/*
 * Mods for Info.plist
 */
export const withExpoBraintreePlist: ConfigPlugin = (expoConfig) => {
  return withInfoPlist(expoConfig, (config) => {
    const bundleIdentifier = config.ios?.bundleIdentifier ?? '';
    const bundleIdentifierWithBraintreeSchema = `${bundleIdentifier}.braintree`;
    const bundleUrlTypes = config.modResults.CFBundleURLTypes ?? [];

    // Check if an entry with the specific Braintree URL scheme already exists
    const isBraintreeEntryNotExist = !bundleUrlTypes.find((urlType) => {
      return urlType.CFBundleURLSchemes?.includes(
        bundleIdentifierWithBraintreeSchema
      );
    });

    // If Braintree entry doesn't exist, add a new one
    if (isBraintreeEntryNotExist) {
      bundleUrlTypes.push({
        CFBundleURLSchemes: [bundleIdentifierWithBraintreeSchema],
      });
    }

    // Assign the modified bundleUrlTypes back to the config
    config.modResults.CFBundleURLTypes = bundleUrlTypes;

    return config;
  });
};

/*
 * Add allowlist Venmo URL scheme
 * @see https://developer.paypal.com/braintree/docs/guides/venmo/client-side/ios/v6#allowlist-venmo-url-scheme
 */
export const withVenmoScheme: ConfigPlugin = (expoConfig) => {
  return withInfoPlist(expoConfig, (config) => {
    // Ensure LSApplicationQueriesSchemes exists in Info.plist
    config.modResults.LSApplicationQueriesSchemes =
      config.modResults.LSApplicationQueriesSchemes || [];

    // Hardcoded scheme for Venmo
    const venmoScheme = 'com.venmo.touch.v2';

    // Add the Venmo scheme to the LSApplicationQueriesSchemes array if not already present
    if (!config.modResults.LSApplicationQueriesSchemes.includes(venmoScheme)) {
      config.modResults.LSApplicationQueriesSchemes.push(venmoScheme);
    }

    return config;
  });
};

/*
 * Mods for Info.plist
 */

/**
 * Mods for AppDelegate.swift React Native above 0.77.x and Expo above 53
 */

export const modifyAppDelegateSwift = (
  config: ExportedConfigWithProps<AppDelegateProjectFile>
) => {
  const appDelegate = config.modResults;
  let contents = eol.split(appDelegate.contents);

  // Step 1 Add method to properly handle openUrl method in AppDelegate.m
  //   func application(
  //   _ application: UIApplication,
  //   open url: URL,
  //   options: [UIApplication.OpenURLOptionsKey : Any] = [:]
  // ) -> Bool {

  //   if url.scheme?.localizedCaseInsensitiveCompare(
  //     ExpoBraintreeConfig.paymentURLScheme
  //   ) == .orderedSame {
  //     return ExpoBraintreeConfig.handleUrl(url: url)
  //   }

  //   return RCTLinkingManager.application(
  //     application,
  //     open: url,
  //     options: options
  //   )
  // }
  // Step 1.1
  // We need to try to find if in AppDelegate.swift there is already an application method to handle url
  // we also need to be sure that we do not break other plugins as well
  const openUrlIdentifier = `options: [UIApplication.OpenURLOptionsKey: Any] = [:]`;

  const expoBraintreeConfigHandleUrl = `return ExpoBraintreeConfig.handleUrl(url: url)`;
  const expoBraintreeOpenUrlLines = [
    `    // @generated by react-native-expo-braintree (DO NOT MODIFY)`,
    `    if url.scheme?.localizedCaseInsensitiveCompare(`,
    `      ExpoBraintreeConfig.paymentURLScheme`,
    `    ) == .orderedSame {`,
    `      ${expoBraintreeConfigHandleUrl}`,
    `    }`,
    `    // @generated by react-native-expo-braintree (DO NOT MODIFY)`,
  ];
  const openUrlIdentifierElementIndex = contents.findIndex((content) =>
    content.includes(openUrlIdentifier)
  );
  // Step 2 If openUrlIdentifierElementIndex exist in AppDelegate.swift then we only need to add expoBraintreeOpenUrlLines at the top of that method
  /* eslint-disable no-extra-boolean-cast */
  if (!!~openUrlIdentifierElementIndex) {
    contents.splice(
      // We are adding +1 to the index to insert content after ') -> Bool {' block
      openUrlIdentifierElementIndex + 2,
      0,
      ...expoBraintreeOpenUrlLines
    );
  }
  // Step 3 If openUrlIdentifierElementIndex do not exist in AppDelegate.swift add a warning that something went wrong
  else {
    WarningAggregator.addWarningIOS(
      'withExpoBraintree',
      `Unable to find  "options: [UIApplication.OpenURLOptionsKey: Any] = [:]" in AppDelegate.swift, automatic changes not applied expo-braintree might not working as expected`
    );
  }

  const expoBraintreeConfigHandleUrlElementIndex = contents.findIndex(
    (content) => content.includes(expoBraintreeConfigHandleUrl)
  );

  // If openUrlMethodElementIndex exist in AppDelegate.mm and expoBraintreeOpenUrlLineIndex do not exist
  if (!~expoBraintreeConfigHandleUrlElementIndex) {
    WarningAggregator.addWarningIOS(
      'withExpoBraintree',
      `Unable to find  "return ExpoBraintreeConfig.handleUrl(url: url)" in AppDelegate.swift, automatic changes not applied expo-braintree might not working as expected`
    );
  }
  return contents;
};

/**
 * Mods for AppDelegate.swift
 */

/**
 * Mods for AppDelegate.mm / AppDelegate.m React Native below 0.77.x and Expo below 53
 */

export const modifyAppDelegateObjectiveC = (
  config: ExportedConfigWithProps<AppDelegateProjectFile>,
  xCodeProjectAppName?: string
) => {
  if (!xCodeProjectAppName) {
    WarningAggregator.addWarningIOS(
      'withExpoBraintree',
      `xCodeProjectAppName props not provided but in case of using plugin in objective c world it is required`
    );
  }
  const appDelegate = config.modResults;
  let contents = eol.split(appDelegate.contents);
  // Step 1 Edit Import part
  // Editing import part for -swift.h file to be able to use Braintree
  const importSwiftHeaderFileContent = `#import "${xCodeProjectAppName}-Swift.h"`;
  const importSwiftHeaderFileIndex = contents.findIndex((content) =>
    content.includes(importSwiftHeaderFileContent)
  );
  // If importSwiftHeaderFileContent do not exist in AppDelegate.mm
  if (!~importSwiftHeaderFileIndex) {
    contents = [importSwiftHeaderFileContent, ...contents];
  }
  const importExpoModulesSwiftHeader = `#import "ExpoModulesCore-Swift.h"`;
  const importExpoModulesSwiftHeaderFileIndex = contents.findIndex((content) =>
    content.includes(importExpoModulesSwiftHeader)
  );
  // If importExpoModulesSwiftHeader do not exist in AppDelegate.mm
  if (!~importExpoModulesSwiftHeaderFileIndex) {
    contents = [importExpoModulesSwiftHeader, ...contents];
  }
  // Step 2 Add method to properly handle openUrl method in AppDelegate.m
  const openUrlMethod =
    '- (BOOL)application:(UIApplication *)application openURL';
  const expoBraintreeOpenUrlLines = [
    '  if ([url.scheme localizedCaseInsensitiveCompare:[BraintreeExpoConfig getPaymentUrlScheme]] == NSOrderedSame) {',
    '    return [BraintreeExpoConfig handleUrl:url];',
    '  }',
  ];
  const openUrlMethodElementIndex = contents.findIndex((content) =>
    content.includes(openUrlMethod)
  );
  const expoBraintreeOpenUrlLineIndex = contents.findIndex((content) =>
    content.includes(expoBraintreeOpenUrlLines?.[0] ?? '')
  );
  // If openUrlMethodElementIndex exist in AppDelegate.mm and expoBraintreeOpenUrlLineIndex do not exist
  if (!~expoBraintreeOpenUrlLineIndex && !!~openUrlMethodElementIndex) {
    contents.splice(
      // We are adding +1 to the index to insert content after '{' block
      openUrlMethodElementIndex + 1,
      0,
      ...expoBraintreeOpenUrlLines
    );
  }
  return contents;
};

/**
 * Mods for AppDelegate.mm
 */

export const withExpoBraintreeAppDelegate: ConfigPlugin<
  ExpoBraintreePluginProps
> = (expoConfig, { xCodeProjectAppName }) => {
  let appDelegateLanguage: AppleLanguage | null = null;
  return withAppDelegate(expoConfig, (config) => {
    appDelegateLanguage = config.modResults.language;
    switch (appDelegateLanguage) {
      case 'objc':
      case 'objcpp':
        const resultObjectiVeC = modifyAppDelegateObjectiveC(
          config,
          xCodeProjectAppName
        );
        config.modResults.contents = resultObjectiVeC.join('\n');
        return config;
      case 'swift':
        const resultSwift = modifyAppDelegateSwift(config);
        config.modResults.contents = resultSwift.join('\n');
        return config;
      default:
        WarningAggregator.addWarningIOS(
          'withExpoBraintree',
          `${appDelegateLanguage} AppDelegate file is not supported yet`
        );
        return config;
    }
  });
};

// Add a new wrapper Swift file to the Xcode project for Swift compatibility.
export const withBraintreeWrapperFile: ConfigPlugin<{
  appDelegateLanguage: AppleLanguage | null;
}> = (config, { appDelegateLanguage }) => {
  const braintreeWrapperObjectiveC = [
    'import Braintree',
    'import Foundation',
    '',
    '@objc public class BraintreeExpoConfig: NSObject {',
    '',
    '@objc(getPaymentUrlScheme)',
    'public static func getPaymentUrlScheme() -> String {',
    '  let bundleIdentifier = Bundle.main.bundleIdentifier ?? ""',
    '  return bundleIdentifier + ".braintree"',
    '}',
    '',
    '@objc(handleUrl:)',
    'public static func handleUrl(url: URL) -> Bool {',
    '  return BTAppContextSwitcher.sharedInstance.handleOpen(url)',
    '}',
    '}',
  ];

  const braintreeWrapperSwift = [
    'import Braintree',
    'import Foundation',
    '',
    'public final class ExpoBraintreeConfig {',
    '',
    ' private init() {}',
    '',
    ' public static var paymentURLScheme: String {',
    '   let bundleIdentifier = Bundle.main.bundleIdentifier ?? ""',
    '   return bundleIdentifier + ".braintree"',
    ' }',
    '',
    ' public static func handleUrl(url: URL) -> Bool {',
    '   return BTAppContextSwitcher.sharedInstance.handleOpen(url)',
    ' }',
    '}',
  ];

  switch (appDelegateLanguage) {
    case 'objc':
    case 'objcpp':
      return IOSConfig.XcodeProjectFile.withBuildSourceFile(config, {
        filePath: 'ExpoBraintreeConfig.swift',
        contents: braintreeWrapperObjectiveC.join('\n'),
      });
    case 'swift':
      return IOSConfig.XcodeProjectFile.withBuildSourceFile(config, {
        filePath: 'ExpoBraintreeConfig.swift',
        contents: braintreeWrapperSwift.join('\n'),
      });
    default:
      WarningAggregator.addWarningIOS(
        'withExpoBraintree',
        `${appDelegateLanguage} AppDelegate file is not supported yet`
      );
      return config;
  }
};
