import {Children, cloneElement, memo, useCallback, useEffect, useMemo, useState} from 'react'
import {
  Animated,
  type GestureResponderEvent,
  type LayoutChangeEvent,
  Pressable,
  StyleSheet,
  TouchableOpacity,
  View,
} from 'react-native'
import {
  type AppBskyActorDefs,
  type AppBskyFeedDefs,
  AppBskyFeedPost,
  AppBskyGraphFollow,
  moderateProfile,
  type ModerationDecision,
  type ModerationOpts,
} from '@atproto/api'
import {AtUri} from '@atproto/api'
import {TID} from '@atproto/common-web'
import {msg, plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'

import {DM_SERVICE_HEADERS, MAX_POST_LINES} from '#/lib/constants'
import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue'
import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types'
import {forceLTR} from '#/lib/strings/bidi'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {niceDate} from '#/lib/strings/time'
import {s} from '#/lib/styles'
import {logger} from '#/logger'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useConfirmFollowUnfollow} from '#/state/preferences/confirm-follow-unfollow'
import {type FeedNotification} from '#/state/queries/notifications/feed'
import {useProfileFollowMutationQueue} from '#/state/queries/profile'
import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache'
import {useAgent, useSession} from '#/state/session'
import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard'
import {Post} from '#/view/com/post/Post'
import {formatCount} from '#/view/com/util/numeric/format'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {PreviewableUserAvatar, UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, native, platform, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {FollowConfirmationDialog} from '#/components/dialogs/FollowConfirmationDialog'
import {BellRinging_Filled_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging'
import {
  Check_Stroke2_Corner0_Rounded as CheckIcon,
  DoubleCheck_Stroke2_Corner0_Rounded as DoubleCheckIcon,
} from '#/components/icons/Check'
import {
  ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
  ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon,
} from '#/components/icons/Chevron'
import {Contacts_Filled_Corner2_Rounded as ContactsIconFilled} from '#/components/icons/Contacts'
import {
  Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled,
  LikeRepost_Stroke2_Corner2_Rounded as RepostHeartIcon,
} from '#/components/icons/Heart2'
import {PersonPlus_Filled_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import {
  Repost_Stroke2_Corner3_Rounded as RepostIcon,
  RepostRepost_Stroke2_Corner2_Rounded as RepostRepostIcon,
} from '#/components/icons/Repost'
import {StarterPack} from '#/components/icons/StarterPack'
import {VerifiedCheck} from '#/components/icons/VerifiedCheck'
import {InlineLinkText, Link} from '#/components/Link'
import * as MediaPreview from '#/components/MediaPreview'
import {ProfileBadges} from '#/components/ProfileBadges'
import * as ProfileCard from '#/components/ProfileCard'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import * as Prompt from '#/components/Prompt'
import {Notification as StarterPackCard} from '#/components/StarterPack/StarterPackCard'
import {SubtleHover} from '#/components/SubtleHover'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env'
import * as bsky from '#/types/bsky'

const MAX_AUTHORS = 5

const EXPANDED_AUTHOR_EL_HEIGHT = 35

interface Author {
  profile: AppBskyActorDefs.ProfileView
  href: string
  moderation: ModerationDecision
}

let NotificationFeedItem = ({
  item,
  moderationOpts,
  highlightUnread,
  hideTopBorder,
}: {
  item: FeedNotification
  moderationOpts: ModerationOpts
  highlightUnread: boolean
  hideTopBorder?: boolean
}): React.ReactNode => {
  const queryClient = useQueryClient()
  const t = useTheme()
  const {_, i18n} = useLingui()
  const ax = useAnalytics()
  const profileCardEnabled = ax.features.enabled(
    ax.features.NotificationsExpandedProfileCardEnable,
  )
  const [isAuthorsExpanded, setIsAuthorsExpanded] = useState<boolean>(false)
  const [isHoveringAuthorsList, setIsHoveringAuthorsList] = useState(false)
  const itemHref = useMemo(() => {
    switch (item.type) {
      case 'post-like':
      case 'repost':
      case 'like-via-repost':
      case 'repost-via-repost': {
        if (item.subjectUri) {
          const urip = new AtUri(item.subjectUri)
          return `/profile/${urip.host}/post/${urip.rkey}`
        }
        break
      }
      case 'follow':
      case 'contact-match':
      case 'verified':
      case 'unverified': {
        return makeProfileLink(item.notification.author)
      }
      case 'reply':
      case 'mention':
      case 'quote': {
        const uripReply = new AtUri(item.notification.uri)
        return `/profile/${uripReply.host}/post/${uripReply.rkey}`
      }
      case 'feedgen-like':
      case 'starterpack-joined': {
        if (item.subjectUri) {
          const urip = new AtUri(item.subjectUri)
          return `/profile/${urip.host}/feed/${urip.rkey}`
        }
        break
      }
      case 'subscribed-post': {
        const posts: string[] = []
        for (const post of [item.notification, ...(item.additional ?? [])]) {
          posts.push(post.uri)
        }
        return `/notifications/activity?posts=${encodeURIComponent(posts.slice(0, 25).join(','))}`
      }
    }

    return ''
  }, [item])

  const onToggleAuthorsExpanded = (e?: GestureResponderEvent) => {
    if (e) {
      e.preventDefault()
      e.stopPropagation()
    }
    setIsAuthorsExpanded(currentlyExpanded => !currentlyExpanded)
  }

  const onBeforePress = useCallback(() => {
    unstableCacheProfileView(queryClient, item.notification.author)
  }, [queryClient, item.notification.author])

  const authors: Author[] = useMemo(() => {
    return [
      {
        profile: item.notification.author,
        href: makeProfileLink(item.notification.author),
        moderation: moderateProfile(item.notification.author, moderationOpts),
      },
      ...(item.additional?.map(({author}) => ({
        profile: author,
        href: makeProfileLink(author),
        moderation: moderateProfile(author, moderationOpts),
      })) || []),
    ].filter(
      (author, index, arr) =>
        arr.findIndex(au => au.profile.did === author.profile.did) === index,
    )
  }, [item, moderationOpts])

  const niceTimestamp = niceDate(i18n, item.notification.indexedAt)
  const firstAuthor = authors[0]
  const firstAuthorName = sanitizeDisplayName(
    firstAuthor.profile.displayName || firstAuthor.profile.handle,
  )

  // Calculate if this is a follow-back notification
  const isFollowBack = useMemo(() => {
    if (item.type !== 'follow') return false
    if (
      item.notification.author.viewer?.following &&
      bsky.dangerousIsType<AppBskyGraphFollow.Record>(
        item.notification.record,
        AppBskyGraphFollow.isRecord,
      )
    ) {
      let followingTimestamp
      try {
        const rkey = new AtUri(item.notification.author.viewer.following).rkey
        followingTimestamp = TID.fromStr(rkey).timestamp()
      } catch (e) {
        return false
      }
      if (followingTimestamp) {
        const followedTimestamp =
          new Date(item.notification.record.createdAt).getTime() * 1000
        return followedTimestamp > followingTimestamp
      }
    }
    return false
  }, [item])

  if (item.subjectUri && !item.subject && item.type !== 'feedgen-like') {
    // don't render anything if the target post was deleted or unfindable
    return <View />
  }

  if (
    item.type === 'reply' ||
    item.type === 'mention' ||
    item.type === 'quote'
  ) {
    if (!item.subject) {
      return null
    }
    const isHighlighted = highlightUnread && !item.notification.isRead
    return (
      <View testID={`feedItem-by-${item.notification.author.handle}`}>
        <Post
          post={item.subject}
          style={
            isHighlighted && {
              backgroundColor: t.palette.primary_25,
              borderColor: t.palette.primary_100,
            }
          }
          hideTopBorder={hideTopBorder}
        />
      </View>
    )
  }

  const firstAuthorLink = (
    <ProfileHoverCard did={firstAuthor.profile.did} inline>
      <View style={[a.flex_row, a.align_center, a.flex_shrink]}>
        <InlineLinkText
          key={firstAuthor.href}
          style={[t.atoms.text, a.font_semi_bold, a.text_md, a.leading_tight]}
          to={firstAuthor.href}
          disableMismatchWarning
          emoji
          label={_(msg`Go to ${firstAuthorName}'s profile`)}>
          {forceLTR(firstAuthorName)}
        </InlineLinkText>
        <ProfileBadges
          profile={firstAuthor.profile}
          size="md"
          pdsInteractive={false}
          style={[
            a.pl_2xs,
            a.self_center,
            {
              marginTop: platform({web: 1, ios: 0, android: -1}),
            },
          ]}
        />
      </View>
    </ProfileHoverCard>
  )
  const additionalAuthorsCount = authors.length - 1
  const hasMultipleAuthors = additionalAuthorsCount > 0
  const formattedAuthorsCount = hasMultipleAuthors
    ? formatCount(i18n, additionalAuthorsCount)
    : ''

  let a11yLabel = ''
  let notificationContent: React.ReactElement<any>
  let icon = (
    <HeartIconFilled
      size="xl"
      style={[
        {color: t.palette.pink},
        // {position: 'relative', top: -4}
      ]}
    />
  )

  if (item.type === 'post-like') {
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
            one: `${formattedAuthorsCount} other`,
            other: `${formattedAuthorsCount} others`,
          })} liked your post`,
        )
      : _(msg`${firstAuthorName} liked your post`)
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
        liked your post
      </Trans>
    ) : (
      <Trans>{firstAuthorLink} liked your post</Trans>
    )
  } else if (item.type === 'repost') {
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
            one: `${formattedAuthorsCount} other`,
            other: `${formattedAuthorsCount} others`,
          })} reposted your post`,
        )
      : _(msg`${firstAuthorName} reposted your post`)
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
        reposted your post
      </Trans>
    ) : (
      <Trans>{firstAuthorLink} reposted your post</Trans>
    )
    icon = <RepostIcon size="xl" style={{color: t.palette.positive_500}} />
  } else if (item.type === 'follow') {
    if (isFollowBack && !hasMultipleAuthors) {
      /*
       * Follow-backs are ungrouped, grouped follow-backs not supported atm,
       * see `src/state/queries/notifications/util.ts`
       */
      a11yLabel = _(msg`${firstAuthorName} followed you back`)
      notificationContent = <Trans>{firstAuthorLink} followed you back</Trans>
    } else {
      a11yLabel = hasMultipleAuthors
        ? _(
            msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
              one: `${formattedAuthorsCount} other`,
              other: `${formattedAuthorsCount} others`,
            })} followed you`,
          )
        : _(msg`${firstAuthorName} followed you`)
      notificationContent = hasMultipleAuthors ? (
        <Trans>
          {firstAuthorLink} and{' '}
          <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
            <Plural
              value={additionalAuthorsCount}
              one={`${formattedAuthorsCount} other`}
              other={`${formattedAuthorsCount} others`}
            />
          </Text>{' '}
          followed you
        </Trans>
      ) : (
        <Trans>{firstAuthorLink} followed you</Trans>
      )
    }
    icon = <PersonPlusIcon size="xl" style={{color: t.palette.primary_500}} />
  } else if (item.type === 'contact-match') {
    a11yLabel = _(msg`Your contact ${firstAuthorName} is on Bluesky`)
    notificationContent = (
      <Trans>Your contact {firstAuthorLink} is on Bluesky</Trans>
    )
    icon = (
      <ContactsIconFilled size="xl" style={{color: t.palette.primary_500}} />
    )
  } else if (item.type === 'feedgen-like') {
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
            one: `${formattedAuthorsCount} other`,
            other: `${formattedAuthorsCount} others`,
          })} liked your custom feed`,
        )
      : _(msg`${firstAuthorName} liked your custom feed`)
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
        liked your custom feed
      </Trans>
    ) : (
      <Trans>{firstAuthorLink} liked your custom feed</Trans>
    )
  } else if (item.type === 'starterpack-joined') {
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
            one: `${formattedAuthorsCount} other`,
            other: `${formattedAuthorsCount} others`,
          })} signed up with your starter pack`,
        )
      : _(msg`${firstAuthorName} signed up with your starter pack`)
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
        signed up with your starter pack
      </Trans>
    ) : (
      <Trans>{firstAuthorLink} signed up with your starter pack</Trans>
    )
    icon = (
      <View style={{height: 30, width: 30}}>
        <StarterPack width={30} gradient="sky" />
      </View>
    )
  } else if (item.type === 'verified') {
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
            one: `${formattedAuthorsCount} other`,
            other: `${formattedAuthorsCount} others`,
          })} verified you`,
        )
      : _(msg`${firstAuthorName} verified you`)
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
        verified you
      </Trans>
    ) : (
      <Trans>{firstAuthorLink} verified you</Trans>
    )
    icon = <VerifiedCheck size="xl" />
  } else if (item.type === 'unverified') {
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
            one: `${formattedAuthorsCount} other`,
            other: `${formattedAuthorsCount} others`,
          })} removed their verifications from your account`,
        )
      : _(msg`${firstAuthorName} removed their verification from your account`)
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
        removed their verifications from your account
      </Trans>
    ) : (
      <Trans>
        {firstAuthorLink} removed their verification from your account
      </Trans>
    )
    icon = <VerifiedCheck size="xl" fill={t.palette.contrast_500} />
  } else if (item.type === 'like-via-repost') {
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
            one: `${formattedAuthorsCount} other`,
            other: `${formattedAuthorsCount} others`,
          })} liked your repost`,
        )
      : _(msg`${firstAuthorName} liked your repost`)
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
        liked your repost
      </Trans>
    ) : (
      <Trans>{firstAuthorLink} liked your repost</Trans>
    )
    icon = <RepostHeartIcon size="xl" style={{color: t.palette.like}} />
  } else if (item.type === 'repost-via-repost') {
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`${firstAuthorName} and ${plural(additionalAuthorsCount, {
            one: `${formattedAuthorsCount} other`,
            other: `${formattedAuthorsCount} others`,
          })} reposted your repost`,
        )
      : _(msg`${firstAuthorName} reposted your repost`)
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
        reposted your repost
      </Trans>
    ) : (
      <Trans>{firstAuthorLink} reposted your repost</Trans>
    )
    icon = (
      <RepostRepostIcon size="xl" style={{color: t.palette.positive_500}} />
    )
  } else if (item.type === 'subscribed-post') {
    const postsCount = 1 + (item.additional?.length || 0)
    a11yLabel = hasMultipleAuthors
      ? _(
          msg`New posts from ${firstAuthorName} and ${plural(
            additionalAuthorsCount,
            {
              one: `${formattedAuthorsCount} other`,
              other: `${formattedAuthorsCount} others`,
            },
          )}`,
        )
      : _(
          msg`New ${plural(postsCount, {
            one: 'post',
            other: 'posts',
          })} from ${firstAuthorName}`,
        )
    notificationContent = hasMultipleAuthors ? (
      <Trans>
        New posts from {firstAuthorLink} and{' '}
        <Text style={[a.text_md, a.font_semi_bold, a.leading_snug]}>
          <Plural
            value={additionalAuthorsCount}
            one={`${formattedAuthorsCount} other`}
            other={`${formattedAuthorsCount} others`}
          />
        </Text>{' '}
      </Trans>
    ) : (
      <Trans>
        New <Plural value={postsCount} one="post" other="posts" /> from{' '}
        {firstAuthorLink}
      </Trans>
    )
    icon = <BellRingingIcon size="xl" style={{color: t.palette.primary_500}} />
  } else {
    return null
  }
  a11yLabel += ` · ${niceTimestamp}`

  return (
    <Link
      label={a11yLabel}
      testID={`feedItem-by-${item.notification.author.handle}`}
      style={[
        a.flex_row,
        a.align_start,
        {padding: 10},
        a.pr_lg,
        t.atoms.border_contrast_low,
        item.notification.isRead
          ? undefined
          : {
              backgroundColor: t.palette.primary_25,
              borderColor: t.palette.primary_100,
            },
        !hideTopBorder && a.border_t,
        // Clip horizontal overflow (in case of long handles) but let the timestamp overflow the bottom of the cell.
        platform({
          web: {overflowX: 'clip', overflowY: 'visible'},
          native: {overflow: 'hidden'},
        }),
      ]}
      to={itemHref}
      accessible={!isAuthorsExpanded}
      accessibilityActions={
        hasMultipleAuthors
          ? [
              {
                name: 'toggleAuthorsExpanded',
                label: isAuthorsExpanded
                  ? _(msg`Collapse list of users`)
                  : _(msg`Expand list of users`),
              },
            ]
          : [
              {
                name: 'viewProfile',
                label: _(
                  msg`View ${
                    authors[0].profile.displayName || authors[0].profile.handle
                  }'s profile`,
                ),
              },
            ]
      }
      onAccessibilityAction={e => {
        if (e.nativeEvent.actionName === 'activate') {
          onBeforePress()
        }
        if (e.nativeEvent.actionName === 'toggleAuthorsExpanded') {
          onToggleAuthorsExpanded()
        }
      }}>
      {({hovered}) => (
        <>
          <SubtleHover
            hover={hovered && (!profileCardEnabled || !isHoveringAuthorsList)}
          />
          <View style={[styles.layoutIcon, a.pr_sm]}>
            {/* TODO: Prevent conditional rendering and move toward composable
          notifications for clearer accessibility labeling */}
            {icon}
          </View>
          <View style={[a.flex_1]}>
            <ExpandListPressable
              hasMultipleAuthors={hasMultipleAuthors}
              onToggleAuthorsExpanded={onToggleAuthorsExpanded}
              onHoverIn={
                profileCardEnabled
                  ? () => setIsHoveringAuthorsList(true)
                  : undefined
              }
              onHoverOut={
                profileCardEnabled
                  ? () => setIsHoveringAuthorsList(false)
                  : undefined
              }>
              <CondensedAuthorsList
                visible={!isAuthorsExpanded}
                authors={authors}
                onToggleAuthorsExpanded={onToggleAuthorsExpanded}
                showDmButton={item.type === 'starterpack-joined'}
              />
              <ExpandedAuthorsList
                visible={isAuthorsExpanded}
                authors={authors}
                moderationOpts={moderationOpts}
              />
              <View
                style={[{paddingTop: 6}, a.self_start]}
                accessibilityLabel={a11yLabel}>
                <NotificationContent
                  content={notificationContent}
                  timestamp={item.notification.indexedAt}
                  niceTimestamp={niceTimestamp}
                />
              </View>
            </ExpandListPressable>
            {(item.type === 'follow' && !hasMultipleAuthors && !isFollowBack) ||
            (item.type === 'contact-match' &&
              !item.notification.author.viewer?.following) ? (
              <FollowBackButton profile={item.notification.author} />
            ) : null}
            {item.type === 'post-like' ||
            item.type === 'repost' ||
            item.type === 'like-via-repost' ||
            item.type === 'repost-via-repost' ||
            item.type === 'subscribed-post' ? (
              <View style={[a.pt_2xs]}>
                <AdditionalPostText post={item.subject} />
              </View>
            ) : null}
            {item.type === 'feedgen-like' && item.subjectUri ? (
              <FeedSourceCard
                feedUri={item.subjectUri}
                link={false}
                style={[
                  t.atoms.bg,
                  t.atoms.border_contrast_low,
                  a.border,
                  a.p_md,
                  styles.feedcard,
                ]}
                showLikes
              />
            ) : null}
            {item.type === 'starterpack-joined' ? (
              <View>
                <View
                  style={[
                    a.border,
                    a.p_sm,
                    a.rounded_sm,
                    a.mt_sm,
                    t.atoms.border_contrast_low,
                  ]}>
                  <StarterPackCard starterPack={item.subject} />
                </View>
              </View>
            ) : null}
          </View>
        </>
      )}
    </Link>
  )
}
NotificationFeedItem = memo(NotificationFeedItem)
export {NotificationFeedItem}

