import client from '@api/client'
import { HeaderLeft, HeaderRight } from '@components/Header'
import { store } from '@root/store'
import formatText from '@screens/Shared/Compose/formatText'
import ComposeRoot from '@screens/Shared/Compose/Root'
import { getLocalAccountPreferences } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import * as Crypto from 'expo-crypto'
import React, {
  createContext,
  createRef,
  Dispatch,
  ReactNode,
  RefObject,
  useCallback,
  useEffect,
  useReducer,
  useState
} from 'react'
import {
  Alert,
  Keyboard,
  KeyboardAvoidingView,
  StyleSheet,
  Text,
  TextInput
} from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { useQueryClient } from 'react-query'

const Stack = createNativeStackNavigator()

export type ComposeState = {
  spoiler: {
    active: boolean
    count: number
    raw: string
    formatted: ReactNode
    selection: { start: number; end: number }
  }
  text: {
    count: number
    raw: string
    formatted: ReactNode
    selection: { start: number; end: number }
  }
  tag?: {
    type: 'url' | 'accounts' | 'hashtags'
    text: string
    offset: number
    length: number
  }
  emoji: {
    active: boolean
    emojis: { title: string; data: Mastodon.Emoji[] }[] | undefined
  }
  poll: {
    active: boolean
    total: number
    options: {
      '0': string | undefined
      '1': string | undefined
      '2': string | undefined
      '3': string | undefined
    }
    multiple: boolean
    expire:
      | '300'
      | '1800'
      | '3600'
      | '21600'
      | '86400'
      | '259200'
      | '604800'
      | string
  }
  attachments: {
    sensitive: boolean
    uploads: (Mastodon.Attachment & { local_url?: string })[]
  }
  attachmentUploadProgress?: { progress: number; aspect?: number }
  visibility: 'public' | 'unlisted' | 'private' | 'direct'
  visibilityLock: boolean
  replyToStatus?: Mastodon.Status
  textInputFocus: {
    current: 'text' | 'spoiler'
    refs: { text: RefObject<TextInput>; spoiler: RefObject<TextInput> }
  }
}

export type ComposeAction =
  | {
      type: 'spoiler'
      payload: Partial<ComposeState['spoiler']>
    }
  | {
      type: 'text'
      payload: Partial<ComposeState['text']>
    }
  | {
      type: 'tag'
      payload: ComposeState['tag']
    }
  | {
      type: 'emoji'
      payload: ComposeState['emoji']
    }
  | {
      type: 'poll'
      payload: Partial<ComposeState['poll']>
    }
  | {
      type: 'attachments'
      payload: Partial<ComposeState['attachments']>
    }
  | {
      type: 'attachmentUploadProgress'
      payload: ComposeState['attachmentUploadProgress']
    }
  | {
      type: 'attachmentEdit'
      payload: Mastodon.Attachment & { local_url?: string }
    }
  | {
      type: 'visibility'
      payload: ComposeState['visibility']
    }
  | {
      type: 'textInputFocus'
      payload: Partial<ComposeState['textInputFocus']>
    }

