import { useNavigation } from '@react-navigation/native'
import { debounce, uniqBy } from 'lodash'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
  KeyboardAvoidingView,
  Modal,
  Platform,
  Pressable,
  RefreshControlProps,
  SectionList,
  Text,
  TextInput,
  View,
} from 'react-native'

import { colors } from '../Colors'
import { TextStyles } from '../Styles'
import { ScreenNames } from '../constants/ScreenNames'
import { EventType, sendAnalyticsEvent } from '../hooks/useAnalytics'
import { NetworkError } from '../hooks/useNetwork'
import { useScreenRefreshControl } from '../hooks/useScreenRefreshControl'
import { useTracksStreams } from '../hooks/useTracksStreams'
import { Strings } from '../i18n'
import ClearX from '../icons/ClearX'
import { CloseXIcon } from '../icons/CloseXIcon'
import { SearchIcon } from '../icons/SearchIcon'
import { defaultTimeInterval } from '../types/TimeInterval'
import { Track } from '../types/Track'
import { trackItemFieldsFromTrack } from '../types/graphqlCompatibilityHelpers'
import { timeRangeInterval } from '../util'
import { Button } from './Button'
import { ErrorBoundary } from './ErrorBoundary'
import { TrackListSkeleton } from './Skeleton'
import { TrackListItem as TrackListItemType } from './TrackList'
import { SelectableListItem } from './listitems/SelectableListItem'
import { TrackListItem } from './listitems/TrackListItem'

const SELECTED_TRACKS = 'SelectedTracks'
const ALL_TRACKS = 'AllTracks'

const titles = [SELECTED_TRACKS, ALL_TRACKS] as const
type TitleType = typeof titles[number]

export const useTrackComparisonSelectionModal = (initialOpen = false) => {
  const [
    trackComparisonSelectionModalOpen,
    setTrackComparisonSelectionModalOpen,
  ] = useState(initialOpen)
  return {
    trackComparisonSelectionModalOpen,
    setTrackComparisonSelectionModalOpen,
  }
}

export const TrackComparisonSelectionModalContainer: React.FC<{
  initialSelectedTrackIsrcs?: string[]
  open: boolean
  onClose: () => void
}> = ({ initialSelectedTrackIsrcs, open, onClose }) => {
  const interval = timeRangeInterval(defaultTimeInterval)
  const { streams, error, retry, refreshing } = useTracksStreams(null, interval)

  const [tracks, setTracks] = useState<Track[]>()
  const [initialSelectedTracks, setInitialSelectedTracks] = useState<Track[]>()
  const navigation = useNavigation()

  useEffect(() => {
    if (streams) {
      const uniqueTracks = uniqBy(streams, ({ item }) => item.isrc)
        .filter(({ data }) => data.length > 0) // Only show tracks that has been streamed
        .map(({ item }) => item)
        .sort(sortTracksByReleaseDate)

      setTracks(uniqueTracks)

      if (initialSelectedTrackIsrcs && initialSelectedTrackIsrcs.length > 0) {
        const selectedTracks = uniqueTracks.filter((track) =>
          initialSelectedTrackIsrcs.includes(track.isrc)
        )

        setInitialSelectedTracks(selectedTracks)
      }
    }
  }, [streams, initialSelectedTrackIsrcs])

  const { refreshControl } = useScreenRefreshControl(retry, refreshing)

  const onCompare = (selectedTracks: Track[]) => {
    const isrcs = selectedTracks.map((track) => track.isrc)

    // Allow closing modal to happen before navigating
    setTimeout(
      () =>
        navigation.navigate(ScreenNames.TracksComparison, {
          isrcs,
        }),
      300
    )

    sendTrackComparisonSetAnalyticsEvent(isrcs)
  }

  const sendTrackComparisonSetAnalyticsEvent = (isrcs: string[]) => {
    sendAnalyticsEvent(EventType.TRACK_COMPARISON_SET, {
      isrcs,
      subject: 'user',
      verb: 'filtered',
      object: 'track_comparison',
    })
  }

  return (
    <TrackComparisonSelectionModal
      tracks={tracks}
      initialSelectedTracks={initialSelectedTracks}
      open={open}
      onCompare={onCompare}
      onClose={onClose}
      error={error}
      refreshControl={refreshControl}
    />
  )
}

