import {useEffect, useState} from 'react'
import {Pressable, View} from 'react-native'
import {
  ChatBskyActorDefs,
  ChatBskyConvoDefs,
  ChatBskyConvoUnlockConvo,
  type ModerationOpts,
} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'

import {HITSLOP_10} from '#/lib/constants'
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
import {useCallOnce} from '#/lib/once'
import {
  type CommonNavigatorParams,
  type NativeStackScreenProps,
  type NavigationProp,
} from '#/lib/routes/types'
import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useConvoQuery} from '#/state/queries/messages/conversation'
import {useEditGroupChatName} from '#/state/queries/messages/edit-group-chat-name'
import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
import {useListConvoMembersQuery} from '#/state/queries/messages/list-convo-members'
import {useListJoinRequestsQuery} from '#/state/queries/messages/list-join-requests'
import {useLockConvo} from '#/state/queries/messages/lock-conversation'
import {useMuteConvo} from '#/state/queries/messages/mute-conversation'
import {useSession} from '#/state/session'
import {List} from '#/view/com/util/List'
import {atoms as a, useTheme} from '#/alf'
import {AvatarBubbles} from '#/components/AvatarBubbles'
import {Button, type ButtonColor, ButtonIcon} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {AfterReportConversationDialog} from '#/components/dms/AfterReportConversationDialog'
import {ReportConversationDialog} from '#/components/dms/ReportConversationDialog'
import {
  type ConvoWithDetails,
  type GroupConvoMember,
  parseConvoView,
} from '#/components/dms/util'
import {Error} from '#/components/Error'
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeftIcon} from '#/components/icons/ArrowBoxLeft'
import {
  Bell2_Stroke2_Corner0_Rounded as BellIcon,
  Bell2Off_Stroke2_Corner0_Rounded as BellOffIcon,
} from '#/components/icons/Bell2'
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
import {type Props as SVGIconProps} from '#/components/icons/common'
import {EditBig_Stroke2_Corner2_Rounded as EditIcon} from '#/components/icons/EditBig'
import {Flag_Stroke2_Corner0_Rounded as FlagIcon} from '#/components/icons/Flag'
import {Lock_Stroke2_Corner0_Rounded as LockIcon} from '#/components/icons/Lock'
import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
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'
import {InviteLinkDialog} from '../components/InviteLinkDialog'
import {AddMembersLink} from './AddMembersLink'
import {Member, MemberPlaceholder} from './Member'
import {MembersAndRequests} from './MembersAndRequests'
import {
  EditNamePrompt,
  LeaveAndLockChatPrompt,
  LeaveChatPrompt,
  LockChatPrompt,
} from './prompts'

type Item =
  | {type: 'MEMBERS_AND_REQUESTS'; key: string}
  | {type: 'ADD_MEMBERS_LINK'; key: string}
  | {
      type: 'CHAT_MEMBER'
      key: string
      profile: GroupConvoMember
      status: 'owner' | 'standard'
    }
  | {
      type: 'CHAT_MEMBER_PLACEHOLDER'
      key: string
    }

type Props = NativeStackScreenProps<
  CommonNavigatorParams,
  'MessagesConversationSettings'
>

export function MessagesConversationSettingsScreen({route}: Props) {
  const navigation = useNavigation<NavigationProp>()

  const convoId = route.params.conversation

  return (
    <Layout.Screen>
      <Layout.Header.Outer>
        <Layout.Header.BackButton
          onPress={evt => {
            if (IS_WEB && !navigation.canGoBack()) {
              evt.preventDefault()
              navigation.navigate('MessagesConversation', {
                conversation: convoId,
              })
            }
          }}
        />
        <Layout.Header.Content>
          <Layout.Header.TitleText>
            <Trans>Group chat settings</Trans>
          </Layout.Header.TitleText>
        </Layout.Header.Content>
        <Layout.Header.Slot />
      </Layout.Header.Outer>
      <SettingsInner convoId={convoId} />
    </Layout.Screen>
  )
}

