import { Link } from 'expo-router'
import { useCallback, useState } from 'react'
import { Pressable } from 'react-native'
import EmojiText from 'src/components/common/EmojiText'
import UserAvatar from 'src/components/common/UserAvatar'
import ImageComponent from 'src/components/ImageComponent'
import {
  ModerationWarningPanel,
  SeveredRelationshipsPanel,
} from 'src/components/notifications/NotificationPayloadPanels'
import type { Account, Status } from 'src/lib/api-types'
import { haptic } from 'src/lib/haptics'
import {
  isUnread,
  markNotificationReadOnTap,
  useActiveLocalMarker,
} from 'src/lib/notifications/marker'
import { enforceLen, htmlToTextWithLineBreaks, timeAgo } from 'src/utils'
import { Text, useTheme, View, XStack, YStack } from 'tamagui'

type NotificationGroupShape = {
  group_key: string
  type: string
  notifications_count: number
  sample_account_ids: string[]
  status_id?: string | null
  most_recent_notification_id?: string
  latest_page_notification_at?: string
  event?: {
    type?: string
    target_name?: string
    followers_count?: number
    following_count?: number
  }
  moderation_warning?: {
    action?: string
    text?: string
  }
  pf_event?: { name?: string }
  meta?: { status?: string }
  rsvp?: { status?: string }
}

interface Props {
  group: NotificationGroupShape
  accounts: Account[]
  statuses: Status[]
}

const rsvpStatusLabel = (status?: string): string => {
  if (status === 'going') return 'going'
  if (status === 'interested') return 'interested'
  if (status === 'declined') return 'no longer going'
  return 'responding'
}

const verbFor = (group: NotificationGroupShape, status?: Status): string => {
  const eventName = status?.pf_event?.name || group.pf_event?.name || 'an event'
  switch (group.type) {
    case 'favourite':
    case 'like':
      return 'liked your post'
    case 'follow':
      return 'followed you'
    case 'follow_request':
      return 'requested to follow you'
    case 'mention':
      return 'mentioned you'
    case 'reblog':
      return 'shared your post'
    case 'event:rsvp':
      return `is ${rsvpStatusLabel(group.meta?.status || group.rsvp?.status)} to your event`
    case 'event:update':
      return `updated ${eventName}`
    case 'event:cancelled':
      return `cancelled ${eventName}`
    case 'event:comment':
      return `commented on ${eventName}`
    case 'quote':
      return 'quoted your post'
    case 'quoted_update':
      return 'edited a post you quoted'
    case 'severed_relationships':
      return 'Some relationships were severed by moderation action'
    case 'moderation_warning':
      return 'You received a moderation warning'
    case 'poll':
      return 'a poll you voted in has ended'
    case 'status':
      return 'posted a status you subscribed to'
    case 'update':
      return 'edited a post you liked'
    default:
      return group.type
  }
}