export const TrackComparisonSelectionModal: React.FC<{
  tracks?: Track[]
  initialSelectedTracks?: Track[]
  open: boolean
  onCompare: (selectedTracks: Track[]) => void
  onClose: () => void
  error: NetworkError | null
  refreshControl: React.ReactElement<RefreshControlProps>
}> = ({
  tracks,
  initialSelectedTracks,
  open,
  onCompare,
  onClose,
  error,
  refreshControl,
}) => {
  const [allTrackItems, setAllTrackItems] = useState<
    TrackListItemType[] | undefined
  >([])
  const [selectedTrackItems, setSelectedTrackItems] = useState<
    TrackListItemType[]
  >([])
  const [filterInputText, setFilterInputText] = useState('')
  const [wordsToFilter, setWordsToFilter] = useState<string[]>([])

  const [tracksSections, setTracksSections] =
    useState<
      [
        { title: TitleType; data: TrackListItemType[] },
        { title: TitleType; data: TrackListItemType[] }
      ]
    >()

  useEffect(() => {
    setAllTrackItems(tracks?.map((track) => ({ track })))
    setSelectedTrackItems(
      initialSelectedTracks?.map((track) => ({ track })) ?? []
    )
  }, [tracks, initialSelectedTracks])

  useEffect(() => {
    const availableTracks =
      allTrackItems
        ?.filter(
          (item) =>
            !selectedTrackItems?.some(
              ({ track: { isrc } }) => isrc === item.track.isrc
            )
        )
        .filter(({ track }) =>
          wordsToFilter.every((word) => track.name.toLowerCase().includes(word))
        ) ?? []

    setTracksSections([
      { title: SELECTED_TRACKS, data: selectedTrackItems },
      { title: ALL_TRACKS, data: availableTracks },
    ])
  }, [allTrackItems, selectedTrackItems, wordsToFilter])

  const sectionListRef = useRef<SectionList>(null)

  const handleOnTrackListItemSelectedChange = useCallback(
    (trackListItem, selected) => {
      if (selected) {
        setSelectedTrackItems((prevSelectedTracks) => [
          ...prevSelectedTracks,
          trackListItem,
        ])

        sectionListRef.current?.scrollToLocation({
          sectionIndex: 0,
          itemIndex: 0,
        })
      } else {
        setSelectedTrackItems((prevSelectedTracks) =>
          prevSelectedTracks.filter(
            ({ track }) => track.isrc !== trackListItem.track.isrc
          )
        )
      }

      clearFilteredResult()
    },
    [sectionListRef]
  )

  const updateWordsToFilter = useMemo(
    () =>
      debounce((filterText: string) => {
        const wordsToFilter = filterText
          .split(' ')
          .filter(Boolean)
          .map((s) => s.toLocaleLowerCase())

        setWordsToFilter(wordsToFilter)
      }, 300),
    []
  )

  useEffect(
    () => updateWordsToFilter(filterInputText),
    [updateWordsToFilter, filterInputText]
  )

  const clearFilteredResult = () => {
    setWordsToFilter([])
    setFilterInputText('')
  }

  const numberOfSelectedTracks = tracksSections?.[0]?.data.length ?? 0
  const hasAnyTrackSelected = numberOfSelectedTracks > 0
  const hasTracksSectionsData =
    tracksSections?.length === 2 &&
    (tracksSections[0].data.length > 0 || tracksSections[1].data.length > 0)
  const hasAllTrackItems = allTrackItems && allTrackItems.length > 0
  const showTracksSections = tracksSections && hasAllTrackItems
  const showNoTracksStreamed =
    tracksSections && allTrackItems && allTrackItems.length === 0
  const showNoFilteredResult =
    hasAllTrackItems && !hasTracksSectionsData && filterInputText !== ''

  return (
    <Modal
      animationType="slide"
      transparent={false}
      visible={open}
      onRequestClose={onClose}
    >
      <KeyboardAvoidingView
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={{
          flex: 1,
          paddingTop: 44,
          paddingBottom: 16,
          paddingHorizontal: 16,
          backgroundColor: colors.metals.metal3,
        }}
      >
        <Header onClose={onClose} />
        <ErrorBoundary error={error} refreshControl={refreshControl}>
          {showNoFilteredResult ? (
            <NoFilteredResult
              onPress={() => {
                setWordsToFilter([])
                setFilterInputText('')
              }}
            />
          ) : (
            <View style={{ flex: 1 }}>
              {showTracksSections ? (
                <SectionList
                  ref={sectionListRef}
                  sections={tracksSections}
                  keyExtractor={(track) => track.track.isrc}
                  renderItem={({ item, section }) => (
                    <TrackComparisonListItem
                      key={item.track.isrc}
                      trackListItem={item}
                      onChange={handleOnTrackListItemSelectedChange}
                      selected={section.title === SELECTED_TRACKS}
                    />
                  )}
                  renderSectionHeader={({ section: { title } }) => {
                    const titleText = title as TitleType

                    if (!hasAnyTrackSelected) {
                      return null
                    }

                    return (
                      <TrackListSectionHeader>
                        {Strings.Shared[titleText]}
                      </TrackListSectionHeader>
                    )
                  }}
                  renderSectionFooter={({ section: { title } }) => {
                    const hasFilteredOutAllAvailableTracks =
                      (title as TitleType) === 'AllTracks' &&
                      tracksSections[0].data.length < allTrackItems.length &&
                      tracksSections[1].data.length === 0 &&
                      filterInputText !== ''

                    return hasFilteredOutAllAvailableTracks ? (
                      <View style={{ marginVertical: 40 }}>
                        <NoFilteredResult onPress={clearFilteredResult} />
                      </View>
                    ) : null
                  }}
                  stickySectionHeadersEnabled={false}
                />
              ) : showNoTracksStreamed ? (
                <NoTracksMessage />
              ) : (
                <TrackListSkeleton numItems={5} />
              )}
            </View>
          )}
          {(showTracksSections || showNoFilteredResult) && (
            <View
              style={{
                height: 72,
                paddingBottom: 8,
                flexDirection: 'row',
                justifyContent: 'flex-start',
                alignItems: 'center',
              }}
            >
              <FilterTextInput
                text={filterInputText}
                setText={setFilterInputText}
              />
              <Button
                type="filled"
                muted={numberOfSelectedTracks < 2}
                onPress={() => {
                  onClose()
                  onCompare(selectedTrackItems.map(({ track }) => track))
                }}
              >
                {Strings.Shared.Compare}
              </Button>
            </View>
          )}
        </ErrorBoundary>
      </KeyboardAvoidingView>
    </Modal>
  )
}

