import React, { useContext, useEffect, useRef, useState } from 'react'
import {
  ActivityIndicator,
  Animated,
  RefreshControlProps,
  StyleProp,
  Text,
  View,
  ViewStyle,
} from 'react-native'

import { colors } from '../Colors'
import { dspNames, PlaylistDsp } from '../Consts'
import { ReactNavigationProps } from '../Navigation'
import { TextStyles } from '../Styles'
import { AutoLayout } from '../components/AutoLayout'
import { BarSelect } from '../components/BarSelect'
import { CommonScreenContainer } from '../components/CommonScreenContainer'
import { DropdownButton } from '../components/DropdownButton'
import { ErrorBoundary } from '../components/ErrorBoundary'
import { GraphDetailScreenHeader } from '../components/GraphDetailScreenHeader'
import { HeaderNav } from '../components/HeaderNav'
import { MarketSelectorButton } from '../components/MarketSelectorButton'
import { PlaylistListModeSelectorModal } from '../components/PlaylistListModeSelectorModal'
import { PlaylistTypeSelectorModal } from '../components/PlaylistTypeSelectorModal'
import { ProductImage } from '../components/ProductImage'
import {
  TrackPlaylistCurrent,
  TrackPlaylistPast,
} from '../components/TrackPlaylistV2'
import { EntityPositionAndChange } from '../components/listitems/EntityPositionListItem'
import { ScreenNames } from '../constants/ScreenNames'
import { ArtistContext } from '../contexts/ArtistContext'
import { ScreenContext } from '../contexts/ScreenContext'
import {
  OrderDirection,
  PlaylistType,
  TrackCurrentPlaylistsOrder,
  TrackCurrentPlaylistsOrderField,
  TrackPlaylistCurrentFieldsFragment,
  TrackPlaylistPreviousFieldsFragment,
  TrackPreviousPlaylistsOrder,
  TrackPreviousPlaylistsOrderField,
} from '../graphql/__generated__/sdk'
import { EventType, sendAnalyticsEvent } from '../hooks/useAnalytics'
import { useScreenRefreshControl } from '../hooks/useScreenRefreshControl'
import { useTrack } from '../hooks/useTrack'
import { useTrackPlaylistsData } from '../hooks/useTrackPlaylistsData'
import { useTrackPlaylistsPrevious } from '../hooks/useTrackPlaylistsPrevious'
import { useTrackTopCountryCodes } from '../hooks/useTrackTopCountries'
import { Strings } from '../i18n'
import { labelForPlaylistType } from '../playlists'
import { defaultTimeInterval } from '../types/TimeInterval'
import { formatDate, notEmpty, timeRangeInterval } from '../util'

const allPlaylistListModes = ['Current', 'Past'] as const
export type PlaylistListMode = typeof allPlaylistListModes[number]
const playlistListModesForDsps: Record<PlaylistDsp, PlaylistListMode[]> = {
  spotify: [...allPlaylistListModes],
  apple: [...allPlaylistListModes],
  amazon: ['Current'], // Delphi doesn't support Past Playlists for Amazon
}

const sortingsForModes: Record<
  PlaylistListMode,
  { item: PlaylistSortingOrder; disabled: boolean }[]
> = {
  Current: [
    { item: 'streams', disabled: false },
    { item: 'date_added', disabled: false },
    { item: 'position', disabled: false },
  ],
  Past: [
    { item: 'streams', disabled: true },
    { item: 'date_dropped', disabled: false },
    { item: 'position', disabled: true },
  ],
}

const sortByStreamsLast7Days = {
  field: TrackCurrentPlaylistsOrderField.StreamsLast_7Days,
  direction: OrderDirection.Desc,
}
const sortByCurrentPosition = {
  field: TrackCurrentPlaylistsOrderField.CurrentPosition,
  direction: OrderDirection.Asc,
}
const sortByPositionChange = {
  field: TrackCurrentPlaylistsOrderField.PositionChange,
  direction: OrderDirection.Desc,
}
const sortByPlaylistName = {
  field: TrackCurrentPlaylistsOrderField.PlaylistName,
  direction: OrderDirection.Desc,
}
const sortByEarliestPositionDate = {
  field: TrackCurrentPlaylistsOrderField.EarliestPositionDate,
  direction: OrderDirection.Desc,
}

export const orderBysCurrent: Record<
  PlaylistSortingOrder,
  TrackCurrentPlaylistsOrder[]
