import {
  Fragment,
  useCallback,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react'
import {TextInput, View} from 'react-native'
import {moderateProfile, type ModerationOpts} from '@atproto/api'
import {Plural, Trans, useLingui} from '@lingui/react/macro'

import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {sanitizeHandle} from '#/lib/strings/handles'
import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {useListConvosQuery} from '#/state/queries/messages/list-conversations'
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} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {
  canBeMessaged,
  type ConvoWithDetails,
  parseConvoView,
} from '#/components/dms/util'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
import {IS_WEB} from '#/env'
import type * as bsky from '#/types/bsky'
import {AvatarBubbles} from '../AvatarBubbles'
import {Error} from '../Error'
import {ProfileBadges} from '../ProfileBadges'

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

type ExistingChatItem = {
  type: 'existingChat'
  key: string
  convo: ConvoWithDetails
}

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

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

type ErrorItem = {
  type: 'error'
  key: string
}

type Item =
  | ProfileItem
  | ExistingChatItem
  | EmptyItem
  | PlaceholderItem
  | ErrorItem

export function SearchablePeopleList({
  title,
  showRecentConvos,
  sortByMessageDeclaration,
  excludeSelf = true,
  onSelectChat,
  renderProfileCard,
}: {
  title: string
  showRecentConvos?: boolean
  sortByMessageDeclaration?: boolean
  excludeSelf?: boolean
} & (
  | {
      renderProfileCard: (item: ProfileItem) => React.ReactNode
      onSelectChat?: undefined
    }
  | {
      onSelectChat: (
        chat: {kind: 'user'; did: string} | {kind: 'convo'; id: string},
      ) => void
      renderProfileCard?: undefined
    }
)) {
  const t = useTheme()
  const {t: l} = useLingui()
  const moderationOpts = useModerationOpts()
  const control = Dialog.useDialogContext()
  const [headerHeight, setHeaderHeight] = useState(0)
  const listRef = useRef<ListMethods>(null)
  const {currentAccount} = useSession()
  const inputRef = useRef<TextInput>(null)

  const [searchText, setSearchText] = useState('')

  const enableSquareButtons = useEnableSquareButtons()

  const {
    data: results,
    isError,
    isFetching,
  } = useActorAutocompleteQuery(searchText, true, 12)
  const {data: follows} = useProfileFollowsQuery(currentAccount?.did)
  const {data: convos} = useListConvosQuery({
    enabled: showRecentConvos,
    status: 'accepted',
    lockStatus: 'unlocked',
  })

  const items = useMemo(() => {
    let _items: Item[] = []

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

        if (sortByMessageDeclaration) {
          _items = _items.sort(item => {
            return item.type === 'profile' && canBeMessaged(item.profile)
              ? -1
              : 1
          })
        }
      }
    } else {
      const placeholders: Item[] = Array(10)
        .fill(0)
        .map((__, i) => ({
          type: 'placeholder',
          key: i + '',
        }))

      if (showRecentConvos) {
        if (convos && follows) {
          const usedDids = new Set()

          for (const page of convos.pages) {
            for (const convoView of page.convos) {
              const convo = parseConvoView(convoView, currentAccount?.did)

              if (!convo) continue

              if (convo.kind === 'group') {
                _items.push({
                  type: 'existingChat',
                  key: convo.view.id,
                  convo,
                })
              } else {
                if (convo.primaryMember.handle === 'missing.invalid') continue
                if (usedDids.has(convo.primaryMember.did)) continue

                usedDids.add(convo.primaryMember.did)

                _items.push({
                  type: 'existingChat',
                  key: convo.view.id,
                  convo: convo,
                })
              }
            }
          }

          let followsItems: ProfileItem[] = []

          for (const page of follows.pages) {
            for (const profile of page.follows) {
              if (usedDids.has(profile.did)) continue

              followsItems.push({
                type: 'profile',
                key: profile.did,
                profile,
              })
            }
          }

          if (sortByMessageDeclaration) {
            // only sort follows
            followsItems = followsItems.sort(item => {
              return canBeMessaged(item.profile) ? -1 : 1
            })
          }

          // then append
          _items.push(...followsItems)
        } else {
          _items.push(...placeholders)
        }
      } else if (follows) {
        for (const page of follows.pages) {
          for (const profile of page.follows) {
            _items.push({
              type: 'profile',
              key: profile.did,
              profile,
            })
          }
        }

        if (sortByMessageDeclaration) {
          _items = _items.sort(item => {
            return item.type === 'profile' && canBeMessaged(item.profile)
              ? -1
              : 1
          })
        }
      } else {
        _items.push(...placeholders)
      }
    }

    return _items
  }, [
    l,
    searchText,
    results,
    isError,
    currentAccount?.did,
    excludeSelf,
    follows,
    convos,
    showRecentConvos,
    sortByMessageDeclaration,
  ])

  if (searchText && !isFetching && !items.length && !isError) {
    items.push({type: 'empty', key: 'empty', message: l`No results`})
  }

  const renderItems = useCallback(
    ({item}: {item: Item}) => {
      switch (item.type) {
        case 'existingChat': {
          if (renderProfileCard) {
            // should be unreachable
            return null
          } else {
            return (
              <ExistingChatCard
                key={item.key}
                convo={item.convo}
                moderationOpts={moderationOpts!}
                onPress={id => onSelectChat({kind: 'convo', id})}
              />
            )
          }
        }
        case 'profile': {
          if (renderProfileCard) {
            return <Fragment key={item.key}>{renderProfileCard(item)}</Fragment>
          } else {
            return (
              <DefaultProfileCard
                key={item.key}
                profile={item.profile}
                moderationOpts={moderationOpts!}
                onPress={did => onSelectChat({kind: 'user', did})}
              />
            )
          }
        }
        case 'placeholder': {
          return <ProfileCardSkeleton key={item.key} />
        }
        case 'empty': {
          return <Empty key={item.key} message={item.message} />
        }
        case 'error': {
          return <Error key={item.key} message={l`Failed to load profiles`} />
        }
        default:
          return null
      }
    },
    [moderationOpts, onSelectChat, renderProfileCard, l],
  )

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

  const listHeader = useMemo(() => {
    return (
      <View
        onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}
        style={[
          a.relative,
          web(a.pt_lg),
          native(a.pt_4xl),
          android({
            borderTopLeftRadius: a.rounded_md.borderRadius,
            borderTopRightRadius: a.rounded_md.borderRadius,
          }),
          a.pb_xs,
          a.px_lg,
          a.border_b,
          t.atoms.border_contrast_low,
          t.atoms.bg,
        ]}>
        <View style={[a.relative, native(a.align_center), a.justify_center]}>
          <Text
            style={[
              a.z_10,
              a.text_lg,
              a.font_bold,
              a.leading_tight,
              t.atoms.text_contrast_high,
            ]}>
            {title}
          </Text>
          {IS_WEB ? (
            <Button
              label={l`Close`}
              size="small"
              shape={enableSquareButtons ? 'square' : 'round'}
              variant={IS_WEB ? 'ghost' : 'solid'}
              color="secondary"
              style={[
                a.absolute,
                a.z_20,
                web({right: -4}),
                native({right: 0}),
                native({height: 32, width: 32, borderRadius: 16}),
              ]}
              onPress={() => control.close()}>
              <ButtonIcon icon={X} size="md" />
            </Button>
          ) : null}
        </View>

        <View style={web([a.pt_xs])}>
          <SearchInput
            inputRef={inputRef}
            value={searchText}
            onChangeText={text => {
              setSearchText(text)
              listRef.current?.scrollToOffset({offset: 0, animated: false})
            }}
            onEscape={control.close}
          />
        </View>
      </View>
    )
  }, [
    t.atoms.border_contrast_low,
    t.atoms.bg,
    t.atoms.text_contrast_high,
    l,
    title,
    searchText,
    control,
    enableSquareButtons,
  ])

  return (
    <Dialog.InnerFlatList
      ref={listRef}
      data={items}
      renderItem={renderItems}
      ListHeaderComponent={listHeader}
      stickyHeaderIndices={[0]}
      keyExtractor={(item: Item) => item.key}
      style={[
        web([a.py_0, {height: '100vh', maxHeight: 600}, a.px_0]),
        native({height: '100%'}),
      ]}
      webInnerContentContainerStyle={a.py_0}
      webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
      scrollIndicatorInsets={{top: headerHeight}}
      keyboardDismissMode="on-drag"
    />
  )
}

