import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { WSConnectionStatus } from '../services/api/api.service.js'

const retryTimeout = (multiplier) => 1000 * multiplier

const isVisible = (store, message, visibility) => {
  if (visibility === 'all') {
    return true
  } else if (visibility === 'following') {
    return store.getters.relationship(message.in_reply_to_user_id).following
  } else if (visibility === 'self') {
    return message.in_reply_to_user_id === store.rootState.users.currentUser.id
  }
  return false
}
const api = {
  state: {
    retryMultiplier: 1,
    backendInteractor: backendInteractorService(),
    fetchers: {},
    socket: null,
    mastoUserSocket: null,
    mastoUserSocketStatus: null,
    followRequests: []
  },
  mutations: {
    setBackendInteractor (state, backendInteractor) {
      state.backendInteractor = backendInteractor
    },
    addFetcher (state, { fetcherName, fetcher }) {
      state.fetchers[fetcherName] = fetcher
    },
    removeFetcher (state, { fetcherName, fetcher }) {
      state.fetchers[fetcherName].stop()
      delete state.fetchers[fetcherName]
    },
    setWsToken (state, token) {
      state.wsToken = token
    },
    setSocket (state, socket) {
      state.socket = socket
    },
    setFollowRequests (state, value) {
      state.followRequests = value
    },
    setMastoUserSocketStatus (state, value) {
      state.mastoUserSocketStatus = value
    },
    incrementRetryMultiplier (state) {
      state.retryMultiplier = Math.max(++state.retryMultiplier, 3)
    },
    resetRetryMultiplier (state) {
      state.retryMultiplier = 1
    }
  },
  actions: {
    /**
     * Global MastoAPI socket control, in future should disable ALL sockets/(re)start relevant sockets
     *
     * @param {Boolean} [initial] - whether this enabling happened at boot time or not
     */
    enableMastoSockets (store, initial) {
      const { state, dispatch, commit } = store
      // Do not initialize unless nonexistent or closed
      if (
        state.mastoUserSocket &&
          ![
            WebSocket.CLOSED,
            WebSocket.CLOSING
          ].includes(state.mastoUserSocket.getState())
      ) {
        return
      }
      if (initial) {
        commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING_INITIAL)
      } else {
        commit('setMastoUserSocketStatus', WSConnectionStatus.STARTING)
      }
      return dispatch('startMastoUserSocket')
    },
    disableMastoSockets (store) {
      const { state, dispatch, commit } = store
      if (!state.mastoUserSocket) return
      commit('setMastoUserSocketStatus', WSConnectionStatus.DISABLED)
      return dispatch('stopMastoUserSocket')
    },

    // MastoAPI 'User' sockets
    startMastoUserSocket (store) {
      return new Promise((resolve, reject) => {
        try {
          const { state, commit, dispatch, rootState } = store
          const timelineData = rootState.statuses.timelines.friends
          state.mastoUserSocket = state.backendInteractor.startUserSocket({ store })
          state.mastoUserSocket.addEventListener(
            'message',
            ({ detail: message }) => {
              const replyVisibility = rootState.config.replyVisibility
              if (!message) return // pings
              if (message.event === 'notification') {
                dispatch('addNewNotifications', {
                  notifications: [message.notification],
                  older: false
                })
              } else if (message.event === 'update' && isVisible(store, message.status, replyVisibility)) {
                dispatch('addNewStatuses', {
                  statuses: [message.status],
                  userId: false,
                  showImmediately: timelineData.visibleStatuses.length === 0,
                  timeline: 'friends'
                })
              } else if (message.event === 'status.update' && isVisible(store, message.status, replyVisibility)) {
                dispatch('addNewStatuses', {
                  statuses: [message.status],
                  userId: false,
                  showImmediately: message.status.id in timelineData.visibleStatusesObject,
                  timeline: 'friends'
                })
              } else if (message.event === 'delete') {
                dispatch('deleteStatusById', message.id)
              }
            }
          )
          state.mastoUserSocket.addEventListener('open', () => {
            // Do not show notification when we just opened up the page
            if (state.mastoUserSocketStatus !== WSConnectionStatus.STARTING_INITIAL) {
              dispatch('pushGlobalNotice', {
                level: 'success',
                messageKey: 'timeline.socket_reconnected',
                timeout: 5000
              })
            }
            // Stop polling if we were errored or disabled
            if (new Set([
              WSConnectionStatus.ERROR,
              WSConnectionStatus.DISABLED
            ]).has(state.mastoUserSocketStatus)) {
              dispatch('stopFetchingTimeline', { timeline: 'friends' })
              dispatch('stopFetchingNotifications')
            }
            commit('resetRetryMultiplier')
            commit('setMastoUserSocketStatus', WSConnectionStatus.JOINED)
          })
          state.mastoUserSocket.addEventListener('error', ({ detail: error }) => {
            console.error('Error in MastoAPI websocket:', error)
          })
          state.mastoUserSocket.addEventListener('close', ({ detail: closeEvent }) => {
            const ignoreCodes = new Set([
              1000, // Normal (intended) closure
              1001 // Going away
            ])
            const { code } = closeEvent
            if (ignoreCodes.has(code)) {
              console.debug(`Not restarting socket becasue of closure code ${code} is in ignore list`)
              commit('setMastoUserSocketStatus', WSConnectionStatus.CLOSED)
            } else {
              console.warn(`MastoAPI websocket disconnected, restarting. CloseEvent code: ${code}`)
              setTimeout(() => {
                dispatch('startMastoUserSocket')
              }, retryTimeout(state.retryMultiplier))
              commit('incrementRetryMultiplier')
              if (state.mastoUserSocketStatus !== WSConnectionStatus.ERROR) {
                dispatch('startFetchingTimeline', { timeline: 'friends' })
                dispatch('startFetchingNotifications')
                dispatch('startFetchingAnnouncements')
                dispatch('startFetchingReports')
                dispatch('pushGlobalNotice', {
                  level: 'error',
                  messageKey: 'timeline.socket_broke',
                  messageArgs: [code],
                  timeout: 5000
                })
              }
              commit('setMastoUserSocketStatus', WSConnectionStatus.ERROR)
            }
          })
          resolve()
        } catch (e) {
          reject(e)
        }
      })
    },
    stopMastoUserSocket ({ state, dispatch }) {
      dispatch('startFetchingTimeline', { timeline: 'friends' })
      dispatch('startFetchingNotifications')
      state.mastoUserSocket.close()
    },

    // Timelines
    startFetchingTimeline (store, {
      timeline = 'friends',
      tag = false,
      userId = false,
      listId = false
    }) {
      if (store.state.fetchers[timeline]) return

      const fetcher = store.state.backendInteractor.startFetchingTimeline({
        timeline, store, userId, listId, tag
      })
      store.commit('addFetcher', { fetcherName: timeline, fetcher })
    },
    stopFetchingTimeline (store, timeline) {
      const fetcher = store.state.fetchers[timeline]
      if (!fetcher) return
      store.commit('removeFetcher', { fetcherName: timeline, fetcher })
    },
    fetchTimeline (store, timeline, { ...rest }) {
      store.state.backendInteractor.fetchTimeline({
        store,
        timeline,
        ...rest
      })
    },
    startFetchingConfig (store) {
      if (store.state.fetchers.config) return

      const fetcher = store.state.backendInteractor.startFetchingConfig({ store })
      store.commit('addFetcher', { fetcherName: 'config', fetcher })
    },
    stopFetchingConfig (store) {
      const fetcher = store.state.fetchers.config
      if (!fetcher) return
      store.commit('removeFetcher', { fetcherName: 'config', fetcher })
    },
    // Notifications
    startFetchingNotifications (store) {
      if (store.state.fetchers.notifications) return
      const fetcher = store.state.backendInteractor.startFetchingNotifications({ store })
      store.commit('addFetcher', { fetcherName: 'notifications', fetcher })
    },
    stopFetchingNotifications (store) {
      const fetcher = store.state.fetchers.notifications
      if (!fetcher) return
      store.commit('removeFetcher', { fetcherName: 'notifications', fetcher })
    },
    fetchNotifications (store, { ...rest }) {
      store.state.backendInteractor.fetchNotifications({
        store,
        ...rest
      })
    },

    // Follow requests
    startFetchingFollowRequests (store) {
      if (store.state.fetchers['followRequests']) return
      const fetcher = store.state.backendInteractor.startFetchingFollowRequests({ store })

      store.commit('addFetcher', { fetcherName: 'followRequests', fetcher })
    },
    stopFetchingFollowRequests (store) {
      const fetcher = store.state.fetchers.followRequests
      if (!fetcher) return
      store.commit('removeFetcher', { fetcherName: 'followRequests', fetcher })
    },
    removeFollowRequest (store, request) {
      let requests = store.state.followRequests.filter((it) => it !== request)
      store.commit('setFollowRequests', requests)
    },

    // Lists
    startFetchingLists (store) {
      if (store.state.fetchers['lists']) return
      const fetcher = store.state.backendInteractor.startFetchingLists({ store })
      store.commit('addFetcher', { fetcherName: 'lists', fetcher })
    },
    stopFetchingLists (store) {
      const fetcher = store.state.fetchers.lists
      if (!fetcher) return
      store.commit('removeFetcher', { fetcherName: 'lists', fetcher })
    },

    // Lists
    startFetchingAnnouncements (store) {
      if (store.state.fetchers['announcements']) return
      const fetcher = store.state.backendInteractor.startFetchingAnnouncements({ store })
      store.commit('addFetcher', { fetcherName: 'announcements', fetcher })
    },
    stopFetchingAnnouncements (store) {
      const fetcher = store.state.fetchers.announcements
      if (!fetcher) return
      store.commit('removeFetcher', { fetcherName: 'announcements', fetcher })
    },

    // Reports
    startFetchingReports (store) {
      if (store.state.fetchers['reports']) return
      const fetcher = store.state.backendInteractor.startFetchingReports({ store })
      store.commit('addFetcher', { fetcherName: 'reports', fetcher })
    },
    stopFetchingReports (store) {
      const fetcher = store.state.fetchers.reports
      if (!fetcher) return
      store.commit('removeFetcher', { fetcherName: 'reports', fetcher })
    },

    getSupportedTranslationlanguages (store) {
      store.state.backendInteractor.getSupportedTranslationlanguages({ store })
        .then((data) => {
          store.dispatch('setInstanceOption', { name: 'supportedTranslationLanguages', value: data })
        })
    },
    listSettingsProfiles (store) {
      store.state.backendInteractor.listSettingsProfiles({ store })
        .then((data) => {
          store.commit('setInstanceOption', { name: 'settingsProfiles', value: data })
        })
    },
    // Pleroma websocket
    setWsToken (store, token) {
      store.commit('setWsToken', token)
    },
    disconnectFromSocket ({ commit, state }) {
      state.socket && state.socket.disconnect()
      commit('setSocket', null)
    }
  }
}

export default api
