/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.blob

import android.net.Uri
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.ReactTestHelper
import java.nio.ByteBuffer
import java.util.UUID
import kotlin.random.Random
import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.api.mockito.PowerMockito.mockStatic
import org.powermock.api.mockito.PowerMockito.`when` as whenever
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.rule.PowerMockRule
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

@PrepareForTest(Arguments::class)
@RunWith(RobolectricTestRunner::class)
@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*")
@Config(manifest = Config.NONE)
class BlobModuleTest {
  private lateinit var bytes: ByteArray
  private lateinit var blobId: String
  private lateinit var blobModule: BlobModule

  @get:Rule var rule = PowerMockRule()

  @Before
  fun prepareModules() {
    mockStatic(Arguments::class.java)
    whenever(Arguments.createMap()).thenAnswer { JavaOnlyMap() }

    bytes = ByteArray(120)
    Random.Default.nextBytes(bytes)

    blobModule = BlobModule(ReactTestHelper.createCatalystContextForTest())
    blobId = blobModule.store(bytes)
  }

  @After
  fun cleanUp() {
    blobModule.remove(blobId)
  }

  @Test
  fun testResolve() {
    assertArrayEquals(bytes, blobModule.resolve(blobId, 0, bytes.size))
    val expectedRange = bytes.copyOfRange(30, bytes.size)
    assertArrayEquals(expectedRange, blobModule.resolve(blobId, 30, bytes.size - 30))
  }

  @Test
  fun testResolveUri() {
    val uri =
        Uri.Builder()
            .appendPath(blobId)
            .appendQueryParameter("offset", "0")
            .appendQueryParameter("size", bytes.size.toString())
            .build()

    assertArrayEquals(bytes, blobModule.resolve(uri))
  }

  @Test
  fun testResolveMap() {
    val blob =
        JavaOnlyMap().apply {
          putString("blobId", blobId)
          putInt("offset", 0)
          putInt("size", bytes.size)
        }

    assertArrayEquals(bytes, blobModule.resolve(blob))
  }

  @Test
  fun testRemove() {
    assertNotNull(blobModule.resolve(blobId, 0, bytes.size))

    blobModule.remove(blobId)

    assertNull(blobModule.resolve(blobId, 0, bytes.size))
  }

  @Test
  fun testCreateFromParts() {
    val id = UUID.randomUUID().toString()

    val blobData =
        JavaOnlyMap().apply {
          putString("blobId", blobId)
          putInt("offset", 0)
          putInt("size", bytes.size)
        }
    val blob =
        JavaOnlyMap().apply {
          putMap("data", blobData)
          putString("type", "blob")
        }

    val stringData = "i \u2665 dogs"
    val stringBytes = stringData.encodeToByteArray()
    val string =
        JavaOnlyMap().apply {
          putString("data", stringData)
          putString("type", "string")
        }

    val parts =
        JavaOnlyArray().apply {
          pushMap(blob)
          pushMap(string)
        }

    blobModule.createFromParts(parts, id)

    val resultSize = bytes.size + stringBytes.size

    val result = blobModule.resolve(id, 0, resultSize)

    val buffer =
        ByteBuffer.allocate(resultSize).apply {
          put(bytes)
          put(stringBytes)
        }

    assertArrayEquals(result, buffer.array())
  }

