import {useCallback, useMemo, useState} from 'react'
import {View} from 'react-native'
import {
  ChatBskyConvoDefs,
  moderateProfile,
  type ModerationDecision,
  type ModerationOpts,
} from '@atproto/api'
import {plural} from '@lingui/core/macro'
import {useLingui} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query'

import {GestureActionView} from '#/lib/custom-animations/GestureActionView'
import {useHaptics} from '#/lib/haptics'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {decrementBadgeCount} from '#/lib/notifications/notifications'
import {sanitizeHandle} from '#/lib/strings/handles'
import {
  type Shadow,
  useMaybeProfileShadow,
  useProfileShadow,
} from '#/state/cache/profile-shadow'
import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {
  precacheConvoQuery,
  useMarkAsReadMutation,
} from '#/state/queries/messages/conversation'
import {JOIN_REQUESTS_THRESHOLD} from '#/state/queries/messages/list-join-requests'
import {unstableCacheProfileView} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import * as tokens from '#/alf/tokens'
import {AvatarBubbles} from '#/components/AvatarBubbles'
import {useDialogControl} from '#/components/Dialog'
import {ConvoMenu} from '#/components/dms/ConvoMenu'
import {getMessageInfo} from '#/components/dms/getMessageInfo'
import {getReactionInfo} from '#/components/dms/getReactionInfo'
import {getSystemMessageInfo} from '#/components/dms/getSystemMessageInfo'
import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt'
import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util'
import {Bell2Off_Filled_Corner0_Rounded as BellStrokeIcon} from '#/components/icons/Bell2'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {Envelope_Open_Stroke2_Corner0_Rounded as EnvelopeOpenIcon} from '#/components/icons/EnveopeOpen'
import {Lock_Stroke2_Corner2_Rounded as LockIcon} from '#/components/icons/Lock'
import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash'
import {Link} from '#/components/Link'
import {useMenuControl} from '#/components/Menu'
import {PostAlerts} from '#/components/moderation/PostAlerts'
import {createPortalGroup} from '#/components/Portal'
import {ProfileBadges} from '#/components/ProfileBadges'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE} from '#/env'
import type * as bsky from '#/types/bsky'
import {useIsWithinSplitView} from './splitView/context'

export const ChatListItemPortal = createPortalGroup()

export function ChatListItem({
  convo: convoView,
  showMenu = true,
  selected = false,
  children,
}: {
  convo: ChatBskyConvoDefs.ConvoView
  showMenu?: boolean
  selected?: boolean
  children?: React.ReactNode
}) {
  const {currentAccount} = useSession()
  const moderationOpts = useModerationOpts()

  if (!moderationOpts) {
    return null
  }

  const convo = parseConvoView(convoView, currentAccount?.did)

  switch (convo?.kind) {
    case 'direct': {
      return (
        <DirectChatItem
          convo={convo}
          moderationOpts={moderationOpts}
          showMenu={showMenu}
          selected={selected}>
          {children}
        </DirectChatItem>
      )
    }
    case 'group': {
      return (
        <GroupChatItem
          convo={convo}
          moderationOpts={moderationOpts}
          showMenu={showMenu}
          selected={selected}>
          {children}
        </GroupChatItem>
      )
    }
    default: {
      return null
    }
  }
}

