import type { ReactNode } from 'react'
import { useState } from 'react'
import type { Insets } from 'react-native'
import { FootnoteText } from 'src/components/ui'
import { truncateKeepingShortcodes } from 'src/utils'
import { Text, View } from 'tamagui'

const moreButtonHitSlop: Insets = { left: 15, right: 15, top: 6, bottom: 7 }

// Render prop for data-layer truncation. Consumers that pipe custom emoji
// through AutolinkText pass a function; we invoke it with the truncated
// string when collapsed and the full string when expanded. That keeps the
// collapsed caption from laying out every inline emoji in the dropped tail
// — the old behavior (wrap the child in a `<Text numberOfLines>`) left the
// inline `<Image>` children measuring even when RN had clipped the line
// count, so emoji from past the visible area could still ghost above the
// "View More" ellipsis.
export type ExpandableTextRenderer = (text: string, isExpanded: boolean) => ReactNode

interface ExpandableTextProps {
  // Raw text the child renders. Used for the length-based overflow
  // decision; we intentionally don't measure layout (see comment below).
  text: string
  // Render output — typically an `<AutolinkText>`. A function receives the
  // truncated (collapsed) or full (expanded) string so emoji in the
  // dropped tail never lay out. A ReactNode is the legacy form, used when
  // the child's text content isn't a 1:1 mirror of `text` (e.g. the diff
  // view on post history, which renders a custom highlighted subtree).
  children: ReactNode | ExpandableTextRenderer
  // Characters before "View More" appears. Captions default to 420; bios to
  // 160; comments to 280 — pass the right number per call site.
  thresholdChars?: number
  // Safety clamp on the truncated height under the legacy ReactNode branch.
  // Ignored when `children` is a render function — that branch already
  // truncates at the data layer.
  maxLines?: number
}

// Overflow detection is character-count-based, not layout-measured. The old
// `ReadMore` primitive used `onTextLayout` on the outer `<Text>`, which
// silently dropped on Android once inline `<Image>` children (custom emoji)
// were introduced — the text layout pipeline returns only the visible line
// count when it encounters inline-image spans under truncation, so
// `e.nativeEvent.lines.length > maxLines` never flipped true. Character-count
// thresholds are deterministic, cheap, and close enough for UX purposes:
// captions over 420 chars almost always overflow 7 lines at body size
// regardless of line-break layout. Edge cases where a short-char-count-but-
// many-linebreaks caption hits the `maxLines` clamp still render correctly —
// the clamp is a visual ceiling, not the overflow trigger.
export default function ExpandableText({
  text,
  children,
  thresholdChars = 280,
  maxLines = 3,
}: ExpandableTextProps) {
  const [expanded, setExpanded] = useState(false)
  const needsTruncation = text.length > thresholdChars
  const usesRenderProp = typeof children === 'function'

  if (!needsTruncation) {
    if (usesRenderProp) {
      return <>{(children as ExpandableTextRenderer)(text, true)}</>
    }
    return <>{children}</>
  }

  const displayText = expanded ? text : truncateKeepingShortcodes(text, thresholdChars)

  const content = usesRenderProp ? (
    (children as ExpandableTextRenderer)(displayText, expanded)
  ) : (
    <Text numberOfLines={expanded ? undefined : maxLines}>{children}</Text>
  )

  return (
    <View>
      {content}
      <View flexDirection="row">
        <View
          onPress={() => setExpanded((v) => !v)}
          hitSlop={moreButtonHitSlop}
          pressStyle={{ opacity: 0.6 }}
        >
          <FootnoteText tone="tertiary" emphasis="strong" py="$1">
            {expanded ? 'View Less' : 'View More'}
          </FootnoteText>
        </View>
      </View>
    </View>
  )
}