> = {
  streams: [
    sortByStreamsLast7Days,
    sortByCurrentPosition,
    sortByPositionChange,
  ],
  date_added: [
    sortByEarliestPositionDate,
    sortByStreamsLast7Days,
    sortByPlaylistName,
  ],
  date_dropped: [],
  position: [
    sortByCurrentPosition,
    sortByStreamsLast7Days,
    sortByPositionChange,
  ],
}

export const orderBysPrevious: Record<
  PlaylistSortingOrder,
  TrackPreviousPlaylistsOrder[]
> = {
  streams: [],
  date_added: [],
  date_dropped: [
    {
      field: TrackPreviousPlaylistsOrderField.LastPositionDate,
      direction: OrderDirection.Desc,
    },
    {
      field: TrackPreviousPlaylistsOrderField.PlaylistName,
      direction: OrderDirection.Desc,
    },
  ],
  position: [],
}

export const PlaylistTypeSelectorButton: React.FC<{
  playlistType: PlaylistType | null
  onPress: () => void
  style?: StyleProp<ViewStyle>
}> = ({ playlistType, onPress, style }) => (
  <DropdownButton
    label={labelForPlaylistType(playlistType)}
    onPress={onPress}
    style={[{ flex: 3 }, style]}
  />
)

export const ModeSelectorButton: React.FC<{
  playlistListMode: PlaylistListMode
  onPress: () => void
  style?: StyleProp<ViewStyle>
}> = ({ playlistListMode, onPress, style }) => (
  <DropdownButton
    label={Strings.PlaylistListMode[playlistListMode].Short}
    onPress={onPress}
    style={[{ flex: 2 }, style]}
  />
)

const sortOptions = [
  'streams',
  'date_added',
  'date_dropped',
  'position',
] as const
type PlaylistSortingOrder = typeof sortOptions[number]

export const PlaylistListHeader: React.FC<{
  playlistListModes: PlaylistListMode[]
  playlistListMode: PlaylistListMode
  numPlaylists?: number
  sortings: { item: PlaylistSortingOrder; disabled: boolean }[]
  sorting: PlaylistSortingOrder
  toggleSort: (order: PlaylistSortingOrder) => void
  setPlaylistTypeModalOpen: (open: boolean) => void
  setPlaylistListModeModalOpen: (open: boolean) => void
  playlistType: PlaylistType | null
  style?: Animated.AnimatedProps<ViewStyle>
}> = ({
  playlistListModes,
  playlistListMode,
  numPlaylists,
  sortings,
  sorting,
  toggleSort,
  setPlaylistTypeModalOpen,
  setPlaylistListModeModalOpen,
  playlistType,
  style,
}) => (
  <Animated.View style={style}>
    <AutoLayout style={{ flexDirection: 'row' }} gap={4}>
      <PlaylistTypeSelectorButton
        playlistType={playlistType}
        onPress={() => {
          setPlaylistTypeModalOpen(true)
        }}
      />
      {playlistListModes.length > 1 ? (
        <ModeSelectorButton
          playlistListMode={playlistListMode}
          onPress={() => {
            setPlaylistListModeModalOpen(true)
          }}
        />
      ) : null}
    </AutoLayout>
    <View
      style={{
        flexDirection: 'column',
        justifyContent: 'space-between',
      }}
    >
      <BarSelect<PlaylistSortingOrder>
        options={sortings}
        value={sorting}
        onChange={toggleSort}
        translateLabel={(order) => {
          switch (order) {
            case 'position':
              return `${Strings.Shared.Position}  ↓`
            case 'date_added':
              return `${Strings.Shared.DateAdded}  ↓`
            case 'date_dropped':
              return `${Strings.Shared.DateDropped}  ↓`
            case 'streams':
              return `${Strings.Shared.Streams}  ↓`
          }
        }}
      />
      {numPlaylists != null && (
        <Text style={[TextStyles.small, { color: colors.white }]}>
          {numPlaylists === 1
            ? Strings.Shared.OnePlaylistSingular
            : Strings.Shared.NPlaylistsPlural.replace(
                '%d',
                numPlaylists.toString()
              )}
        </Text>
      )}
    </View>
  </Animated.View>
)

export const PlaylistListEmpty: React.FC<{
  playlistListMode: PlaylistListMode
  playlistType: PlaylistType | null
  dsp: PlaylistDsp
}> = ({ playlistListMode, playlistType, dsp }) => {
  const EmptyStringForListMode: Record<PlaylistListMode, string> = {
    Current: Strings.PlaylistsEmpty,
    Past: Strings.PastPlaylistsEmpty,
  }

  const EmptyString = EmptyStringForListMode[playlistListMode]

  return (
    <View
      style={{
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        padding: 24,
      }}
    >
      <Text
        style={[TextStyles.body, { color: colors.white, textAlign: 'center' }]}
      >
        {EmptyString.replace(
          '%s',
          [
            dspNames[dsp],
            playlistType ? labelForPlaylistType(playlistType) : null,
          ]
            .filter(notEmpty)
            .join(' ')
        )}
      </Text>
    </View>
  )
}