function DirectChatItem({
  convo,
  moderationOpts,
  showMenu,
  selected,
  children,
}: {
  convo: Extract<ConvoWithDetails, {kind: 'direct'}>
  moderationOpts: ModerationOpts
  showMenu?: boolean
  selected?: boolean
  children?: React.ReactNode
}) {
  const {t: l} = useLingui()
  const profile = useProfileShadow(convo.primaryMember)
  const {isWithinLeftPanel} = useIsWithinSplitView()

  const moderation = useMemo(
    () => moderateProfile(profile, moderationOpts),
    [profile, moderationOpts],
  )

  const isDeletedAccount = profile.handle === 'missing.invalid'
  const displayName = isDeletedAccount
    ? l`Deleted Account`
    : createSanitizedDisplayName(profile, true, moderation.ui('displayName'))

  return (
    <BaseChatItem
      convo={convo}
      avatar={
        <PreviewableUserAvatar
          profile={profile}
          size={isWithinLeftPanel ? 48 : 52}
          moderation={moderation.ui('avatar')}
        />
      }
      primaryProfile={profile}
      primaryProfileModeration={moderation}
      title={displayName}
      subtitle={
        isDeletedAccount ? undefined : sanitizeHandle(profile.handle, '@')
      }
      accessibilityHint={
        !isDeletedAccount
          ? l`Go to conversation with ${profile.handle}`
          : l`Go to conversation with a deleted or a deactivated account`
      }
      showMenu={showMenu}
      selected={selected}
      isDeletedAccount={isDeletedAccount}
      isBlockedAccount={moderation.blocked}
      showProfileBadges
      postAlerts={
        isWithinLeftPanel ? null : (
          <PostAlerts
            modui={moderation.ui('contentList')}
            size="sm"
            style={[a.pb_2xs, a.max_w_full, a.overflow_hidden]}
          />
        )
      }>
      {children}
    </BaseChatItem>
  )
}

function GroupChatItem({
  convo,
  moderationOpts,
  showMenu,
  selected,
  children,
}: {
  convo: Extract<ConvoWithDetails, {kind: 'group'}>
  moderationOpts: ModerationOpts
  showMenu?: boolean
  selected?: boolean
  children?: React.ReactNode
}) {
  const {t: l} = useLingui()
  const groupOwner = useMaybeProfileShadow(convo.primaryMember)
  const {isWithinLeftPanel} = useIsWithinSplitView()

  const moderation = useMemo(
    () =>
      groupOwner ? moderateProfile(groupOwner, moderationOpts) : undefined,
    [groupOwner, moderationOpts],
  )

  const chatName = convo.details.name

  return (
    <BaseChatItem
      convo={convo}
      avatar={
        <AvatarBubbles
          profiles={convo.members}
          size={isWithinLeftPanel ? 48 : 52}
        />
      }
      title={chatName}
      accessibilityHint={l`Go to the group chat named "${chatName}"`}
      primaryProfile={groupOwner}
      primaryProfileModeration={moderation}
      isBlockedAccount={false}
      isDeletedAccount={false}
      requestInfo={
        convo.details.unreadJoinRequestCount
          ? convo.details.unreadJoinRequestCount > JOIN_REQUESTS_THRESHOLD
            ? l({
                message: `${JOIN_REQUESTS_THRESHOLD}+ new join requests`,
                context:
                  'Displayed when there are more than 20 requests to join a group chat',
              })
            : plural(convo.details.unreadJoinRequestCount, {
                one: '# new join request',
                other: '# new join requests',
              })
          : undefined
      }
      showProfileBadges={false}
      selected={selected}
      showMenu={showMenu}>
      {children}
    </BaseChatItem>
  )
}