function DefaultProfileCard({
  profile,
  moderationOpts,
  onPress,
}: {
  profile: bsky.profile.AnyProfileView
  moderationOpts: ModerationOpts
  onPress: (did: string) => void
}) {
  const t = useTheme()
  const {t: l} = useLingui()
  const enabled = canBeMessaged(profile)
  const moderation = moderateProfile(profile, moderationOpts)
  const handle = sanitizeHandle(profile.handle, '@')
  const displayName = createSanitizedDisplayName(
    profile,
    true,
    moderation.ui('displayName'),
  )

  const handleOnPress = useCallback(() => {
    onPress(profile.did)
  }, [onPress, profile.did])

  return (
    <Button
      disabled={!enabled}
      label={l`Start chat with ${displayName}`}
      onPress={handleOnPress}>
      {({hovered, pressed, focused}) => (
        <View
          style={[
            a.flex_1,
            a.py_sm,
            a.px_lg,
            !enabled
              ? {opacity: 0.5}
              : pressed || focused || hovered
                ? t.atoms.bg_contrast_25
                : t.atoms.bg,
          ]}>
          <ProfileCard.Header>
            <ProfileCard.Avatar
              profile={profile}
              moderationOpts={moderationOpts}
              disabledPreview
            />
            <View style={[a.flex_1]}>
              <ProfileCard.Name
                profile={profile}
                moderationOpts={moderationOpts}
              />
              {enabled ? (
                <ProfileCard.Handle profile={profile} />
              ) : (
                <Text
                  style={[a.leading_snug, t.atoms.text_contrast_high]}
                  numberOfLines={2}>
                  <Trans>{handle} can't be messaged</Trans>
                </Text>
              )}
            </View>
          </ProfileCard.Header>
        </View>
      )}
    </Button>
  )
}