const DateAdded: React.FC<{
  position: { earliestPositionDate: string | null }
}> = ({ position: { earliestPositionDate } }) =>
  earliestPositionDate ? (
    <Text style={[TextStyles.tinyBold, { color: colors.white }]}>
      {Strings.Shared.AddedOnDate.replace(
        '%s',
        formatDate(earliestPositionDate)
      )}
    </Text>
  ) : null

const DateDropped: React.FC<{
  position: { lastPositionDate: string | null }
}> = ({ position: { lastPositionDate } }) =>
  lastPositionDate ? (
    <Text style={[TextStyles.tinyBold, { color: colors.white }]}>
      {Strings.Shared.DroppedOnDate.replace('%s', formatDate(lastPositionDate))}
    </Text>
  ) : null

const CurrentPlaylistsList: React.FC<{
  isrc: string
  interval: Interval
  dsp: PlaylistDsp
  countryCode: string
  playlistType: PlaylistType | null

  onPressPlaylist: (playListId: string) => void
  sorting: PlaylistSortingOrder
  paddingTop: number
  refreshControl: React.ReactElement<RefreshControlProps>
  scrollY: Animated.Value
  ListEmptyComponent: React.ReactElement
  setNumplaylists: (num?: number) => void
}> = ({
  isrc,
  interval,
  dsp,
  countryCode,
  playlistType,
  onPressPlaylist,
  sorting,
  paddingTop,
  refreshControl,
  scrollY,
  ListEmptyComponent,
  setNumplaylists,
}) => {
  /*
   * Don't display playlist position and position change for algorithmic playlists.
   * Playlist position and change for algorithmic playlists is a side effect of how
   * they are obtained, and shouldn't be defined in the source data
   */
  const renderExtra = (trackPlaylist: TrackPlaylistCurrentFieldsFragment) =>
    sorting === 'date_added' ? (
      <DateAdded position={trackPlaylist} />
    ) : trackPlaylist.playlist.type === PlaylistType.Algorithmic ? (
      <Text style={[TextStyles.small, { color: colors.white }]}>
        {Strings.PlaylistTypes.algorithmic}
      </Text>
    ) : (
      <EntityPositionAndChange
        position={{
          current: trackPlaylist.currentPosition ?? undefined,
          previous: trackPlaylist.previousPosition ?? undefined,
        }}
      />
    )
  const orderBy = orderBysCurrent[sorting]
  const playlistsConnection = useTrackPlaylistsData(
    isrc,
    interval,
    dsp,
    countryCode,
    playlistType != null ? [playlistType] : [],
    orderBy
  ).data?.artist?.track?.currentPlaylists
  const playlistTrackPositions:
    | TrackPlaylistCurrentFieldsFragment[]
    | undefined = playlistsConnection?.edges.map(({ node }) => node)

  useEffect(() => {
    setNumplaylists(playlistsConnection?.totalCount)
  }, [playlistsConnection?.totalCount, setNumplaylists])

  return playlistTrackPositions != null ? (
    <Animated.FlatList
      renderItem={({ item }) => (
        <TrackPlaylistCurrent
          key={item.playlist.id}
          playlist={item}
          positionDate={
            sorting === 'date_added' && item.earliestPositionDate
              ? item.earliestPositionDate
              : undefined
          }
          extra={renderExtra(item)}
          showDetails
          style={{
            padding: 12,
            backgroundColor: colors.metals.metal2,
            borderRadius: 8,
            marginBottom: 8,
          }}
          onPress={() => onPressPlaylist(item.playlist.id)}
        />
      )}
      keyExtractor={(item) => item.playlist.id}
      data={playlistTrackPositions}
      contentContainerStyle={
        playlistTrackPositions.length === 0 ? { flex: 1 } : { paddingTop }
      }
      ListEmptyComponent={ListEmptyComponent}
      onScroll={Animated.event(
        [{ nativeEvent: { contentOffset: { y: scrollY } } }],
        { useNativeDriver: true }
      )}
      scrollEventThrottle={1}
      refreshControl={refreshControl}
    />
  ) : (
    <ActivityIndicator style={{ flex: 1 }} />
  )
}

