import emojidata from 'unicode-emoji-json/data-by-emoji.json'
import generator, { MegalodonInterface } from 'megalodon'
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
import { RootState } from '@/store/index'
import { LocalTag } from '~/src/types/localTag'
import { InsertAccountCache } from '~/src/types/insertAccountCache'
import { CachedAccount } from '~/src/types/cachedAccount'
import { MyWindow } from '~/src/types/global'

const win = (window as any) as MyWindow

type Suggest = {
  name: string
  image?: string | null
  code?: string | null
}

type SuggestAccount = Suggest

type SuggestHashtag = Suggest

type SuggestEmoji = Suggest

export type StatusState = {
  filteredSuggestion: Array<Suggest>
  filteredAccounts: Array<SuggestAccount>
  filteredHashtags: Array<SuggestHashtag>
  filteredEmojis: Array<SuggestEmoji>
  openSuggest: boolean
  startIndex: number | null
  matchWord: string | null
  client: MegalodonInterface | null
}

const state = (): StatusState => ({
  filteredSuggestion: [],
  filteredAccounts: [],
  filteredHashtags: [],
  filteredEmojis: [],
  openSuggest: false,
  startIndex: null,
  matchWord: null,
  client: null
})

export const MUTATION_TYPES = {
  APPEND_FILTERED_ACCOUNTS: 'appendFilteredAccounts',
  CLEAR_FILTERED_ACCOUNTS: 'clearFilteredAccounts',
  APPEND_FILTERED_HASHTAGS: 'appendFilteredHashtags',
  CLEAR_FILTERED_HASHTAGS: 'clearFilteredHashtags',
  UPDATE_FILTERED_EMOJIS: 'updateFilteredEmojis',
  CLEAR_FILTERED_EMOJIS: 'clearFilteredEmojis',
  CHANGE_OPEN_SUGGEST: 'changeOpenSuggest',
  CHANGE_START_INDEX: 'changeStartIndex',
  CHANGE_MATCH_WORD: 'changeMatchWord',
  FILTERED_SUGGESTION_FROM_HASHTAGS: 'filteredSuggestionFromHashtags',
  FILTERED_SUGGESTION_FROM_ACCOUNTS: 'filteredSuggestionFromAccounts',
  FILTERED_SUGGESTION_FROM_EMOJIS: 'filteredSuggestionFromEmojis',
  CLEAR_FILTERED_SUGGESTION: 'clearFilteredSuggestion',
  SET_CLIENT: 'setClient',
  CLEAR_CLIENT: 'clearClient'
}

const mutations: MutationTree<StatusState> = {
  [MUTATION_TYPES.APPEND_FILTERED_ACCOUNTS]: (state, accounts: Array<string>) => {
    const suggestion = accounts.map(a => ({
      name: `@${a}`,
      image: null
    }))
    const appended = state.filteredAccounts.concat(suggestion)
    const unique = appended.filter((v1, i1, a1) => {
      return (
        a1.findIndex(v2 => {
          return v1.name === v2.name
        }) === i1
      )
    })
    state.filteredAccounts = unique.sort((a, b) => {
      if (a.name < b.name) return -1
      if (a.name > b.name) return 1
      return 0
    })
  },
  [MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS]: state => {
    state.filteredAccounts = []
  },
  [MUTATION_TYPES.APPEND_FILTERED_HASHTAGS]: (state, tags: Array<string>) => {
    const suggestion = tags.map(t => ({
      name: `#${t}`,
      image: null
    }))
    const appended = state.filteredHashtags.concat(suggestion)
    const unique = appended.filter((v1, i1, a1) => {
      return (
        a1.findIndex(v2 => {
          return v1.name === v2.name
        }) === i1
      )
    })
    Array.from(new Set(appended))
    state.filteredHashtags = unique.sort((a, b) => {
      if (a.name < b.name) return -1
      if (a.name > b.name) return 1
      return 0
    })
  },
  [MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS]: state => {
    state.filteredHashtags = []
  },
  [MUTATION_TYPES.UPDATE_FILTERED_EMOJIS]: (state, emojis: Array<SuggestEmoji>) => {
    state.filteredEmojis = emojis
  },
  [MUTATION_TYPES.CHANGE_OPEN_SUGGEST]: (state, value: boolean) => {
    state.openSuggest = value
  },
  [MUTATION_TYPES.CHANGE_START_INDEX]: (state, index: number | null) => {
    state.startIndex = index
  },
  [MUTATION_TYPES.CHANGE_MATCH_WORD]: (state, word: string | null) => {
    state.matchWord = word
  },
  [MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS]: state => {
    state.filteredSuggestion = state.filteredHashtags
  },
  [MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS]: state => {
    state.filteredSuggestion = state.filteredAccounts
  },
  [MUTATION_TYPES.FILTERED_SUGGESTION_FROM_EMOJIS]: state => {
    state.filteredSuggestion = state.filteredEmojis
  },
  [MUTATION_TYPES.CLEAR_FILTERED_SUGGESTION]: state => {
    state.filteredSuggestion = []
  },
  [MUTATION_TYPES.SET_CLIENT]: (state, client: MegalodonInterface) => {
    state.client = client
  },
  [MUTATION_TYPES.CLEAR_CLIENT]: state => {
    state.client = null
  }
}

type WordStart = {
  word: string
  start: number
}

