import {useCallback, useMemo, useState} from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'

import {languageName} from '#/locale/helpers'
import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages'
import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
import {useLanguagePrefs} from '#/state/preferences/languages'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {atoms as a, tokens, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {SearchInput} from '#/components/forms/SearchInput'
import * as Toggle from '#/components/forms/Toggle'
import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times'
import {Text} from '#/components/Typography'
import {IS_NATIVE, IS_WEB} from '#/env'

type FlatListItem =
  | {
      type: 'header'
      label: string
    }
  | {
      type: 'item'
      lang: Language
    }

export function LanguageSelectDialog({
  titleText,
  subtitleText,
  control,
  /**
   * Optionally can be passed to show different values than what is saved in
   * langPrefs.
   */
  currentLanguages,
  onSelectLanguages,
  maxLanguages,
}: {
  control: Dialog.DialogControlProps
  titleText?: React.ReactNode
  subtitleText?: React.ReactNode
  /**
   * Defaults to the primary language
   */
  currentLanguages?: string[]
  onSelectLanguages: (languages: string[]) => void
  maxLanguages?: number
}) {
  const renderErrorBoundary = useCallback(
    (error: any) => <DialogError details={String(error)} />,
    [],
  )

  return (
    <Dialog.Outer control={control} nativeOptions={{fullHeight: true}}>
      <Dialog.Handle />
      <ErrorBoundary renderError={renderErrorBoundary}>
        <DialogInner
          titleText={titleText}
          subtitleText={subtitleText}
          currentLanguages={currentLanguages}
          onSelectLanguages={onSelectLanguages}
          maxLanguages={maxLanguages}
        />
      </ErrorBoundary>
    </Dialog.Outer>
  )
}

export function DialogInner({
  titleText,
  subtitleText,
  currentLanguages,
  onSelectLanguages,
  maxLanguages,
}: {
  titleText?: React.ReactNode
  subtitleText?: React.ReactNode
  currentLanguages?: string[]
  onSelectLanguages?: (languages: string[]) => void
  maxLanguages?: number
}) {
  const control = Dialog.useDialogContext()
  const [headerHeight, setHeaderHeight] = useState(0)
  const [footerHeight, setFooterHeight] = useState(0)

  const allowedLanguages = useMemo(() => {
    const uniqueLanguagesMap = LANGUAGES.filter(lang => !!lang.code2).reduce(
      (acc, lang) => {
        acc[lang.code2] = lang
        return acc
      },
      {} as Record<string, Language>,
    )

    return Object.values(uniqueLanguagesMap)
  }, [])

  const langPrefs = useLanguagePrefs()
  const [checkedLanguagesCode2, setCheckedLanguagesCode2] = useState<string[]>(
    currentLanguages || [langPrefs.primaryLanguage],
  )
  const [search, setSearch] = useState('')

  const t = useTheme()
  const {_} = useLingui()

  const enableSquareButtons = useEnableSquareButtons()

  const handleClose = () => {
    control.close(() => {
      onSelectLanguages?.(checkedLanguagesCode2)
    })
  }

  // NOTE(@elijaharita): Displayed languages are split into 3 lists for
  // ordering.
  const displayedLanguages = useMemo(() => {
    function mapCode2List(code2List: string[]) {
      return code2List.map(code2 => LANGUAGES_MAP_CODE2[code2]).filter(Boolean)
    }

    // NOTE(@elijaharita): Get recent language codes and map them to language
    // objects. Both the user account's saved language history and the current
    // checked languages are displayed here.
    const recentLanguagesCode2 =
      Array.from(
        new Set([...checkedLanguagesCode2, ...langPrefs.postLanguageHistory]),
      ).slice(0, 5) || []
    const recentLanguages = mapCode2List(recentLanguagesCode2)

    // NOTE(@elijaharita): helper functions
    const searchLower = search.toLowerCase()
    const matchesSearch = (lang: Language) =>
      languageName(lang, langPrefs.appLanguage)
        .toLowerCase()
        .includes(searchLower) || lang.name.toLowerCase().includes(searchLower)
    const isChecked = (lang: Language) =>
      checkedLanguagesCode2.includes(lang.code2)
    const isInRecents = (lang: Language) =>
      recentLanguagesCode2.includes(lang.code2)

    const checkedRecent = recentLanguages.filter(isChecked)

    if (search) {
      // NOTE(@elijaharita): if a search is active, we ALWAYS show checked
      // items, as well as any items that match the search.
      const uncheckedRecent = recentLanguages
        .filter(lang => !isChecked(lang))
        .filter(matchesSearch)
      const unchecked = allowedLanguages.filter(lang => !isChecked(lang))
      const all = unchecked
        .filter(matchesSearch)
        .filter(lang => !isInRecents(lang))

      return {
        all,
        checkedRecent,
        uncheckedRecent,
      }
    } else {
      // NOTE(@elijaharita): if no search is active, we show everything.
      const uncheckedRecent = recentLanguages.filter(lang => !isChecked(lang))
      const all = allowedLanguages
        .filter(lang => !recentLanguagesCode2.includes(lang.code2))
        .filter(lang => !isInRecents(lang))

      return {
        all,
        checkedRecent,
        uncheckedRecent,
      }
    }
  }, [
    allowedLanguages,
    search,
    langPrefs.postLanguageHistory,
    checkedLanguagesCode2,
    langPrefs.appLanguage,
  ])

  const listHeader = (
    <View
      style={[a.pb_xs, t.atoms.bg, IS_NATIVE && a.pt_2xl]}
      onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}>
      <View style={[a.flex_row, a.w_full, a.justify_between]}>
        <View>
          <Text
            nativeID="dialog-title"
            style={[
              t.atoms.text,
              a.text_left,
              a.font_semi_bold,
              a.text_xl,
              a.mb_sm,
            ]}>
            {titleText ?? <Trans>Choose languages</Trans>}
          </Text>
          {subtitleText && (
            <Text
              nativeID="dialog-description"
              style={[
                t.atoms.text_contrast_medium,
                a.text_left,
                a.text_md,
                a.mb_lg,
              ]}>
              {subtitleText}
            </Text>
          )}
        </View>

        {IS_WEB && (
          <Button
            variant="ghost"
            size="small"
            color="secondary"
            shape={enableSquareButtons ? 'square' : 'round'}
            label={_(msg`Close dialog`)}
            onPress={handleClose}>
            <ButtonIcon icon={XIcon} />
          </Button>
        )}
      </View>

      <View style={[a.w_full, a.flex_row, a.align_stretch, a.gap_xs, a.pb_0]}>
        <SearchInput
          value={search}
          onChangeText={setSearch}
          placeholder={_(msg`Search languages`)}
          label={_(msg`Search languages`)}
          maxLength={50}
          onClearText={() => setSearch('')}
        />
      </View>
    </View>
  )

  const isCheckedRecentEmpty =
    displayedLanguages.checkedRecent.length > 0 ||
    displayedLanguages.uncheckedRecent.length > 0

  const isDisplayedLanguagesEmpty = displayedLanguages.all.length === 0

  const flatListData = [
    ...(isCheckedRecentEmpty
      ? [{type: 'header', label: _(msg`Recently used`)}]
      : []),
    ...displayedLanguages.checkedRecent.map(lang => ({type: 'item', lang})),
    ...displayedLanguages.uncheckedRecent.map(lang => ({type: 'item', lang})),
    ...(isDisplayedLanguagesEmpty
      ? []
      : [{type: 'header', label: _(msg`All languages`)}]),
    ...displayedLanguages.all.map(lang => ({type: 'item', lang})),
  ]

  const numItems = flatListData.length

  return (
    <Toggle.Group
      values={checkedLanguagesCode2}
      onChange={setCheckedLanguagesCode2}
      type="checkbox"
      maxSelections={maxLanguages}
      label={_(msg`Select languages`)}
      style={web([a.contents])}>
      <Dialog.InnerFlatList
        data={flatListData}
        ListHeaderComponent={listHeader}
        stickyHeaderIndices={[0]}
        contentContainerStyle={[
          a.gap_0,
          IS_NATIVE && {paddingBottom: footerHeight + tokens.space.xl},
        ]}
        style={[IS_NATIVE && a.px_lg, IS_WEB && {paddingBottom: 120}]}
        scrollIndicatorInsets={{top: headerHeight, bottom: footerHeight}}
        renderItem={({item, index}: {item: FlatListItem; index: number}) => {
          if (item.type === 'header') {
            return (
              <Text
                key={index}
                style={[
                  a.px_0,
                  a.py_md,
                  a.font_semi_bold,
                  a.text_xs,
                  t.atoms.text_contrast_low,
                  a.pt_3xl,
                ]}>
                {item.label}
              </Text>
            )
          }
          const lang = item.lang
          const name = languageName(lang, langPrefs.appLanguage)

          const isLastItem = index === numItems - 1

          return (
            <Toggle.Item
              key={lang.code2}
              name={lang.code2}
              label={name}
              style={[
                t.atoms.border_contrast_low,
                !isLastItem && a.border_b,
                a.rounded_0,
                a.px_0,
                a.py_md,
              ]}>
              <Toggle.LabelText style={[a.flex_1]}>{name}</Toggle.LabelText>
              <Toggle.Checkbox />
            </Toggle.Item>
          )
        }}
        footer={
          <Dialog.FlatListFooter
            onLayout={evt => setFooterHeight(evt.nativeEvent.layout.height)}>
            <Button
              label={_(msg`Close dialog`)}
              onPress={handleClose}
              color="primary"
              size="large">
              <ButtonText>
                <Trans>Done</Trans>
              </ButtonText>
            </Button>
          </Dialog.FlatListFooter>
        }
      />
    </Toggle.Group>
  )
}

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

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