import {
  useCallback,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
} from 'react'
import {LayoutAnimation, type TextInput, View} from 'react-native'
import {Trans, useLingui} from '@lingui/react/macro'

import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {useListConvoMembersQuery} from '#/state/queries/messages/list-convo-members'
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
import {useSession} from '#/state/session'
import {type ListMethods} from '#/view/com/util/List'
import {android, atoms as a, native, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {canBeAddedToGroup, type ConvoWithDetails} from '#/components/dms/util'
import * as Toggle from '#/components/forms/Toggle'
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {IS_NATIVE, IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky'
import {ChatProfileTabs} from './ChatProfileTabs'
import {EmptyMemberList} from './components/EmptyMemberList'
import {GroupChatProfileCard} from './components/GroupChatProfileCard'
import {ProfileCardSkeleton} from './components/ProfileCardSkeleton'
import {UserLabel} from './components/UserLabel'
import {UserSearchInput} from './components/UserSearchInput'

type LabelItem = {
  type: 'label'
  key: string
  message: string
}

type ProfileItem = {
  type: 'profile'
  key: string
  profile: bsky.profile.AnyProfileView
}

type EmptyItem = {
  type: 'empty'
  key: string
  message: string
}

type PlaceholderItem = {
  type: 'placeholder'
  key: string
}

type LoadingItem = {
  type: 'loading'
  key: string
}

type Item = LabelItem | ProfileItem | EmptyItem | PlaceholderItem | LoadingItem

export type State = {
  groupChatDids: string[]
  groupChatProfiles: bsky.profile.AnyProfileView[]
  searchText: string
}

export type Action =
  | {
      type: 'setDids'
      groupChatDids: string[]
      groupChatProfiles: bsky.profile.AnyProfileView[]
    }
  | {
      type: 'removeDids'
      groupChatDids: string[]
      groupChatProfiles: bsky.profile.AnyProfileView[]
    }
  | {
      type: 'setSearchText'
      searchText: string
    }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'setDids': {
      return {
        ...state,
        groupChatDids: action.groupChatDids,
        groupChatProfiles: action.groupChatProfiles,
        searchText: '',
      }
    }
    case 'removeDids': {
      return {
        ...state,
        groupChatDids: action.groupChatDids,
        groupChatProfiles: action.groupChatProfiles,
      }
    }
    case 'setSearchText': {
      return {
        ...state,
        searchText: action.searchText,
      }
    }
  }
}