function BaseChatItem({
  convo,
  avatar,
  title,
  subtitle,
  requestInfo,
  accessibilityHint,
  isDeletedAccount,
  isBlockedAccount,
  primaryProfile,
  primaryProfileModeration,
  showMenu,
  selected,
  showProfileBadges,
  postAlerts,
  children,
}: {
  convo: ConvoWithDetails
  avatar: React.ReactNode
  title: string
  subtitle?: string
  requestInfo?: string
  accessibilityHint: string
  isDeletedAccount: boolean
  isBlockedAccount: boolean
  primaryProfile?: Shadow<bsky.profile.AnyProfileView>
  primaryProfileModeration?: ModerationDecision
  showMenu?: boolean
  selected?: boolean
  showProfileBadges: boolean
  postAlerts?: React.ReactNode
  children?: React.ReactNode
}) {
  const ax = useAnalytics()
  const t = useTheme()
  const {t: l, i18n} = useLingui()
  const {currentAccount} = useSession()
  const menuControl = useMenuControl()
  const leaveConvoControl = useDialogControl()
  const {mutate: markAsRead} = useMarkAsReadMutation()

  const enableSquareButtons = useEnableSquareButtons()

  const {gtMobile} = useBreakpoints()
  const {isWithinLeftPanel} = useIsWithinSplitView()

  const playHaptic = useHaptics()
  const queryClient = useQueryClient()
  const hasUnread =
    !isDeletedAccount &&
    (convo.view.unreadCount > 0 ||
      (convo.kind === 'group' &&
        (convo.details.unreadJoinRequestCount ?? 0) > 0)) &&
    (convo.kind !== 'group' || convo.details.lockStatus === 'unlocked')

  const blockInfo = useMemo(() => {
    if (!primaryProfileModeration) return {listBlocks: [], userBlock: undefined}
    const modui = primaryProfileModeration.ui('profileView')
    const blocks = modui.alerts.filter(alert => alert.type === 'blocking')
    const listBlocks = blocks.filter(alert => alert.source.type === 'list')
    const userBlock = blocks.find(alert => alert.source.type === 'user')
    return {
      listBlocks,
      userBlock,
    }
  }, [primaryProfileModeration])

  const isDimStyle =
    convo.view.muted ||
    isBlockedAccount ||
    isDeletedAccount ||
    (convo.kind === 'group' && convo.details.lockStatus !== 'unlocked')

  const {lastMessage, LastMessageIcon, lastMessageSentAt} = useMemo(() => {
    let lastMessage = l`No messages yet`

    let LastMessageIcon: React.ComponentType<SVGIconProps> | null = null

    let lastMessageSentAt: string | null = null

    // Deleted message
    if (ChatBskyConvoDefs.isDeletedMessageView(convo.view.lastMessage)) {
      lastMessageSentAt = convo.view.lastMessage.sentAt

      lastMessage = isDeletedAccount
        ? l`Conversation deleted`
        : l`Message deleted`
    }

    // Message
    if (ChatBskyConvoDefs.isMessageView(convo.view.lastMessage)) {
      const info = getMessageInfo({
        convo: convo.view,
        currentAccountDid: currentAccount?.did,
        i18n,
      })
      if (info) {
        lastMessage = info.isBlockedMessage
          ? l`This message is hidden`
          : (info.message ?? lastMessage)
        lastMessageSentAt = info.sentAt
      }
    }

    // Reaction
    if (ChatBskyConvoDefs.isMessageAndReactionView(convo.view.lastReaction)) {
      const info = getReactionInfo({
        convo: convo.view,
        currentAccountDid: currentAccount?.did,
        i18n,
      })
      if (
        info &&
        (!lastMessageSentAt ||
          new Date(lastMessageSentAt) < new Date(info.createdAt))
      ) {
        lastMessage = info.message
        lastMessageSentAt = info.createdAt
      }
    }

    // System message
    if (ChatBskyConvoDefs.isSystemMessageView(convo.view.lastMessage)) {
      const info = getSystemMessageInfo(
        convo.view.lastMessage.data,
        new Map(convo.view.members.map(m => [m.did, m])),
        {short: true},
      )
      if (info) {
        lastMessage = i18n._(info.message)
        LastMessageIcon = info.Icon
        lastMessageSentAt = convo.view.lastMessage.sentAt
      }
    }

    // Chat locked - override message
    if (convo.kind === 'group' && convo.details.lockStatus !== 'unlocked') {
      lastMessage = l`This chat is locked`
      LastMessageIcon = LockIcon
    }

    return {
      lastMessage,
      LastMessageIcon,
      lastMessageSentAt,
    }
  }, [l, convo, currentAccount?.did, isDeletedAccount, i18n])

  const [showActions, setShowActions] = useState(false)

  const onMouseEnter = useCallback(() => {
    setShowActions(true)
  }, [])

  const onMouseLeave = useCallback(() => {
    setShowActions(false)
  }, [])

  const onFocus = useCallback<React.FocusEventHandler>(e => {
    if (e.nativeEvent.relatedTarget == null) return
    setShowActions(true)
  }, [])

  const onPress = useCallback(() => {
    for (const member of convo.view.members) {
      unstableCacheProfileView(queryClient, member)
    }
    precacheConvoQuery(queryClient, convo.view)
    void decrementBadgeCount(convo.view.unreadCount)
    ax.metric('chat:open', {logContext: 'ChatsList'})
  }, [ax, queryClient, convo])

  const onLongPress = useCallback(() => {
    playHaptic()
    menuControl.open()
  }, [playHaptic, menuControl])

  const markReadAction = {
    threshold: 120,
    color: t.palette.primary_500,
    icon: EnvelopeOpenIcon,
    action: () => {
      markAsRead({
        convoId: convo.view.id,
      })
    },
  }

  const deleteAction = {
    threshold: 225,
    color: t.palette.negative_500,
    icon: TrashIcon,
    action: () => {
      leaveConvoControl.open()
    },
  }

  const isGroupConvo = convo.kind === 'group'

  const actions = hasUnread
    ? {
        leftFirst: markReadAction,
        leftSecond: deleteAction,
      }
    : {
        leftFirst: deleteAction,
      }

  const avatarSize = isWithinLeftPanel ? 48 : 52

  return (
    <ChatListItemPortal.Provider>
      <GestureActionView actions={actions}>
        <View
          onMouseEnter={onMouseEnter}
          onMouseLeave={onMouseLeave}
          // @ts-expect-error web only
          onFocus={onFocus}
          onBlur={onMouseLeave}
          style={[a.relative, t.atoms.bg, isWithinLeftPanel && a.mx_sm]}>
          <View
            style={[
              a.z_10,
              a.absolute,
              {top: tokens.space.md, left: tokens.space.lg},
              isGroupConvo && a.pointer_events_none,
            ]}>
            {avatar}
          </View>

          <Link
            to={`/messages/${convo.view.id}`}
            // In split view, this list stays mounted alongside the open convo,
            // so push would stack duplicate routes on repeated clicks.
            action={isWithinLeftPanel ? 'navigate' : 'push'}
            label={title}
            accessibilityHint={accessibilityHint}
            accessibilityActions={
              showMenu && IS_NATIVE
                ? [
                    {
                      name: 'magicTap',
                      label: l`Open conversation options`,
                    },
                    {
                      name: 'longpress',
                      label: l`Open conversation options`,
                    },
                  ]
                : undefined
            }
            onPressIn={() => precacheConvoQuery(queryClient, convo.view)}
            onPress={onPress}
            onLongPress={showMenu && IS_NATIVE ? onLongPress : undefined}
            onAccessibilityAction={showMenu ? onLongPress : undefined}>
            {({hovered, pressed, focused}) => (
              <View
                style={[
                  a.flex_row,
                  isDeletedAccount || isGroupConvo
                    ? a.align_center
                    : a.align_start,
                  a.flex_1,
                  a.px_lg,
                  a.py_md,
                  a.gap_md,
                  isWithinLeftPanel && [a.rounded_sm, a.mt_2xs],
                  {
                    backgroundColor: hasUnread
                      ? t.palette.primary_25
                      : t.palette.contrast_0,
                  },
                  (hovered || pressed || focused) && t.atoms.bg_contrast_25,
                  selected && t.atoms.bg_contrast_50,
                ]}>
                {/* Avatar goes here */}
                <View style={{width: avatarSize, height: avatarSize}} />

                <View
                  style={[a.flex_1, a.justify_center, web({paddingRight: 40})]}>
                  <View
                    style={[a.w_full, a.flex_row, a.align_center, a.pb_2xs]}>
                    <View style={[a.flex_shrink]}>
                      <Text
                        emoji
                        numberOfLines={1}
                        style={[
                          a.text_md,
                          t.atoms.text,
                          a.font_semi_bold,
                          {lineHeight: 21},
                          isDimStyle && t.atoms.text_contrast_medium,
                        ]}>
                        {title}
                      </Text>
                    </View>

                    {showProfileBadges && primaryProfile && (
                      <ProfileBadges
                        profile={primaryProfile}
                        size="sm"
                        style={[a.pl_xs, a.self_center]}
                      />
                    )}

                    {lastMessageSentAt && (
                      <View style={[a.pl_xs]}>
                        <TimeElapsed timestamp={lastMessageSentAt}>
                          {({timeElapsed}) => (
                            <Text
                              style={[
                                a.text_sm,
                                {lineHeight: 21},
                                t.atoms.text_contrast_medium,
                                web({whiteSpace: 'preserve nowrap'}),
                              ]}>
                              {timeElapsed}
                            </Text>
                          )}
                        </TimeElapsed>
                      </View>
                    )}
                    {(convo.view.muted || isBlockedAccount) && (
                      <Text
                        style={[
                          a.text_sm,
                          {lineHeight: 21},
                          t.atoms.text_contrast_medium,
                          web({whiteSpace: 'preserve nowrap'}),
                        ]}>
                        {' '}
                        <BellStrokeIcon
                          size="xs"
                          style={[t.atoms.text_contrast_medium]}
                        />
                      </Text>
                    )}
                    {hasUnread && (
                      <View
                        style={[
                          enableSquareButtons ? a.rounded_sm : a.rounded_full,
                          {
                            backgroundColor: isDimStyle
                              ? t.palette.contrast_200
                              : t.palette.primary_500,
                            height: 8,
                            width: 8,
                            marginLeft: 6,
                          },
                          web({whiteSpace: 'preserve nowrap'}),
                        ]}
                      />
                    )}
                  </View>

                  {subtitle && (
                    <Text
                      numberOfLines={1}
                      style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}
                      emoji>
                      {subtitle}
                    </Text>
                  )}

                  {postAlerts}

                  {requestInfo && (
                    <Text
                      numberOfLines={1}
                      style={[
                        hasUnread ? a.font_medium : t.atoms.text_contrast_high,
                        isDimStyle && t.atoms.text_contrast_medium,
                        a.pb_2xs,
                      ]}
                      emoji>
                      {requestInfo}
                    </Text>
                  )}

                  <View style={[a.flex_row, a.align_center]}>
                    {LastMessageIcon && (
                      <LastMessageIcon
                        size="xs"
                        style={[
                          a.mr_2xs,
                          hasUnread
                            ? t.atoms.text_contrast_high
                            : t.atoms.text_contrast_medium,
                        ]}
                      />
                    )}
                    <Text
                      emoji
                      numberOfLines={2}
                      style={[
                        hasUnread ? a.font_medium : t.atoms.text_contrast_high,
                        isDimStyle && t.atoms.text_contrast_medium,
                      ]}>
                      {lastMessage}
                    </Text>
                  </View>

                  {children}
                </View>
              </View>
            )}
          </Link>

          <ChatListItemPortal.Outlet />

          {/* TODO: Allow showing menu for groups where the owner has left! */}
          {showMenu && primaryProfile && (
            <ConvoMenu
              convo={convo}
              profile={primaryProfile}
              control={menuControl}
              currentScreen="list"
              showMarkAsRead={convo.view.unreadCount > 0}
              hideTrigger={IS_NATIVE}
              blockInfo={blockInfo}
              style={[
                a.absolute,
                a.h_full,
                a.self_end,
                a.justify_center,
                {
                  right: tokens.space.lg,
                  opacity:
                    !gtMobile || showActions || menuControl.isOpen ? 1 : 0,
                },
              ]}
            />
          )}

          <LeaveConvoPrompt
            control={leaveConvoControl}
            convoId={convo.view.id}
            currentScreen="list"
          />
        </View>
      </GestureActionView>
    </ChatListItemPortal.Provider>
  )
}