function NotificationContent({
  content,
  timestamp,
  niceTimestamp,
}: {
  content: React.ReactElement<{component?: typeof NotificationSentence}>
  timestamp: string
  niceTimestamp: string
}) {
  function Wrapper({children}: {children: React.ReactNode}) {
    return (
      <NotificationSentence
        timestamp={timestamp}
        niceTimestamp={niceTimestamp}>
        {children}
      </NotificationSentence>
    )
  }
  return cloneElement(content, {component: Wrapper})
}

function NotificationTimestamp({
  timestamp,
  niceTimestamp,
}: {
  timestamp: string
  niceTimestamp: string
}) {
  const t = useTheme()
  return (
    <TimeElapsed timestamp={timestamp}>
      {({timeElapsed}) => (
        <Text
          style={[
            a.text_md,
            a.leading_snug,
            t.atoms.text_contrast_medium,
            native({includeFontPadding: false}),
          ]}
          title={niceTimestamp}>
          {' '}&middot;{' '}
          {timeElapsed}
        </Text>
      )}
    </TimeElapsed>
  )
}

function NotificationSentence({
  children,
  timestamp,
  niceTimestamp,
}: {
  children: React.ReactNode
  timestamp: string
  niceTimestamp: string
}) {
  const t = useTheme()
  return (
    <View style={[a.flex_row, a.flex_wrap, a.align_baseline, a.flex_shrink]}>
      {renderInlineTransChildren(children, t)}
      <NotificationTimestamp
        timestamp={timestamp}
        niceTimestamp={niceTimestamp}
      />
    </View>
  )
}

