package com.myidsdk import android.content.Context import com.facebook.react.bridge.ReadableMap import uz.myid.android.sdk.capture.MyIdConfig import uz.myid.android.sdk.capture.model.MyIdCameraResolution import uz.myid.android.sdk.capture.model.MyIdCameraSelector import uz.myid.android.sdk.capture.model.MyIdCameraShape import uz.myid.android.sdk.capture.model.MyIdEntryType import uz.myid.android.sdk.capture.model.MyIdEnvironment import uz.myid.android.sdk.capture.model.MyIdImageFormat import uz.myid.android.sdk.capture.model.MyIdLocale import uz.myid.android.sdk.capture.model.MyIdOrganizationDetails import uz.myid.android.sdk.capture.model.MyIdResidency import uz.myid.android.sdk.capture.model.MyIdScreenOrientation /** * Translates the JS config payload into a [MyIdConfig]. * * A `withX(...)` call only happens when the key is actually present, so an * omitted option keeps the SDK's own default instead of being overwritten with * a value invented here. * * An unrecognised enum string skips its setter rather than throwing — a typo * should not hard-fail a verification the user already started, and skipping is * what actually leaves the SDK default in place. */ internal object MyidSdkConfigMapper { fun build(config: ReadableMap, context: Context): MyIdConfig { val builder = MyIdConfig.Builder(config.requireString("sessionId")) .withClientHash( config.requireString("clientHash"), config.requireString("clientHashId") ) config.optInt("minAge")?.let { builder.withMinAge(it) } config.optDouble("distance")?.let { builder.withDistance(it.toFloat()) } config.optBoolean("withSoundGuides")?.let { builder.withSoundGuides(it) } config.optBoolean("showErrorScreen")?.let { builder.withErrorScreen(it) } config.optString("huaweiAppId")?.let { builder.withHuaweiAppId(it) } // An unrecognised value maps to null and the setter is skipped, so the SDK // keeps its own default. Substituting a guess here would silently change // behaviour on a typo — and the guess is not always right: the SDK defaults // imageFormat to JPEG, not PNG. config.optEnum("environment")?.let { when (it) { "DEBUG" -> MyIdEnvironment.Debug "PRODUCTION" -> MyIdEnvironment.Production else -> null } }?.let(builder::withEnvironment) config.optEnum("entryType")?.let { when (it) { "IDENTIFICATION" -> MyIdEntryType.Identification "VIDEO_IDENTIFICATION" -> MyIdEntryType.VideoIdentification "FACE_DETECTION" -> MyIdEntryType.FaceDetection else -> null } }?.let(builder::withEntryType) config.optEnum("residency")?.let { when (it) { "USER_DEFINED" -> MyIdResidency.UserDefined "RESIDENT" -> MyIdResidency.Resident "NON_RESIDENT" -> MyIdResidency.NonResident else -> null } }?.let(builder::withResidency) config.optEnum("locale")?.let { when (it) { "UZBEK" -> MyIdLocale.Uzbek "UZBEK_CYRILLIC" -> MyIdLocale.UzbekCyrillic "KARAKALPAK" -> MyIdLocale.Karakalpak "TAJIK" -> MyIdLocale.Tajik "ENGLISH" -> MyIdLocale.English "RUSSIAN" -> MyIdLocale.Russian else -> null } }?.let(builder::withLocale) config.optEnum("cameraShape")?.let { when (it) { "CIRCLE" -> MyIdCameraShape.Circle "ELLIPSE" -> MyIdCameraShape.Ellipse else -> null } }?.let(builder::withCameraShape) config.optEnum("cameraSelector")?.let { when (it) { "FRONT" -> MyIdCameraSelector.Front "BACK" -> MyIdCameraSelector.Back else -> null } }?.let(builder::withCameraSelector) config.optEnum("cameraResolution")?.let { when (it) { "LOW" -> MyIdCameraResolution.Low "HIGH" -> MyIdCameraResolution.High else -> null } }?.let(builder::withCameraResolution) config.optEnum("imageFormat")?.let { when (it) { "JPEG" -> MyIdImageFormat.JPEG "PNG" -> MyIdImageFormat.PNG else -> null } }?.let(builder::withImageFormat) config.optEnum("screenOrientation")?.let { when (it) { "PORTRAIT" -> MyIdScreenOrientation.Portrait "LANDSCAPE" -> MyIdScreenOrientation.Landscape "FULL" -> MyIdScreenOrientation.Full else -> null } }?.let(builder::withScreenOrientation) // Only forwarded when the caller actually supplied something. withOrganizationDetails // copies the object verbatim, so an empty one would replace the SDK default // (which carries MyID's call-centre number) with blanks. // // Note the same copy semantics mean supplying only `logo` clears the default // phone number; the SDK exposes no way to read it back. Documented in README. config.optMap("organizationDetails") ?.takeIf { it.hasKey("phone") || it.hasKey("logo") } ?.let { details -> builder.withOrganizationDetails( MyIdOrganizationDetails( phoneNumber = details.optString("phone"), logo = details.optString("logo")?.let { context.drawableIdOrNull(it) } ) ) } return builder.build() } private fun ReadableMap.requireString(key: String): String = optString(key) ?: throw IllegalArgumentException("Missing required config field: $key") private fun ReadableMap.optString(key: String): String? = if (hasKey(key) && !isNull(key)) getString(key) else null /** Enum values travel as strings; normalise case so 'debug' works like 'DEBUG'. */ private fun ReadableMap.optEnum(key: String): String? = optString(key)?.uppercase() private fun ReadableMap.optInt(key: String): Int? = if (hasKey(key) && !isNull(key)) getDouble(key).toInt() else null private fun ReadableMap.optDouble(key: String): Double? = if (hasKey(key) && !isNull(key)) getDouble(key) else null private fun ReadableMap.optBoolean(key: String): Boolean? = if (hasKey(key) && !isNull(key)) getBoolean(key) else null private fun ReadableMap.optMap(key: String): ReadableMap? = if (hasKey(key) && !isNull(key)) getMap(key) else null /** Resolves a drawable by name; a missing asset degrades to no logo. */ private fun Context.drawableIdOrNull(name: String): Int? = resources.getIdentifier(name, "drawable", packageName).takeIf { it != 0 } }