import {
  useCallback,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from 'react'
import {type TextInput, View} from 'react-native'
import {useWindowDimensions} from 'react-native'
import {Image} from 'expo-image'
import {Trans, useLingui} from '@lingui/react/macro'

import {cleanError} from '#/lib/strings/errors'
import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
import {
  useFeaturedGifsQuery as useKlipyFeaturedGifsQuery,
  useGifSearchQuery as useKlipyGifSearchQuery,
} from '#/state/queries/klipy'
import {
  type Gif,
  gifPreviewUrl,
  useTenorFeaturedGifsQuery,
  useTenorGifSearchQuery,
} from '#/state/queries/tenor'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {type ListMethods} from '#/view/com/util/List'
import {atoms as a, ios, native, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import * as TextField from '#/components/forms/TextField'
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arrow'
import {MagnifyingGlass_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass'
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env'

export function GifSelectDialog({
  controlRef,
  onClose,
  onSelectGif: onSelectGifProp,
}: {
  controlRef: React.RefObject<{open: () => void} | null>
  onClose?: () => void
  onSelectGif: (gif: Gif) => void
}) {
  const control = Dialog.useDialogControl()

  useImperativeHandle(controlRef, () => ({
    open: () => control.open(),
  }))

  const onSelectGif = useCallback(
    (gif: Gif) => {
      control.close(() => onSelectGifProp(gif))
    },
    [control, onSelectGifProp],
  )

  const renderErrorBoundary = useCallback(
    (error: any) => <DialogError details={String(error)} />,
    [],
  )

  return (
    <Dialog.Outer
      control={control}
      onClose={onClose}
      nativeOptions={{
        bottomInset: 0,
        // use system corner radius on iOS
        ...ios({cornerRadius: undefined}),
        fullHeight: true,
      }}>
      <Dialog.Handle />
      <ErrorBoundary renderError={renderErrorBoundary}>
        <GifList control={control} onSelectGif={onSelectGif} />
      </ErrorBoundary>
    </Dialog.Outer>
  )
}

function GifList({
  control,
  onSelectGif,
}: {
  control: Dialog.DialogControlProps
  onSelectGif: (gif: Gif) => void
}) {
  const ax = useAnalytics()
  const {t: l} = useLingui()
  const t = useTheme()
  const {gtMobile} = useBreakpoints()
  const textInputRef = useRef<TextInput>(null)
  const listRef = useRef<ListMethods>(null)
  const [undeferredSearch, setSearch] = useState('')
  const search = useThrottledValue(undeferredSearch, 500)
  const {height} = useWindowDimensions()
  const klipyEnabled = ax.features.enabled(ax.features.KlipyGifProviderEnable)

  const isSearching = search.length > 0

  const klipyTrending = useKlipyFeaturedGifsQuery({enabled: klipyEnabled})
  const klipySearch = useKlipyGifSearchQuery(search, {enabled: klipyEnabled})
  const tenorTrending = useTenorFeaturedGifsQuery({enabled: !klipyEnabled})
  const tenorSearch = useTenorGifSearchQuery(search, {enabled: !klipyEnabled})

  const enableSquareButtons = useEnableSquareButtons()

  const {
    data,
    fetchNextPage,
    isFetchingNextPage,
    hasNextPage,
    error,
    isPending,
    isError,
    refetch,
  } = klipyEnabled
    ? isSearching
      ? klipySearch
      : klipyTrending
    : isSearching
      ? tenorSearch
      : tenorTrending

  const flattenedData = useMemo(() => {
    return data?.pages.flatMap(page => page.results) || []
  }, [data])

  const renderItem = useCallback(
    ({item}: {item: Gif}) => {
      return <GifPreview gif={item} onSelectGif={onSelectGif} />
    },
    [onSelectGif],
  )

  const onEndReached = useCallback(() => {
    if (isFetchingNextPage || !hasNextPage || error) return
    fetchNextPage()
  }, [isFetchingNextPage, hasNextPage, error, fetchNextPage])

  const hasData = flattenedData.length > 0

  const onGoBack = useCallback(() => {
    if (isSearching) {
      // clear the input and reset the state
      textInputRef.current?.clear()
      setSearch('')
    } else {
      control.close()
    }
  }, [control, isSearching])

  const listHeader = useMemo(() => {
    return (
      <View
        style={[
          native(a.pt_4xl),
          a.relative,
          a.mb_lg,
          a.flex_row,
          a.align_center,
          !gtMobile && web(a.gap_md),
          a.pb_sm,
          t.atoms.bg,
        ]}>
        {!gtMobile && IS_WEB && (
          <Button
            size="small"
            variant="ghost"
            color="secondary"
            shape={enableSquareButtons ? 'square' : 'round'}
            onPress={() => control.close()}
            label={l`Close GIF dialog`}>
            <ButtonIcon icon={Arrow} size="md" />
          </Button>
        )}

        <TextField.Root style={[!gtMobile && IS_WEB && a.flex_1]}>
          <TextField.Icon icon={Search} />
          <TextField.Input
            label={l`Search GIFs`}
            placeholder={klipyEnabled ? l`Search KLIPY` : l`Search Tenor`}
            onChangeText={text => {
              setSearch(text)
              listRef.current?.scrollToOffset({offset: 0, animated: false})
            }}
            returnKeyType="search"
            clearButtonMode="while-editing"
            inputRef={textInputRef}
            maxLength={50}
            onKeyPress={({nativeEvent}) => {
              if (nativeEvent.key === 'Escape') {
                control.close()
              }
            }}
          />
        </TextField.Root>
      </View>
    )
  }, [gtMobile, t.atoms.bg, l, control, klipyEnabled, enableSquareButtons])

  return (
    <>
      {gtMobile && <Dialog.Close />}
      <Dialog.InnerFlatList
        ref={listRef}
        key={gtMobile ? '3 cols' : '2 cols'}
        data={flattenedData}
        renderItem={renderItem}
        numColumns={gtMobile ? 3 : 2}
        columnWrapperStyle={[a.gap_sm]}
        contentContainerStyle={[native([a.px_xl, {minHeight: height}])]}
        webInnerStyle={[web({minHeight: '80vh'})]}
        webInnerContentContainerStyle={[web(a.pb_0)]}
        ListHeaderComponent={
          <>
            {listHeader}
            {!hasData && (
              <ListMaybePlaceholder
                isLoading={isPending}
                isError={isError}
                onRetry={refetch}
                onGoBack={onGoBack}
                emptyType="results"
                sideBorders={false}
                topBorder={false}
                errorTitle={l`Failed to load GIFs`}
                errorMessage={
                  klipyEnabled
                    ? l`There was an issue connecting to KLIPY.`
                    : l`There was an issue connecting to Tenor.`
                }
                emptyMessage={
                  isSearching
                    ? l`No search results found for "${search}".`
                    : klipyEnabled
                      ? l`No featured GIFs found. There may be an issue with KLIPY.`
                      : l`No featured GIFs found. There may be an issue with Tenor.`
                }
              />
            )}
          </>
        }
        stickyHeaderIndices={[0]}
        onEndReached={onEndReached}
        onEndReachedThreshold={4}
        keyExtractor={(item: Gif) => item.id}
        keyboardDismissMode="on-drag"
        ListFooterComponent={
          hasData ? (
            <ListFooter
              isFetchingNextPage={isFetchingNextPage}
              error={cleanError(error)}
              onRetry={fetchNextPage}
              style={{borderTopWidth: 0}}
            />
          ) : null
        }
      />
    </>
  )
}

function DialogError({details}: {details?: string}) {
  const {t: l} = useLingui()
  const control = Dialog.useDialogContext()

  return (
    <Dialog.ScrollableInner style={a.gap_md} label={l`An error has occurred`}>
      <Dialog.Close />
      <ErrorScreen
        title={l`Oh no!`}
        message={l`There was an unexpected issue in the application. Please let us know if this happened to you!`}
        details={details}
      />
      <Button
        label={l`Close dialog`}
        onPress={() => control.close()}
        color="primary"
        size="large"
        variant="solid">
        <ButtonText>
          <Trans>Close</Trans>
        </ButtonText>
      </Button>
    </Dialog.ScrollableInner>
  )
}

export function GifPreview({
  gif,
  onSelectGif,
}: {
  gif: Gif
  onSelectGif: (gif: Gif) => void
}) {
  const ax = useAnalytics()
  const {gtTablet} = useBreakpoints()
  const {t: l} = useLingui()
  const t = useTheme()

  const onPress = useCallback(() => {
    ax.metric('composer:gif:select', {})
    onSelectGif(gif)
  }, [ax, onSelectGif, gif])

  return (
    <Button
      label={l`Select GIF "${gif.title}"`}
      style={[a.flex_1, gtTablet ? {maxWidth: '33%'} : {maxWidth: '50%'}]}
      onPress={onPress}>
      {({pressed}) => (
        <Image
          style={[
            a.flex_1,
            a.mb_sm,
            a.rounded_sm,
            a.aspect_square,
            {opacity: pressed ? 0.8 : 1},
            t.atoms.bg_contrast_25,
          ]}
          source={{
            uri: gifPreviewUrl(gif.media_formats.tinygif.url),
          }}
          contentFit="cover"
          accessibilityLabel={gif.title}
          accessibilityHint=""
          cachePolicy="none"
          accessibilityIgnoresInvertColors
        />
      )}
    </Button>
  )
}