function renderInlineTransChildren(
  children: React.ReactNode,
  t: ReturnType<typeof useTheme>,
): React.ReactNode {
  return Children.map(children, (child, i) => {
    if (typeof child === 'string') {
      if (!child) {
        return null
      }
      return (
        <Text
          key={`notification-str-${i}`}
          style={[a.text_md, a.leading_snug, t.atoms.text]}>
          {child}
        </Text>
      )
    }
    if (Array.isArray(child)) {
      return renderInlineTransChildren(child, t)
    }
    return child
  })
}

function ExpandListPressable({
  hasMultipleAuthors,
  children,
  onToggleAuthorsExpanded,
  onHoverIn,
  onHoverOut,
}: {
  hasMultipleAuthors: boolean
  children: React.ReactNode
  onToggleAuthorsExpanded: (e: GestureResponderEvent) => void
  onHoverIn?: () => void
  onHoverOut?: () => void
}) {
  if (hasMultipleAuthors) {
    return (
      <Pressable
        onPress={onToggleAuthorsExpanded}
        onHoverIn={onHoverIn}
        onHoverOut={onHoverOut}
        style={[styles.expandedAuthorsTrigger]}
        accessible={false}>
        {children}
      </Pressable>
    )
  } else {
    return <>{children}</>
  }
}