const PastPlaylistsList: React.FC<{
  isrc: string
  dsp: PlaylistDsp
  countryCode: string
  playlistType: PlaylistType | null
  sorting: PlaylistSortingOrder
  paddingTop: number
  refreshControl: React.ReactElement<RefreshControlProps>
  scrollY: Animated.Value
  ListEmptyComponent: React.ReactElement
  setNumplaylists: (num?: number) => void
}> = ({
  isrc,
  dsp,
  countryCode,
  playlistType,
  sorting,
  paddingTop,
  refreshControl,
  scrollY,
  ListEmptyComponent,
  setNumplaylists,
}) => {
  /*
   * Don't display playlist position and position change for algorithmic playlists.
   * Playlist position and change for algorithmic playlists is a side effect of how
   * they are obtained, and shouldn't be defined in the source data
   */
  const renderExtra = (trackPlaylist: TrackPlaylistPreviousFieldsFragment) =>
    sorting === 'date_dropped' ? (
      <DateDropped position={trackPlaylist} />
    ) : (
      <></>
    )

  const orderBy = orderBysPrevious[sorting]
  const playlistsConnection = useTrackPlaylistsPrevious(
    isrc,
    dsp,
    countryCode,
    playlistType != null ? [playlistType] : [],
    orderBy
  ).data?.artist?.track?.previousPlaylists

  const playlistTrackPositions:
    | TrackPlaylistPreviousFieldsFragment[]
    | undefined = playlistsConnection?.edges.map(({ node }) => node)

  useEffect(() => {
    setNumplaylists(playlistsConnection?.totalCount)
  }, [playlistsConnection?.totalCount, setNumplaylists])

  return playlistTrackPositions != null ? (
    <Animated.FlatList
      renderItem={({ item }) => (
        <TrackPlaylistPast
          key={item.playlist.id}
          playlist={item}
          earliestPositionDate={item.earliestPositionDate ?? undefined}
          lastPositionDate={item.lastPositionDate ?? undefined}
          numDaysOn={item.numDaysOn ?? undefined}
          extra={renderExtra(item)}
          style={{
            padding: 12,
            backgroundColor: colors.metals.metal2,
            borderRadius: 8,
            marginBottom: 8,
          }}
          pressDisabled // Navigating to past playlists is disabled
        />
      )}
      keyExtractor={(item) => item.playlist.id}
      data={playlistTrackPositions}
      contentContainerStyle={
        playlistTrackPositions.length === 0 ? { flex: 1 } : { paddingTop }
      }
      ListEmptyComponent={ListEmptyComponent}
      onScroll={Animated.event(
        [{ nativeEvent: { contentOffset: { y: scrollY } } }],
        { useNativeDriver: true }
      )}
      scrollEventThrottle={1}
      refreshControl={refreshControl}
    />
  ) : (
    <ActivityIndicator style={{ flex: 1 }} />
  )
}

export const TrackPlaylistsScreen: React.FC<
  ReactNavigationProps<ScreenNames.TrackPlaylists>