const actions: ActionTree<StatusState, RootState> = {
  suggestAccount: async ({ commit, rootState, dispatch }, wordStart: WordStart) => {
    dispatch('cancelRequest')
    commit(MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS)
    commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS)
    const { word, start } = wordStart
    const searchCache = async () => {
      const target = word.replace('@', '')
      const accounts: Array<CachedAccount> = await win.ipcRenderer.invoke('get-cache-accounts', rootState.TimelineSpace.account._id)
      const matched = accounts.map(account => account.acct).filter(acct => acct.includes(target))
      if (matched.length === 0) throw new Error('Empty')
      commit(MUTATION_TYPES.APPEND_FILTERED_ACCOUNTS, matched)
      commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
      commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
      commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
      commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS)
      return matched
    }
    const searchAPI = async () => {
      const client = generator(
        rootState.TimelineSpace.sns,
        rootState.TimelineSpace.account.baseURL,
        rootState.TimelineSpace.account.accessToken,
        rootState.App.userAgent
      )
      commit(MUTATION_TYPES.SET_CLIENT, client)
      const res = await client.searchAccount(word)
      if (res.data.length === 0) throw new Error('Empty')
      commit(
        MUTATION_TYPES.APPEND_FILTERED_ACCOUNTS,
        res.data.map(account => account.acct)
      )
      await win.ipcRenderer.invoke('insert-cache-accounts', {
        ownerID: rootState.TimelineSpace.account._id!,
        accts: res.data.map(a => a.acct)
      } as InsertAccountCache)
      commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
      commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
      commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
      commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS)
      return res.data
    }
    await Promise.all([searchCache(), searchAPI()])
  },
  suggestHashtag: async ({ commit, rootState, dispatch }, wordStart: WordStart) => {
    dispatch('cancelRequest')
    commit(MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS)
    commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS)
    const { word, start } = wordStart
    const searchCache = async () => {
      const target = word.replace('#', '')
      const tags: Array<LocalTag> = await win.ipcRenderer.invoke('get-cache-hashtags')
      const matched = tags.map(tag => tag.tagName).filter(tag => tag.includes(target))
      if (matched.length === 0) throw new Error('Empty')
      commit(MUTATION_TYPES.APPEND_FILTERED_HASHTAGS, matched)
      commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
      commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
      commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
      commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS)
      return matched
    }
    const searchAPI = async () => {
      const client = generator(
        rootState.TimelineSpace.sns,
        rootState.TimelineSpace.account.baseURL,
        rootState.TimelineSpace.account.accessToken,
        rootState.App.userAgent
      )
      commit(MUTATION_TYPES.SET_CLIENT, client)
      const res = await client.search(word, 'hashtags')
      if (res.data.hashtags.length === 0) throw new Error('Empty')
      commit(
        MUTATION_TYPES.APPEND_FILTERED_HASHTAGS,
        res.data.hashtags.map(tag => tag.name)
      )
      await win.ipcRenderer.invoke(
        'insert-cache-hashtags',
        res.data.hashtags.map(tag => tag.name)
      )
      commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
      commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
      commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
      commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS)
      return res.data.hashtags
    }
    await Promise.all([searchCache(), searchAPI()])
  },
  suggestEmoji: ({ commit, rootState }, wordStart: WordStart) => {
    const { word, start } = wordStart
    // Find native emojis
    const filteredEmojiName: Array<string> = Object.keys(emojidata).filter((emoji: string) => `:${emojidata[emoji].name}:`.includes(word))
    const filteredNativeEmoji: Array<SuggestEmoji> = filteredEmojiName.map((emoji: string) => {
      return {
        name: `:${emojidata[emoji].name}:`,
        code: emoji
      }
    })
    // Find custom emojis
    const filteredCustomEmoji: Array<Suggest> = rootState.TimelineSpace.emojis
      .map(emoji => {
        return {
          name: `:${emoji.shortcode}:`,
          image: emoji.url
        }
      })
      .filter(emoji => emoji.name.includes(word))
    const filtered: Array<SuggestEmoji> = filteredNativeEmoji.concat(filteredCustomEmoji)
    if (filtered.length === 0) throw new Error('Empty')
    commit(
      MUTATION_TYPES.UPDATE_FILTERED_EMOJIS,
      filtered.filter((e, i, array) => {
        return array.findIndex(ar => e.name === ar.name) === i
      })
    )
    commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true)
    commit(MUTATION_TYPES.CHANGE_START_INDEX, start)
    commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word)
    commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_EMOJIS)
    return filtered
  },
  cancelRequest: ({ state }) => {
    if (state.client) {
      state.client.cancel()
    }
  },
  closeSuggest: ({ commit, dispatch }) => {
    dispatch('cancelRequest')
    commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, false)
    commit(MUTATION_TYPES.CHANGE_START_INDEX, null)
    commit(MUTATION_TYPES.CHANGE_MATCH_WORD, null)
    commit(MUTATION_TYPES.CLEAR_FILTERED_SUGGESTION)
    commit(MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS)
    commit(MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS)
    commit(MUTATION_TYPES.CLEAR_CLIENT)
  }
}

const getters: GetterTree<StatusState, RootState> = {
  pickerEmojis: (_state, _getters, rootState) => {
    return rootState.TimelineSpace.emojis
      .map(emoji => {
        return {
          name: `:${emoji.shortcode}:`,
          image: emoji.url
        }
      })
      .filter((e, i, array) => {
        return array.findIndex(ar => e.name === ar.name) === i
      })
      .map(e => {
        return {
          name: e.name,
          short_names: [e.name],
          text: e.name,
          emoticons: [],
          keywords: [e.name],
          imageUrl: e.image
        }
      })
  }
}

const Status: Module<StatusState, RootState> = {
  namespaced: true,
  state: state,
  mutations: mutations,
  actions: actions,
  getters: getters
}

export default Status