export function AddMembersFlow({
  convo,
  title,
  onAddMembers,
}: {
  convo: Extract<ConvoWithDetails, {kind: 'group'}>
  title: string
  onAddMembers: (
    dids: string[],
    profiles: bsky.profile.AnyProfileView[],
  ) => void
}) {
  const t = useTheme()
  const {t: l} = useLingui()
  const moderationOpts = useModerationOpts()
  const {currentAccount} = useSession()

  const control = Dialog.useDialogContext()

  const [headerHeight, setHeaderHeight] = useState(0)
  const [footerHeight, setFooterHeight] = useState(0)

  const listRef = useRef<ListMethods>(null)
  const inputRef = useRef<TextInput>(null)

  const [{groupChatDids, groupChatProfiles, searchText}, dispatch] = useReducer(
    reducer,
    {
      groupChatDids: [],
      groupChatProfiles: [],
      searchText: '',
    },
  )

  const {
    data: autocompleteResults,
    isError,
    isFetching: isAutocompleteFetching,
  } = useActorAutocompleteQuery(searchText, true, 12)
  const {data: follows} = useProfileFollowsQuery(currentAccount?.did)
  const {data: memberListData = [], isPending: isMemberListPending} =
    useListConvoMembersQuery({
      convoId: convo.view.id,
      placeholderData: convo.members,
    })
  const memberDidSet = useMemo(
    () => new Set(memberListData.map(profile => profile.did)),
    [memberListData],
  )

  // The existing members (including the viewer) already occupy slots, so the
  // number of people that can still be added is whatever's left.
  const remainingSlots = Math.max(
    0,
    convo.details.memberLimit - memberListData.length,
  )

  const onRemoveDid = useCallback(
    (did: string) => {
      LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
      dispatch({
        type: 'removeDids',
        groupChatDids: groupChatDids.filter(d => d !== did),
        groupChatProfiles: groupChatProfiles.filter(
          profile => profile.did !== did,
        ),
      })
    },
    [groupChatDids, groupChatProfiles],
  )

  const items = useMemo<Item[]>(() => {
    if (isMemberListPending) {
      // Still fetching chat member DIDs for filtering, so force the loading state.
      return []
    }

    const _items: Item[] = []

    if (isError) {
      _items.push({
        type: 'empty',
        key: 'empty',
        message: l`We’re having network issues, try again`,
      })
    } else if (searchText.length) {
      if (autocompleteResults?.length) {
        for (const profile of autocompleteResults) {
          if (
            profile.did === currentAccount?.did ||
            memberDidSet.has(profile.did)
          )
            continue
          _items.push({
            type: 'profile',
            key: profile.did,
            profile,
          })
        }

        _items.sort(item => {
          return item.type === 'profile' && canBeAddedToGroup(item.profile)
            ? -1
            : 1
        })
      }
    } else {
      if (follows) {
        for (const page of follows.pages) {
          for (const profile of page.follows) {
            if (!canBeAddedToGroup(profile)) continue
            _items.push({
              type: 'profile',
              key: profile.did,
              profile,
            })
          }
        }
      } else {
        for (let i = 0; i < 10; i++) {
          _items.push({type: 'placeholder', key: i + ''})
        }
      }
    }

    if (searchText === '' && _items.length > 0) {
      _items.unshift({
        type: 'label',
        key: 'suggested',
        message: l`Suggested`,
      })
    }

    if (searchText && isAutocompleteFetching && _items.length > 0) {
      // Stale results are still showing while autocomplete refetches -
      // append an inline indicator so the user sees that work is happening.
      _items.push({type: 'loading', key: 'loading'})
    } else if (
      searchText &&
      !isAutocompleteFetching &&
      !_items.length &&
      !isError
    ) {
      _items.push({type: 'empty', key: 'empty', message: l`No results`})
    }

    return _items
  }, [
    autocompleteResults,
    currentAccount?.did,
    follows,
    isAutocompleteFetching,
    isError,
    isMemberListPending,
    l,
    memberDidSet,
    searchText,
  ])

  const handlePressBack = useCallback(() => {
    control.close()
  }, [control])

  const handlePressAdd = useCallback(() => {
    onAddMembers(groupChatDids, groupChatProfiles)
  }, [groupChatDids, groupChatProfiles, onAddMembers])

  const renderItems = useCallback(
    ({item}: {item: Item}) => {
      switch (item.type) {
        case 'label': {
          return <UserLabel key={item.key} message={item.message} />
        }
        case 'profile': {
          return (
            <GroupChatProfileCard
              key={item.key}
              profile={item.profile}
              moderationOpts={moderationOpts!}
            />
          )
        }
        case 'placeholder': {
          return <ProfileCardSkeleton key={item.key} />
        }
        case 'loading': {
          return (
            <View style={[a.px_lg, a.py_xl, a.align_center]}>
              <Loader size="lg" />
            </View>
          )
        }
        case 'empty': {
          return <EmptyMemberList key={item.key} message={item.message} />
        }
        default:
          return null
      }
    },
    [moderationOpts],
  )

  useLayoutEffect(() => {
    if (IS_WEB) {
      setImmediate(() => {
        inputRef?.current?.focus()
      })
    }
  }, [])

  let buttonLabel = l`Continue to group name`
  let buttonText = l`Next`
  let showButton = groupChatProfiles.length > 0
  let isButtonDisabled = !showButton

  const showChatProfileTabs = groupChatProfiles.length > 0

  const listHeader = useMemo(
    () => (
      <View onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}>
        <View
          style={[
            a.relative,
            web(a.pt_lg),
            native(a.pt_4xl),
            android({
              borderTopLeftRadius: a.rounded_md.borderRadius,
              borderTopRightRadius: a.rounded_md.borderRadius,
            }),
            a.px_lg,
            a.border_b,
            t.atoms.border_contrast_low,
            t.atoms.bg,
          ]}>
          <View
            style={[
              a.flex_row,
              a.gap_sm,
              a.relative,
              a.align_center,
              a.justify_between,
              web(a.pb_lg),
            ]}>
            {IS_NATIVE ? (
              <Button
                label={l`Back`}
                size="large"
                shape="round"
                variant="ghost"
                color="secondary"
                style={[native([a.absolute, a.z_20])]}
                onPress={handlePressBack}>
                <ButtonIcon icon={ArrowLeftIcon} size="lg" />
              </Button>
            ) : null}
            <Text
              style={[
                a.flex_grow,
                a.z_10,
                a.text_lg,
                a.font_bold,
                a.leading_tight,
                t.atoms.text_contrast_high,
                a.text_center,
                a.px_5xl,
              ]}>
              {title}
            </Text>
            {IS_WEB ? (
              <Button
                label={l`Close`}
                size="small"
                shape="round"
                variant="ghost"
                color="secondary"
                style={[a.absolute, a.z_20, {right: -4}]}
                onPress={() => control.close()}>
                <ButtonIcon icon={XIcon} size="lg" />
              </Button>
            ) : showButton ? (
              <Button
                label={buttonLabel}
                size="small"
                color="primary"
                style={[
                  native([
                    a.absolute,
                    a.z_20,
                    {
                      right: 8,
                    },
                  ]),
                ]}
                disabled={isButtonDisabled}
                onPress={handlePressAdd}>
                <ButtonText>
                  <Trans>Add</Trans>
                </ButtonText>
              </Button>
            ) : null}
          </View>
          <View style={[web(a.pt_xs), native(a.pt_md)]}>
            <UserSearchInput
              inputRef={inputRef}
              value={searchText}
              onChangeText={text => {
                dispatch({type: 'setSearchText', searchText: text})
                listRef.current?.scrollToOffset({offset: 0, animated: false})
              }}
              onEscape={control.close}
            />
          </View>
        </View>
        {showChatProfileTabs ? (
          <View style={[a.pb_sm, a.pt_md, t.atoms.bg]}>
            <ChatProfileTabs
              testID="newGroupChatMembers"
              profiles={groupChatProfiles}
              onRemove={onRemoveDid}
            />
          </View>
        ) : null}
      </View>
    ),
    [
      buttonLabel,
      control,
      groupChatProfiles,
      handlePressAdd,
      handlePressBack,
      isButtonDisabled,
      l,
      onRemoveDid,
      searchText,
      showButton,
      showChatProfileTabs,
      t.atoms.bg,
      t.atoms.border_contrast_low,
      t.atoms.text_contrast_high,
      title,
    ],
  )

  const setGroupChatMembers = (dids: string[]) => {
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)

    const added = dids.filter(d => !groupChatDids.includes(d))
    const removed = groupChatDids.filter(d => !dids.includes(d))
    const newDids = [
      ...groupChatDids.filter(d => !removed.includes(d)),
      ...added,
    ]

    const kept = groupChatProfiles.filter(p => dids.includes(p.did))
    const keptDids = new Set(kept.map(p => p.did))
    const addedProfiles = items
      .filter(
        (item): item is ProfileItem =>
          item.type === 'profile' &&
          dids.includes(item.profile.did) &&
          !keptDids.has(item.profile.did),
      )
      .map(item => item.profile)
      .sort((a, b) => dids.indexOf(a.did) - dids.indexOf(b.did))

    dispatch({
      type: 'setDids',
      groupChatDids: newDids,
      groupChatProfiles: [...kept, ...addedProfiles],
    })
  }

  return (
    <Toggle.Group
      values={groupChatDids}
      onChange={setGroupChatMembers}
      type="checkbox"
      maxSelections={remainingSlots}
      label={l`Add group chat members`}
      style={web([a.contents])}>
      <Dialog.InnerFlatList
        ref={listRef}
        data={items}
        renderItem={renderItems}
        ListHeaderComponent={listHeader}
        stickyHeaderIndices={[0]}
        ListEmptyComponent={
          isMemberListPending || isAutocompleteFetching ? (
            <View style={[a.flex_1, a.align_center, a.justify_center]}>
              <Loader size="xl" />
            </View>
          ) : null
        }
        keyExtractor={(item: Item) => item.key}
        style={[
          web([a.py_0, {height: '100vh', maxHeight: 600}, a.px_0]),
          native({height: '100%'}),
        ]}
        contentContainerStyle={items.length === 0 ? {flexGrow: 1} : undefined}
        webInnerContentContainerStyle={[
          a.py_0,
          {paddingBottom: footerHeight},
          items.length === 0 && {flexGrow: 1},
        ]}
        webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
        scrollIndicatorInsets={{top: headerHeight, bottom: footerHeight}}
        keyboardDismissMode="on-drag"
        footer={
          IS_WEB ? (
            <Dialog.FlatListFooter
              onLayout={evt => setFooterHeight(evt.nativeEvent.layout.height)}>
              <View style={[a.flex_row, a.align_center, a.justify_between]}>
                <Button
                  label={l`Back`}
                  size="small"
                  color="secondary"
                  onPress={handlePressBack}>
                  <ButtonIcon icon={ArrowLeftIcon} size="md" />
                  <ButtonText>
                    <Trans>Back</Trans>
                  </ButtonText>
                </Button>
                <Button
                  label={buttonLabel}
                  size="small"
                  color="primary"
                  disabled={isButtonDisabled}
                  onPress={handlePressAdd}>
                  <ButtonText>{buttonText} </ButtonText>
                </Button>
              </View>
            </Dialog.FlatListFooter>
          ) : null
        }
      />
    </Toggle.Group>
  )
}
