import React from 'react'
import {View} from 'react-native'
import {
  AppBskyActorDefs,
  AppBskyFeedDefs,
  moderateProfile,
  ModerationDecision,
  ModerationOpts,
} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'

import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useGetPopularFeedsQuery} from '#/state/queries/feed'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows'
import {cleanError} from 'lib/strings/errors'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {
  FeedFeedLoadingPlaceholder,
  ProfileCardFeedLoadingPlaceholder,
} from 'view/com/util/LoadingPlaceholder'
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
import {Button} from '#/components/Button'
import * as FeedCard from '#/components/FeedCard'
import {ArrowBottom_Stroke2_Corner0_Rounded as ArrowBottom} from '#/components/icons/Arrow'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Props as SVGIconProps} from '#/components/icons/common'
import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle'
import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'

function SuggestedItemsHeader({
  title,
  description,
  style,
  icon: Icon,
}: {
  title: string
  description: string
  icon: React.ComponentType<SVGIconProps>
} & ViewStyleProp) {
  const t = useTheme()

  return (
    <View
      style={[
        isWeb
          ? [a.flex_row, a.px_lg, a.py_lg, a.pt_2xl, a.gap_md]
          : [{flexDirection: 'row-reverse'}, a.p_lg, a.pt_2xl, a.gap_md],
        a.border_b,
        t.atoms.border_contrast_low,
        style,
      ]}>
      <View style={[a.flex_1, a.gap_sm]}>
        <View style={[a.flex_row, a.align_center, a.gap_sm]}>
          <Icon
            size="lg"
            fill={t.palette.primary_500}
            style={{marginLeft: -2}}
          />
          <Text style={[a.text_2xl, a.font_heavy, t.atoms.text]}>{title}</Text>
        </View>
        <Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
          {description}
        </Text>
      </View>
    </View>
  )
}

type LoadMoreItems =
  | {
      type: 'profile'
      key: string
      avatar: string
      moderation: ModerationDecision
    }
  | {
      type: 'feed'
      key: string
      avatar: string
      moderation: undefined
    }

function LoadMore({
  item,
  moderationOpts,
}: {
  item: ExploreScreenItems & {type: 'loadMore'}
  moderationOpts?: ModerationOpts
}) {
  const t = useTheme()
  const {_} = useLingui()
  const items = React.useMemo(() => {
    return item.items
      .map(_item => {
        if (_item.type === 'profile') {
          return {
            type: 'profile',
            key: _item.profile.did,
            avatar: _item.profile.avatar,
            moderation: moderateProfile(_item.profile, moderationOpts!),
          }
        } else if (_item.type === 'feed') {
          return {
            type: 'feed',
            key: _item.feed.uri,
            avatar: _item.feed.avatar,
            moderation: undefined,
          }
        }
        return undefined
      })
      .filter(Boolean) as LoadMoreItems[]
  }, [item.items, moderationOpts])

  if (items.length === 0) return null

  const type = items[0].type

  return (
    <View style={[]}>
      <Button
        label={_(msg`Load more`)}
        onPress={item.onLoadMore}
        style={[a.relative, a.w_full]}>
        {({hovered, pressed}) => (
          <View
            style={[
              a.flex_1,
              a.flex_row,
              a.align_center,
              a.px_lg,
              a.py_md,
              (hovered || pressed) && t.atoms.bg_contrast_25,
            ]}>
            <View
              style={[
                a.relative,
                {
                  height: 32,
                  width: 32 + 15 * items.length,
                },
              ]}>
              <View
                style={[
                  a.align_center,
                  a.justify_center,
                  t.atoms.bg_contrast_25,
                  a.absolute,
                  {
                    width: 30,
                    height: 30,
                    left: 0,
                    borderWidth: 1,
                    backgroundColor: t.palette.primary_500,
                    borderColor: t.atoms.bg.backgroundColor,
                    borderRadius: type === 'profile' ? 999 : 4,
                    zIndex: 4,
                  },
                ]}>
                <ArrowBottom fill={t.palette.white} />
              </View>
              {items.map((_item, i) => {
                return (
                  <View
                    key={_item.key}
                    style={[
                      t.atoms.bg_contrast_25,
                      a.absolute,
                      {
                        width: 30,
                        height: 30,
                        left: (i + 1) * 15,
                        borderWidth: 1,
                        borderColor: t.atoms.bg.backgroundColor,
                        borderRadius: _item.type === 'profile' ? 999 : 4,
                        zIndex: 3 - i,
                      },
                    ]}>
                    {moderationOpts && (
                      <>
                        {_item.type === 'profile' ? (
                          <UserAvatar
                            size={28}
                            avatar={_item.avatar}
                            moderation={_item.moderation.ui('avatar')}
                          />
                        ) : _item.type === 'feed' ? (
                          <UserAvatar
                            size={28}
                            avatar={_item.avatar}
                            type="algo"
                          />
                        ) : null}
                      </>
                    )}
                  </View>
                )
              })}
            </View>

            <Text
              style={[
                a.pl_sm,
                a.leading_snug,
                hovered ? t.atoms.text : t.atoms.text_contrast_medium,
              ]}>
              {type === 'profile' ? (
                <Trans>Load more suggested follows</Trans>
              ) : (
                <Trans>Load more suggested feeds</Trans>
              )}
            </Text>

            <View style={[a.flex_1, a.align_end]}>
              {item.isLoadingMore && <Loader size="lg" />}
            </View>
          </View>
        )}
      </Button>
    </View>
  )
}