function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) {
  const {_} = useLingui()
  const {currentAccount, hasSession} = useSession()
  const profileShadow = useProfileShadow(profile)
  const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
    profileShadow,
    'ProfileCard',
  )
  const confirmFollowUnfollow = useConfirmFollowUnfollow()
  const promptControl = Prompt.usePromptControl()
  const [confirmationAction, setConfirmationAction] =
    useState<'follow' | 'unfollow'>('follow')

  // Don't show button if not logged in or for own profile
  if (!hasSession || profile.did === currentAccount?.did) {
    return null
  }

  const executeFollow = async () => {
    try {
      await queueFollow()
      Toast.show(
        _(
          msg`Following ${sanitizeDisplayName(
            profile.displayName || profile.handle,
          )}`,
        ),
      )
    } catch (err: any) {
      if (err?.name !== 'AbortError') {
        Toast.show(_(msg`An issue occurred, please try again.`), {
          type: 'error',
        })
      }
    }
  }

  const executeUnfollow = async () => {
    try {
      await queueUnfollow()
      Toast.show(
        _(
          msg`No longer following ${sanitizeDisplayName(
            profile.displayName || profile.handle,
          )}`,
        ),
      )
    } catch (err: any) {
      if (err?.name !== 'AbortError') {
        Toast.show(_(msg`An issue occurred, please try again.`), {
          type: 'error',
        })
      }
    }
  }

  const onPressFollow = (e: GestureResponderEvent) => {
    e.preventDefault()
    e.stopPropagation()

    if (confirmFollowUnfollow) {
      setConfirmationAction('follow')
      promptControl.open()
    } else {
      void executeFollow()
    }
  }

  const onPressUnfollow = (e: GestureResponderEvent) => {
    e.preventDefault()
    e.stopPropagation()

    if (confirmFollowUnfollow) {
      setConfirmationAction('unfollow')
      promptControl.open()
    } else {
      void executeUnfollow()
    }
  }

  const onConfirm = () => {
    if (confirmationAction === 'follow') {
      void executeFollow()
    } else {
      void executeUnfollow()
    }
  }

  // Don't show button if viewer data is missing or user is blocked
  if (!profileShadow.viewer) {
    return null
  }
  if (
    profileShadow.viewer.blockedBy ||
    profileShadow.viewer.blocking ||
    profileShadow.viewer.blockingByList
  ) {
    return null
  }

  const isFollowing = profileShadow.viewer.following
  const isFollowedBy = profileShadow.viewer.followedBy
  const followingLabel = _(
    msg({
      message: 'Following',
      comment: 'User is following this account, click to unfollow',
    }),
  )

  return (
    <View style={[a.pt_sm]}>
      {isFollowing ? (
        <Button
          label={followingLabel}
          color="secondary"
          size="small"
          style={[a.self_start]}
          onPress={onPressUnfollow}>
          <ButtonIcon icon={isFollowedBy ? DoubleCheckIcon : CheckIcon} />
          <ButtonText>
            {isFollowedBy ? <Trans>Mutuals</Trans> : <Trans>Following</Trans>}
          </ButtonText>
        </Button>
      ) : (
        <Button
          label={isFollowedBy ? _(msg`Follow back`) : _(msg`Follow`)}
          color="primary"
          size="small"
          style={[a.self_start]}
          onPress={onPressFollow}>
          <ButtonIcon icon={PlusIcon} />
          <ButtonText>
            {isFollowedBy ? <Trans>Follow back</Trans> : <Trans>Follow</Trans>}
          </ButtonText>
        </Button>
      )}
      {confirmFollowUnfollow && (
        <FollowConfirmationDialog
          control={promptControl}
          displayName={sanitizeDisplayName(profile.displayName || profile.handle)}
          handle={profile.handle}
          actionType={confirmationAction}
          onConfirm={onConfirm}
        />
      )}
    </View>
  )
}