const joinActors = (names: string[], extra: number): string => {
  if (names.length === 0) return ''
  if (names.length === 1 && extra === 0) return names[0]
  if (extra > 0) {
    const base = names.slice(0, 2).join(', ')
    const remaining = extra + Math.max(0, names.length - 2)
    return `${base} and ${remaining} ${remaining === 1 ? 'other' : 'others'}`
  }
  if (names.length === 2) return `${names[0]} and ${names[1]}`
  return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`
}

const MENTION_EXCERPT_MAX_CHARS = 120

export default function NotificationGroup({ group, accounts, statuses }: Props) {
  const theme = useTheme()
  const [expanded, setExpanded] = useState(false)
  const marker = useActiveLocalMarker()
  const groupId = group.most_recent_notification_id
    ? String(group.most_recent_notification_id)
    : undefined
  const unread = groupId ? isUnread(groupId, marker) : false

  // Holistic Pass 4 Phase 3.4 — every tap path within the row marks the
  // notification read. Wraps the helper so children can fire onPress without
  // re-deriving the id closure.
  const onMarkRead = useCallback(() => {
    if (!groupId) return
    markNotificationReadOnTap(groupId)
  }, [groupId])

  const sampleAccounts = (group.sample_account_ids ?? [])
    .map((id) => accounts.find((a) => a.id === id))
    .filter((a): a is Account => Boolean(a))

  const displayedAccounts = sampleAccounts.slice(0, 3)
  const total = group.notifications_count ?? sampleAccounts.length
  const remaining = Math.max(0, total - sampleAccounts.length)
  const isGrouped = total > 1 || sampleAccounts.length > 1

  const status = group.status_id
    ? statuses.find((s) => s.id === group.status_id)
    : undefined

  const primaryAccount = sampleAccounts[0]

  // Holistic Pass 4 Phase 3.5 — for mention rows with a status payload,
  // surface a 2-line excerpt of what was said. Mention notifications are
  // never grouped (group.type 'mention' isn't in GROUPED_TYPES) so this
  // always renders against a single-row group; rendering it on the
  // single-actor body is correct.
  const mentionExcerpt =
    group.type === 'mention' && status?.content
      ? enforceLen(
          htmlToTextWithLineBreaks(status.content),
          MENTION_EXCERPT_MAX_CHARS,
          true
        )
      : null

  // Single-actor rows show the actor's display_name (with custom emojis)
  // just like the v1 list did. Grouped rows stay compact with @handles.
  const singleActorName = primaryAccount
    ? enforceLen(primaryAccount.display_name || primaryAccount.acct || '', 25, true)
    : ''
  const actorLabel = isGrouped
    ? joinActors(
        sampleAccounts.map((a) => `@${enforceLen(a.acct, 20, true)}`),
        remaining
      )
    : singleActorName

  const handleToggle = useCallback(() => {
    onMarkRead()
    if (!isGrouped) return
    haptic.selection()
    setExpanded((v) => !v)
  }, [isGrouped, onMarkRead])

  const thumb = status?.media_attachments?.[0]
  const hasImageThumb = thumb?.type === 'image' && thumb.url

  // Destination for the body tap: a status-scoped notification jumps to the
  // post (mentions, reblogs, favourites with context), a follow-typed event
  // jumps to the actor's profile, and moderation payloads stay inert.
  const statusLink =
    status?.id &&
    group.type !== 'severed_relationships' &&
    group.type !== 'moderation_warning'
      ? (`/post/${status.id}` as const)
      : null
  const profileLink =
    !statusLink &&
    primaryAccount &&
    (group.type === 'follow' || group.type === 'follow_request')
      ? (`/profile/${primaryAccount.id}` as const)
      : null
  const singleTapHref = statusLink ?? profileLink

  const textBlockContent = (
    <YStack gap={4} flexShrink={1}>
      <EmojiText
        text={actorLabel}
        emojis={isGrouped ? undefined : primaryAccount?.emojis}
        fontSize="$2"
        fontWeight="bold"
        allowFontScaling={false}
        color={theme.color.val}
      />
      <XStack gap="$1" alignItems="center" flexWrap="wrap">
        <Text fontSize="$2" color={theme.colorSecondary.val} allowFontScaling={false}>
          {verbFor(group, status)}
        </Text>
        {group.latest_page_notification_at ? (
          <Text
            ml="$2"
            fontSize="$2"
            color={theme.colorTertiary.val}
            fontWeight="bold"
            allowFontScaling={false}
          >
            {timeAgo(group.latest_page_notification_at)}
          </Text>
        ) : null}
      </XStack>
      {mentionExcerpt ? (
        <EmojiText
          text={mentionExcerpt}
          emojis={status?.emojis}
          fontSize="$2"
          color={theme.colorSecondary.val}
          allowFontScaling={false}
          numberOfLines={2}
        />
      ) : null}
    </YStack>
  )

  // Single-actor rows make the actor/verb text block tappable — that way the
  // avatar and thumbnail keep their own distinct link targets (profile vs.
  // post) without being swallowed by a row-level Pressable.
  const textBlock =
    !isGrouped && singleTapHref ? (
      <Link href={singleTapHref} asChild>
        <Pressable style={{ flexShrink: 1 }} onPress={onMarkRead}>
          {textBlockContent}
        </Pressable>
      </Link>
    ) : (
      textBlockContent
    )

  const body = (
    <XStack justifyContent="space-between" alignItems="center">
      <XStack gap="$3" alignItems="center" flexShrink={1}>
        <XStack>
          {displayedAccounts.map((a, i) => (
            <View
              key={a.id}
              ml={i === 0 ? 0 : -14}
              borderWidth={2}
              borderColor={theme.background.val}
              borderRadius={999}
            >
              {i === 0 && primaryAccount ? (
                <Link href={`/profile/${a.id}`} asChild>
                  <Pressable onPress={onMarkRead}>
                    <Link.Trigger>
                      <UserAvatar url={a.avatar} size={isGrouped ? 'sm' : undefined} />
                    </Link.Trigger>
                    <Link.Preview style={{ width: 320, height: 420 }} />
                  </Pressable>
                </Link>
              ) : (
                <UserAvatar url={a.avatar} size="sm" />
              )}
            </View>
          ))}
        </XStack>

        {textBlock}
      </XStack>

      {hasImageThumb ? (
        <Link href={`/post/${status!.id}`} asChild>
          <Pressable onPress={onMarkRead}>
            <Link.Trigger>
              <ImageComponent
                placeholder={{ blurhash: thumb!.blurhash || '' }}
                source={{ uri: thumb!.url }}
                recyclingKey={status!.id}
                style={{ width: 50, height: 50, borderRadius: 5 }}
                contentFit="cover"
              />
            </Link.Trigger>
            <Link.Preview style={{ width: 350, height: 450 }} />
          </Pressable>
        </Link>
      ) : null}
    </XStack>
  )

  const panels = (
    <>
      {group.type === 'severed_relationships' ? (
        <SeveredRelationshipsPanel event={group.event} />
      ) : null}
      {group.type === 'moderation_warning' ? (
        <ModerationWarningPanel warning={group.moderation_warning} />
      ) : null}
    </>
  )

  const expandedList =
    expanded && isGrouped && sampleAccounts.length > 1 ? (
      <YStack mt="$2" ml="$8" gap="$1">
        {sampleAccounts.map((a) => (
          <Link key={a.id} href={`/profile/${a.id}`} asChild>
            <Pressable>
              <Text fontSize="$2" color={theme.interactiveActive.val}>
                @{a.acct}
              </Text>
            </Pressable>
          </Link>
        ))}
        {remaining > 0 ? (
          <Text fontSize="$2" color={theme.colorTertiary.val}>
            and {remaining} more
          </Text>
        ) : null}
      </YStack>
    ) : null

  // Grouped rows toggle the actor list inline when tapped anywhere in the
  // row. Single-actor rows delegate tap handling to `textBlock` (status or
  // profile link) so avatar/thumb keep their distinct navigation targets.
  if (isGrouped) {
    return (
      <Pressable onPress={handleToggle}>
        <View
          px="$4"
          py="$2"
          borderLeftWidth={unread ? 3 : 0}
          borderLeftColor={unread ? '$interactiveActive' : 'transparent'}
        >
          {body}
          {panels}
          {expandedList}
        </View>
      </Pressable>
    )
  }

  return (
    <View
      px="$4"
      py="$2"
      borderLeftWidth={unread ? 3 : 0}
      borderLeftColor={unread ? '$interactiveActive' : 'transparent'}
    >
      {body}
      {panels}
    </View>
  )
}