type ExploreScreenItems =
  | {
      type: 'header'
      key: string
      title: string
      description: string
      style?: ViewStyleProp['style']
      icon: React.ComponentType<SVGIconProps>
    }
  | {
      type: 'profile'
      key: string
      profile: AppBskyActorDefs.ProfileViewBasic
    }
  | {
      type: 'feed'
      key: string
      feed: AppBskyFeedDefs.GeneratorView
    }
  | {
      type: 'loadMore'
      key: string
      isLoadingMore: boolean
      onLoadMore: () => void
      items: ExploreScreenItems[]
    }
  | {
      type: 'profilePlaceholder'
      key: string
    }
  | {
      type: 'feedPlaceholder'
      key: string
    }
  | {
      type: 'error'
      key: string
      message: string
      error: string
    }

export function Explore() {
  const {_} = useLingui()
  const t = useTheme()
  const {data: preferences, error: preferencesError} = usePreferencesQuery()
  const moderationOpts = useModerationOpts()
  const {
    data: profiles,
    hasNextPage: hasNextProfilesPage,
    isLoading: isLoadingProfiles,
    isFetchingNextPage: isFetchingNextProfilesPage,
    error: profilesError,
    fetchNextPage: fetchNextProfilesPage,
  } = useSuggestedFollowsQuery({limit: 6, subsequentPageLimit: 10})
  const {
    data: feeds,
    hasNextPage: hasNextFeedsPage,
    isLoading: isLoadingFeeds,
    isFetchingNextPage: isFetchingNextFeedsPage,
    error: feedsError,
    fetchNextPage: fetchNextFeedsPage,
  } = useGetPopularFeedsQuery({limit: 10})

  const isLoadingMoreProfiles = isFetchingNextProfilesPage && !isLoadingProfiles
  const onLoadMoreProfiles = React.useCallback(async () => {
    if (isFetchingNextProfilesPage || !hasNextProfilesPage || profilesError)
      return
    try {
      await fetchNextProfilesPage()
    } catch (err) {
      logger.error('Failed to load more suggested follows', {message: err})
    }
  }, [
    isFetchingNextProfilesPage,
    hasNextProfilesPage,
    profilesError,
    fetchNextProfilesPage,
  ])

  const isLoadingMoreFeeds = isFetchingNextFeedsPage && !isLoadingFeeds
  const onLoadMoreFeeds = React.useCallback(async () => {
    if (isFetchingNextFeedsPage || !hasNextFeedsPage || feedsError) return
    try {
      await fetchNextFeedsPage()
    } catch (err) {
      logger.error('Failed to load more suggested follows', {message: err})
    }
  }, [
    isFetchingNextFeedsPage,
    hasNextFeedsPage,
    feedsError,
    fetchNextFeedsPage,
  ])

  const items = React.useMemo<ExploreScreenItems[]>(() => {
    const i: ExploreScreenItems[] = [
      {
        type: 'header',
        key: 'suggested-follows-header',
        title: _(msg`Suggested accounts`),
        description: _(
          msg`Follow more accounts to get connected to your interests and build your network.`,
        ),
        icon: Person,
      },
    ]

    if (profiles) {
      // Currently the responses contain duplicate items.
      // Needs to be fixed on backend, but let's dedupe to be safe.
      let seen = new Set()
      const profileItems: ExploreScreenItems[] = []
      for (const page of profiles.pages) {
        for (const actor of page.actors) {
          if (!seen.has(actor.did)) {
            seen.add(actor.did)
            profileItems.push({
              type: 'profile',
              key: actor.did,
              profile: actor,
            })
          }
        }
      }

      if (hasNextProfilesPage) {
        // splice off 3 as previews if we have a next page
        const previews = profileItems.splice(-3)
        // push remainder
        i.push(...profileItems)
        i.push({
          type: 'loadMore',
          key: 'loadMoreProfiles',
          isLoadingMore: isLoadingMoreProfiles,
          onLoadMore: onLoadMoreProfiles,
          items: previews,
        })
      } else {
        i.push(...profileItems)
      }
    } else {
      if (profilesError) {
        i.push({
          type: 'error',
          key: 'profilesError',
          message: _(msg`Failed to load suggested follows`),
          error: cleanError(profilesError),
        })
      } else {
        i.push({type: 'profilePlaceholder', key: 'profilePlaceholder'})
      }
    }

    i.push({
      type: 'header',
      key: 'suggested-feeds-header',
      title: _(msg`Discover new feeds`),
      description: _(
        msg`Custom feeds built by the community bring you new experiences and help you find the content you love.`,
      ),
      style: [a.pt_5xl],
      icon: ListSparkle,
    })

    if (feeds && preferences) {
      // Currently the responses contain duplicate items.
      // Needs to be fixed on backend, but let's dedupe to be safe.
      let seen = new Set()
      const feedItems: ExploreScreenItems[] = []
      for (const page of feeds.pages) {
        for (const feed of page.feeds) {
          if (!seen.has(feed.uri)) {
            seen.add(feed.uri)
            feedItems.push({
              type: 'feed',
              key: feed.uri,
              feed,
            })
          }
        }
      }

      // feeds errors can occur during pagination, so feeds is truthy
      if (feedsError) {
        i.push({
          type: 'error',
          key: 'feedsError',
          message: _(msg`Failed to load suggested feeds`),
          error: cleanError(feedsError),
        })
      } else if (preferencesError) {
        i.push({
          type: 'error',
          key: 'preferencesError',
          message: _(msg`Failed to load feeds preferences`),
          error: cleanError(preferencesError),
        })
      } else if (hasNextFeedsPage) {
        const preview = feedItems.splice(-3)
        i.push(...feedItems)
        i.push({
          type: 'loadMore',
          key: 'loadMoreFeeds',
          isLoadingMore: isLoadingMoreFeeds,
          onLoadMore: onLoadMoreFeeds,
          items: preview,
        })
      } else {
        i.push(...feedItems)
      }
    } else {
      if (feedsError) {
        i.push({
          type: 'error',
          key: 'feedsError',
          message: _(msg`Failed to load suggested feeds`),
          error: cleanError(feedsError),
        })
      } else if (preferencesError) {
        i.push({
          type: 'error',
          key: 'preferencesError',
          message: _(msg`Failed to load feeds preferences`),
          error: cleanError(preferencesError),
        })
      } else {
        i.push({type: 'feedPlaceholder', key: 'feedPlaceholder'})
      }
    }

    return i
  }, [
    _,
    profiles,
    feeds,
    preferences,
    onLoadMoreFeeds,
    onLoadMoreProfiles,
    isLoadingMoreProfiles,
    isLoadingMoreFeeds,
    profilesError,
    feedsError,
    preferencesError,
    hasNextProfilesPage,
    hasNextFeedsPage,
  ])

  const renderItem = React.useCallback(
    ({item}: {item: ExploreScreenItems}) => {
      switch (item.type) {
        case 'header': {
          return (
            <SuggestedItemsHeader
              title={item.title}
              description={item.description}
              style={item.style}
              icon={item.icon}
            />
          )
        }
        case 'profile': {
          return (
            <View style={[a.border_b, t.atoms.border_contrast_low]}>
              <ProfileCardWithFollowBtn profile={item.profile} noBg noBorder />
            </View>
          )
        }
        case 'feed': {
          return (
            <View
              style={[
                a.border_b,
                t.atoms.border_contrast_low,
                a.px_lg,
                a.py_lg,
              ]}>
              <FeedCard.Default view={item.feed} />
            </View>
          )
        }
        case 'loadMore': {
          return <LoadMore item={item} moderationOpts={moderationOpts} />
        }
        case 'profilePlaceholder': {
          return <ProfileCardFeedLoadingPlaceholder />
        }
        case 'feedPlaceholder': {
          return <FeedFeedLoadingPlaceholder />
        }
        case 'error': {
          return (
            <View
              style={[
                a.border_t,
                a.pt_md,
                a.px_md,
                t.atoms.border_contrast_low,
              ]}>
              <View
                style={[
                  a.flex_row,
                  a.gap_md,
                  a.p_lg,
                  a.rounded_sm,
                  t.atoms.bg_contrast_25,
                ]}>
                <CircleInfo size="md" fill={t.palette.negative_400} />
                <View style={[a.flex_1, a.gap_sm]}>
                  <Text style={[a.font_bold, a.leading_snug]}>
                    {item.message}
                  </Text>
                  <Text
                    style={[
                      a.italic,
                      a.leading_snug,
                      t.atoms.text_contrast_medium,
                    ]}>
                    {item.error}
                  </Text>
                </View>
              </View>
            </View>
          )
        }
      }
    },
    [t, moderationOpts],
  )

  return (
    <List
      data={items}
      renderItem={renderItem}
      keyExtractor={item => item.key}
      // @ts-ignore web only -prf
      desktopFixedHeight
      contentContainerStyle={{paddingBottom: 200}}
      keyboardShouldPersistTaps="handled"
      keyboardDismissMode="on-drag"
    />
  )
}
