import WidgetService from '@baserow/modules/dashboard/services/widget'
import DataSourceService from '@baserow/modules/dashboard/services/dataSource'
import IntegrationService from '@baserow/modules/core/services/integration'
import debounce from 'lodash/debounce'

export const state = () => ({
  dashboardId: null,
  loading: false,
  editMode: false,
  selectedWidgetId: null,
  widgets: [],
  dataSources: [],
  integrations: [],
  // A cache for data that has been
  // returned as a result of dispatching
  // a data source. The keys are data source ids.
  data: {},
})

let debouncedWidgetUpdate = null

export const mutations = {
  RESET(state) {
    state.dashboardId = null
    state.editMode = false
    state.selectedWidgetId = null
    state.widgets = []
    state.dataSources = []
    state.integrations = []
    state.data = {}
  },
  SET_DASHBOARD_ID(state, dashboardId) {
    state.dashboardId = dashboardId
  },
  TOGGLE_EDIT_MODE(state) {
    state.editMode = !state.editMode
  },
  ADD_WIDGET(state, widget) {
    state.widgets.push(widget)
  },
  ADD_DATA_SOURCE(state, dataSource) {
    state.dataSources.push(dataSource)
  },
  UPDATE_DATA_SOURCE(state, { dataSourceId, values }) {
    const dataSource = state.dataSources.find(
      (dataSource) => dataSource.id === dataSourceId
    )
    Object.assign(dataSource, values)
  },
  UPDATE_DATA(state, { dataSourceId, values }) {
    if (state.data[dataSourceId] === undefined) {
      state.data[dataSourceId] = {}
    }
    state.data = {
      ...state.data,
      [dataSourceId]: { ...values },
    }
  },
  ADD_INTEGRATION(state, integration) {
    state.integrations.push(integration)
  },
  SELECT_WIDGET(state, widgetId) {
    state.selectedWidgetId = widgetId
  },
  UPDATE_WIDGET(state, { widgetId, values }) {
    const widget = state.widgets.find((widget) => widget.id === widgetId)
    Object.assign(widget, values)
  },
  DELETE_WIDGET(state, widgetId) {
    const index = state.widgets.findIndex((widget) => widget.id === widgetId)
    state.widgets.splice(index, 1)
  },
  SET_LOADING(state, value) {
    state.loading = value
  },
}