function SettingsInner({convoId}: {convoId: string}) {
  const {t: l} = useLingui()
  const navigation = useNavigation<NavigationProp>()
  const moderationOpts = useModerationOpts()
  const {currentAccount} = useSession()
  const {data: convoData, error, refetch} = useConvoQuery({convoId})

  const convo = convoData
    ? parseConvoView(convoData, currentAccount?.did)
    : null

  if (error) {
    return (
      <Error
        title={l`Something went wrong`}
        message={l`We couldn’t load this conversation’s settings`}
        onRetry={() => refetch()}
      />
    )
  }

  if (!convo || !moderationOpts) {
    return (
      <View style={[a.flex_1, a.align_center, a.justify_center]}>
        <Loader size="xl" />
      </View>
    )
  }

  if (convo.kind !== 'group') {
    return (
      <Error
        title={l`Wrong kind of conversation`}
        message={l`This screen is only available for group conversations.`}
        onGoBack={() => {
          if (navigation.canGoBack()) {
            navigation.goBack()
          } else {
            navigation.replace('Messages', {animation: 'pop'})
          }
        }}
      />
    )
  }

  return <GroupSettings convo={convo} moderationOpts={moderationOpts} />
}

function keyExtractor(item: Item) {
  return item.key
}

function isGroupMember(
  member: ChatBskyActorDefs.ProfileViewBasic,
): member is GroupConvoMember {
  // Kind is missing when the account has been deleted.
  return (
    member.kind === undefined ||
    bsky.dangerousIsType<ChatBskyActorDefs.GroupConvoMember>(
      member.kind,
      ChatBskyActorDefs.isGroupConvoMember,
    )
  )
}

function GroupSettings({
  convo,
  moderationOpts,
}: {
  convo: Extract<ConvoWithDetails, {kind: 'group'}>
  moderationOpts: ModerationOpts
}) {
  const [isPTRing, setIsPTRing] = useState(false)

  const initialNumToRender = useInitialNumToRender({minItemHeight: 68})
  const bottomBarOffset = useBottomBarOffset()

  const {currentAccount} = useSession()

  const primaryMember = convo.primaryMember
  const isOwner = !!primaryMember && primaryMember.did === currentAccount?.did

  const {data: memberListData = [], refetch} = useListConvoMembersQuery({
    convoId: convo.view.id,
    placeholderData: convo.members,
  })

  const {data: joinRequestsData, hasNextPage: hasMoreRequests} =
    useListJoinRequestsQuery({
      convoId: convo.view.id,
      enabled: isOwner,
    })
  const requestCount =
    joinRequestsData?.pages.reduce(
      (sum, page) => sum + page.requests.length,
      0,
    ) ?? 0

  const groupMembers = memberListData.filter(isGroupMember).sort((a, b) => {
    const aIsOwner = a.did === primaryMember?.did
    const bIsOwner = b.did === primaryMember?.did
    const aIsSelf = a.did === currentAccount?.did
    const bIsSelf = b.did === currentAccount?.did
    if (aIsOwner !== bIsOwner) return aIsOwner ? -1 : 1
    if (aIsSelf !== bIsSelf) return aIsSelf ? -1 : 1
    // Surface blocked members to the owner so they can be removed.
    if (isOwner) {
      const aBlocked = !!isBlockedOrBlocking(a)
      const bBlocked = !!isBlockedOrBlocking(b)
      if (aBlocked !== bBlocked) return aBlocked ? -1 : 1
    }
    return 0
  })

  const items: Item[] = [
    {
      type: 'MEMBERS_AND_REQUESTS',
      key: 'members-and-requests',
    },
    ...(isOwner && convo.details.lockStatus === 'unlocked'
      ? [{type: 'ADD_MEMBERS_LINK', key: 'add-members-link'} as const]
      : []),
  ]
  items.push(
    ...groupMembers.map(
      (profile): Item => ({
        type: 'CHAT_MEMBER',
        key: profile.did,
        profile,
        status: primaryMember?.did === profile.did ? 'owner' : 'standard',
      }),
    ),
  )
  const placeholderCount = Math.max(
    0,
    convo.details.memberCount - groupMembers.length,
  )
  for (let i = 0; i < placeholderCount; i++) {
    items.push({
      type: 'CHAT_MEMBER_PLACEHOLDER',
      key: `chat-member-placeholder-${i}`,
    })
  }

  function renderItem({item}: {item: Item}) {
    switch (item.type) {
      case 'MEMBERS_AND_REQUESTS':
        return (
          <MembersAndRequests
            convo={convo}
            requestCount={requestCount}
            hasMoreRequests={!!hasMoreRequests}
            isOwner={isOwner}
          />
        )
      case 'ADD_MEMBERS_LINK':
        return <AddMembersLink convo={convo} />
      case 'CHAT_MEMBER':
        return (
          <Member
            convo={convo}
            profile={item.profile}
            status={item.status}
            isOwner={isOwner}
          />
        )
      case 'CHAT_MEMBER_PLACEHOLDER':
        return <MemberPlaceholder />
      default:
        return null
    }
  }

  const onRefresh = async () => {
    setIsPTRing(true)
    try {
      await refetch()
    } catch (err) {
      logger.error('Failed to refresh group chat members', {message: err})
    }
    setIsPTRing(false)
  }

  return (
    <List
      data={items}
      contentContainerStyle={{
        paddingBottom: bottomBarOffset + a.pb_xl.paddingBottom,
      }}
      desktopFixedHeight
      initialNumToRender={initialNumToRender}
      keyExtractor={keyExtractor}
      ListHeaderComponent={
        <SettingsHeader
          convo={convo}
          isOwner={isOwner}
          moderationOpts={moderationOpts}
        />
      }
      renderItem={renderItem}
      sideBorders={false}
      windowSize={11}
      refreshing={isPTRing}
      onRefresh={() => void onRefresh()}
    />
  )
}