function ExistingChatCard({
  convo,
  moderationOpts,
  onPress,
}: {
  convo: ConvoWithDetails
  moderationOpts: ModerationOpts
  onPress: (convoId: string) => void
}) {
  const t = useTheme()
  const {t: l} = useLingui()
  const enabled =
    convo.kind === 'group' ? convo.details.lockStatus === 'unlocked' : true
  const name =
    convo.kind === 'group'
      ? convo.details.name
      : createSanitizedDisplayName(
          convo.primaryMember,
          true,
          moderateProfile(convo.primaryMember, moderationOpts).ui(
            'displayName',
          ),
        )

  const handleOnPress = useCallback(() => {
    onPress(convo.view.id)
  }, [onPress, convo.view.id])

  return (
    <Button
      disabled={!enabled}
      label={l`Select chat "${name}"`}
      onPress={handleOnPress}>
      {({hovered, pressed, focused}) => (
        <View
          style={[
            a.flex_1,
            a.py_sm,
            a.px_lg,
            !enabled
              ? {opacity: 0.5}
              : pressed || focused || hovered
                ? t.atoms.bg_contrast_25
                : t.atoms.bg,
          ]}>
          <ProfileCard.Header>
            {convo.kind === 'group' ? (
              <AvatarBubbles profiles={convo.members} size={40} />
            ) : (
              <ProfileCard.Avatar
                profile={convo.primaryMember}
                moderationOpts={moderationOpts}
                disabledPreview
              />
            )}
            <View style={[a.flex_1]}>
              <View style={[a.flex_row, a.align_center, a.max_w_full]}>
                <Text
                  emoji
                  style={[
                    a.text_md,
                    a.font_semi_bold,
                    a.leading_snug,
                    a.self_start,
                    a.flex_shrink,
                  ]}
                  numberOfLines={1}>
                  {name}
                </Text>
                {convo.kind === 'direct' && (
                  <ProfileBadges
                    profile={convo.primaryMember}
                    size="md"
                    style={[a.pl_xs]}
                  />
                )}
              </View>
              {convo.kind === 'direct' ? (
                <ProfileCard.Handle profile={convo.primaryMember} />
              ) : (
                <>
                  {enabled ? (
                    <Text
                      style={[a.leading_snug, t.atoms.text_contrast_medium]}
                      numberOfLines={2}>
                      <Plural
                        value={convo.members.length}
                        one="# member"
                        other="# members"
                      />
                    </Text>
                  ) : (
                    <Text
                      style={[a.leading_snug, t.atoms.text_contrast_high]}
                      numberOfLines={2}>
                      <Trans>Group is locked</Trans>
                    </Text>
                  )}
                </>
              )}
            </View>
          </ProfileCard.Header>
        </View>
      )}
    </Button>
  )
}