function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) {
  const {_} = useLingui()
  const agent = useAgent()
  const navigation = useNavigation<NavigationProp>()
  const [isLoading, setIsLoading] = useState(false)

  if (
    profile.associated?.chat?.allowIncoming === 'none' ||
    (profile.associated?.chat?.allowIncoming === 'following' &&
      !profile.viewer?.followedBy)
  ) {
    return null
  }

  return (
    <Button
      label={_(msg`Say hello!`)}
      variant="ghost"
      color="primary"
      size="small"
      style={[a.self_center, {marginLeft: 'auto'}]}
      disabled={isLoading}
      onPress={async () => {
        try {
          setIsLoading(true)
          const res = await agent.api.chat.bsky.convo.getConvoForMembers(
            {
              members: [profile.did, agent.session!.did],
            },
            {headers: DM_SERVICE_HEADERS},
          )
          navigation.navigate('MessagesConversation', {
            conversation: res.data.convo.id,
          })
        } catch (e) {
          logger.error('Failed to get conversation', {safeMessage: e})
        } finally {
          setIsLoading(false)
        }
      }}>
      <ButtonText>
        <Trans>Say hello!</Trans>
      </ButtonText>
    </Button>
  )
}

function CondensedAuthorsList({
  visible,
  authors,
  onToggleAuthorsExpanded,
  showDmButton = true,
}: {
  visible: boolean
  authors: Author[]
  onToggleAuthorsExpanded: (e: GestureResponderEvent) => void
  showDmButton?: boolean
}) {
  const t = useTheme()
  const {_} = useLingui()

  if (!visible) {
    return (
      <View style={[a.flex_row, a.align_center]}>
        <TouchableOpacity
          style={styles.expandedAuthorsCloseBtn}
          onPress={onToggleAuthorsExpanded}
          accessibilityRole="button"
          accessibilityLabel={_(msg`Hide user list`)}
          accessibilityHint={_(
            msg`Collapses list of users for a given notification`,
          )}>
          <ChevronUpIcon
            size="md"
            style={[a.ml_xs, a.mr_md, t.atoms.text_contrast_high]}
          />
          <Text style={[a.text_md, t.atoms.text_contrast_high]}>
            <Trans context="action">Hide</Trans>
          </Text>
        </TouchableOpacity>
      </View>
    )
  }
  if (authors.length === 1) {
    return (
      <View style={[a.flex_row, a.align_center]}>
        <PreviewableUserAvatar
          size={35}
          profile={authors[0].profile}
          moderation={authors[0].moderation.ui('avatar')}
          type={authors[0].profile.associated?.labeler ? 'labeler' : 'user'}
        />
        {showDmButton ? <SayHelloBtn profile={authors[0].profile} /> : null}
      </View>
    )
  }
  return (
    <TouchableOpacity
      accessibilityRole="none"
      onPress={onToggleAuthorsExpanded}>
      <View style={[a.flex_row, a.align_center]}>
        {authors.slice(0, MAX_AUTHORS).map(author => (
          <View key={author.href} style={s.mr5}>
            <PreviewableUserAvatar
              size={35}
              profile={author.profile}
              moderation={author.moderation.ui('avatar')}
              type={author.profile.associated?.labeler ? 'labeler' : 'user'}
            />
          </View>
        ))}
        {authors.length > MAX_AUTHORS ? (
          <Text
            style={[
              a.font_semi_bold,
              {paddingLeft: 6},
              t.atoms.text_contrast_medium,
            ]}>
            +{authors.length - MAX_AUTHORS}
          </Text>
        ) : undefined}
        <ChevronDownIcon
          size="md"
          style={[a.mx_xs, t.atoms.text_contrast_medium]}
        />
      </View>
    </TouchableOpacity>
  )
}

