import { StoreItemLookupError } from '@baserow/modules/core/errors'
import { BuilderApplicationType } from '@baserow/modules/builder/applicationTypes'
import PageService from '@baserow/modules/builder/services/page'
import { generateHash } from '@baserow/modules/core/utils/hashing'

export function populatePage(page) {
  return {
    ...page,
    _: {
      selected: false,
      dataSourceContentLoading: false,
      dataSourceLoading: false,
    },
    dataSources: [],
    elements: [],
    elementMap: {},
    orderedElements: [],
    workflowActions: [],
    contents: null,
  }
}

const state = {
  // Holds the value of which page is currently selected
  selected: {},
  // By default, the device type will be desktop. This will be overridden
  // in the editor, and in the public page. We set a default as otherwise
  // in the public page, we can trigger a repaint, causing some layouts to
  // redraw.
  deviceTypeSelected: 'desktop',
  // A job object that tracks the progress of a page duplication currently running
  duplicateJob: null,
}

const mutations = {
  ADD_ITEM(state, { builder, page }) {
    builder.pages.push(populatePage(page))
  },
  UPDATE_ITEM(state, { page, values }) {
    Object.assign(page, page, values)
  },
  DELETE_ITEM(state, { builder, id }) {
    const index = builder.pages.findIndex((item) => item.id === id)
    builder.pages.splice(index, 1)
  },
  SET_SELECTED(state, { builder, page }) {
    Object.values(builder.pages).forEach((item) => {
      item._.selected = false
    })
    page._.selected = true
    state.selected = page
  },
  UNSELECT(state) {
    if (state.selected?._?.selected) {
      state.selected._.selected = false
    }
    state.selected = {}
  },
  SET_DUPLICATE_JOB(state, job) {
    state.duplicateJob = job
  },
  ORDER_PAGES(state, { builder, order, isHashed = false }) {
    builder.pages.forEach((page) => {
      const pageId = isHashed ? generateHash(page.id) : page.id
      const index = order.findIndex((value) => value === pageId)
      page.order = index === -1 ? 0 : index + 1
    })
  },
  SET_DEVICE_TYPE_SELECTED(state, deviceType) {
    state.deviceTypeSelected = deviceType
  },
}

const actions = {
  forceUpdate({ commit }, { page, values }) {
    commit('UPDATE_ITEM', { page, values })
  },
  forceCreate({ commit }, { builder, page }) {
    commit('ADD_ITEM', { builder, page })
  },
  selectById({ commit, getters }, { builder, pageId }) {
    const type = BuilderApplicationType.getType()

    // Check if the just selected application is a builder
    if (builder.type !== type) {
      throw new StoreItemLookupError(
        `The application doesn't have the required ${type} type.`
      )
    }

    // Check if the provided page id is found in the just selected builder.
    const page = getters.getById(builder, pageId)

    commit('SET_SELECTED', { builder, page })

    return page
  },
  unselect({ commit }) {
    commit('UNSELECT')
  },
  async forceDelete({ commit }, { builder, page }) {
    if (page._.selected) {
      // Redirect back to the dashboard because the page doesn't exist anymore.
      await this.$router.push({ name: 'dashboard' })
    }

    commit('DELETE_ITEM', { builder, id: page.id })
  },
  async create({ commit, dispatch }, { builder, name, path, pathParams }) {
    const { data: page } = await PageService(this.$client).create(
      builder.id,
      name,
      path,
      pathParams
    )

    commit('ADD_ITEM', { builder, page })

    await dispatch('selectById', { builder, pageId: page.id })

    return page
  },
  async update({ dispatch }, { builder, page, values }) {
    const { data } = await PageService(this.$client).update(page.id, values)

    const update = Object.keys(values).reduce((result, key) => {
      result[key] = data[key]
      return result
    }, {})

    await dispatch('forceUpdate', { builder, page, values: update })
  },
  async delete({ dispatch }, { builder, page }) {
    await PageService(this.$client).delete(page.id)

    await dispatch('forceDelete', { builder, page })
  },
  async order(
    { commit, getters },
    { builder, order, oldOrder, isHashed = false }
  ) {
    commit('ORDER_PAGES', { builder, order, isHashed })

    try {
      await PageService(this.$client).order(builder.id, order)
    } catch (error) {
      commit('ORDER_PAGES', { builder, order: oldOrder, isHashed })
      throw error
    }
  },
  setDeviceTypeSelected({ commit }, deviceType) {
    commit('SET_DEVICE_TYPE_SELECTED', deviceType)
  },
  async duplicate({ commit, dispatch }, { page }) {
    const { data: job } = await PageService(this.$client).duplicate(page.id)

    await dispatch('job/create', job, { root: true })

    commit('SET_DUPLICATE_JOB', job)
  },
}

const getters = {
  getAllPages: (state) => (builder) => {
    return builder.pages
  },
  getById: (state, getters) => (builder, pageId) => {
    const index = getters
      .getAllPages(builder)
      .findIndex((item) => item.id === pageId)

    if (index === -1) {
      throw new StoreItemLookupError(
        'The page is not found in the selected application.'
      )
    }

    return getters.getAllPages(builder)[index]
  },
  getVisiblePages: (state, getters) => (builder) => {
    return getters
      .getAllPages(builder)
      .filter((page) => page.shared === false)
      .sort((a, b) => a.order - b.order)
  },
  getSharedPage: (state, getters) => (builder) => {
    return getters.getAllPages(builder).find((page) => page.shared === true)
  },
  getSelected(state) {
    return state.selected
  },
  getDeviceTypeSelected(state) {
    return state.deviceTypeSelected
  },
  getDuplicateJob(state) {
    return state.duplicateJob
  },
}

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