function ProfileCardSkeleton() {
  const t = useTheme()
  const enableSquareButtons = useEnableSquareButtons()

  return (
    <View
      style={[
        a.flex_1,
        a.py_md,
        a.px_lg,
        a.gap_md,
        a.align_center,
        a.flex_row,
      ]}>
      <View
        style={[
          enableSquareButtons ? a.rounded_sm : a.rounded_full,
          {width: 42, height: 42},
          t.atoms.bg_contrast_25,
        ]}
      />

      <View style={[a.flex_1, a.gap_sm]}>
        <View
          style={[
            a.rounded_xs,
            {width: 80, height: 14},
            t.atoms.bg_contrast_25,
          ]}
        />
        <View
          style={[
            a.rounded_xs,
            {width: 120, height: 10},
            t.atoms.bg_contrast_25,
          ]}
        />
      </View>
    </View>
  )
}

function Empty({message}: {message: string}) {
  const t = useTheme()
  return (
    <View style={[a.p_lg, a.py_xl, a.align_center, a.gap_md]}>
      <Text style={[a.text_sm, a.italic, t.atoms.text_contrast_high]}>
        {message}
      </Text>

      <Text style={[a.text_xs, t.atoms.text_contrast_low]}>(╯°□°)╯︵ ┻━┻</Text>
    </View>
  )
}

function SearchInput({
  value,
  onChangeText,
  onEscape,
  inputRef,
}: {
  value: string
  onChangeText: (text: string) => void
  onEscape: () => void
  inputRef: React.RefObject<TextInput | null>
}) {
  const t = useTheme()
  const {t: l} = useLingui()
  const {
    state: hovered,
    onIn: onMouseEnter,
    onOut: onMouseLeave,
  } = useInteractionState()
  const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
  const interacted = hovered || focused

  return (
    <View
      {...web({
        onMouseEnter,
        onMouseLeave,
      })}
      style={[a.flex_row, a.align_center, a.gap_sm]}>
      <Search
        size="md"
        fill={interacted ? t.palette.primary_500 : t.palette.contrast_300}
      />

      <TextInput
        ref={inputRef}
        placeholder={l`Search`}
        value={value}
        onChangeText={onChangeText}
        onFocus={onFocus}
        onBlur={onBlur}
        style={[a.flex_1, a.py_md, a.text_md, t.atoms.text]}
        placeholderTextColor={t.palette.contrast_500}
        keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
        returnKeyType="search"
        clearButtonMode="while-editing"
        maxLength={50}
        onKeyPress={({nativeEvent}) => {
          if (nativeEvent.key === 'Escape') {
            onEscape()
          }
        }}
        autoCorrect={false}
        autoComplete="off"
        autoCapitalize="none"
        autoFocus
        accessibilityLabel={l`Search profiles`}
        accessibilityHint={l`Searches for profiles`}
      />
    </View>
  )
}