function ExpandedAuthorsList({
  visible,
  authors,
  moderationOpts,
}: {
  visible: boolean
  authors: Author[]
  moderationOpts: ModerationOpts
}) {
  const ax = useAnalytics()
  const profileCardEnabled = ax.features.enabled(
    ax.features.NotificationsExpandedProfileCardEnable,
  )
  const isNativeFade = !IS_WEB && profileCardEnabled
  const heightInterp = useAnimatedValue(visible ? 1 : 0)
  const opacityInterp = useAnimatedValue(visible ? 1 : 0)
  const [measuredHeight, setMeasuredHeight] = useState(0)
  useEffect(() => {
    Animated.timing(isNativeFade ? opacityInterp : heightInterp, {
      toValue: visible ? 1 : 0,
      duration: 200,
      useNativeDriver: isNativeFade,
    }).start()
  }, [heightInterp, opacityInterp, visible, isNativeFade])
  const onInnerLayout = (e: LayoutChangeEvent) => {
    if (measuredHeight === 0) {
      setMeasuredHeight(e.nativeEvent.layout.height)
    }
  }

  if (isNativeFade) {
    return (
      <Animated.View style={{opacity: opacityInterp}}>
        {visible && (
          <View style={[a.pt_sm, a.pb_md]}>
            {authors.map((author, i) => (
              <ExpandedAuthorProfileCard
                key={author.profile.did}
                author={author}
                moderationOpts={moderationOpts}
                isLast={i === authors.length - 1}
              />
            ))}
          </View>
        )}
      </Animated.View>
    )
  }

  const targetHeight = profileCardEnabled
    ? measuredHeight
    : authors.length * (EXPANDED_AUTHOR_EL_HEIGHT + 10) /*10=margin*/
  const heightStyle = {
    height: Animated.multiply(heightInterp, targetHeight),
    opacity: Animated.divide(heightInterp, 1),
  }
  return (
    <Animated.View style={[a.overflow_hidden, heightStyle]}>
      {profileCardEnabled ? (
        <View onLayout={onInnerLayout} style={[a.pt_sm, a.pb_md]}>
          {authors.map((author, i) => (
            <ExpandedAuthorProfileCard
              key={author.profile.did}
              author={author}
              moderationOpts={moderationOpts}
              isLast={i === authors.length - 1}
            />
          ))}
        </View>
      ) : (
        authors.map(author => (
          <ExpandedAuthorCard key={author.profile.did} author={author} />
        ))
      )}
    </Animated.View>
  )
}

