import Vue from 'vue'
import Vuex from 'vuex'
import arrayMove from 'array-move'
import axios from 'axios'
import { modelsFromSection, paginateModels, sectionTitle, extendModelFromMaterial } from '@/pagination'

Vue.use(Vuex)

function skeletonCatalog() {
  return { sections: [] }
}

export const store = new Vuex.Store({
  state: {
    catalog: skeletonCatalog(),
    materials: {},
    selectedSectionID: null,
    selectedModelID: null,
    selectedMaterialID: null,
    loadingCatalog: false,
    savingCatalog: false,
    catalogSavedOK: false,
    loadingProducts: false,
  },
  getters: {

    // NOTE don't use if you're not doing anything interesting, just use state.

    material: state => id => {
      return state.materials[id]
    },

    section: state => id => {
      return state.catalog.sections.find(s => s.id === id)
    },

    nextSectionID: state => {
      let id = 0
      for (let section of state.catalog.sections) {
        id = Math.max(id, section.id)
      }
      return id + 1
    },

    selectedSection: (state, getters) => {
      return getters.section(state.selectedSectionID)
    },

    selectedModel: (state, getters) => {
      if (state.selectedModelID) {
        let section = getters.selectedSection
        return getters.sectionModel(section, state.selectedModelID)
      } else {
        return null
      }
    },

    // find a model within a section
    // NOTE: returns copies of the original catalog data
    sectionModel: (state, getters) => (section, model) => {
      let modelID = (typeof model === 'object') ? model.model : model
      let models = getters.sectionModels(section)
      return models.find(m => m.model === modelID)
    },

    // NOTE: returns copies of the original catalog data
    sectionModels: (state, getters) => (section) => {
      return modelsFromSection(section, getters)
    },

    modelMaterials: (state, getters) => (model) => {
      if (model && model.ids && model.ids.length > 0) {
        return model.ids.map(id => getters.material(id))
      }
      return []
    },

    selectedMaterial: (state, getters) => {
      return getters.material(state.selectedMaterialID)
    },

    catalogProperty: state => key => {
      return state.catalog[key]
    },

    loadingCatalog(state) {
      return state.loadingCatalog
    },

    savingCatalog(state) {
      return state.savingCatalog
    },

    loadingProducts(state) {
      return state.loadingProducts
    },

  },
  mutations: {
    //
    // selection
    //

    selectSection(state, id) {
      if (state.selectedSectionID !== id) {
        state.selectedSectionID = id
        state.selectedModelID = null
        state.selectedMaterialID = null
      }
    },

    selectModel(state, id) {
      if (state.selectedModelID !== id) {
        state.selectedModelID = id
        state.selectedMaterialID = null
      }
    },

    selectMaterial(state, id) {
      if (state.selectedMaterialID !== id) {
        state.selectedMaterialID = id
      }
    },

    //
    // manipulation
    //

    addSection(state, section) {
      state.catalog.sections.push(section)
    },

    deleteSection(state, section) {
      if (section) {
        state.catalog.sections = state.catalog.sections.filter(s => s.id !== section.id)
      }
    },

    setSectionName(state, { section, name }) {
      section.name = name
    },

    // move a section in the section list
    reorderSection(state, { from, to }) {
      if (from === to) return
      state.catalog.sections = arrayMove(state.catalog.sections, from, to)
    },

    setSectionPages(state, { section, pages }) {
      if (section) {
        // update actual catalog data (we might be passed a copy)
        let catSection = state.catalog.sections.find(s => s.id === section.id)
        if (catSection) {
          catSection.pages = pages
        } else {
          console.error('couldn\'t find catalog section', section.id)
        }
      }
    },

    setModelName(state, { section, model, name }) {
      let catSection = state.catalog.sections.find(s => s.id === section.id)
      for (let catPage of catSection.pages) {
        for (let catModel of catPage) {
          if (catModel.model === model.model) {
            catModel.name = name
          }
        }
      }
    },

    addMaterialsToLibrary(state, materials) {
      console.log('adding', materials.length, 'materials')
      let map = {}
      for (let m of materials) {
        map[m.id] = m
      }
      state.materials = { ...state.materials, ...map }
    },

    setCatalog(state, cat) {
      if (cat !== null) {
        state.catalog = cat
      } else {
        state.catalog = skeletonCatalog()
      }
    },

    setCatalogProperty(state, { key, value }) {
      state.catalog[key] = value
    },

    setLoadingCatalog(state, value) {
      state.loadingCatalog = value
    },

    setSavingCatalog(state, value) {
      state.savingCatalog = value
    },

    setCatalogSavedOK(state, value) {
      state.catalogSavedOK = value
    },

    setLoadingProducts(state, value) {
      state.loadingProducts = value
    },

  },
  actions: {
    //
    // selection
    //

    selectSection(context, id) {
      context.commit('selectSection', id)
    },

    selectModel(context, id) {
      context.commit('selectModel', id)
    },

    selectMaterial(context, id) {
      context.commit('selectMaterial', id)
    },

    //
    // manipulation
    //

    newSection({ commit, getters }, name) {
      let section = {
        name: name,
        id: getters.nextSectionID,
        pages: [],
      }
      commit('addSection', section)
    },

    deleteSection({ commit, getters }, section) {
      commit('deleteSection', section)
    },

    setSectionName({ commit }, payload) {
      commit('setSectionName', payload)
    },

    // move a section in the section list
    reorderSection({ commit }, payload) {
      commit('reorderSection', payload)
    },

    // move a model in the section's model list
    reorderModel({ commit, getters }, { section, fromIndex, toIndex }) {
      let models = getters.sectionModels(section)
      arrayMove.mutate(models, fromIndex, toIndex)
      let pages = paginateModels(models)
      commit('setSectionPages', { section: section, pages: pages })
    },

    // move a material in the models's material list
    reorderMaterial({ commit, getters }, { section, model, fromIndex, toIndex }) {
      let models = getters.sectionModels(section)
      let updateModel = models.find(m => m.model === model.model)
      updateModel.ids = model.ids
      arrayMove.mutate(updateModel.ids, fromIndex, toIndex)
      let pages = paginateModels(models)
      commit('setSectionPages', { section: section, pages: pages })
    },

    setSectionPages({ commit }, payload) {
      commit('setSectionPages', payload)
    },

    setModelName({ commit }, payload) {
      commit('setModelName', payload)
    },

    deleteSectionModel({ commit, getters }, { section, model }) {
      let models = getters.sectionModels(section)
      models = models.filter(m => m.model !== model.model)
      let pages = paginateModels(models)
      commit('setSectionPages', { section: section, pages: pages })
    },

    moveModelToSection({ commit, getters }, { model, oldSection, newSection }) {
      // remove model from old section
      let oldModels = getters.sectionModels(oldSection)
      oldModels = oldModels.filter(m => m.model !== model.model)
      let oldPages = paginateModels(oldModels)
      commit('setSectionPages', { section: oldSection, pages: oldPages })

      // add model to new section
      let newModels = getters.sectionModels(newSection)
      let existingModel = newModels.find(m => m.model === model.model)
      if (existingModel) {
        // merge
        var ids = existingModel.ids
        ids.push(...model.ids)
        // uniquify
        ids = ids.filter((v, i, a) => a.indexOf(v) === i)
        existingModel.ids = ids
      } else {
        // add at end
        newModels.push(model)
      }
      let newPages = paginateModels(newModels)
      commit('setSectionPages', { section: newSection, pages: newPages })
    },

    moveMaterialToSection({ commit, getters }, { material, oldSection, oldModel, newSection }) {
      // ensure material, not just id
      if (typeof material === 'string' || typeof material === 'number') {
        material = getters.material(material)
      }

      // remove material from old model
      let oldModels = getters.sectionModels(oldSection)
      let realOldModel = oldModels.find(m => m.model === oldModel.model)
      realOldModel.ids = realOldModel.ids.filter(id => id !== material.id)
      let oldPages = paginateModels(oldModels)
      commit('setSectionPages', { section: oldSection, pages: oldPages })

      // add material to new section
      let newModels = getters.sectionModels(newSection)
      let existingModel = newModels.find(m => m.model === material.model)
      if (existingModel) {
        // merge
        var ids = existingModel.ids
        ids.push(material.id)
        // uniquify
        ids = ids.filter((v, i, a) => a.indexOf(v) === i)
        existingModel.ids = ids
      } else {
        // add at end
        newModels.push({ ids: [material.id] })
      }
      let newPages = paginateModels(newModels)
      commit('setSectionPages', { section: newSection, pages: newPages })
    },

    addMaterialToCatalog({ commit, getters, state }, material) {
      // ensure material, not just id
      if (typeof material === 'string' || typeof material === 'number') {
        material = getters.material(material)
      }

      let foundSection = null
      let foundModels = null

      // look for an existing model in all our sections.
      // add to first one found.
      for (let section of state.catalog.sections) {
        let models = getters.sectionModels(section)
        let existingModel = models.find(m => m.model === material.model)
        if (existingModel) {
          // NOTE prevent duplicates here, but also allow adding the same
          // id again to a catalog.  put in a new section so it can be
          // placed elsewhere?  handle later.
          let existingMaterial = existingModel.ids.find(m => m === material.id)
          foundSection = section
          foundModels = models
          if (!existingMaterial) {
            existingModel.ids.push(material.id)
          } else {
            console.log('skip duplicate')
          }
          break
        }
      }

      if (!foundSection) {
        // see if we match a section, add a new model at the end
        let title = sectionTitle(material)
        for (let section of state.catalog.sections) {
          if (title.toLowerCase() === section.name.toLowerCase()) {
            let newModel = extendModelFromMaterial(material)
            let models = getters.sectionModels(section)
            models.push(newModel)
            foundSection = section
            foundModels = models
            break
          }
        }
      }

      // still nothing, add into a new section
      if (!foundSection) {
        let title = sectionTitle(material)
        let newModel = extendModelFromMaterial(material)
        foundModels = [newModel]
        foundSection = {
          'id': getters.nextSectionID,
          'name': title,
        }
        commit('addSection', foundSection)
      }

      let pages = paginateModels(foundModels)
      commit('setSectionPages', { section: foundSection, pages: pages })
    },

    deleteModelMaterial({ commit, getters }, { section, material }) {
      let models = getters.sectionModels(section)
      let catalogModel = models.find(m => m.model === material.model)
      catalogModel.ids = catalogModel.ids.filter(id => id !== material.id)
      let pages = paginateModels(models)
      commit('setSectionPages', { section: section, pages: pages })
    },

    setCatalogProperty(context, data) {
      context.commit('setCatalogProperty', data)
    },

    newCatalog({ commit }) {
      commit('setCatalog', skeletonCatalog())
    },

    async fetchProducts({ commit, dispatch, getters }, text) {
      commit('setLoadingProducts', true)
      axios.post('/api/v1/products/search',
                 {
                   text: text,
                   season: getters.catalogProperty('season'),
                   region: getters.catalogProperty('region')
                 })
        .then(function(response) {
          commit('setLoadingProducts', false)

          if (response.data && response.data.missing) {
            // TODO display these somehow
            console.log('missing ids:', response.data.missing)
          }

          if (response.data && response.data.found) {
            console.log('found prods:', response.data.found)
            commit('addMaterialsToLibrary', response.data.found)
            for (let material of response.data.found) {
              dispatch('addMaterialToCatalog', material)
            }
          }
        })
        .catch(function(error) {
          commit('setLoadingProducts', false)
          // TODO set saving error property
          console.error('error searching for prods: ', error)
        })
    },

    async loadCatalog({ commit }, id) {
      try {
        commit('setLoadingCatalog', true)
        const response = await axios.get('/api/v1/catalogs/id/' + id)
        // bundled materials
        if (response.data && response.data.products) {
          console.log('recieved materials:', response.data.products)
          commit('addMaterialsToLibrary', response.data.products)
        } else {
          console.log('no materials')
        }
        // the catalog
        if (response.data && response.data.catalog) {
          console.log('recieved catalog:', response.data.catalog)
          commit('setCatalog', response.data.catalog)
        } else {
          console.log('no catalog')
          commit('newCatalog')
        }
        commit('setLoadingCatalog', false)
      } catch (error) {
        // TODO set loading error property
        console.error('error loading catalog: ', error)
        commit('newCatalog')
        commit('setLoadingCatalog', false)
      }
    },

    async saveCatalog({ commit, state }) {
      commit('setSavingCatalog', true)

      axios.post('/api/v1/catalogs/save', state.catalog)
        .then(function(response) {
          console.log('save response', response)
          if (response.data) {
            console.log('recieved catalog:', response.data)
            commit('setCatalog', response.data)
            commit('setCatalogSavedOK', true)
          } else {
            console.error('no catalog returned from save!')
          }
          commit('setSavingCatalog', false)
        })
        .catch(function(error) {
          // TODO set saving error property
          console.error('error saving catalog: ', error)
          commit('setSavingCatalog', false)
        })
    },

  }
})