  @Test
  fun testRelease() {
    assertNotNull(blobModule.resolve(blobId, 0, bytes.size))

    blobModule.release(blobId)

    assertNull(blobModule.resolve(blobId, 0, bytes.size))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.camera

import android.util.Base64
import android.util.Base64InputStream
import com.facebook.react.bridge.ReactApplicationContext
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.util.*
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.*
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*")
class ImageStoreManagerTest {

  private lateinit var reactApplicationContext: ReactApplicationContext
  private lateinit var imageStoreManager: ImageStoreManager

  @Before
  fun setUp() {
    reactApplicationContext = mock(ReactApplicationContext::class.java)
    imageStoreManager = ImageStoreManager(reactApplicationContext)
  }

  @Test
  fun itDoesNotAddLineBreaks_whenBasicStringProvided() {
    val exampleString = "test".toByteArray()
    Assert.assertEquals("dGVzdA==", invokeConversion(ByteArrayInputStream(exampleString)))
  }

  @Test
  fun itDoesNotAddLineBreaks_whenEmptyStringProvided() {
    val exampleString = "".toByteArray()
    Assert.assertEquals("", invokeConversion(ByteArrayInputStream(exampleString)))
  }

  @Test
  fun itDoesNotAddLineBreaks_whenStringWithSpecialCharsProvided() {
    val exampleString = "sdfsdf\nasdfsdfsdfsd\r\nasdas".toByteArray()
    val inputStream = ByteArrayInputStream(exampleString)
    val converted = invokeConversion(inputStream)
    Assert.assertFalse(converted.contains("\n"))
  }

  /**
   * This test tries to test the conversion when going beyond the current buffer size (8192 bytes)
   */
  @Test
  fun itDoesNotAddLineBreaks_whenStringBiggerThanBuffer() {
    val inputStream = ByteArrayInputStream(generateRandomByteString(10000))
    val converted = invokeConversion(inputStream)
    Assert.assertFalse(converted.contains("\n"))
  }

  /** Just to test if using the ByteArrayInputStream isn't missing something */
  @Test
  fun itDoesNotAddLineBreaks_whenBase64InputStream() {
    val exampleString = "dGVzdA==".toByteArray()
    val inputStream = Base64InputStream(ByteArrayInputStream(exampleString), Base64.NO_WRAP)
    Assert.assertEquals("dGVzdA==", invokeConversion(inputStream))
  }

  private fun invokeConversion(inputStream: InputStream): String {
    return ImageStoreManager(reactApplicationContext)
        .convertInputStreamToBase64OutputStream(inputStream)
  }

  private fun generateRandomByteString(length: Int): ByteArray {
    val r = Random()
    val sb = StringBuilder()
    repeat(length) {
      val c = r.nextInt(Char.MAX_VALUE.code).toChar()
      sb.append(c)
    }
    return sb.toString().toByteArray()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.clipboard

import android.annotation.SuppressLint
import android.content.ClipboardManager
import android.content.Context
import com.facebook.react.bridge.ReactApplicationContext
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment

@Suppress("DEPRECATION")
@SuppressLint("ClipboardManager", "DeprecatedClass")
@RunWith(RobolectricTestRunner::class)
class ClipboardModuleTest {
  private lateinit var clipboardModule: ClipboardModule
  private lateinit var clipboardManager: ClipboardManager

  @Before
  fun setUp() {
    clipboardModule = ClipboardModule(ReactApplicationContext(RuntimeEnvironment.application))
    clipboardManager =
        RuntimeEnvironment.application.getSystemService(Context.CLIPBOARD_SERVICE)
            as ClipboardManager
  }

  @Test
  fun testSetString() {
    clipboardModule.setString(TEST_CONTENT)
    assertTrue(clipboardManager.text == TEST_CONTENT)
    clipboardModule.setString(null)
    assertFalse(clipboardManager.hasText())
    clipboardModule.setString("")
    assertFalse(clipboardManager.hasText())
    clipboardModule.setString(" ")
    assertTrue(clipboardManager.hasText())
  }

  companion object {
    private const val TEST_CONTENT = "test"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.deviceinfo

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReactTestHelper
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.DisplayMetricsHolder
import junit.framework.TestCase
import org.assertj.core.api.Assertions
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.*
import org.powermock.api.mockito.PowerMockito
import org.powermock.api.mockito.PowerMockito.`when` as whenever
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.rule.PowerMockRule
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment

@RunWith(RobolectricTestRunner::class)
@PrepareForTest(Arguments::class, DisplayMetricsHolder::class)
@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*")
class DeviceInfoModuleTest : TestCase() {
  @get:Rule var rule = PowerMockRule()

  private lateinit var deviceInfoModule: DeviceInfoModule
  private lateinit var fakePortraitDisplayMetrics: WritableMap
  private lateinit var fakeLandscapeDisplayMetrics: WritableMap
  private lateinit var reactContext: ReactApplicationContext

  @Before
  public override fun setUp() {
    initTestData()
    PowerMockito.mockStatic(DisplayMetricsHolder::class.java)
    reactContext = spy(ReactApplicationContext(RuntimeEnvironment.application))
    val catalystInstanceMock = ReactTestHelper.createMockCatalystInstance()
    reactContext.initializeWithInstance(catalystInstanceMock)
    deviceInfoModule = DeviceInfoModule(reactContext)
  }

  @After
  fun teardown() {
    DisplayMetricsHolder.setWindowDisplayMetrics(null)
    DisplayMetricsHolder.setScreenDisplayMetrics(null)
  }

  @Test
  fun test_itDoesNotEmitAnEvent_whenDisplayMetricsNotChanged() {
    givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics)
    deviceInfoModule.typedExportedConstants
    deviceInfoModule.emitUpdateDimensionsEvent()
    verify(reactContext, times(0))
        ?.emitDeviceEvent(ArgumentMatchers.anyString(), ArgumentMatchers.any())
  }

  @Test
  fun test_itEmitsOneEvent_whenDisplayMetricsChangedOnce() {
    givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics)
    deviceInfoModule.typedExportedConstants
    givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics)
    deviceInfoModule.emitUpdateDimensionsEvent()
    verifyUpdateDimensionsEventsEmitted(reactContext, fakeLandscapeDisplayMetrics)
  }

  @Test
  fun test_itEmitsJustOneEvent_whenUpdateRequestedMultipleTimes() {
    givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics)
    deviceInfoModule.typedExportedConstants
    givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics)
    deviceInfoModule.emitUpdateDimensionsEvent()
    deviceInfoModule.emitUpdateDimensionsEvent()
    verifyUpdateDimensionsEventsEmitted(reactContext, fakeLandscapeDisplayMetrics)
  }

  @Test
  fun test_itEmitsMultipleEvents_whenDisplayMetricsChangedBetweenUpdates() {
    givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics)
    deviceInfoModule.typedExportedConstants
    deviceInfoModule.emitUpdateDimensionsEvent()
    givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics)
    deviceInfoModule.emitUpdateDimensionsEvent()
    givenDisplayMetricsHolderContains(fakePortraitDisplayMetrics)
    deviceInfoModule.emitUpdateDimensionsEvent()
    givenDisplayMetricsHolderContains(fakeLandscapeDisplayMetrics)
    deviceInfoModule.emitUpdateDimensionsEvent()
    verifyUpdateDimensionsEventsEmitted(
        reactContext,
        fakeLandscapeDisplayMetrics,
        fakePortraitDisplayMetrics,
        fakeLandscapeDisplayMetrics)
  }

  private fun initTestData() {
    PowerMockito.mockStatic(Arguments::class.java)
    whenever(Arguments.createMap()).thenAnswer { JavaOnlyMap() }
    fakePortraitDisplayMetrics = Arguments.createMap()
    fakePortraitDisplayMetrics.putInt("width", 100)
    fakePortraitDisplayMetrics.putInt("height", 200)
    fakeLandscapeDisplayMetrics = Arguments.createMap()
    fakeLandscapeDisplayMetrics.putInt("width", 200)
    fakeLandscapeDisplayMetrics.putInt("height", 100)
  }

  companion object {
    private fun givenDisplayMetricsHolderContains(fakeDisplayMetrics: WritableMap?) {
      whenever(DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0)).thenAnswer {
        fakeDisplayMetrics
      }
    }

    private fun verifyUpdateDimensionsEventsEmitted(
        context: ReactContext?,
        vararg expectedEvents: WritableMap
    ) {
      val expectedEventList = listOf(*expectedEvents)
      val captor = ArgumentCaptor.forClass(WritableMap::class.java)
      verify(context, times(expectedEventList.size))
          ?.emitDeviceEvent(ArgumentMatchers.eq("didUpdateDimensions"), captor.capture())
      val actualEvents = captor.allValues
      Assertions.assertThat(actualEvents).isEqualTo(expectedEventList)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.dialog

import android.app.AlertDialog
import android.content.DialogInterface
import android.os.Looper.getMainLooper
import androidx.fragment.app.FragmentActivity
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.ReactApplicationContext
import org.junit.*
import org.junit.Assert.*
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.powermock.api.mockito.PowerMockito.`when` as whenever
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.android.controller.ActivityController

@RunWith(RobolectricTestRunner::class)
class DialogModuleTest {

  private lateinit var activityController: ActivityController<FragmentActivity>
  private lateinit var activity: FragmentActivity
  private lateinit var dialogModule: DialogModule

  class SimpleCallback : Callback {
    var args: Array<out Any?>? = null
      private set

    var calls: Int = 0
      private set

    override fun invoke(vararg args: Any?) {
      this.calls++
      this.args = args
    }
  }

  @Before
  fun setUp() {
    activityController = Robolectric.buildActivity(FragmentActivity::class.java)
    activity = activityController.create().start().resume().get()

    val context: ReactApplicationContext = mock(ReactApplicationContext::class.java)
    whenever(context.hasActiveReactInstance()).thenReturn(true)
    whenever(context.currentActivity).thenReturn(activity)

    dialogModule = DialogModule(context)
    dialogModule.onHostResume()
  }

  @After
  fun tearDown() {
    activityController.pause().stop().destroy()
  }

  @Test
  fun testAllOptions() {
    val options =
        JavaOnlyMap().apply {
          putString("title", "Title")
          putString("message", "Message")
          putString("buttonPositive", "OK")
          putString("buttonNegative", "Cancel")
          putString("buttonNeutral", "Later")
          putBoolean("cancelable", false)
        }

    dialogModule.showAlert(options, null, null)
    shadowOf(getMainLooper()).idle()

    val fragment = getFragment()

    assertNotNull("Fragment was not displayed", fragment)
    assertFalse(fragment!!.isCancelable)

    val dialog = fragment.dialog as AlertDialog
    assertEquals("OK", dialog.getButton(DialogInterface.BUTTON_POSITIVE).text.toString())
    assertEquals("Cancel", dialog.getButton(DialogInterface.BUTTON_NEGATIVE).text.toString())
    assertEquals("Later", dialog.getButton(DialogInterface.BUTTON_NEUTRAL).text.toString())
  }

  @Test
  fun testCallbackPositive() {
    val options = JavaOnlyMap().apply { putString("buttonPositive", "OK") }

    val actionCallback = SimpleCallback()
    dialogModule.showAlert(options, null, actionCallback)
    shadowOf(getMainLooper()).idle()

    val dialog = getFragment()!!.dialog as AlertDialog
    dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
    shadowOf(getMainLooper()).idle()

    assertEquals(1, actionCallback.calls)
    assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.args!![0])
    assertEquals(DialogInterface.BUTTON_POSITIVE, actionCallback.args!![1])
  }

  @Test
  fun testCallbackNegative() {
    val options = JavaOnlyMap().apply { putString("buttonNegative", "Cancel") }

    val actionCallback = SimpleCallback()
    dialogModule.showAlert(options, null, actionCallback)
    shadowOf(getMainLooper()).idle()

    val dialog = getFragment()!!.dialog as AlertDialog
    dialog.getButton(DialogInterface.BUTTON_NEGATIVE).performClick()
    shadowOf(getMainLooper()).idle()

    assertEquals(1, actionCallback.calls)
    assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.args!![0])
    assertEquals(DialogInterface.BUTTON_NEGATIVE, actionCallback.args!![1])
  }

  @Test
  fun testCallbackNeutral() {
    val options = JavaOnlyMap().apply { putString("buttonNeutral", "Later") }

    val actionCallback = SimpleCallback()
    dialogModule.showAlert(options, null, actionCallback)
    shadowOf(getMainLooper()).idle()

    val dialog = getFragment()!!.dialog as AlertDialog
    dialog.getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
    shadowOf(getMainLooper()).idle()

    assertEquals(1, actionCallback.calls)
    assertEquals(DialogModule.ACTION_BUTTON_CLICKED, actionCallback.args!![0])
    assertEquals(DialogInterface.BUTTON_NEUTRAL, actionCallback.args!![1])
  }

  @Test
  fun testCallbackDismiss() {
    val options = JavaOnlyMap()

    val actionCallback = SimpleCallback()
    dialogModule.showAlert(options, null, actionCallback)
    shadowOf(getMainLooper()).idle()

    getFragment()!!.dialog!!.dismiss()
    shadowOf(getMainLooper()).idle()

    assertEquals(1, actionCallback.calls)
    assertEquals(DialogModule.ACTION_DISMISSED, actionCallback.args!![0])
  }

  private fun getFragment(): AlertFragment? {
    return activity.supportFragmentManager.findFragmentByTag(DialogModule.FRAGMENT_TAG)
        as? AlertFragment
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.network

import org.junit.Assert.assertEquals
import org.junit.Test

class HeaderUtilTest {
  companion object {
    const val TABULATION_TEST = "\teyJhbGciOiJS\t"
    const val TABULATION_STRIP_EXPECTED = "eyJhbGciOiJS"
    const val NUMBERS_TEST = "0123456789"
    const val SPECIALS_TEST = "!@#$%^&*()-=_+{}[]\\|;:'\",.<>/?"
    const val ALPHABET_TEST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWHYZ"
    const val VALUE_BANNED_SYMBOLS_TEST = "ï¿½ï¿½ï¿½nameï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½\u007f\u001f"
    const val NAME_BANNED_SYMBOLS_TEST = "ï¿½ï¿½ï¿½nameï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½ï¿½\u007f\u0020\u001f"
    const val BANNED_TEST_EXPECTED = "name"
  }

  @Test
  fun nameStripKeepsLetters() {
    assertEquals(ALPHABET_TEST, HeaderUtil.stripHeaderName(ALPHABET_TEST))
  }

  @Test
  fun valueStripKeepsLetters() {
    assertEquals(ALPHABET_TEST, HeaderUtil.stripHeaderValue(ALPHABET_TEST))
  }

  @Test
  fun nameStripKeepsNumbers() {
    assertEquals(NUMBERS_TEST, HeaderUtil.stripHeaderName(NUMBERS_TEST))
  }

  @Test
  fun valueStripKeepsNumbers() {
    assertEquals(NUMBERS_TEST, HeaderUtil.stripHeaderValue(NUMBERS_TEST))
  }

  @Test
  fun valueStripKeepsSpecials() {
    assertEquals(SPECIALS_TEST, HeaderUtil.stripHeaderValue(SPECIALS_TEST))
  }

  @Test
  fun nameStripKeepsSpecials() {
    assertEquals(SPECIALS_TEST, HeaderUtil.stripHeaderName(SPECIALS_TEST))
  }

  @Test
  fun valueStripKeepsTabs() {
    assertEquals(TABULATION_TEST, HeaderUtil.stripHeaderValue(TABULATION_TEST))
  }

  @Test
  fun nameStripDeletesTabs() {
    assertEquals(TABULATION_STRIP_EXPECTED, HeaderUtil.stripHeaderName(TABULATION_TEST))
  }

  @Test
  fun valueStripRemovesExtraSymbols() {
    assertEquals(BANNED_TEST_EXPECTED, HeaderUtil.stripHeaderValue(VALUE_BANNED_SYMBOLS_TEST))
  }

  @Test
  fun nameStripRemovesExtraSymbols() {
    assertEquals(BANNED_TEST_EXPECTED, HeaderUtil.stripHeaderName(NAME_BANNED_SYMBOLS_TEST))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.network

import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when` as whenever
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.robolectric.RobolectricTestRunner

/** Tests for {@link NetworkingModule}. */
@RunWith(RobolectricTestRunner::class)
@PrepareForTest(ReactCookieJarContainer::class)
@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*")
class ReactCookieJarContainerTest {
  private val httpUrl: HttpUrl = HttpUrl.Builder().host("example.com").scheme("http").build()

  @Test
  fun testMissingJar() {
    val jarContainer: ReactCookieJarContainer = mock(ReactCookieJarContainer::class.java)
    assertThat(jarContainer.loadForRequest(httpUrl).size).isEqualTo(0)
  }

  @Test
  fun testEmptyCookies() {
    val jarContainer: ReactCookieJarContainer = mock(ReactCookieJarContainer::class.java)
    val cookies: List<Cookie> = emptyList()
    whenever(jarContainer.loadForRequest(any(HttpUrl::class.java))).thenReturn(cookies)
    assertThat(jarContainer.loadForRequest(httpUrl).size).isEqualTo(0)
  }

  @Test
  fun testValidCookies() {
    val jarContainer = ReactCookieJarContainer()
    val cookieJar: CookieJar = mock(CookieJar::class.java)
    jarContainer.setCookieJar(cookieJar)
    val cookies: MutableList<Cookie> = mutableListOf()
    cookies.add(Cookie.Builder().name("valid").value("valid value").domain("domain").build())
    whenever(cookieJar.loadForRequest(httpUrl)).thenReturn(cookies)
    assertThat(jarContainer.loadForRequest(httpUrl).size).isEqualTo(1)
  }

  @Test
  fun testInvalidCookies() {
    val jarContainer = ReactCookieJarContainer()
    val cookieJar: CookieJar = mock(CookieJar::class.java)
    jarContainer.setCookieJar(cookieJar)
    val cookies: MutableList<Cookie> = mutableListOf()
    cookies.add(Cookie.Builder().name("valid").value("Ã®nvÃ¡lÃd vÃ¤lÅ«Ä—").domain("domain").build())
    whenever(cookieJar.loadForRequest(httpUrl)).thenReturn(cookies)
    assertThat(jarContainer.loadForRequest(httpUrl).size).isEqualTo(0)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.share

import android.app.Activity
import android.content.Intent
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactTestHelper
import com.facebook.react.bridge.WritableMap
import com.facebook.testutils.shadows.ShadowArguments
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config

@Config(shadows = [ShadowArguments::class])
@RunWith(RobolectricTestRunner::class)
class ShareModuleTest {

  private lateinit var activity: Activity
  private lateinit var shareModule: ShareModule

  @Before
  fun prepareModules() {
    activity = Robolectric.setupActivity(Activity::class.java)

    val applicationContext = ReactTestHelper.createCatalystContextForTest()
    applicationContext.onNewIntent(activity, Intent())
    shareModule = ShareModule(applicationContext)
  }

  @Test
  fun testShareDialog() {
    val title = "Title"
    val message = "Message"
    val dialogTitle = "Dialog Title"

    val content = JavaOnlyMap()
    content.putString("title", title)
    content.putString("message", message)

    val promise = SimplePromise()

    shareModule.share(content, dialogTitle, promise)

    val chooserIntent = shadowOf(RuntimeEnvironment.application).nextStartedActivity
    assertNotNull("Dialog was not displayed", chooserIntent)
    assertEquals(Intent.ACTION_CHOOSER, chooserIntent.action)
    assertEquals(dialogTitle, chooserIntent.extras?.get(Intent.EXTRA_TITLE))

    val contentIntent = chooserIntent.extras?.get(Intent.EXTRA_INTENT) as Intent?
    assertNotNull("Intent was not built correctly", contentIntent)
    assertEquals(Intent.ACTION_SEND, contentIntent?.action)
    assertEquals(title, contentIntent?.extras?.get(Intent.EXTRA_SUBJECT))
    assertEquals(message, contentIntent?.extras?.get(Intent.EXTRA_TEXT))

    assertEquals(1, promise.resolved)
  }

  @Test
  fun testInvalidContent() {
    val dialogTitle = "Dialog Title"

    val promise = SimplePromise()

    shareModule.share(null, dialogTitle, promise)

    assertEquals(1, promise.rejected)
    assertEquals(ShareModule.ERROR_INVALID_CONTENT, promise.errorCode)
  }

  internal class SimplePromise : Promise {
    companion object {
      private const val ERROR_DEFAULT_CODE = "EUNSPECIFIED"
      private const val ERROR_DEFAULT_MESSAGE = "Error not specified."
    }

    var resolved = 0
      private set

    var rejected = 0
      private set

    var value: Any? = null
      private set

    var errorCode: String? = null
      private set

    var errorMessage: String? = null
      private set

    override fun resolve(value: Any?) {
      resolved++
      this.value = value
    }

    override fun reject(code: String?, message: String?) {
      reject(code, message, null, null)
    }

    override fun reject(code: String?, throwable: Throwable?) {
      reject(code, null, throwable, null)
    }

    override fun reject(code: String?, message: String?, throwable: Throwable?) {
      reject(code, message, throwable, null)
    }

    override fun reject(throwable: Throwable?) {
      reject(null, null, throwable, null)
    }

    override fun reject(throwable: Throwable?, userInfo: WritableMap?) {
      reject(null, null, throwable, userInfo)
    }

    override fun reject(code: String?, userInfo: WritableMap) {
      reject(code, null, null, userInfo)
    }

    override fun reject(code: String?, throwable: Throwable?, userInfo: WritableMap?) {
      reject(code, null, throwable, userInfo)
    }

    override fun reject(code: String?, message: String?, userInfo: WritableMap) {
      reject(code, message, null, userInfo)
    }

    override fun reject(
        code: String?,
        message: String?,
        throwable: Throwable?,
        userInfo: WritableMap?
    ) {
      rejected++

      errorCode = code ?: ERROR_DEFAULT_CODE
      errorMessage = message ?: throwable?.message ?: ERROR_DEFAULT_MESSAGE
    }

    @Deprecated("Method deprecated")
    override fun reject(message: String?) {
      reject(null, message, null, null)
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.modules.timing

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.CatalystInstance
import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.common.SystemClock
import com.facebook.react.devsupport.interfaces.DevSupportManager
import com.facebook.react.modules.core.ChoreographerCompat.FrameCallback
import com.facebook.react.modules.core.JSTimers
import com.facebook.react.modules.core.ReactChoreographer
import com.facebook.react.modules.core.ReactChoreographer.CallbackType
import com.facebook.react.modules.core.TimingModule
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PowerMockIgnore
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.rule.PowerMockRule
import org.robolectric.RobolectricTestRunner

@PrepareForTest(Arguments::class, SystemClock::class, ReactChoreographer::class)
@PowerMockIgnore("org.mockito.*", "org.robolectric.*", "androidx.*", "android.*")
@RunWith(RobolectricTestRunner::class)
class TimingModuleTest {
  companion object {
    const val FRAME_TIME_NS = 17 * 1000 * 1000
  }

  private lateinit var timingModule: TimingModule
  private lateinit var reactChoreographerMock: ReactChoreographer
  private lateinit var postFrameCallbackHandler: PostFrameCallbackHandler
  private lateinit var idlePostFrameCallbackHandler: PostFrameCallbackHandler
  private var currentTimeNs = 0L
  private lateinit var jSTimersMock: JSTimers

  @get:Rule val powerMockRule = PowerMockRule()

  @Before
  fun prepareModules() {
    PowerMockito.mockStatic(Arguments::class.java)
    whenever(Arguments.createArray()).thenAnswer {
      return@thenAnswer JavaOnlyArray()
    }

    PowerMockito.mockStatic(SystemClock::class.java)
    whenever(SystemClock.uptimeMillis()).thenAnswer {
      return@thenAnswer currentTimeNs / 1000000
    }
    whenever(SystemClock.currentTimeMillis()).thenAnswer {
      return@thenAnswer currentTimeNs / 1000000
    }
    whenever(SystemClock.nanoTime()).thenAnswer {
      return@thenAnswer currentTimeNs
    }

    reactChoreographerMock = mock(ReactChoreographer::class.java)
    PowerMockito.mockStatic(ReactChoreographer::class.java)
    whenever(ReactChoreographer.getInstance()).thenAnswer { reactChoreographerMock }

    val reactInstance = mock(CatalystInstance::class.java)
    val reactContext = mock(ReactApplicationContext::class.java)
    whenever(reactContext.catalystInstance).thenReturn(reactInstance)
    whenever(reactContext.hasActiveReactInstance()).thenReturn(true)

    postFrameCallbackHandler = PostFrameCallbackHandler()
    idlePostFrameCallbackHandler = PostFrameCallbackHandler()

    whenever(
            reactChoreographerMock.postFrameCallback(
                eq(CallbackType.TIMERS_EVENTS), any(FrameCallback::class.java)))
        .thenAnswer {
          return@thenAnswer postFrameCallbackHandler.answer(it)
        }

    whenever(
            reactChoreographerMock.postFrameCallback(
                eq(CallbackType.IDLE_EVENT), any(FrameCallback::class.java)))
        .thenAnswer {
          return@thenAnswer idlePostFrameCallbackHandler.answer(it)
        }

    timingModule = TimingModule(reactContext, mock(DevSupportManager::class.java))
    jSTimersMock = mock(JSTimers::class.java)
    whenever(reactContext.getJSModule(JSTimers::class.java)).thenReturn(jSTimersMock)
    whenever(reactContext.runOnJSQueueThread(any(Runnable::class.java))).thenAnswer { invocation ->
      (invocation.arguments[0] as Runnable).run()
      return@thenAnswer true
    }

    timingModule.initialize()
  }

  private fun stepChoreographerFrame() {
    val callback = postFrameCallbackHandler.getAndResetFrameCallback()
    val idleCallback = idlePostFrameCallbackHandler.getAndResetFrameCallback()
    currentTimeNs += FRAME_TIME_NS
    whenever(SystemClock.uptimeMillis()).thenAnswer {
      return@thenAnswer currentTimeNs / 1000000
    }
    callback?.doFrame(currentTimeNs)
    idleCallback?.doFrame(currentTimeNs)
  }

  @Test
  fun testSimpleTimer() {
    timingModule.onHostResume()
    timingModule.createTimer(1.0, 1.0, 0.0, false)
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(1.0))
    reset(jSTimersMock)
    stepChoreographerFrame()
    verifyNoMoreInteractions(jSTimersMock)
  }

  @Test
  fun testSimpleRecurringTimer() {
    timingModule.createTimer(100.0, 1.0, 0.0, true)
    timingModule.onHostResume()
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0))
    reset(jSTimersMock)
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0))
  }

  @Test
  fun testCancelRecurringTimer() {
    timingModule.onHostResume()
    timingModule.createTimer(105.0, 1.0, 0.0, true)
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(105.0))
    reset(jSTimersMock)
    timingModule.deleteTimer(105.0)
    stepChoreographerFrame()
    verifyNoMoreInteractions(jSTimersMock)
  }

  @Test
  fun testPausingAndResuming() {
    timingModule.onHostResume()
    timingModule.createTimer(41.0, 1.0, 0.0, true)
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0))
    reset(jSTimersMock)
    timingModule.onHostPause()
    stepChoreographerFrame()
    verifyNoMoreInteractions(jSTimersMock)
    reset(jSTimersMock)
    timingModule.onHostResume()
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0))
  }

  @Test
  fun testHeadlessJsTaskInBackground() {
    timingModule.onHostPause()
    timingModule.onHeadlessJsTaskStart(42)
    timingModule.createTimer(41.0, 1.0, 0.0, true)
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0))
    reset(jSTimersMock)
    timingModule.onHeadlessJsTaskFinish(42)
    stepChoreographerFrame()
    verifyNoMoreInteractions(jSTimersMock)
  }

  @Test
  fun testHeadlessJsTaskInForeground() {
    timingModule.onHostResume()
    timingModule.onHeadlessJsTaskStart(42)
    timingModule.createTimer(41.0, 1.0, 0.0, true)
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0))
    reset(jSTimersMock)
    timingModule.onHeadlessJsTaskFinish(42)
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0))
    reset(jSTimersMock)
    timingModule.onHostPause()
    verifyNoMoreInteractions(jSTimersMock)
  }

  @Test
  fun testHeadlessJsTaskIntertwine() {
    timingModule.onHostResume()
    timingModule.onHeadlessJsTaskStart(42)
    timingModule.createTimer(41.0, 1.0, 0.0, true)
    timingModule.onHostPause()
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0))
    reset(jSTimersMock)
    timingModule.onHostResume()
    timingModule.onHeadlessJsTaskFinish(42)
    stepChoreographerFrame()
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(41.0))
    reset(jSTimersMock)
    timingModule.onHostPause()
    stepChoreographerFrame()
    verifyNoMoreInteractions(jSTimersMock)
  }

  @Test
  fun testSetTimeoutZero() {
    timingModule.createTimer(100.0, 0.0, 0.0, false)
    verify(jSTimersMock).callTimers(JavaOnlyArray.of(100.0))
  }

  @Test
  fun testActiveTimersInRange() {
    timingModule.onHostResume()
    assertThat(timingModule.hasActiveTimersInRange(100)).isFalse
    timingModule.createTimer(41.0, 1.0, 0.0, true)
    assertThat(timingModule.hasActiveTimersInRange(100)).isFalse // Repeating
    timingModule.createTimer(42.0, 150.0, 0.0, false)
    assertThat(timingModule.hasActiveTimersInRange(100)).isFalse // Out of range
    assertThat(timingModule.hasActiveTimersInRange(200)).isTrue // In range
  }

  @Test
  fun testIdleCallback() {
    timingModule.setSendIdleEvents(true)
    timingModule.onHostResume()
    stepChoreographerFrame()
    verify(jSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis().toDouble())
  }

  private class PostFrameCallbackHandler : Answer<Unit> {

    private var frameCallback: FrameCallback? = null

    override fun answer(invocation: InvocationOnMock) {
      invocation.arguments[1]?.let { frameCallback = it as FrameCallback }
    }

    fun getAndResetFrameCallback(): FrameCallback? {
      val callback = frameCallback
      frameCallback = null
      return callback
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.packagerconnection

import com.facebook.react.packagerconnection.ReconnectingWebSocket.ConnectionCallback
import java.io.IOException
import okio.ByteString
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
import org.mockito.Mockito.*
import org.mockito.Mockito.`when` as whenever
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class JSPackagerClientTest {

  private lateinit var settings: PackagerConnectionSettings

  @Before
  fun setup() {
    settings = mock(PackagerConnectionSettings::class.java)
    whenever(settings.debugServerHost).thenReturn("ws://not_needed")
    whenever(settings.packageName).thenReturn("my_test_package")
  }

  @Test
  @Throws(IOException::class)
  fun test_onMessage_ShouldTriggerNotification() {
    val handler = mock(RequestHandler::class.java)
    val client = getClient(createRequestHandler("methodValue", handler))

    client.onMessage("""{"version": 2, "method": "methodValue", "params": "paramsValue"}""")
    verify(handler).onNotification(eq("paramsValue"))
    verify(handler, never()).onRequest(any(), any(Responder::class.java))
  }

  @Test
  @Throws(IOException::class)
  fun test_onMessage_ShouldTriggerRequest() {
    val handler = mock(RequestHandler::class.java)
    val client = getClient(createRequestHandler("methodValue", handler))

    client.onMessage(
        """{"version": 2, "id": "idValue", "method": "methodValue", "params": "paramsValue"}""")
    verify(handler, never()).onNotification(any())
    verify(handler).onRequest(eq("paramsValue"), any(Responder::class.java))
  }

  @Test
  @Throws(IOException::class)
  fun test_onMessage_WithoutParams_ShouldTriggerNotification() {
    val handler = mock(RequestHandler::class.java)
    val client = getClient(createRequestHandler("methodValue", handler))

    client.onMessage("""{"version": 2, "method": "methodValue"}""")
    verify(handler).onNotification(ArgumentMatchers.isNull())
    verify(handler, never()).onRequest(any(), any(Responder::class.java))
  }

  @Test
  @Throws(IOException::class)
  fun test_onMessage_WithInvalidContentType_ShouldNotTriggerCallback() {
    val handler = mock(RequestHandler::class.java)
    val client = getClient(createRequestHandler("methodValue", handler))

    client.onMessage(encodeUtf8("""{"version": 2, "method": "methodValue"}"""))
    verify(handler, never()).onNotification(any())
    verify(handler, never()).onRequest(any(), any(Responder::class.java))
  }

  @Test
  @Throws(IOException::class)
  fun test_onMessage_WithoutMethod_ShouldNotTriggerCallback() {
    val handler = mock(RequestHandler::class.java)
    val client = getClient(createRequestHandler("methodValue", handler))

    client.onMessage("""{"version": 2}""")
    verify(handler, never()).onNotification(any())
    verify(handler, never()).onRequest(any(), any(Responder::class.java))
  }

  @Test
  @Throws(IOException::class)
  fun test_onMessage_With_Null_Action_ShouldNotTriggerCallback() {
    val handler = mock(RequestHandler::class.java)
    val client = getClient(createRequestHandler("methodValue", handler))

    client.onMessage("""{"version": 2, "method": null}""")
    verify(handler, never()).onNotification(any())
    verify(handler, never()).onRequest(any(), any(Responder::class.java))
  }

  @Test
  @Throws(IOException::class)
  fun test_onMessage_WithInvalidMethod_ShouldNotTriggerCallback() {
    val handler = mock(RequestHandler::class.java)
    val client = getClient(createRequestHandler("methodValue", handler))

    client.onMessage(ByteString.EMPTY)
    verify(handler, never()).onNotification(any())
    verify(handler, never()).onRequest(any(), any(Responder::class.java))
  }

  @Test
  @Throws(IOException::class)
  fun test_onMessage_WrongVersion_ShouldNotTriggerCallback() {
    val handler = mock(RequestHandler::class.java)
    val client = getClient(createRequestHandler("methodValue", handler))

    client.onMessage("""{"version": 1, "method": "methodValue"}""")
    verify(handler, never()).onNotification(any())
    verify(handler, never()).onRequest(any(), any(Responder::class.java))
  }

  @Test
  @Throws(IOException::class)
  fun test_onDisconnection_ShouldTriggerDisconnectionCallback() {
    val connectionHandler = mock(ConnectionCallback::class.java)
    val handler = mock(RequestHandler::class.java)
    val client = getClient(requestHandlers = emptyMap(), connectionCallback = connectionHandler)

    client.close()

    verify(connectionHandler, never()).onConnected()
    verify(connectionHandler, times(1)).onDisconnected()

    verify(handler, never()).onNotification(any())
    verify(handler, never()).onRequest(any(), any(Responder::class.java))
  }

  private fun getClient(
      requestHandlers: Map<String, RequestHandler>,
      clientId: String = "test_client",
      settings: PackagerConnectionSettings = this.settings,
      connectionCallback: ConnectionCallback? = null
  ): JSPackagerClient = JSPackagerClient(clientId, settings, requestHandlers, connectionCallback)

  private fun createRequestHandler(
      action: String,
      handler: RequestHandler
  ): Map<String, RequestHandler> = mapOf(action to handler)

  private fun encodeUtf8(input: String): ByteString =
      ByteString.of(*input.toByteArray(Charsets.UTF_8))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.uimanager

import com.facebook.react.common.MapBuilder
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test

class UIManagerModuleConstantsHelperTest {
  @Test
  fun normalizeEventTypes_withNull_doesNothing() {
    UIManagerModuleConstantsHelper.normalizeEventTypes(null)
  }

  @Test
  fun normalizeEventTypes_withEmptyMap_doesNothing() {
    val emptyMap: Map<String, Any?> = MapBuilder.builder<String, Any?>().build()
    UIManagerModuleConstantsHelper.normalizeEventTypes(emptyMap)
    assertTrue(emptyMap.isEmpty())
  }

  @Test
  fun normalizeEventTypes_withOnEvent_doesNormalize() {
    val onClickMap: Map<String, String> =
        MapBuilder.builder<String, String>().put("onClick", "Â¯\\_(ãƒ„)_/Â¯").build()
    UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap)
    assertTrue(onClickMap.containsKey("topOnClick"))
    assertTrue(onClickMap.containsKey("onClick"))
  }

  @Test
  fun normalizeEventTypes_withTopEvent_doesNormalize() {
    val onClickMap: Map<String, String> =
        MapBuilder.builder<String, String>().put("topOnClick", "Â¯\\_(ãƒ„)_/Â¯").build()
    UIManagerModuleConstantsHelper.normalizeEventTypes(onClickMap)
    assertTrue(onClickMap.containsKey("topOnClick"))
    assertFalse(onClickMap.containsKey("onClick"))
  }

  @Suppress("UNCHECKED_CAST")
  @Test
  fun normalizeEventTypes_withNestedObjects_doesNotLoseThem() {
    val nestedObjects: Map<String, Any?> =
        MapBuilder.builder<String, Any?>()
            .put(
                "onColorChanged",
                MapBuilder.of<String, Any?>(
                    "phasedRegistrationNames",
                    MapBuilder.of<String, String>(
                        "bubbled", "onColorChanged", "captured", "onColorChangedCapture")))
            .build()
    UIManagerModuleConstantsHelper.normalizeEventTypes(nestedObjects)
    assertTrue(nestedObjects.containsKey("topOnColorChanged"))
    var innerMap = nestedObjects["topOnColorChanged"] as? Map<String, Any?>
    assertNotNull(innerMap)
    assertTrue(innerMap!!.containsKey("phasedRegistrationNames"))
    var innerInnerMap = innerMap.get("phasedRegistrationNames") as? Map<String, Any?>
    assertNotNull(innerInnerMap)
    assertEquals("onColorChanged", innerInnerMap!!.get("bubbled"))
    assertEquals("onColorChangedCapture", innerInnerMap.get("captured"))
    assertTrue(nestedObjects.containsKey("onColorChanged"))
    innerMap = nestedObjects.get("topOnColorChanged") as? Map<String, Any?>
    assertNotNull(innerMap)
    assertTrue(innerMap!!.containsKey("phasedRegistrationNames"))
    innerInnerMap = innerMap.get("phasedRegistrationNames") as? Map<String, Any?>
    assertNotNull(innerInnerMap)
    assertEquals("onColorChanged", innerInnerMap!!.get("bubbled"))
    assertEquals("onColorChangedCapture", innerInnerMap.get("captured"))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.uimanager.layoutanimation

import java.util.Locale
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class InterpolatorTypeTest {
  @Test
  fun testCamelCase() {
    assertThat(InterpolatorType.fromString("linear")).isEqualTo(InterpolatorType.LINEAR)
    assertThat(InterpolatorType.fromString("easeIn")).isEqualTo(InterpolatorType.EASE_IN)
    assertThat(InterpolatorType.fromString("easeOut")).isEqualTo(InterpolatorType.EASE_OUT)
    assertThat(InterpolatorType.fromString("easeInEaseOut"))
        .isEqualTo(InterpolatorType.EASE_IN_EASE_OUT)
    assertThat(InterpolatorType.fromString("spring")).isEqualTo(InterpolatorType.SPRING)
  }

  @Test
  fun testOtherCases() {
    assertThat(InterpolatorType.fromString("EASEIN")).isEqualTo(InterpolatorType.EASE_IN)
    assertThat(InterpolatorType.fromString("easeout")).isEqualTo(InterpolatorType.EASE_OUT)
    assertThat(InterpolatorType.fromString("easeineaseout"))
        .isEqualTo(InterpolatorType.EASE_IN_EASE_OUT)
  }

  @Test
  fun testLocales() {
    Locale.setDefault(Locale.forLanguageTag("tr-TR"))
    assertThat(InterpolatorType.fromString("easeInEaseOut"))
        .isEqualTo(InterpolatorType.EASE_IN_EASE_OUT)
  }

  @Test(expected = IllegalArgumentException::class)
  @Throws(IllegalArgumentException::class)
  fun testInvalidInterpolatorTypes() {
    InterpolatorType.fromString("ease_in_ease_out")
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.util

import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.JavaOnlyMap
import org.junit.Assert
import org.junit.Test

class JSStackTraceTest {

  @Test
  fun testSymbolication() {
    val values =
        JavaOnlyArray.of(
            JavaOnlyMap.of(
                "methodName",
                "method_from_bundle",
                "column",
                11,
                "lineNumber",
                7,
                "file",
                "Fb4aBundle.js"),
            JavaOnlyMap.of(
                "methodName",
                "method_from_ram_bundle",
                "column",
                13,
                "lineNumber",
                18,
                "file",
                "199.js"),
            JavaOnlyMap.of(
                "methodName",
                "method_from_ram_bundle_with_address",
                "column",
                13,
                "lineNumber",
                18,
                "file",
                "address at 199.js"),
            JavaOnlyMap.of(
                "methodName",
                "method_from_segment",
                "column",
                18,
                "lineNumber",
                9,
                "file",
                "seg-1.js"),
            JavaOnlyMap.of(
                "methodName",
                "method_from_segment_with_address",
                "column",
                18,
                "lineNumber",
                9,
                "file",
                "address at seg-1.js"),
            JavaOnlyMap.of(
                "methodName",
                "method_from_ram_segment",
                "column",
                20,
                "lineNumber",
                10,
                "file",
                "seg-3_198.js"),
            JavaOnlyMap.of(
                "methodName",
                "method_from_ram_segment_with_address",
                "column",
                20,
                "lineNumber",
                10,
                "file",
                "address at seg-3_198.js"))
    val message = JSStackTrace.format("Error", values)
    Assert.assertEquals(
        message,
        """
            Error, stack:
            method_from_bundle@7:11
            method_from_ram_bundle@199.js:18:13
            method_from_ram_bundle_with_address@199.js:18:13
            method_from_segment@seg-1.js:9:18
            method_from_segment_with_address@seg-1.js:9:18
            method_from_ram_segment@seg-3_198.js:10:20
            method_from_ram_segment_with_address@seg-3_198.js:10:20

            """
            .trimIndent())
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.testutils.shadows

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.WritableMap
import org.robolectric.annotation.Implementation
import org.robolectric.annotation.Implements

@Implements(Arguments::class)
class ShadowArguments {
  companion object {
    @JvmStatic @Implementation fun createMap(): WritableMap = JavaOnlyMap()
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.testutils.shadows

import android.content.Context
import com.facebook.soloader.SoLoader
import kotlin.jvm.JvmStatic
import org.robolectric.annotation.Implementation
import org.robolectric.annotation.Implements

@Implements(SoLoader::class)
class ShadowSoLoader {
  companion object {
    @JvmStatic @Implementation fun init(context: Context?, flags: Int) {}

    @JvmStatic @Implementation fun loadLibrary(shortName: String?): Boolean = true
  }
}


package com.helloworld

import android.content.Context
import com.facebook.flipper.android.AndroidFlipperClient
import com.facebook.flipper.android.utils.FlipperUtils
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin
import com.facebook.flipper.plugins.inspector.DescriptorMapping
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin
import com.facebook.react.ReactInstanceEventListener
import com.facebook.react.ReactInstanceManager
import com.facebook.react.bridge.ReactContext
import com.facebook.react.modules.network.NetworkingModule

/**
 * Class responsible of loading Flipper inside your React Native application. This is the debug
 * flavor of it. Here you can add your own plugins and customize the Flipper setup.
 */
object ReactNativeFlipper {
  fun initializeFlipper(context: Context, reactInstanceManager: ReactInstanceManager) {
    if (FlipperUtils.shouldEnableFlipper(context)) {
      val client = AndroidFlipperClient.getInstance(context)
      client.addPlugin(InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()))
      client.addPlugin(DatabasesFlipperPlugin(context))
      client.addPlugin(SharedPreferencesFlipperPlugin(context))
      client.addPlugin(CrashReporterPlugin.getInstance())
      val networkFlipperPlugin = NetworkFlipperPlugin()
      NetworkingModule.setCustomClientBuilder { builder ->
        builder.addNetworkInterceptor(FlipperOkhttpInterceptor(networkFlipperPlugin))
      }
      client.addPlugin(networkFlipperPlugin)
      client.start()

      // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
      // Hence we run if after all native modules have been initialized
      val currentReactContext = reactInstanceManager.currentReactContext
      if (currentReactContext == null) {
        reactInstanceManager.addReactInstanceEventListener(
            object : ReactInstanceEventListener {
              override fun onReactContextInitialized(context: ReactContext) {
                reactInstanceManager.removeReactInstanceEventListener(this)
                context.runOnNativeModulesQueueThread { client.addPlugin(FrescoFlipperPlugin()) }
              }
            })
      } else {
        client.addPlugin(FrescoFlipperPlugin())
      }
    }
  }
}


package com.helloworld

import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate

class MainActivity : ReactActivity() {

  /**
   * Returns the name of the main component registered from JavaScript. This is used to schedule
   * rendering of the component.
   */
  override fun getMainComponentName(): String = "HelloWorld"

  /**
   * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate]
   * which allows you to enable New Architecture with a single boolean flags [fabricEnabled]
   */
  override fun createReactActivityDelegate(): ReactActivityDelegate =
      DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
}


package com.helloworld

import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader

class MainApplication : Application(), ReactApplication {

  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> {
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          return PackageList(this).packages
        }

        override fun getJSMainModuleName(): String = "index"

        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
      }

  override fun onCreate() {
    super.onCreate()
    SoLoader.init(this, false)
    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
      // If you opted-in for the New Architecture, we load the native entry point for this app.
      load()
    }
    ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager)
  }
}


package com.helloworld

import android.content.Context
import com.facebook.react.ReactInstanceManager

/**
 * Class responsible of loading Flipper inside your React Native application. This is the release
 * flavor of it so it's empty as we don't want to load Flipper.
 */
object ReactNativeFlipper {
  @Suppress("UNUSED_PARAMETER")
  fun initializeFlipper(context: Context, reactInstanceManager: ReactInstanceManager) {
    // Do nothing as we don't want to initialize Flipper on Release.
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react

import com.facebook.react.utils.projectPathToLibraryName
import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property

abstract class ReactExtension @Inject constructor(project: Project) {

  private val objects = project.objects

  /**
   * The path to the root of your project. This is the path to where the `package.json` lives. All
   * the CLI commands will be invoked from this folder as working directory.
   *
   * Default: ${rootProject.dir}/../
   */
  val root: DirectoryProperty =
      objects.directoryProperty().convention(project.rootProject.layout.projectDirectory.dir("../"))

  /**
   * The path to the react-native NPM package folder.
   *
   * Default: ${rootProject.dir}/../node_modules/react-native
   */
  val reactNativeDir: DirectoryProperty =
      objects.directoryProperty().convention(root.dir("node_modules/react-native"))

  /**
   * The path to the JS entry file. If not specified, the plugin will try to resolve it using a list
   * of known locations (e.g. `index.android.js`, `index.js`, etc.).
   */
  val entryFile: RegularFileProperty = objects.fileProperty()

  /**
   * The reference to the React Native CLI. If not specified, the plugin will try to resolve it
   * looking for `react-native` CLI inside `node_modules` in [root].
   */
  val cliFile: RegularFileProperty =
      objects.fileProperty().convention(reactNativeDir.file("cli.js"))

  /**
   * The path to the Node executable and extra args. By default it assumes that you have `node`
   * installed and configured in your $PATH. Default: ["node"]
   */
  val nodeExecutableAndArgs: ListProperty<String> =
      objects.listProperty(String::class.java).convention(listOf("node"))

  /** The command to use to invoke bundle. Default is `bundle` and will be invoked on [root]. */
  val bundleCommand: Property<String> = objects.property(String::class.java).convention("bundle")

  /**
   * Custom configuration file for the [bundleCommand]. If provided, it will be passed over with a
   * `--config` flag to the bundle command.
   */
  val bundleConfig: RegularFileProperty = objects.fileProperty()

  /**
   * The Bundle Asset name. This name will be used also for deriving other bundle outputs such as
   * the packager source map, the compiler source map and the output source map file.
   *
   * Default: index.android.bundle
   */
  val bundleAssetName: Property<String> =
      objects.property(String::class.java).convention("index.android.bundle")

  /**
   * Toggles the .so Cleanup step. If enabled, we will clean up all the unnecessary files before the
   * bundle task. If disabled, the developers will have to manually cleanup the files. Default: true
   */
  val enableSoCleanup: Property<Boolean> = objects.property(Boolean::class.java).convention(true)

  /** Extra args that will be passed to the [bundleCommand] Default: [] */
  val extraPackagerArgs: ListProperty<String> =
      objects.listProperty(String::class.java).convention(emptyList())

  /**
   * Allows to specify the debuggable variants (by default just 'debug'). Variants in this list
   * will:
   * - Not be bundled (the bundle file will not be created and won't be copied over).
   * - Have the Hermes Debug flags set. That's useful if you have another variant (say `canary`)
   *   where you want dev mode to be enabled. Default: ['debug']
   */
  val debuggableVariants: ListProperty<String> =
      objects.listProperty(String::class.java).convention(listOf("debug"))

  /** Hermes Config */

  /**
   * The command to use to invoke hermesc (the hermes compiler). Default is "", the plugin will
   * autodetect it.
   */
  val hermesCommand: Property<String> = objects.property(String::class.java).convention("")

  /**
   * Whether to enable Hermes only on certain variants. If specified as a non-empty list, hermesc
   * and the .so cleanup for Hermes will be executed only for variants in this list. An empty list
   * assumes you're either using Hermes for all variants or not (see [enableHermes]).
   *
   * Default: []
   */
  val enableHermesOnlyInVariants: ListProperty<String> =
      objects.listProperty(String::class.java).convention(emptyList())

  /** Flags to pass to Hermesc. Default: ["-O", "-output-source-map"] */
  val hermesFlags: ListProperty<String> =
      objects.listProperty(String::class.java).convention(listOf("-O", "-output-source-map"))

  /** Codegen Config */

  /**
   * The path to the react-native-codegen NPM package folder.
   *
   * Default: ${rootProject.dir}/../node_modules/@react-native/codegen
   */
  val codegenDir: DirectoryProperty =
      objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen"))

  /**
   * The root directory for all JS files for the app.
   *
   * Default: the parent folder of the `/android` folder.
   */
  val jsRootDir: DirectoryProperty = objects.directoryProperty()

  /**
   * The library name that will be used for the codegen artifacts.
   *
   * Default: <UpperCamelVersionOfProjectPath>Spec (e.g. for :example:project it will be
   * ExampleProjectSpec).
   */
  val libraryName: Property<String> =
      objects.property(String::class.java).convention(projectPathToLibraryName(project.path))

  /**
   * Java package name to use for any codegen artifacts produced during build time. Default:
   * com.facebook.fbreact.specs
   */
  val codegenJavaPackageName: Property<String> =
      objects.property(String::class.java).convention("com.facebook.fbreact.specs")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react

import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.gradle.internal.tasks.factory.dependsOn
import com.facebook.react.internal.PrivateReactExtension
import com.facebook.react.tasks.BuildCodegenCLITask
import com.facebook.react.tasks.GenerateCodegenArtifactsTask
import com.facebook.react.tasks.GenerateCodegenSchemaTask
import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFields
import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts
import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
import com.facebook.react.utils.DependencyUtils.configureDependencies
import com.facebook.react.utils.DependencyUtils.configureRepositories
import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings
import com.facebook.react.utils.JdkConfiguratorUtils.configureJavaToolChains
import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk
import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
import com.facebook.react.utils.findPackageJsonFile
import java.io.File
import kotlin.system.exitProcess
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.internal.jvm.Jvm

class ReactPlugin : Plugin<Project> {
  override fun apply(project: Project) {
    checkJvmVersion(project)
    val extension = project.extensions.create("react", ReactExtension::class.java, project)

    // We register a private extension on the rootProject so that project wide configs
    // like codegen config can be propagated from app project to libraries.
    val rootExtension =
        project.rootProject.extensions.findByType(PrivateReactExtension::class.java)
            ?: project.rootProject.extensions.create(
                "privateReact", PrivateReactExtension::class.java, project)

    // App Only Configuration
    project.pluginManager.withPlugin("com.android.application") {
      // We wire the root extension with the values coming from the app (either user populated or
      // defaults).
      rootExtension.root.set(extension.root)
      rootExtension.reactNativeDir.set(extension.reactNativeDir)
      rootExtension.codegenDir.set(extension.codegenDir)
      rootExtension.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)

      project.afterEvaluate {
        val reactNativeDir = extension.reactNativeDir.get().asFile
        val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")
        val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)
        val versionString = versionAndGroupStrings.first
        val groupString = versionAndGroupStrings.second
        configureDependencies(project, versionString, groupString)
        configureRepositories(project, reactNativeDir)
      }

      configureReactNativeNdk(project, extension)
      configureBuildConfigFields(project)
      configureDevPorts(project)
      configureBackwardCompatibilityReactMap(project)

      project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
        onVariants(selector().all()) { variant ->
          project.configureReactTasks(variant = variant, config = extension)
        }
      }
      configureCodegen(project, extension, rootExtension, isLibrary = false)
    }

    // Library Only Configuration
    project.pluginManager.withPlugin("com.android.library") {
      configureCodegen(project, extension, rootExtension, isLibrary = true)
    }

    // Library and App Configurations
    configureJavaToolChains(project)
  }

  private fun checkJvmVersion(project: Project) {
    val jvmVersion = Jvm.current()?.javaVersion?.majorVersion
    if ((jvmVersion?.toIntOrNull() ?: 0) <= 16) {
      project.logger.error(
          """

      ********************************************************************************

      ERROR: requires JDK17 or higher.
      Incompatible major version detected: '$jvmVersion'

      ********************************************************************************

      """
              .trimIndent())
      exitProcess(1)
    }
  }

  /** This function sets up `react-native-codegen` in our Gradle plugin. */
  @Suppress("UnstableApiUsage")
  private fun configureCodegen(
      project: Project,
      localExtension: ReactExtension,
      rootExtension: PrivateReactExtension,
      isLibrary: Boolean
  ) {
    // First, we set up the output dir for the codegen.
    val generatedSrcDir = File(project.buildDir, "generated/source/codegen")

    // We specify the default value (convention) for jsRootDir.
    // It's the root folder for apps (so ../../ from the Gradle project)
    // and the package folder for library (so ../ from the Gradle project)
    if (isLibrary) {
      localExtension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
    } else {
      localExtension.jsRootDir.convention(localExtension.root)
    }

    val buildCodegenTask =
        project.tasks.register("buildCodegenCLI", BuildCodegenCLITask::class.java) {
          it.codegenDir.set(rootExtension.codegenDir)
          val bashWindowsHome = project.findProperty("REACT_WINDOWS_BASH") as String?
          it.bashWindowsHome.set(bashWindowsHome)

          // Please note that appNeedsCodegen is triggering a read of the package.json at
          // configuration time as we need to feed the onlyIf condition of this task.
          // Therefore, the appNeedsCodegen needs to be invoked inside this lambda.
          val needsCodegenFromPackageJson = project.needsCodegenFromPackageJson(rootExtension.root)
          it.onlyIf { isLibrary || needsCodegenFromPackageJson }
        }

    // We create the task to produce schema from JS files.
    val generateCodegenSchemaTask =
        project.tasks.register(
            "generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) { it ->
              it.dependsOn(buildCodegenTask)
              it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
              it.codegenDir.set(rootExtension.codegenDir)
              it.generatedSrcDir.set(generatedSrcDir)

              // We're reading the package.json at configuration time to properly feed
              // the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the
              // parsePackageJson should be invoked inside this lambda.
              val packageJson = findPackageJsonFile(project, rootExtension.root)
              val parsedPackageJson = packageJson?.let { JsonUtils.fromCodegenJson(it) }

              val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
              if (jsSrcsDirInPackageJson != null) {
                it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson))
              } else {
                it.jsRootDir.set(localExtension.jsRootDir)
              }
              val needsCodegenFromPackageJson =
                  project.needsCodegenFromPackageJson(rootExtension.root)
              it.onlyIf { isLibrary || needsCodegenFromPackageJson }
            }

    // We create the task to generate Java code from schema.
    val generateCodegenArtifactsTask =
        project.tasks.register(
            "generateCodegenArtifactsFromSchema", GenerateCodegenArtifactsTask::class.java) {
              it.dependsOn(generateCodegenSchemaTask)
              it.reactNativeDir.set(rootExtension.reactNativeDir)
              it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
              it.generatedSrcDir.set(generatedSrcDir)
              it.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root))
              it.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
              it.libraryName.set(localExtension.libraryName)

              // Please note that appNeedsCodegen is triggering a read of the package.json at
              // configuration time as we need to feed the onlyIf condition of this task.
              // Therefore, the appNeedsCodegen needs to be invoked inside this lambda.
              val needsCodegenFromPackageJson =
                  project.needsCodegenFromPackageJson(rootExtension.root)
              it.onlyIf { isLibrary || needsCodegenFromPackageJson }
            }

    // We update the android configuration to include the generated sources.
    // This equivalent to this DSL:
    //
    // android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } }
    project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
      ext.sourceSets.getByName("main").java.srcDir(File(generatedSrcDir, "java"))
    }

    // `preBuild` is one of the base tasks automatically registered by AGP.
    // This will invoke the codegen before compiling the entire project.
    project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react

import com.android.build.api.variant.Variant
import com.facebook.react.tasks.BundleHermesCTask
import com.facebook.react.utils.KotlinStdlibCompatUtils.capitalizeCompat
import com.facebook.react.utils.NdkConfiguratorUtils.configureJsEnginePackagingOptions
import com.facebook.react.utils.NdkConfiguratorUtils.configureNewArchPackagingOptions
import com.facebook.react.utils.ProjectUtils.isHermesEnabled
import com.facebook.react.utils.detectedCliFile
import com.facebook.react.utils.detectedEntryFile
import java.io.File
import org.gradle.api.Project

@Suppress("SpreadOperator", "UnstableApiUsage")
internal fun Project.configureReactTasks(variant: Variant, config: ReactExtension) {
  val targetName = variant.name.capitalizeCompat()
  val targetPath = variant.name

  // Resources: generated/assets/react/<variant>/index.android.bundle
  val resourcesDir = File(buildDir, "generated/res/react/$targetPath")
  // Bundle: generated/assets/react/<variant>/index.android.bundle
  val jsBundleDir = File(buildDir, "generated/assets/react/$targetPath")
  // Sourcemap: generated/sourcemaps/react/<variant>/index.android.bundle.map
  val jsSourceMapsDir = File(buildDir, "generated/sourcemaps/react/$targetPath")
  // Intermediate packager:
  // intermediates/sourcemaps/react/<variant>/index.android.bundle.packager.map
  // Intermediate compiler:
  // intermediates/sourcemaps/react/<variant>/index.android.bundle.compiler.map
  val jsIntermediateSourceMapsDir = File(buildDir, "intermediates/sourcemaps/react/$targetPath")

  // The location of the cli.js file for React Native
  val cliFile = detectedCliFile(config)

  val isHermesEnabledInProject = project.isHermesEnabled
  val isHermesEnabledInThisVariant =
      if (config.enableHermesOnlyInVariants.get().isNotEmpty()) {
        config.enableHermesOnlyInVariants.get().contains(variant.name) && isHermesEnabledInProject
      } else {
        isHermesEnabledInProject
      }
  val isDebuggableVariant =
      config.debuggableVariants.get().any { it.equals(variant.name, ignoreCase = true) }

  configureNewArchPackagingOptions(project, variant)
  configureJsEnginePackagingOptions(config, variant, isHermesEnabledInThisVariant)

  if (!isDebuggableVariant) {
    val entryFileEnvVariable = System.getenv("ENTRY_FILE")
    val bundleTask =
        tasks.register("createBundle${targetName}JsAndAssets", BundleHermesCTask::class.java) {
          it.root.set(config.root)
          it.nodeExecutableAndArgs.set(config.nodeExecutableAndArgs)
          it.cliFile.set(cliFile)
          it.bundleCommand.set(config.bundleCommand)
          it.entryFile.set(detectedEntryFile(config, entryFileEnvVariable))
          it.extraPackagerArgs.set(config.extraPackagerArgs)
          it.bundleConfig.set(config.bundleConfig)
          it.bundleAssetName.set(config.bundleAssetName)
          it.jsBundleDir.set(jsBundleDir)
          it.resourcesDir.set(resourcesDir)
          it.hermesEnabled.set(isHermesEnabledInThisVariant)
          it.minifyEnabled.set(!isHermesEnabledInThisVariant)
          it.devEnabled.set(false)
          it.jsIntermediateSourceMapsDir.set(jsIntermediateSourceMapsDir)
          it.jsSourceMapsDir.set(jsSourceMapsDir)
          it.hermesCommand.set(config.hermesCommand)
          it.hermesFlags.set(config.hermesFlags)
          it.reactNativeDir.set(config.reactNativeDir)
        }
    variant.sources.res?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::resourcesDir)
    variant.sources.assets?.addGeneratedSourceDirectory(bundleTask, BundleHermesCTask::jsBundleDir)
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.internal

import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.ListProperty

/**
 * A private extension we set on the rootProject to make easier to share values at execution time
 * between app project and library project.
 *
 * Specifically, the [codegenDir], [reactNativeDir] and other properties should be provided by apps
 * (for setups like a monorepo which are app specific) and libraries should honor those values.
 *
 * Users are not supposed to access directly this extension from their build.gradle file.
 */
abstract class PrivateReactExtension @Inject constructor(project: Project) {

  private val objects = project.objects

  val root: DirectoryProperty =
      objects
          .directoryProperty()
          .convention(
              // This is the default for the project root if the users hasn't specified anything.
              // If the project is called "react-native-github"
              //   - We're inside the Github Repo -> root is defined by RN Tester (so no default
              // needed)
              //   - We're inside an includedBuild as we're performing a build from source
              //     (then we're inside `node_modules/react-native`, so default should be ../../)
              // If the project is called in any other name
              //   - We're inside a user project, so inside the ./android folder. Default should be
              // ../
              // User can always override this default by setting a `root =` inside the template.
              if (project.rootProject.name == "react-native-github") {
                project.rootProject.layout.projectDirectory.dir("../../")
              } else {
                project.rootProject.layout.projectDirectory.dir("../")
              })

  val reactNativeDir: DirectoryProperty =
      objects.directoryProperty().convention(root.dir("node_modules/react-native"))

  val nodeExecutableAndArgs: ListProperty<String> =
      objects.listProperty(String::class.java).convention(listOf("node"))

  val codegenDir: DirectoryProperty =
      objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen"))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.model

data class ModelCodegenConfig(
    val name: String?,
    val type: String?,
    val jsSrcsDir: String?,
    val android: ModelCodegenConfigAndroid?
)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.model

data class ModelCodegenConfigAndroid(val javaPackageName: String?)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.model

data class ModelPackageJson(val codegenConfig: ModelCodegenConfig?)


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.tasks

import com.facebook.react.utils.Os.unixifyPath
import com.facebook.react.utils.windowsAwareBashCommandLine
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

/**
 * A Task that will call the `scripts/oss/build.sh` script to trigger the creation of the codegen
 * lib artifacts.
 *
 * NOTE: This task is required when using react-native-codegen from source, instead of npm.
 */
abstract class BuildCodegenCLITask : Exec() {

  @get:Internal abstract val codegenDir: DirectoryProperty

  @get:Internal abstract val bashWindowsHome: Property<String>

  @get:InputFiles
  val input: FileCollection by lazy {
    codegenDir.get().files("scripts", "src", "package.json", ".babelrc", ".prettierrc")
  }

  @get:OutputDirectories
  val output: FileCollection by lazy { codegenDir.get().files("lib", "node_modules") }

  init {
    // We need this condition as we want a single instance of BuildCodegenCLITask to execute
    // per project. Therefore we can safely skip the task if the lib/cli/ folder is available.
    onlyIf {
      val cliDir = codegenDir.file("lib/cli/").get().asFile
      !cliDir.exists() || cliDir.listFiles()?.size == 0
    }
  }

  override fun exec() {
    commandLine(
        windowsAwareBashCommandLine(
            codegenDir.asFile.get().canonicalPath.unixifyPath().plus(BUILD_SCRIPT_PATH),
            bashWindowsHome = bashWindowsHome.orNull,
        ))
    super.exec()
  }

  companion object {
    private const val BUILD_SCRIPT_PATH = "/scripts/oss/build.sh"
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.tasks

import com.facebook.react.utils.Os.cliPath
import com.facebook.react.utils.detectOSAwareHermesCommand
import com.facebook.react.utils.moveTo
import com.facebook.react.utils.windowsAwareCommandLine
import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileTree
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

abstract class BundleHermesCTask : DefaultTask() {

  init {
    group = "react"
  }

  @get:Internal abstract val root: DirectoryProperty

  @get:InputFiles
  val sources: ConfigurableFileTree =
      project.fileTree(root) {
        it.include("**/*.js")
        it.include("**/*.jsx")
        it.include("**/*.ts")
        it.include("**/*.tsx")
        it.exclude("**/android/**/*")
        it.exclude("**/ios/**/*")
        it.exclude("**/build/**/*")
        it.exclude("**/node_modules/**/*")
      }

  @get:Input abstract val nodeExecutableAndArgs: ListProperty<String>

  @get:InputFile abstract val cliFile: RegularFileProperty

  @get:Internal abstract val reactNativeDir: DirectoryProperty

  @get:Input abstract val bundleCommand: Property<String>

  @get:InputFile abstract val entryFile: RegularFileProperty

  @get:InputFile @get:Optional abstract val bundleConfig: RegularFileProperty

  @get:Input abstract val bundleAssetName: Property<String>

  @get:Input abstract val minifyEnabled: Property<Boolean>

  @get:Input abstract val hermesEnabled: Property<Boolean>

  @get:Input abstract val devEnabled: Property<Boolean>

  @get:Input abstract val extraPackagerArgs: ListProperty<String>

  @get:Input abstract val hermesCommand: Property<String>

  @get:Input abstract val hermesFlags: ListProperty<String>

  @get:OutputDirectory abstract val jsBundleDir: DirectoryProperty

  @get:OutputDirectory abstract val resourcesDir: DirectoryProperty

  @get:OutputDirectory abstract val jsIntermediateSourceMapsDir: RegularFileProperty

  @get:OutputDirectory abstract val jsSourceMapsDir: DirectoryProperty

  @TaskAction
  fun run() {
    jsBundleDir.get().asFile.mkdirs()
    resourcesDir.get().asFile.mkdirs()
    jsIntermediateSourceMapsDir.get().asFile.mkdirs()
    jsSourceMapsDir.get().asFile.mkdirs()
    val bundleAssetFilename = bundleAssetName.get()

    val bundleFile = File(jsBundleDir.get().asFile, bundleAssetFilename)
    val packagerSourceMap = resolvePackagerSourceMapFile(bundleAssetFilename)

    val bundleCommand = getBundleCommand(bundleFile, packagerSourceMap)
    runCommand(bundleCommand)

    if (hermesEnabled.get()) {
      val detectedHermesCommand = detectOSAwareHermesCommand(root.get().asFile, hermesCommand.get())
      val bytecodeFile = File("${bundleFile}.hbc")
      val outputSourceMap = resolveOutputSourceMap(bundleAssetFilename)
      val compilerSourceMap = resolveCompilerSourceMap(bundleAssetFilename)

      val hermesCommand = getHermescCommand(detectedHermesCommand, bytecodeFile, bundleFile)
      runCommand(hermesCommand)
      bytecodeFile.moveTo(bundleFile)

      if (hermesFlags.get().contains("-output-source-map")) {
        val hermesTempSourceMapFile = File("$bytecodeFile.map")
        hermesTempSourceMapFile.moveTo(compilerSourceMap)

        val reactNativeDir = reactNativeDir.get().asFile
        val composeScriptFile = File(reactNativeDir, "scripts/compose-source-maps.js")
        val composeSourceMapsCommand =
            getComposeSourceMapsCommand(
                composeScriptFile, packagerSourceMap, compilerSourceMap, outputSourceMap)
        runCommand(composeSourceMapsCommand)
      }
    }
  }

  internal fun resolvePackagerSourceMapFile(bundleAssetName: String) =
      if (hermesEnabled.get()) {
        File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.packager.map")
      } else {
        resolveOutputSourceMap(bundleAssetName)
      }

  internal fun resolveOutputSourceMap(bundleAssetName: String) =
      File(jsSourceMapsDir.get().asFile, "$bundleAssetName.map")

  internal fun resolveCompilerSourceMap(bundleAssetName: String) =
      File(jsIntermediateSourceMapsDir.get().asFile, "$bundleAssetName.compiler.map")

  private fun runCommand(command: List<Any>) {
    project.exec {
      it.workingDir(root.get().asFile)
      it.commandLine(command)
    }
  }

  internal fun getBundleCommand(bundleFile: File, sourceMapFile: File): List<Any> {
    val rootFile = root.get().asFile
    val commandLine =
        mutableListOf<String>().apply {
          addAll(nodeExecutableAndArgs.get())
          add(cliFile.get().asFile.cliPath(rootFile))
          add(bundleCommand.get())
          add("--platform")
          add("android")
          add("--dev")
          add(devEnabled.get().toString())
          add("--reset-cache")
          add("--entry-file")
          add(entryFile.get().asFile.cliPath(rootFile))
          add("--bundle-output")
          add(bundleFile.cliPath(rootFile))
          add("--assets-dest")
          add(resourcesDir.get().asFile.cliPath(rootFile))
          add("--sourcemap-output")
          add(sourceMapFile.cliPath(rootFile))
          if (bundleConfig.isPresent) {
            add("--config")
            add(bundleConfig.get().asFile.cliPath(rootFile))
          }
          add("--minify")
          add(minifyEnabled.get().toString())
          addAll(extraPackagerArgs.get())
          add("--verbose")
        }
    return windowsAwareCommandLine(commandLine)
  }

  internal fun getHermescCommand(
      hermesCommand: String,
      bytecodeFile: File,
      bundleFile: File
  ): List<Any> {
    val rootFile = root.get().asFile
    return windowsAwareCommandLine(
        hermesCommand,
        "-emit-binary",
        "-max-diagnostic-width=80",
        "-out",
        bytecodeFile.cliPath(rootFile),
        bundleFile.cliPath(rootFile),
        *hermesFlags.get().toTypedArray())
  }

  internal fun getComposeSourceMapsCommand(
      composeScript: File,
      packagerSourceMap: File,
      compilerSourceMap: File,
      outputSourceMap: File
  ): List<Any> {
    val rootFile = root.get().asFile
    return windowsAwareCommandLine(
        *nodeExecutableAndArgs.get().toTypedArray(),
        composeScript.cliPath(rootFile),
        packagerSourceMap.cliPath(rootFile),
        compilerSourceMap.cliPath(rootFile),
        "-o",
        outputSourceMap.cliPath(rootFile))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.tasks

import com.facebook.react.utils.JsonUtils
import com.facebook.react.utils.Os.cliPath
import com.facebook.react.utils.windowsAwareCommandLine
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Exec
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory

abstract class GenerateCodegenArtifactsTask : Exec() {

  @get:Internal abstract val reactNativeDir: DirectoryProperty

  @get:Internal abstract val generatedSrcDir: DirectoryProperty

  @get:InputFile abstract val packageJsonFile: RegularFileProperty

  @get:Input abstract val nodeExecutableAndArgs: ListProperty<String>

  @get:Input abstract val codegenJavaPackageName: Property<String>

  @get:Input abstract val libraryName: Property<String>

  @get:InputFile
  val generatedSchemaFile: Provider<RegularFile> = generatedSrcDir.file("schema.json")

  @get:OutputDirectory val generatedJavaFiles: Provider<Directory> = generatedSrcDir.dir("java")

  @get:OutputDirectory val generatedJniFiles: Provider<Directory> = generatedSrcDir.dir("jni")

  override fun exec() {
    val (resolvedLibraryName, resolvedCodegenJavaPackageName) = resolveTaskParameters()
    setupCommandLine(resolvedLibraryName, resolvedCodegenJavaPackageName)
    super.exec()
  }

  internal fun resolveTaskParameters(): Pair<String, String> {
    val parsedPackageJson =
        if (packageJsonFile.isPresent && packageJsonFile.get().asFile.exists()) {
          JsonUtils.fromCodegenJson(packageJsonFile.get().asFile)
        } else {
          null
        }
    val resolvedLibraryName = parsedPackageJson?.codegenConfig?.name ?: libraryName.get()
    val resolvedCodegenJavaPackageName =
        parsedPackageJson?.codegenConfig?.android?.javaPackageName ?: codegenJavaPackageName.get()
    return resolvedLibraryName to resolvedCodegenJavaPackageName
  }

  internal fun setupCommandLine(libraryName: String, codegenJavaPackageName: String) {
    val workingDir = project.projectDir
    commandLine(
        windowsAwareCommandLine(
            *nodeExecutableAndArgs.get().toTypedArray(),
            reactNativeDir.file("scripts/generate-specs-cli.js").get().asFile.cliPath(workingDir),
            "--platform",
            "android",
            "--schemaPath",
            generatedSchemaFile.get().asFile.cliPath(workingDir),
            "--outputDir",
            generatedSrcDir.get().asFile.cliPath(workingDir),
            "--libraryName",
            libraryName,
            "--javaPackageName",
            codegenJavaPackageName))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.tasks

import com.facebook.react.utils.Os.cliPath
import com.facebook.react.utils.windowsAwareCommandLine
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*

/**
 * A task that will collect all the *.js files inside the provided [jsRootDir] and will run the
 * `combine-js-to-schema-cli.js` on top of it (from `react-native-codegen`). The output is a
 * `schema.json` file that contains an intermediate representation of the code to be generated.
 */
abstract class GenerateCodegenSchemaTask : Exec() {

  @get:Internal abstract val jsRootDir: DirectoryProperty

  @get:Internal abstract val codegenDir: DirectoryProperty

  @get:Internal abstract val generatedSrcDir: DirectoryProperty

  @get:Input abstract val nodeExecutableAndArgs: ListProperty<String>

  @get:InputFiles
  val jsInputFiles =
      project.fileTree(jsRootDir) {
        it.include("**/*.js")
        it.include("**/*.ts")
        // Those are known build paths where the source map or other
        // .js files could be stored/generated. We want to make sure we don't pick them up
        // for execution avoidance.
        it.exclude("**/build/ASSETS/**/*")
        it.exclude("**/build/RES/**/*")
        it.exclude("**/build/generated/**/*")
        it.exclude("**/build/intermediates/**/*")
      }

  @get:OutputFile
  val generatedSchemaFile: Provider<RegularFile> = generatedSrcDir.file("schema.json")

  override fun exec() {
    wipeOutputDir()
    setupCommandLine()
    super.exec()
  }

  internal fun wipeOutputDir() {
    generatedSrcDir.asFile.get().apply {
      deleteRecursively()
      mkdirs()
    }
  }

  internal fun setupCommandLine() {
    val workingDir = project.projectDir
    commandLine(
        windowsAwareCommandLine(
            *nodeExecutableAndArgs.get().toTypedArray(),
            codegenDir
                .file("lib/cli/combine/combine-js-to-schema-cli.js")
                .get()
                .asFile
                .cliPath(workingDir),
            "--platform",
            "android",
            generatedSchemaFile.get().asFile.cliPath(workingDir),
            jsRootDir.asFile.get().cliPath(workingDir),
        ))
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.tasks.internal

import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

/**
 * A task that takes care of extracting Boost from a source folder/zip and preparing it to be
 * consumed by the NDK
 */
abstract class PrepareBoostTask : DefaultTask() {

  @get:InputFiles abstract val boostPath: ConfigurableFileCollection

  @get:Input abstract val boostVersion: Property<String>

  @get:OutputDirectory abstract val outputDir: DirectoryProperty

  @TaskAction
  fun taskAction() {
    project.copy { it ->
      it.from(boostPath)
      it.from(project.file("src/main/jni/third-party/boost"))
      it.include(
          "CMakeLists.txt",
          "boost_${boostVersion.get()}/boost/**/*.hpp",
          "boost/boost/**/*.hpp",
          "asm/**/*.S")
      it.includeEmptyDirs = false
      it.into(outputDir)
    }
    File(outputDir.asFile.get(), "boost").apply {
      renameTo(File(this.parentFile, "boost_${boostVersion.get()}"))
    }
  }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.tasks.internal

import java.io.File
import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.DuplicatesStrategy
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*

/**
 * A task that takes care of extracting Glog from a source folder/zip and preparing it to be
 * consumed by the NDK. This task will also take care of applying the mapping for Glog parameters.
 */
abstract class PrepareGlogTask : DefaultTask() {

  @get:InputFiles abstract val glogPath: ConfigurableFileCollection

  @get:Input abstract val glogVersion: Property<String>

  @get:OutputDirectory abstract val outputDir: DirectoryProperty

  @TaskAction
  fun taskAction() {
    project.copy {
      it.from(glogPath)
      it.from(project.file("src/main/jni/third-party/glog/"))
      it.include("glog-${glogVersion.get()}/src/**/*", "CMakeLists.txt", "config.h")
      it.duplicatesStrategy = DuplicatesStrategy.WARN
      it.includeEmptyDirs = false
      it.filesMatching("**/*.h.in") { matchedFile ->
        matchedFile.filter(
            mapOf(
                "tokens" to
                    mapOf(
                        "ac_cv_have_unistd_h" to "1",
                        "ac_cv_have_stdint_h" to "1",
                        "ac_cv_have_systypes_h" to "1",
                        "ac_cv_have_inttypes_h" to "1",
                        "ac_cv_have_libgflags" to "0",
                        "ac_google_start_namespace" to "namespace google {",
                        "ac_cv_have_uint16_t" to "1",
                        "ac_cv_have_u_int16_t" to "1",
                        "ac_cv_have___uint16" to "0",
                        "ac_google_end_namespace" to "}",
                        "ac_cv_have___builtin_expect" to "1",
                        "ac_google_namespace" to "google",
                        "ac_cv___attribute___noinline" to "__attribute__ ((noinline))",
                        "ac_cv___attribute___noreturn" to "__attribute__ ((noreturn))",
                        "ac_cv___attribute___printf_4_5" to
                            "__attribute__((__format__ (__printf__, 4, 5)))")),
            ReplaceTokens::class.java)
        matchedFile.path = (matchedFile.name.removeSuffix(".in"))
      }
      it.into(outputDir)
    }
    val exportedDir = File(outputDir.asFile.get(), "exported/glog/").apply { mkdirs() }
    project.copy {
      it.from(outputDir)
      it.include(
          "stl_logging.h",
          "logging.h",
          "raw_logging.h",
          "vlog_is_on.h",
          "**/src/glog/log_severity.h")
      it.eachFile { file -> file.path = file.name }
      it.includeEmptyDirs = false
      it.into(exportedDir)
    }
  }
}