import {useMemo} from 'react'
import {type StyleProp, type TextStyle} from 'react-native'
import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api'

import {toShortUrl} from '#/lib/strings/url-helpers'
import {atoms as a, flatten, type TextStyleProp} from '#/alf'
import {isOnlyEmoji} from '#/alf/typography'
import {InlineLinkText, type LinkProps} from '#/components/Link'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {RichTextTag} from '#/components/RichTextTag'
import {Text, type TextProps} from '#/components/Typography'

const WORD_WRAP = {wordWrap: 1}
// lifted from facet detection in `RichText` impl, _without_ `gm` flags
const URL_REGEX =
  /(^|\s|\()(?!javascript:)([a-z][a-z0-9+.-]*:\/\/[\S]+|(?:[a-z0-9]+\.)+[a-z0-9]+(:[0-9]+)?[\S]*|[a-z][a-z0-9+.-]*:[^\s()]+)/i

const MARKUP_FACET_TYPE = 'com.example.richtext.facet#markup'
const MARKUP_STYLES = new Set(['bold', 'italic', 'strikethrough', 'underline'])

type MarkupStyle = 'bold' | 'italic' | 'strikethrough' | 'underline'
type MarkupFeature = {
  $type?: string
  style?: MarkupStyle
  strip?: [number, number]
}

function toStripValue(value: unknown) {
  if (typeof value !== 'number' || !Number.isFinite(value)) {
    return 0
  }
  return Math.max(0, Math.trunc(value))
}

function stripTextByChars(text: string, stripStart: number, stripEnd: number) {
  if (!stripStart && !stripEnd) return text
  const chars = Array.from(text)
  const safeStart = Math.min(stripStart, chars.length)
  const safeEnd = Math.min(stripEnd, chars.length - safeStart)
  return chars.slice(safeStart, chars.length - safeEnd).join('')
}

function isMarkupFeature(feature: unknown): feature is MarkupFeature {
  if (!feature || typeof feature !== 'object') return false
  const candidate = feature as MarkupFeature
  if (!candidate.style || !MARKUP_STYLES.has(candidate.style)) return false
  return !candidate.$type || candidate.$type === MARKUP_FACET_TYPE
}

function getMarkupInfo(text: string, facet?: AppBskyRichtextFacet.Main) {
  const features = facet?.features ?? []
  const markupFeatures = features.filter(isMarkupFeature)
  if (!markupFeatures.length) return undefined

  let stripStart = 0
  let stripEnd = 0
  let hasItalic = false
  let hasBold = false
  let hasStrikethrough = false
  let hasUnderline = false

  for (const feature of markupFeatures) {
    if (feature.strip?.length === 2) {
      stripStart = Math.max(stripStart, toStripValue(feature.strip[0]))
      stripEnd = Math.max(stripEnd, toStripValue(feature.strip[1]))
    }
    if (feature.style === 'italic') hasItalic = true
    if (feature.style === 'bold') hasBold = true
    if (feature.style === 'strikethrough') hasStrikethrough = true
    if (feature.style === 'underline') hasUnderline = true
  }

  const markupStyle: TextStyle = {}
  if (hasItalic) markupStyle.fontStyle = 'italic'
  if (hasBold) markupStyle.fontWeight = 'bold'
  if (hasStrikethrough || hasUnderline) {
    const decorations = [] as string[]
    if (hasUnderline) decorations.push('underline')
    if (hasStrikethrough) decorations.push('line-through')
    markupStyle.textDecorationLine = decorations.join(' ')
  }

  return {
    text: stripTextByChars(text, stripStart, stripEnd),
    style: markupStyle,
  }
}

export type RichTextProps = TextStyleProp &
  Pick<TextProps, 'selectable' | 'onLayout' | 'onTextLayout'> & {
    value: RichTextAPI | string
    testID?: string
    numberOfLines?: number
    disableLinks?: boolean
    enableTags?: boolean
    authorHandle?: string
    onLinkPress?: LinkProps['onPress']
    interactiveStyle?: StyleProp<TextStyle>
    emojiMultiplier?: number
    shouldProxyLinks?: boolean
    /**
     * DANGEROUS: Disable facet lexicon validation
     *
     * `detectFacetsWithoutResolution()` generates technically invalid facets,
     * with a handle in place of the DID. This means that RichText that uses it
     * won't be able to render links.
     *
     * Use with care - only use if you're rendering facets you're generating yourself.
     */
    disableMentionFacetValidation?: true
  }

export function RichText({
  testID,
  value,
  style,
  numberOfLines,
  disableLinks,
  selectable,
  enableTags = false,
  authorHandle,
  onLinkPress,
  interactiveStyle,
  emojiMultiplier = 1.85,
  onLayout,
  onTextLayout,
  shouldProxyLinks,
  disableMentionFacetValidation,
}: RichTextProps) {
  const richText = useMemo(() => {
    if (value instanceof RichTextAPI) {
      return value
    } else {
      const rt = new RichTextAPI({text: value})
      rt.detectFacetsWithoutResolution()
      return rt
    }
  }, [value])

  const plainStyles = [a.leading_snug, style]
  const interactiveStyles = [plainStyles, interactiveStyle]

  const {text, facets} = richText

  if (!facets?.length) {
    if (isOnlyEmoji(text)) {
      const flattenedStyle = flatten(style) ?? {}
      const fontSize =
        (flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier
      return (
        <Text
          emoji
          selectable={selectable}
          testID={testID}
          style={[plainStyles, {fontSize}]}
          onLayout={onLayout}
          onTextLayout={onTextLayout}
          // @ts-ignore web only -prf
          dataSet={WORD_WRAP}>
          {text}
        </Text>
      )
    }
    return (
      <Text
        emoji
        selectable={selectable}
        testID={testID}
        style={plainStyles}
        numberOfLines={numberOfLines}
        onLayout={onLayout}
        onTextLayout={onTextLayout}
        // @ts-ignore web only -prf
        dataSet={WORD_WRAP}>
        {text}
      </Text>
    )
  }

  const els = []
  let key = 0
  // N.B. must access segments via `richText.segments`, not via destructuring
  for (const segment of richText.segments()) {
    const link = segment.link
    const mention = segment.mention
    const tag = segment.tag
    const markupInfo = getMarkupInfo(segment.text, segment.facet)
    const segmentText = markupInfo?.text ?? segment.text
    const markupStyle = markupInfo?.style
    const interactiveStylesWithMarkup = markupStyle
      ? [interactiveStyles, markupStyle]
      : interactiveStyles

    if (
      mention &&
      (disableMentionFacetValidation ||
        AppBskyRichtextFacet.validateMention(mention).success) &&
      !disableLinks
    ) {
      els.push(
        <ProfileHoverCard key={key} did={mention.did}>
          <InlineLinkText
            selectable={selectable}
            to={`/profile/${mention.did}`}
            style={interactiveStylesWithMarkup}
            // @ts-ignore TODO
            dataSet={WORD_WRAP}
            shouldProxy={shouldProxyLinks}
            onPress={onLinkPress}>
            {segmentText}
          </InlineLinkText>
        </ProfileHoverCard>,
      )
    } else if (link && AppBskyRichtextFacet.validateLink(link).success) {
      const isValidLink = URL_REGEX.test(link.uri)
      if (!isValidLink || disableLinks) {
        els.push(toShortUrl(segmentText))
      } else {
        els.push(
          <InlineLinkText
            selectable={selectable}
            key={key}
            to={link.uri}
            style={interactiveStylesWithMarkup}
            // @ts-ignore TODO
            dataSet={WORD_WRAP}
            shareOnLongPress
            shouldProxy={shouldProxyLinks}
            onPress={onLinkPress}
            emoji>
            {toShortUrl(segmentText)}
          </InlineLinkText>,
        )
      }
    } else if (
      !disableLinks &&
      enableTags &&
      tag &&
      AppBskyRichtextFacet.validateTag(tag).success
    ) {
      els.push(
        <RichTextTag
          key={key}
          display={segmentText}
          tag={tag.tag}
          textStyle={interactiveStylesWithMarkup}
          authorHandle={authorHandle}
        />,
      )
    } else if (markupStyle) {
      if (segmentText) {
        els.push(
          <Text
            key={key}
            selectable={selectable}
            style={[plainStyles, markupStyle]}
            emoji>
            {segmentText}
          </Text>,
        )
      }
    } else {
      els.push(segmentText)
    }
    key++
  }

  return (
    <Text
      emoji
      selectable={selectable}
      testID={testID}
      style={plainStyles}
      numberOfLines={numberOfLines}
      onLayout={onLayout}
      onTextLayout={onTextLayout}
      // @ts-ignore web only -prf
      dataSet={WORD_WRAP}>
      {els}
    </Text>
  )
}