const TrackListSectionHeader: React.FC = ({ children }) => (
  <Text
    style={{
      marginBottom: 8,
      ...TextStyles.body,
      color: colors.white,
    }}
  >
    {children}
  </Text>
)

const TrackComparisonListItem: React.FC<{
  trackListItem: TrackListItemType
  onChange: (trackListItem: TrackListItemType, selected: boolean) => void
  selected?: boolean
  disabled?: boolean
}> = ({ trackListItem, onChange, selected, disabled }) => {
  const { track } = trackListItem

  return (
    <View style={{ marginBottom: 4 }}>
      <SelectableListItem
        selected={selected}
        disabled={disabled}
        onChange={(selected) => onChange(trackListItem, selected)}
        style={{ borderRadius: 4 }}
      >
        <TrackListItem track={trackItemFieldsFromTrack(track)} />
      </SelectableListItem>
    </View>
  )
}

const Header: React.FC<{ onClose: () => void }> = ({ onClose }) => (
  <View
    style={{
      flexDirection: 'row',
      alignItems: 'center',
      justifyContent: 'space-between',
      marginBottom: 16,
    }}
  >
    <Text style={{ ...TextStyles.h4, color: colors.white }}>
      {Strings.TrackComparison.SelectUpTo}
    </Text>
    <Pressable
      onPress={onClose}
      style={{
        padding: 4,
      }}
    >
      <CloseXIcon />
    </Pressable>
  </View>
)

const FilterTextInput: React.FC<{
  text?: string
  setText: (text: string) => void
}> = ({ text, setText }) => (
  <View
    style={{
      flex: 1,
      flexDirection: 'row',
      alignItems: 'center',
      height: 40,
      marginRight: 8,
      backgroundColor: colors.metals.metal2,
      borderRadius: 22,
    }}
  >
    <SearchIcon
      size={22}
      stroke={colors.metals.metal1}
      strokeWidth={3}
      style={{
        marginLeft: 16,
        paddingVertical: 22,
      }}
    />
    <TextInput
      autoCorrect={false}
      onChangeText={setText}
      value={text}
      style={{
        flex: 1,
        color: colors.white,
        paddingHorizontal: 16,
        fontSize: 16,
      }}
      placeholder={Strings.TrackComparison.SearchByTrackName}
      placeholderTextColor={colors.offWhite}
    />
    {text ? (
      <Pressable onPress={() => setText('')}>
        <ClearX
          size={24}
          style={{
            marginRight: 12,
            paddingVertical: 22,
          }}
        />
      </Pressable>
    ) : null}
  </View>
)

const NoFilteredResult: React.FC<{ onPress: () => void }> = ({ onPress }) => (
  <View
    style={{
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
    }}
  >
    <View style={{ marginBottom: 32 }}>
      <Text
        style={[
          TextStyles.h4,
          {
            color: colors.white,
            textAlign: 'center',
            marginBottom: 8,
          },
        ]}
      >
        {Strings.Shared.NoResults}
      </Text>
      <Text
        style={[
          TextStyles.bodyMedium,
          {
            color: colors.offWhite,
            textAlign: 'center',
          },
        ]}
      >
        {Strings.TrackComparison.TryAgain}
      </Text>
    </View>
    <Button onPress={onPress}>{Strings.Shared.TryAgain}</Button>
  </View>
)

function sortTracksByReleaseDate(a: Track, b: Track): number {
  if (a.releaseDate != null && b.releaseDate != null) {
    return new Date(b.releaseDate).getTime() - new Date(a.releaseDate).getTime()
  }

  if (a.releaseDate == null && b.releaseDate == null) {
    return 0
  }

  if (a.releaseDate == null) {
    return 1
  }

  return -1
}

const NoTracksMessage: React.FC = () => (
  <View
    style={{
      flex: 1,
      justifyContent: 'center',
      alignItems: 'center',
    }}
  >
    <Text
      style={[
        TextStyles.bodyBold,
        {
          color: colors.white,
          textAlign: 'center',
        },
      ]}
    >
      {Strings.TrackComparison.NoStreams}
    </Text>
  </View>
)