const composeInitialState: ComposeState = {
  spoiler: {
    active: false,
    count: 0,
    raw: '',
    formatted: undefined,
    selection: { start: 0, end: 0 }
  },
  text: {
    count: 0,
    raw: '',
    formatted: undefined,
    selection: { start: 0, end: 0 }
  },
  tag: undefined,
  emoji: { active: false, emojis: undefined },
  poll: {
    active: false,
    total: 2,
    options: {
      '0': undefined,
      '1': undefined,
      '2': undefined,
      '3': undefined
    },
    multiple: false,
    expire: '86400'
  },
  attachments: { sensitive: false, uploads: [] },
  attachmentUploadProgress: undefined,
  visibility: 'public',
  visibilityLock: false,
  replyToStatus: undefined,
  textInputFocus: {
    current: 'text',
    refs: { text: createRef(), spoiler: createRef() }
  }
}
const composeExistingState = ({
  type,
  incomingStatus,
  visibilityLock
}: {
  type: 'reply' | 'conversation' | 'edit'
  incomingStatus: Mastodon.Status
  visibilityLock?: boolean
}): ComposeState => {
  switch (type) {
    case 'edit':
      return {
        ...composeInitialState,
        ...(incomingStatus.spoiler_text?.length && {
          spoiler: {
            active: true,
            count: incomingStatus.spoiler_text.length,
            raw: incomingStatus.spoiler_text,
            formatted: incomingStatus.spoiler_text,
            selection: { start: 0, end: 0 }
          }
        }),
        text: {
          count: incomingStatus.text!.length,
          raw: incomingStatus.text!,
          formatted: undefined,
          selection: { start: 0, end: 0 }
        },
        ...(incomingStatus.poll && {
          poll: {
            active: true,
            total: incomingStatus.poll.options.length,
            options: {
              '0': incomingStatus.poll.options[0]?.title || undefined,
              '1': incomingStatus.poll.options[1]?.title || undefined,
              '2': incomingStatus.poll.options[2]?.title || undefined,
              '3': incomingStatus.poll.options[3]?.title || undefined
            },
            multiple: incomingStatus.poll.multiple,
            expire: '86400' // !!!
          }
        }),
        ...(incomingStatus.media_attachments && {
          attachments: {
            sensitive: incomingStatus.sensitive,
            uploads: incomingStatus.media_attachments
          }
        }),
        visibility:
          incomingStatus.visibility ||
          getLocalAccountPreferences(store.getState())[
            'posting:default:visibility'
          ],
        ...(incomingStatus.visibility === 'direct' && { visibilityLock: true })
      }
    case 'reply':
      const actualStatus = incomingStatus.reblog || incomingStatus
      const allMentions = Array.isArray(actualStatus.mentions)
        ? actualStatus.mentions.map(mention => `@${mention.acct}`)
        : []
      let replyPlaceholder = allMentions.join(' ')

      if (replyPlaceholder.length === 0) {
        replyPlaceholder = `@${actualStatus.account.acct} `
      } else {
        replyPlaceholder = replyPlaceholder + ' '
      }
      return {
        ...composeInitialState,
        text: {
          count: replyPlaceholder.length,
          raw: replyPlaceholder,
          formatted: undefined,
          selection: { start: 0, end: 0 }
        },
        ...(visibilityLock && {
          visibility: 'direct',
          visibilityLock: true
        }),
        replyToStatus: actualStatus
      }
    case 'conversation':
      return {
        ...composeInitialState,
        text: {
          count: incomingStatus.account.acct.length + 2,
          raw: `@${incomingStatus.account.acct} `,
          formatted: undefined,
          selection: { start: 0, end: 0 }
        },
        visibility: 'direct',
        visibilityLock: true
      }
  }
}
const composeReducer = (
  state: ComposeState,
  action: ComposeAction
): ComposeState => {
  switch (action.type) {
    case 'spoiler':
      return { ...state, spoiler: { ...state.spoiler, ...action.payload } }
    case 'text':
      return { ...state, text: { ...state.text, ...action.payload } }
    case 'tag':
      return { ...state, tag: action.payload }
    case 'emoji':
      return { ...state, emoji: action.payload }
    case 'poll':
      return { ...state, poll: { ...state.poll, ...action.payload } }
    case 'attachments':
      return {
        ...state,
        attachments: { ...state.attachments, ...action.payload }
      }
    case 'attachmentUploadProgress':
      return { ...state, attachmentUploadProgress: action.payload }
    case 'attachmentEdit':
      return {
        ...state,
        attachments: {
          ...state.attachments,
          uploads: state.attachments.uploads.map(upload =>
            upload.id === action.payload.id ? action.payload : upload
          )
        }
      }
    case 'visibility':
      return { ...state, visibility: action.payload }
    case 'textInputFocus':
      return {
        ...state,
        textInputFocus: { ...state.textInputFocus, ...action.payload }
      }
    default:
      throw new Error('Unexpected action')
  }
}