function ExpandedAuthorProfileCard({
  author,
  moderationOpts,
  isLast,
}: {
  author: Author
  moderationOpts: ModerationOpts
  isLast: boolean
}) {
  return (
    <ProfileCard.Link
      profile={author.profile}
      style={isLast ? undefined : a.pb_md}>
      <ProfileCard.Outer>
        <ProfileCard.Header>
          <ProfileCard.Avatar
            profile={author.profile}
            moderationOpts={moderationOpts}
          />
          <ProfileCard.NameAndHandle
            profile={author.profile}
            moderationOpts={moderationOpts}
          />
          <ProfileCard.FollowButton
            profile={author.profile}
            moderationOpts={moderationOpts}
            logContext="ProfileCard"
          />
        </ProfileCard.Header>
      </ProfileCard.Outer>
    </ProfileCard.Link>
  )
}

function ExpandedAuthorCard({author}: {author: Author}) {
  const t = useTheme()
  const {_} = useLingui()
  return (
    <Link
      label={author.profile.displayName || author.profile.handle}
      accessibilityHint={_(msg`Opens this profile`)}
      to={makeProfileLink({
        did: author.profile.did,
        handle: author.profile.handle,
      })}
      style={styles.expandedAuthor}>
      <View style={[a.mr_sm]}>
        <ProfileHoverCard did={author.profile.did}>
          <UserAvatar
            size={35}
            avatar={author.profile.avatar}
            moderation={author.moderation.ui('avatar')}
            type={author.profile.associated?.labeler ? 'labeler' : 'user'}
          />
        </ProfileHoverCard>
      </View>
      <View style={[a.flex_1]}>
        <View style={[a.flex_row, a.align_end]}>
          <Text
            numberOfLines={1}
            emoji
            style={[
              a.text_md,
              a.font_semi_bold,
              a.leading_tight,
              {maxWidth: '70%'},
            ]}>
            {sanitizeDisplayName(
              author.profile.displayName || author.profile.handle,
            )}
          </Text>
          <ProfileBadges
            profile={author.profile}
            size="md"
            pdsInteractive={false}
            style={[a.pl_2xs, a.self_center]}
          />
          <Text
            numberOfLines={1}
            style={[
              a.pl_xs,
              a.text_md,
              a.leading_tight,
              a.flex_shrink,
              t.atoms.text_contrast_medium,
            ]}>
            {sanitizeHandle(author.profile.handle, '@')}
          </Text>
        </View>
      </View>
    </Link>
  )
}