function SettingsHeader({
  convo,
  isOwner,
  moderationOpts,
}: {
  convo: Extract<ConvoWithDetails, {kind: 'group'}>
  isOwner: boolean
  moderationOpts: ModerationOpts
}) {
  const t = useTheme()
  const {i18n, t: l} = useLingui()
  const ax = useAnalytics()

  const navigation = useNavigation<NavigationProp>()

  const convoId = convo.view.id

  const groupName = convo.details.name
  const [newGroupName, setNewGroupName] = useState(groupName)

  const lockStatus = convo.details.lockStatus

  const {joinLink} = convo.details
  const isJoinLinkEnabled = isOwner || joinLink?.enabledStatus === 'enabled'

  const reportSubjectDid = convo.primaryMember?.did

  const logViewOnce = useCallOnce()
  useEffect(() => {
    logViewOnce(() => {
      ax.metric('groupchat:settings:view', {convoId, isOwner})
    })
  }, [ax, convoId, isOwner, logViewOnce])

  const {mutate: editGroupName, isPending: isEditingName} =
    useEditGroupChatName(convoId, {
      onSuccess: () => {
        ax.metric('groupchat:owner:editName', {convoId})
        Toast.show(l({message: 'Group chat name updated', context: 'toast'}))
      },
      onError: e => {
        setNewGroupName(groupName)
        logger.error('Failed to edit group chat name', {message: e})
        Toast.show(l`Failed to edit group chat name`, {type: 'error'})
      },
    })

  const {mutate: muteConvo, isPending: isMuting} = useMuteConvo(convoId, {
    onSuccess: data => {
      if (data.convo.muted) {
        ax.metric('groupchat:mute', {convoId})
        Toast.show(l({message: 'Group chat muted', context: 'toast'}))
      } else {
        ax.metric('groupchat:unmute', {convoId})
        Toast.show(l({message: 'Group chat unmuted', context: 'toast'}))
      }
    },
    onError: e => {
      logger.error('Failed to mute group chat', {message: e})
      Toast.show(l`Failed to mute group chat`, {type: 'error'})
    },
  })

  const {mutate: leaveConvo, isPending: isLeaving} = useLeaveConvo(convoId, {
    onSuccess: () => {
      ax.metric('groupchat:leave', {convoId, isOwner})
      navigation.replace('Messages', {animation: 'pop'})
    },
    onError: e => {
      logger.error('Failed to leave group chat', {message: e})
      Toast.show(l({message: 'Failed to leave group chat', context: 'toast'}), {
        type: 'error',
      })
    },
  })

  const {
    mutate: lockConvo,
    mutateAsync: lockConvoAsync,
    isPending: isLocking,
  } = useLockConvo(convoId, {
    onSuccess: (data, {silent}) => {
      if (!ChatBskyConvoDefs.isGroupConvo(data.convo.kind)) return
      if (silent) return
      if (data.convo.kind.lockStatus === 'locked') {
        ax.metric('groupchat:owner:lock', {convoId})
        Toast.show(l({message: 'Group chat locked', context: 'toast'}))
      } else {
        ax.metric('groupchat:owner:unlock', {convoId})
        Toast.show(l({message: 'Group chat unlocked', context: 'toast'}))
      }
    },
    onError: (e, {lock}) => {
      if (lock) {
        logger.error('Failed to lock group chat', {message: e})
        Toast.show(l`Failed to lock group chat`, {type: 'error'})
      } else if (
        e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError
      ) {
        Toast.show(l`This chat is locked by a moderation action`, {
          type: 'error',
        })
      } else {
        logger.error('Failed to unlock group chat', {message: e})
        Toast.show(l`Failed to unlock group chat`, {type: 'error'})
      }
    },
  })

  const leaveAndLockConvo = async () => {
    try {
      if (lockStatus === 'unlocked') {
        await lockConvoAsync({lock: true, silent: true})
      }
    } catch {
      // Handled by onError in useLockConvo
      return
    }
    // Owners can only leave a locked chat
    leaveConvo()
  }

  const inviteLinkDialog = Dialog.useDialogControl()
  const editNamePrompt = Prompt.usePromptControl()
  const lockChatPrompt = Prompt.usePromptControl()
  const leaveChatPrompt = Prompt.usePromptControl()
  const leaveAndLockChatPrompt = Prompt.usePromptControl()
  const reportControl = Prompt.usePromptControl()
  const deleteControl = Prompt.usePromptControl()

  const createdAt = new Date(convo.details.createdAt)

  // A lock forced by a moderation action cannot be undone by the owner.
  const canLockGroupChat =
    isOwner &&
    lockStatus !== 'locked-permanently' &&
    !convo.details.lockStatusModerationOverride

  const groupNameComponent = (
    <Text
      style={[a.text_2xl, a.font_bold, a.text_center, a.pt_lg, t.atoms.text]}>
      {groupName}
    </Text>
  )

  return (
    <>
      <View
        style={[a.px_xl, a.py_4xl, a.border_b, t.atoms.border_contrast_low]}>
        <View style={[a.align_center, a.justify_center]}>
          <AvatarBubbles profiles={convo.members} />
        </View>
        {isOwner ? (
          <Pressable
            accessibilityRole="button"
            accessibilityHint={l`Edit this group chat’s name`}
            hitSlop={HITSLOP_10}
            onPress={() => {
              setNewGroupName(groupName)
              editNamePrompt.open()
            }}>
            {groupNameComponent}
          </Pressable>
        ) : (
          groupNameComponent
        )}
        <Text
          style={[
            a.text_sm,
            a.text_center,
            a.pt_xs,
            a.px_xl,
            t.atoms.text_contrast_high,
          ]}>
          <Trans>
            Created{' '}
            {i18n.date(createdAt, {
              month: 'long',
              day: 'numeric',
              year: 'numeric',
            })}
          </Trans>
        </Text>
        <View
          style={[
            a.flex_row,
            a.align_center,
            a.justify_center,
            a.gap_2xl,
            a.pt_2xl,
            a.flex_wrap,
          ]}>
          <SettingsButton
            color={convo.view.muted ? 'negative_subtle' : 'secondary'}
            disabled={isMuting || lockStatus !== 'unlocked'}
            icon={convo.view.muted ? BellOffIcon : BellIcon}
            label={
              convo.view.muted
                ? l`Unmute this group chat`
                : l`Mute this group chat`
            }
            text={convo.view.muted ? l`Muted` : l`Mute`}
            onPress={() => muteConvo({mute: !convo.view.muted})}
          />
          {isOwner ? (
            <SettingsButton
              disabled={isEditingName || lockStatus !== 'unlocked'}
              icon={EditIcon}
              label={l`Edit this group chat’s name`}
              text={l`Edit name`}
              onPress={() => {
                setNewGroupName(groupName)
                editNamePrompt.open()
              }}
            />
          ) : null}
          {isJoinLinkEnabled ? (
            <SettingsButton
              disabled={lockStatus !== 'unlocked'}
              icon={ChainLinkIcon}
              label={
                isOwner
                  ? l`Create or modify an invite link for this group chat`
                  : l`View the invite link for this group chat`
              }
              text={l`Invite link`}
              onPress={inviteLinkDialog.open}
            />
          ) : null}
          {canLockGroupChat ? (
            <SettingsButton
              color={lockStatus === 'locked' ? 'negative_subtle' : 'secondary'}
              disabled={isLocking}
              icon={LockIcon}
              label={
                lockStatus === 'locked'
                  ? l`Unlock this group chat`
                  : l`Lock this group chat`
              }
              text={lockStatus === 'locked' ? l`Locked` : l`Lock`}
              onPress={
                lockStatus === 'locked'
                  ? () => lockConvo({lock: false})
                  : lockChatPrompt.open
              }
            />
          ) : null}
          {!isOwner && reportSubjectDid ? (
            <SettingsButton
              icon={FlagIcon}
              label={l`Report this group chat`}
              text={l`Report`}
              onPress={reportControl.open}
            />
          ) : null}
          <SettingsButton
            disabled={isLeaving || (isOwner && isLocking)}
            icon={ArrowBoxLeftIcon}
            label={l`Leave this group chat`}
            text={l`Leave`}
            onPress={
              isOwner ? leaveAndLockChatPrompt.open : leaveChatPrompt.open
            }
          />
        </View>
      </View>
      <EditNamePrompt
        control={editNamePrompt}
        value={newGroupName}
        onChangeText={setNewGroupName}
        onConfirm={() => editGroupName({name: newGroupName})}
      />
      {convo.primaryMember && (
        <InviteLinkDialog
          convo={convo}
          owner={convo.primaryMember}
          control={inviteLinkDialog}
          isOwner={isOwner}
          moderationOpts={moderationOpts}
        />
      )}
      <LockChatPrompt
        control={lockChatPrompt}
        onConfirm={() => lockConvo({lock: true})}
      />
      <LeaveChatPrompt
        control={leaveChatPrompt}
        groupName={groupName}
        onConfirm={leaveConvo}
      />
      <LeaveAndLockChatPrompt
        control={leaveAndLockChatPrompt}
        groupName={groupName}
        onConfirm={() => {
          void leaveAndLockConvo()
        }}
      />
      {reportSubjectDid ? (
        <>
          <ReportConversationDialog
            control={reportControl}
            convoId={convo.view.id}
            did={reportSubjectDid}
            onAfterSubmit={deleteControl.open}
          />
          <AfterReportConversationDialog
            control={deleteControl}
            currentScreen="conversation"
            params={{
              convoId: convo.view.id,
              did: reportSubjectDid,
            }}
          />
        </>
      ) : null}
    </>
  )
}

function SettingsButton({
  color = 'secondary',
  disabled,
  icon,
  label,
  text,
  onPress,
}: {
  color?: ButtonColor
  disabled?: boolean
  icon: React.ComponentType<SVGIconProps>
  label: string
  text: string
  onPress: () => void
}) {
  const t = useTheme()

  return (
    <View style={[a.align_center]}>
      <Button
        color={color}
        disabled={disabled}
        size="large"
        shape="round"
        label={label}
        onPress={onPress}
        style={[
          {
            width: 48,
            height: 48,
          },
        ]}>
        <ButtonIcon icon={icon} size="md" />
      </Button>
      <Text
        numberOfLines={1}
        maxFontSizeMultiplier={2.5}
        style={[
          a.text_xs,
          a.font_medium,
          a.text_center,
          a.pt_xs,
          t.atoms.text,
        ]}>
        {text}
      </Text>
    </View>
  )
}