type ContextType = {
  composeState: ComposeState
  composeDispatch: Dispatch<ComposeAction>
}
export const ComposeContext = createContext<ContextType>({} as ContextType)

export interface Props {
  route: {
    params:
      | {
          type?: 'reply' | 'conversation' | 'edit'
          incomingStatus: Mastodon.Status
          visibilityLock?: boolean
        }
      | undefined
  }
  navigation: any
}

const Compose: React.FC<Props> = ({ route: { params }, navigation }) => {
  const { theme } = useTheme()
  const queryClient = useQueryClient()

  const [hasKeyboard, setHasKeyboard] = useState(false)
  useEffect(() => {
    Keyboard.addListener('keyboardWillShow', _keyboardDidShow)
    Keyboard.addListener('keyboardWillHide', _keyboardDidHide)

    // cleanup function
    return () => {
      Keyboard.removeListener('keyboardWillShow', _keyboardDidShow)
      Keyboard.removeListener('keyboardWillHide', _keyboardDidHide)
    }
  }, [])
  const _keyboardDidShow = () => {
    setHasKeyboard(true)
  }
  const _keyboardDidHide = () => {
    setHasKeyboard(false)
  }

  const [composeState, composeDispatch] = useReducer(
    composeReducer,
    params?.type && params?.incomingStatus
      ? composeExistingState({
          type: params.type,
          incomingStatus: params.incomingStatus,
          visibilityLock: params.visibilityLock
        })
      : {
          ...composeInitialState,
          visibility: getLocalAccountPreferences(store.getState())[
            'posting:default:visibility'
          ] as ComposeState['visibility']
        }
  )
  const [isSubmitting, setIsSubmitting] = useState(false)

  useEffect(() => {
    switch (params?.type) {
      case 'edit':
        if (params.incomingStatus.spoiler_text) {
          formatText({
            textInput: 'spoiler',
            composeDispatch,
            content: params.incomingStatus.spoiler_text,
            disableDebounce: true
          })
        }
        formatText({
          textInput: 'text',
          composeDispatch,
          content: params.incomingStatus.text!,
          disableDebounce: true
        })
        break
      case 'reply':
        const actualStatus =
          params.incomingStatus.reblog || params.incomingStatus
        const allMentions = actualStatus.mentions.map(
          mention => `@${mention.acct}`
        )
        let replyPlaceholder = allMentions.join(' ')
        if (replyPlaceholder.length === 0) {
          replyPlaceholder = `@${actualStatus.account.acct} `
        } else {
          replyPlaceholder = replyPlaceholder + ' '
        }
        formatText({
          textInput: 'text',
          composeDispatch,
          content: replyPlaceholder,
          disableDebounce: true
        })
        break
      case 'conversation':
        formatText({
          textInput: 'text',
          composeDispatch,
          content: `@${params.incomingStatus.account.acct} `,
          disableDebounce: true
        })
        break
    }
  }, [params?.type])

  const tootPost = async () => {
    setIsSubmitting(true)
    if (composeState.text.count < 0) {
      Alert.alert('字数超限', '', [
        {
          text: '返回继续编辑'
        }
      ])
    } else {
      const formData = new FormData()

      if (params?.type === 'conversation' || params?.type === 'reply') {
        formData.append('in_reply_to_id', composeState.replyToStatus!.id)
      }

      if (composeState.spoiler.active) {
        formData.append('spoiler_text', composeState.spoiler.raw)
      }

      formData.append('status', composeState.text.raw)

      if (composeState.poll.active) {
        Object.values(composeState.poll.options)
          .filter(e => e?.length)
          .forEach(e => formData.append('poll[options][]', e!))
        formData.append('poll[expires_in]', composeState.poll.expire)
        formData.append('poll[multiple]', composeState.poll.multiple.toString())
      }

      if (composeState.attachments.uploads.length) {
        formData.append(
          'sensitive',
          composeState.attachments.sensitive.toString()
        )
        composeState.attachments.uploads.forEach(e =>
          formData.append('media_ids[]', e!.id)
        )
      }

      formData.append('visibility', composeState.visibility)

      client({
        method: 'post',
        instance: 'local',
        url: 'statuses',
        headers: {
          'Idempotency-Key': await Crypto.digestStringAsync(
            Crypto.CryptoDigestAlgorithm.SHA256,
            composeState.spoiler.raw +
              composeState.text.raw +
              composeState.poll.options['0'] +
              composeState.poll.options['1'] +
              composeState.poll.options['2'] +
              composeState.poll.options['3'] +
              composeState.poll.multiple +
              composeState.poll.expire +
              composeState.attachments.sensitive +
              composeState.attachments.uploads.map(upload => upload.id) +
              composeState.visibility +
              (params?.type === 'edit' ? Math.random() : '')
          )
        },
        body: formData
      })
        .then(
          res => {
            if (res.body.id) {
              setIsSubmitting(false)
              Alert.alert('发布成功', '', [
                {
                  text: '好的',
                  onPress: () => {
                    queryClient.invalidateQueries(['Following'])
                    navigation.goBack()
                  }
                }
              ])
            } else {
              setIsSubmitting(false)
              Alert.alert('发布失败', '', [
                {
                  text: '返回重试'
                }
              ])
            }
          },
          error => {
            setIsSubmitting(false)
            Alert.alert('发布失败', error.body, [
              {
                text: '返回重试'
              }
            ])
          }
        )
        .catch(() => {
          setIsSubmitting(false)
          Alert.alert('发布失败', '', [
            {
              text: '返回重试'
            }
          ])
        })
    }
  }

  const totalTextCount =
    (composeState.spoiler.active ? composeState.spoiler.count : 0) +
    composeState.text.count
  const rawCount = composeState.text.raw.length

  const postButtonText = {
    conversation: '回复私信',
    reply: '发布回复',
    edit: '发嘟嘟'
  }

  const headerLeft = useCallback(
    () => (
      <HeaderLeft
        type='text'
        content='退出编辑'
        onPress={() =>
          Alert.alert('确认取消编辑？', '', [
            { text: '继续编辑', style: 'cancel' },
            {
              text: '退出编辑',
              style: 'destructive',
              onPress: () => navigation.goBack()
            }
          ])
        }
      />
    ),
    []
  )
  const headerCenter = useCallback(
    () => (
      <Text
        style={[
          styles.count,
          {
            color: totalTextCount > 500 ? theme.red : theme.secondary
          }
        ]}
      >
        {totalTextCount} / 500
      </Text>
    ),
    [totalTextCount]
  )
  const headerRight = useCallback(
    () => (
      <HeaderRight
        type='text'
        content={params?.type ? postButtonText[params.type] : '发嘟嘟'}
        onPress={async () => tootPost()}
        loading={isSubmitting}
        disabled={rawCount < 1 || totalTextCount > 500}
      />
    ),
    [isSubmitting, rawCount, totalTextCount]
  )

  const screenComponent = useCallback(
    () => (
      <ComposeContext.Provider value={{ composeState, composeDispatch }}>
        <ComposeRoot />
      </ComposeContext.Provider>
    ),
    []
  )

  return (
    <KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
      <SafeAreaView
        style={{ flex: 1 }}
        edges={hasKeyboard ? ['left', 'right'] : ['left', 'right', 'bottom']}
      >
        <Stack.Navigator>
          <Stack.Screen
            name='Screen-Shared-Compose-Root'
            options={{ headerLeft, headerCenter, headerRight }}
            component={screenComponent}
          />
        </Stack.Navigator>
      </SafeAreaView>
    </KeyboardAvoidingView>
  )
}

const styles = StyleSheet.create({
  count: {
    textAlign: 'center',
    ...StyleConstants.FontStyle.M
  }
})

export default Compose