export const actions = {
  setLoading({ commit }, value) {
    commit('SET_LOADING', value)
  },
  reset({ commit }) {
    commit('RESET')
  },
  toggleEditMode({ commit }) {
    commit('TOGGLE_EDIT_MODE')
  },
  enterEditMode({ getters, commit }) {
    if (!getters.isEditMode) {
      commit('TOGGLE_EDIT_MODE')
    }
  },
  selectWidget({ commit }, widgetId) {
    commit('SELECT_WIDGET', widgetId)
  },
  updateWidget({ commit }, { widgetId, values, originalValues }) {
    return new Promise((resolve, reject) => {
      commit('UPDATE_WIDGET', { widgetId, values })

      let previousOriginalValues = originalValues
      if (debouncedWidgetUpdate) {
        debouncedWidgetUpdate.cancel()
        previousOriginalValues = debouncedWidgetUpdate.originalValues
      }

      debouncedWidgetUpdate = debounce(async () => {
        try {
          await WidgetService(this.$client).update(widgetId, values)
          debouncedWidgetUpdate = null
          resolve()
        } catch (error) {
          commit('UPDATE_WIDGET', { widgetId, values: previousOriginalValues })
          reject(error)
        }
      }, 1000)
      debouncedWidgetUpdate.originalValues = previousOriginalValues
      debouncedWidgetUpdate()
    })
  },
  handleWidgetUpdated({ commit }, widget) {
    commit('UPDATE_WIDGET', { widgetId: widget.id, values: widget })
  },
  async updateDataSource({ commit, dispatch }, { dataSourceId, values }) {
    commit('UPDATE_DATA', { dataSourceId, values: null })
    const { data } = await DataSourceService(this.$client).update(
      dataSourceId,
      values
    )
    await dispatch('handleDataSourceUpdated', data)
  },
  async handleDataSourceUpdated({ commit, dispatch }, dataSource) {
    commit('UPDATE_DATA_SOURCE', {
      dataSourceId: dataSource.id,
      values: dataSource,
    })
    try {
      await dispatch('dispatchDataSource', dataSource.id)
    } catch (error) {
      commit('UPDATE_DATA', {
        dataSourceId: dataSource.id,
        values: { _error: true },
      })
    }
  },
  async fetchInitial({ commit, dispatch }, { dashboardId, forEditing }) {
    commit('RESET')
    commit('SET_DASHBOARD_ID', dashboardId)
    const { data } = await WidgetService(this.$client).getAllWidgets(
      dashboardId
    )
    data.forEach((widget) => {
      commit('ADD_WIDGET', widget)
    })
    await dispatch('setLoading', false)
    await dispatch('fetchNewDataSources', dashboardId)

    if (forEditing) {
      const { data: integrationsData } = await IntegrationService(
        this.$client
      ).fetchAll(dashboardId)
      integrationsData.forEach((integration) => {
        commit('ADD_INTEGRATION', integration)
      })
    }
  },
  async fetchNewDataSources({ commit, dispatch, getters }, dashboardId) {
    const { data: dataSourcesData } = await DataSourceService(
      this.$client
    ).getAllDataSources(dashboardId)
    dataSourcesData.forEach(async (dataSource) => {
      if (!getters.getDataSourceById(dataSource.id)) {
        commit('ADD_DATA_SOURCE', dataSource)
        await dispatch('dispatchDataSource', dataSource.id)
      }
    })
  },
  async createWidget({ commit, dispatch }, { dashboard, widget }) {
    const tempId = Date.now()
    commit('ADD_WIDGET', { id: tempId, ...widget })
    let widgetData
    try {
      const { data } = await WidgetService(this.$client).create(
        dashboard.id,
        widget
      )
      widgetData = data
    } catch (error) {
      commit('DELETE_WIDGET', tempId)
      throw error
    }
    return await dispatch('handleNewWidgetCreated', {
      tempWidgetId: tempId,
      createdWidget: widgetData,
    })
  },
  async handleNewWidgetCreated(
    { commit, dispatch },
    { tempWidgetId, createdWidget }
  ) {
    commit('UPDATE_WIDGET', { widgetId: tempWidgetId, values: createdWidget })
    dispatch('selectWidget', createdWidget.id)
    await dispatch('fetchNewDataSources', createdWidget.dashboard_id)
  },
  async dispatchDataSource({ commit }, dataSourceId) {
    commit('UPDATE_DATA', { dataSourceId, values: null })
    try {
      const { data } = await DataSourceService(this.$client).dispatch(
        dataSourceId
      )
      commit('UPDATE_DATA', { dataSourceId, values: data })
    } catch (error) {
      commit('UPDATE_DATA', { dataSourceId, values: { _error: true } })
    }
  },
  async deleteWidget({ dispatch }, widgetId) {
    await WidgetService(this.$client).delete(widgetId)
    dispatch('handleWidgetDeleted', widgetId)
  },
  handleWidgetDeleted({ commit }, widgetId) {
    commit('DELETE_WIDGET', widgetId)
  },
}

export const getters = {
  getDashboardId(state) {
    return state.dashboardId
  },
  isEditMode(state) {
    return state.editMode
  },
  isLoading(state) {
    return state.loading
  },
  isEmpty(state) {
    return state.widgets.length === 0
  },
  getWidgetById: (state, getters) => (widgetId) => {
    return state.widgets.find((widget) => widget.id === widgetId)
  },
  getWidgets(state) {
    return state.widgets.toSorted((a, b) => a.order - b.order)
  },
  getSelectedWidgetId(state) {
    return state.selectedWidgetId
  },
  getSelectedWidget(state) {
    return state.widgets.find((widget) => widget.id === state.selectedWidgetId)
  },
  getDataSourceById: (state, getters) => (dataSourceId) => {
    return state.dataSources.find(
      (dataSource) => dataSource.id === dataSourceId
    )
  },
  getData(state) {
    return state.data
  },
  getDataForDataSource: (state, getters) => (dataSourceId) => {
    return state.data[dataSourceId]
  },
  getIntegrations(state) {
    return state.integrations
  },
  getIntegrationById: (state) => (integrationId) => {
    return state.integrations.find(
      (integration) => integration.id === integrationId
    )
  },
}

export default {
  namespaced: true,
  state,
  getters,
  actions,
  mutations,
}