import CoreTelephony
import ABI47_0_0ExpoModulesCore

public class CellularModule: Module {
  public func definition() -> ModuleDefinition {
    Name("ExpoCellular")

    Constants {
      Self.getCurrentCellularInfo()
    }

    AsyncFunction("getCellularGenerationAsync") { () -> Int in
      Self.currentCellularGeneration().rawValue
    }

    AsyncFunction("allowsVoipAsync") { () -> Bool? in
      Self.currentCarrier()?.allowsVOIP
    }

    AsyncFunction("getIsoCountryCodeAsync") { () -> String? in
      Self.currentCarrier()?.isoCountryCode
    }

    AsyncFunction("getCarrierNameAsync") { () -> String? in
      Self.currentCarrier()?.carrierName
    }

    AsyncFunction("getMobileCountryCodeAsync") { () -> String? in
      Self.currentCarrier()?.mobileCountryCode
    }

    AsyncFunction("getMobileNetworkCodeAsync") { () -> String? in
      Self.currentCarrier()?.mobileNetworkCode
    }
  }

  // MARK: - internals

  // Keep this enum in sync with JavaScript
  // Based on the EffectiveConnectionType enum described in the W3C Network Information API spec
  // (https://wicg.github.io/netinfo/).
  enum CellularGeneration: Int {
    case unknown = 0
    case cellular2G = 1
    case cellular3G = 2
    case cellular4G = 3
    case cellular5G = 4
  }

  static func currentCellularGeneration() -> CellularGeneration {
    let radioAccessTechnology = currentRadioAccessTechnology()

    switch radioAccessTechnology {
    case CTRadioAccessTechnologyGPRS,
         CTRadioAccessTechnologyEdge,
         CTRadioAccessTechnologyCDMA1x:
      return .cellular2G
    case CTRadioAccessTechnologyWCDMA,
         CTRadioAccessTechnologyHSDPA,
         CTRadioAccessTechnologyHSUPA,
         CTRadioAccessTechnologyCDMAEVDORev0,
         CTRadioAccessTechnologyCDMAEVDORevA,
         CTRadioAccessTechnologyCDMAEVDORevB,
         CTRadioAccessTechnologyeHRPD:
      return .cellular3G
    case CTRadioAccessTechnologyLTE:
      return .cellular4G
    default:
      if #available(iOS 14.1, *) {
        if radioAccessTechnology == CTRadioAccessTechnologyNRNSA ||
            radioAccessTechnology == CTRadioAccessTechnologyNR {
          return .cellular5G
        }
      }
      return .unknown
    }
  }

  static func getCurrentCellularInfo() -> [String: Any?] {
    let carrier = Self.currentCarrier()
    let generation = Self.currentCellularGeneration()

    return [
      "allowsVoip": carrier?.allowsVOIP,
      "carrier": carrier?.carrierName,
      "isoCountryCode": carrier?.isoCountryCode,
      "mobileCountryCode": carrier?.mobileCountryCode,
      "mobileNetworkCode": carrier?.mobileNetworkCode,
      "generation": generation.rawValue
    ]
  }

  static func currentRadioAccessTechnology() -> String? {
    let netinfo = CTTelephonyNetworkInfo()

    if #available(iOS 12.0, *) {
      return netinfo.serviceCurrentRadioAccessTechnology?.values.first
    } else {
      return netinfo.currentRadioAccessTechnology
    }
  }

  static func currentCarrier() -> CTCarrier? {
    let netinfo = CTTelephonyNetworkInfo()

    if #available(iOS 12.0, *), let providers = netinfo.serviceSubscriberCellularProviders {
      for carrier in providers.values where carrier.carrierName != nil {
        return carrier
      }
      return providers.values.first
    }
    return netinfo.subscriberCellularProvider
  }
}