> = ({ navigation, route }) => {
  const screenName = 'Track Playlists Screen'
  const { isrc, dsp } = route.params
  const artist = useContext(ArtistContext)

  const { data, retry, refreshing, showToast, error } = useTrack(isrc)
  const onPullToRefresh = () => {
    sendAnalyticsEvent(EventType.PULL_TO_REFRESH, {
      artist_selected: artist.name,
      page: screenName,
      subject: 'user',
      verb: 'refreshed',
      object: 'track',
    })
    retry()
  }

  const { refreshControl, screenContext } = useScreenRefreshControl(
    onPullToRefresh,
    refreshing
  )

  const track = data?.artist?.track
  const countryCode = route.params.market ?? 'worldwide'
  const timeInterval = defaultTimeInterval
  const interval = timeRangeInterval(timeInterval)

  const [sorting, setSorting] = useState<PlaylistSortingOrder>('streams')

  const playlistListModes = playlistListModesForDsps[dsp]
  const [playlistListMode, setPlaylistListMode] = useState<PlaylistListMode>(
    playlistListModes[0]
  )
  const [playlistListModeModalIsOpen, setPlaylistListModeModalOpen] =
    useState(false)
  const [playlistType, setPlaylistType] = useState<PlaylistType | null>(null)
  const [playlistTypeModalIsOpen, setPlaylistTypeModalOpen] = useState(false)
  const [numPlaylists, setNumPlaylists] = useState<number | undefined>(
    undefined
  )

  const sortings = sortingsForModes[playlistListMode]

  useEffect(() => {
    const currentSorting = sortings.find((s) => s.item === sorting)
    if (currentSorting == null || currentSorting?.disabled) {
      const newSorting = sortings.find((s) => !s.disabled)?.item
      if (newSorting) {
        setSorting(newSorting)
      }
    }
  }, [playlistListMode, sortings, sorting])

  const scrollY = useRef(new Animated.Value(0)).current
  const headerHeight = 132
  const minScroll = 0
  const clampedScrollY = scrollY.interpolate({
    inputRange: [minScroll, minScroll + 1],
    outputRange: [0, 1],
    extrapolateLeft: 'clamp',
  })
  const minusScrollY = Animated.multiply(clampedScrollY, -1)
  const headerTranslate = Animated.diffClamp(minusScrollY, -headerHeight, 0)

  const trackTopMarketCountryCodes = useTrackTopCountryCodes(isrc, timeInterval)

  return (
    <ScreenContext.Provider value={screenContext}>
      <CommonScreenContainer screenName={screenName}>
        <ErrorBoundary error={error} refreshControl={refreshControl}>
          <HeaderNav
            style={{
              // Ensures header is over the hidden (scrolled-up) PlaylistListHeader
              zIndex: 1,
            }}
            title={track?.name ?? ''}
            showToast={showToast}
            navigation={navigation}
            right={
              <MarketSelectorButton
                countryCode={countryCode}
                onPress={() => {
                  navigation.navigate('MarketSelector', {
                    previousScreen: 'TrackPlaylists',
                    topMarketCountryCodes: trackTopMarketCountryCodes,
                  })
                }}
              />
            }
          />
          <View
            style={{
              flex: 1,
              paddingHorizontal: 8,
            }}
          >
            <GraphDetailScreenHeader
              image={
                track != null ? (
                  <ProductImage
                    product={track.product}
                    isExplicit={track.isExplicit}
                    size={48}
                  />
                ) : null
              }
              title={Strings.Shared.Playlists}
              dsp={dsp}
            />

            <View style={{ flex: 1, zIndex: -1 }}>
              {playlistListMode === 'Current' ? (
                <CurrentPlaylistsList
                  isrc={isrc}
                  interval={interval}
                  dsp={dsp}
                  countryCode={countryCode}
                  playlistType={playlistType}
                  onPressPlaylist={(playlistId) => {
                    navigation.navigate('TrackPlaylist', {
                      isrc,
                      playlist_id: playlistId,
                      market: countryCode,
                    })
                  }}
                  sorting={sorting}
                  paddingTop={headerHeight}
                  refreshControl={refreshControl}
                  scrollY={scrollY}
                  ListEmptyComponent={
                    <PlaylistListEmpty
                      playlistListMode={playlistListMode}
                      playlistType={playlistType}
                      dsp={dsp}
                    />
                  }
                  setNumplaylists={setNumPlaylists}
                />
              ) : (
                <PastPlaylistsList
                  isrc={isrc}
                  dsp={dsp}
                  countryCode={countryCode}
                  playlistType={playlistType}
                  sorting={sorting}
                  paddingTop={headerHeight}
                  refreshControl={refreshControl}
                  scrollY={scrollY}
                  ListEmptyComponent={
                    <PlaylistListEmpty
                      playlistListMode={playlistListMode}
                      playlistType={playlistType}
                      dsp={dsp}
                    />
                  }
                  setNumplaylists={setNumPlaylists}
                />
              )}
              <PlaylistListHeader
                playlistListModes={playlistListModes}
                playlistListMode={playlistListMode}
                numPlaylists={numPlaylists}
                sortings={sortings}
                sorting={sorting}
                toggleSort={setSorting}
                setPlaylistTypeModalOpen={setPlaylistTypeModalOpen}
                setPlaylistListModeModalOpen={setPlaylistListModeModalOpen}
                playlistType={playlistType}
                style={{
                  height: headerHeight,
                  position: 'absolute',
                  top: 0,
                  left: 0,
                  right: 0,
                  backgroundColor: colors.black,
                  transform: [{ translateY: headerTranslate }],
                }}
              />
            </View>

            <PlaylistListModeSelectorModal
              options={playlistListModes}
              isOpen={playlistListModeModalIsOpen}
              playlistListMode={playlistListMode}
              setPlaylistListMode={setPlaylistListMode}
              setIsOpen={setPlaylistListModeModalOpen}
            />
            <PlaylistTypeSelectorModal
              isOpen={playlistTypeModalIsOpen}
              playlistType={playlistType}
              setPlaylistType={setPlaylistType}
              setIsOpen={setPlaylistTypeModalOpen}
            />
          </View>
        </ErrorBoundary>
      </CommonScreenContainer>
    </ScreenContext.Provider>
  )
}
