package com.halilibo.richtext.ui.string

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.buildAnnotatedString
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.TimeSource

class StreamingTextAccentRangesTest {

  @Test
  fun excludesLinks() {
    val text = "before link after"
    val annotated = buildAnnotatedString {
      append(text)
      addLink(
        LinkAnnotation.Url("https://example.com"),
        start = text.indexOf("link"),
        end = text.indexOf("link") + "link".length,
      )
    }

    assertEquals(
      listOf("before ", " after"),
      annotated.streamingTextAccentRanges(
        start = 0,
        end = text.length,
        excludesEmoji = false,
      ).map { text.substring(it.start, it.end) },
    )
  }

  @Test
  fun includesEmojiByDefault() {
    val text = "before 😎 middle ❤️ after 1️⃣ done"

    assertEquals(
      listOf(text),
      buildAnnotatedString { append(text) }
        .streamingTextAccentRanges(
          start = 0,
          end = text.length,
          excludesEmoji = false,
        )
        .map { text.substring(it.start, it.end) },
    )
  }

  @Test
  fun excludesEmojiClustersWhenRequested() {
    val text = "before 😎 middle ❤️ after 1️⃣ done"

    assertEquals(
      listOf("before ", " middle ", " after ", " done"),
      buildAnnotatedString { append(text) }
        .streamingTextAccentRanges(
          start = 0,
          end = text.length,
          excludesEmoji = true,
        )
        .map { text.substring(it.start, it.end) },
    )
  }

  @Test
  fun excludesZeroWidthJoinerEmojiClusterWhenRequested() {
    val text = "before 👩‍💻 after"

    assertEquals(
      listOf("before ", " after"),
      buildAnnotatedString { append(text) }
        .streamingTextAccentRanges(
          start = 0,
          end = text.length,
          excludesEmoji = true,
        )
        .map { text.substring(it.start, it.end) },
    )
  }

  @Test
  fun excludesRegionalIndicatorEmojiClusterWhenRequested() {
    val text = "before 🇺🇸 after"

    assertEquals(
      listOf("before ", " after"),
      buildAnnotatedString { append(text) }
        .streamingTextAccentRanges(
          start = 0,
          end = text.length,
          excludesEmoji = true,
        )
        .map { text.substring(it.start, it.end) },
    )
  }

  @Test
  fun decayStartIsSharedAcrossRecreatedAnimationStates() {
    val decayStartTimeMark = TimeSource.Monotonic.markNow()
    val accent = StreamingTextAccent(
      color = Color.Red,
      decayDurationMs = 2_000,
      decayStartTimeMark = decayStartTimeMark,
    )

    assertEquals(
      0.5f,
      MarkdownAnimationState().streamingTextAccentInitialAlpha(
        accent,
        now = decayStartTimeMark + 1_000.milliseconds,
      ),
    )
    assertEquals(
      0f,
      MarkdownAnimationState().streamingTextAccentInitialAlpha(
        accent,
        now = decayStartTimeMark + 2_000.milliseconds,
      ),
    )
  }

  @Test
  fun initialAlphaUsesScheduledRevealTimeForStrength() {
    val decayStartTimeMark = TimeSource.Monotonic.markNow()
    val accent = StreamingTextAccent(
      color = Color.Red,
      decayDurationMs = 2_000,
      decayStartTimeMark = decayStartTimeMark,
    )

    assertEquals(
      0.25f,
      MarkdownAnimationState().streamingTextAccentInitialAlpha(
        accent = accent,
        admissionTimeMark = decayStartTimeMark + 200.milliseconds,
        startTimeMark = decayStartTimeMark + 1_500.milliseconds,
      ),
    )
  }

  @Test
  fun durationUsesConfiguredFadeWithinDecayWindow() {
    val decayStartTimeMark = TimeSource.Monotonic.markNow()
    val accent = StreamingTextAccent(
      color = Color.Red,
      fadeOutMs = 600,
      decayDurationMs = 2_000,
      decayStartTimeMark = decayStartTimeMark,
    )

    assertEquals(
      600,
      MarkdownAnimationState().streamingTextAccentDurationMs(
        accent = accent,
        startTimeMark = decayStartTimeMark + 500.milliseconds,
      ),
    )
  }

  @Test
  fun durationEndsAtDecayWindow() {
    val decayStartTimeMark = TimeSource.Monotonic.markNow()
    val accent = StreamingTextAccent(
      color = Color.Red,
      fadeOutMs = 2_000,
      decayDurationMs = 2_000,
      decayStartTimeMark = decayStartTimeMark,
    )

    assertEquals(
      500,
      MarkdownAnimationState().streamingTextAccentDurationMs(
        accent = accent,
        startTimeMark = decayStartTimeMark + 1_500.milliseconds,
      ),
    )
  }

  @Test
  fun additionsUseImmutableAppendedRanges() {
    val accentedEndByAnimationIndex = mutableMapOf<Int, Int>()

    assertEquals(
      listOf(
        StreamingTextAccentAddition(0, StreamingTextAccentRange(0, 4)),
        StreamingTextAccentAddition(4, StreamingTextAccentRange(4, 13)),
      ),
      streamingTextAccentAdditions(
        phraseSegments = listOf(0, 4, 13),
        renderedEnd = 13,
        accentedEndByAnimationIndex = accentedEndByAnimationIndex,
      ),
    )
    assertEquals(
      listOf(
        StreamingTextAccentAddition(13, StreamingTextAccentRange(13, 20)),
      ),
      streamingTextAccentAdditions(
        phraseSegments = listOf(0, 4, 13, 20),
        renderedEnd = 20,
        accentedEndByAnimationIndex = accentedEndByAnimationIndex,
      ),
    )
  }

  @Test
  fun additionsTrackGrowthWithinAnUnfinishedPhrase() {
    val accentedEndByAnimationIndex = mutableMapOf<Int, Int>()

    assertEquals(
      listOf(
        StreamingTextAccentAddition(0, StreamingTextAccentRange(0, 2)),
      ),
      streamingTextAccentAdditions(
        phraseSegments = listOf(0),
        renderedEnd = 2,
        accentedEndByAnimationIndex = accentedEndByAnimationIndex,
      ),
    )
    assertEquals(
      listOf(
        StreamingTextAccentAddition(0, StreamingTextAccentRange(2, 3)),
      ),
      streamingTextAccentAdditions(
        phraseSegments = listOf(0),
        renderedEnd = 3,
        accentedEndByAnimationIndex = accentedEndByAnimationIndex,
      ),
    )
  }
}