function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) {
  const t = useTheme()
  if (
    post &&
    bsky.dangerousIsType<AppBskyFeedPost.Record>(
      post?.record,
      AppBskyFeedPost.isRecord,
    )
  ) {
    const text = post.record.text

    return (
      <>
        {text?.length > 0 && (
          <Text
            emoji
            style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_medium]}
            numberOfLines={MAX_POST_LINES}>
            {text}
          </Text>
        )}
        <MediaPreview.Embed
          embed={post.embed}
          style={styles.additionalPostImages}
          peekable
        />
      </>
    )
  }
}

const styles = StyleSheet.create({
  layoutIcon: {
    width: 60,
    alignItems: 'flex-end',
    paddingTop: 2,
  },
  icon: {
    marginRight: 10,
    marginTop: 4,
  },
  additionalPostImages: {
    marginTop: 5,
    marginLeft: 2,
    opacity: 0.8,
  },
  feedcard: {
    borderRadius: 8,
    marginTop: 6,
  },
  addedContainer: {
    paddingTop: 4,
    paddingLeft: 36,
  },
  expandedAuthorsTrigger: {
    zIndex: 1,
  },
  expandedAuthorsCloseBtn: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingTop: 10,
    paddingBottom: 6,
  },
  expandedAuthor: {
    flexDirection: 'row',
    alignItems: 'center',
    marginTop: 10,
    height: EXPANDED_AUTHOR_EL_HEIGHT,
  },
})
