import { sort } from '@/assets/fastSort'
import { decode } from '@/plugins/init.client'

const STANDARD_GENRES = ['Adventure', 'Autobiography', 'Biography', 'Childrens', 'Comedy', 'Crime', 'Dystopian', 'Fantasy', 'Fiction', 'Health', 'History', 'Horror', 'Mystery', 'New Adult', 'Nonfiction', 'Philosophy', 'Politics', 'Religion', 'Romance', 'Sci-Fi', 'Self-Help', 'Short Story', 'Technology', 'Thriller', 'True Crime', 'Western', 'Young Adult']

export const state = () => ({
  audiobooks: [],
  loadedLibraryId: '',
  lastLoad: 0,
  listeners: [],
  genres: [...STANDARD_GENRES],
  tags: [],
  series: [],
  keywordFilter: null,
  selectedSeries: null,
  libraryPage: null,
  searchResults: {},
  searchResultAudiobooks: []
})

export const getters = {
  getAudiobook: (state) => id => {
    return state.audiobooks.find(ab => ab.id === id)
  },
  getAudiobooksWithIssues: (state) => {
    return state.audiobooks.filter(ab => {
      return ab.hasMissingParts || ab.hasInvalidParts || ab.isMissing || ab.isIncomplete
    })
  },
  getEntitiesShowing: (state, getters, rootState, rootGetters) => () => {
    if (!state.libraryPage) {
      return getters.getFiltered()
    } else if (state.libraryPage === 'search') {
      return state.searchResultAudiobooks
    } else if (state.libraryPage === 'series') {
      var series = getters.getSeriesGroups()
      if (state.selectedSeries) {
        var _series = series.find(__series => __series.name === state.selectedSeries)
        if (!_series) return []
        return _series.books || []
      }
      return series
    }
    return []
  },
  getFiltered: (state, getters, rootState, rootGetters) => () => {
    var filtered = state.audiobooks
    var settings = rootState.user.settings || {}
    var filterBy = settings.filterBy || ''

    var searchGroups = ['genres', 'tags', 'series', 'authors', 'progress', 'narrators']
    var group = searchGroups.find(_group => filterBy.startsWith(_group + '.'))
    if (group) {
      var filterVal = filterBy.replace(`${group}.`, '')
      var filter = decode(filterVal)
      if (group === 'genres') filtered = filtered.filter(ab => ab.book && ab.book.genres.includes(filter))
      else if (group === 'tags') filtered = filtered.filter(ab => ab.tags.includes(filter))
      else if (group === 'series') {
        if (filter === 'No Series') filtered = filtered.filter(ab => ab.book && !ab.book.series)
        else filtered = filtered.filter(ab => ab.book && ab.book.series === filter)
      }
      else if (group === 'authors') filtered = filtered.filter(ab => ab.book && ab.book.authorFL && ab.book.authorFL.split(', ').includes(filter))
      else if (group === 'narrators') filtered = filtered.filter(ab => ab.book && ab.book.narrator === filter)
      else if (group === 'progress') {
        filtered = filtered.filter(ab => {
          var userAudiobook = rootGetters['user/getUserAudiobook'](ab.id)
          var isRead = userAudiobook && userAudiobook.isRead
          if (filter === 'Read' && isRead) return true
          if (filter === 'Unread' && !isRead) return true
          if (filter === 'In Progress' && (userAudiobook && !userAudiobook.isRead && userAudiobook.progress > 0)) return true
          return false
        })
      }
    } else if (filterBy === 'issues') {
      filtered = filtered.filter(ab => {
        return ab.hasMissingParts || ab.hasInvalidParts || ab.isMissing || ab.isIncomplete
      })
    }

    if (state.keywordFilter) {
      const keywordFilterKeys = ['title', 'subtitle', 'author', 'series', 'narrator']
      const keyworkFilter = state.keywordFilter.toLowerCase()
      return filtered.filter(ab => {
        if (!ab.book) return false
        return !!keywordFilterKeys.find(key => (ab.book[key] && ab.book[key].toLowerCase().includes(keyworkFilter)))
      })
    }
    return filtered
  },
  getFilteredAndSorted: (state, getters, rootState) => () => {
    var settings = rootState.user.settings
    var direction = settings.orderDesc ? 'desc' : 'asc'

    var filtered = getters.getFiltered()

    var orderByNumber = settings.orderBy === 'book.volumeNumber'
    return sort(filtered)[direction]((ab) => {
      // Supports dot notation strings i.e. "book.title"
      var value = settings.orderBy.split('.').reduce((a, b) => a[b], ab)
      if (orderByNumber && !isNaN(value)) return Number(value)
      return value
    })
  },
  getSeriesGroups: (state, getters, rootState) => () => {
    var series = {}
    state.audiobooks.forEach((audiobook) => {
      if (audiobook.book && audiobook.book.series) {
        if (series[audiobook.book.series]) {
          var bookLastUpdate = audiobook.book.lastUpdate
          if (bookLastUpdate > series[audiobook.book.series].lastUpdate) series[audiobook.book.series].lastUpdate = bookLastUpdate
          series[audiobook.book.series].books.push(audiobook)
        } else {
          series[audiobook.book.series] = {
            type: 'series',
            name: audiobook.book.series || '',
            books: [audiobook],
            lastUpdate: audiobook.book.lastUpdate
          }
        }
      }
    })
    var seriesArray = Object.values(series).map((_series) => {
      _series.books = sort(_series.books)['asc']((ab) => {
        return ab.book && ab.book.volumeNumber && !isNaN(ab.book.volumeNumber) ? Number(ab.book.volumeNumber) : null
      })
      return _series
    })
    if (state.keywordFilter) {
      const keywordFilter = state.keywordFilter.toLowerCase()
      return seriesArray.filter((_series) => _series.name.toLowerCase().includes(keywordFilter))
    }
    return seriesArray
  },
  getUniqueAuthors: (state) => {
    var abAuthors = []
    state.audiobooks.forEach((ab) => {
      if (ab.book && ab.book.authorFL) {
        abAuthors = abAuthors.concat(ab.book.authorFL.split(', '))
      }
    })
    return [...new Set(abAuthors)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
  },
  getUniqueNarrators: (state) => {
    var _narrators = state.audiobooks.filter(ab => !!(ab.book && ab.book.narrator)).map(ab => ab.book.narrator)
    return [...new Set(_narrators)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
  },
  getGenresUsed: (state) => {
    var _genres = []
    state.audiobooks.filter(ab => !!(ab.book && ab.book.genres)).forEach(ab => _genres = _genres.concat(ab.book.genres))
    return [...new Set(_genres)].sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
  },
  getBookCoverSrc: (state, getters, rootState, rootGetters) => (bookItem, placeholder = '/book_placeholder.jpg') => {
    var book = bookItem.book
    if (!book || !book.cover || book.cover === placeholder) return placeholder
    var cover = book.cover

    // Absolute URL covers (should no longer be used)
    if (cover.startsWith('http:') || cover.startsWith('https:')) return cover

    // Server hosted covers
    try {
      // Ensure cover is refreshed if cached
      var bookLastUpdate = book.lastUpdate || Date.now()
      var userToken = rootGetters['user/getToken']

      cover = cover.replace(/\\/g, '/')

      // Map old covers to new format /s/book/{bookid}/*
      if (cover.startsWith('/local')) {
        cover = cover.replace('local', `s/book/${bookItem.id}`)
        if (cover.includes(bookItem.path + '/')) { // Remove book path
          cover = cover.replace(bookItem.path + '/', '')
        }
      }

      // Easier to replace these special characters then to encodeUriComponent of the filename
      var encodedCover = cover.replace(/%/g, '%25').replace(/#/g, '%23')

      var url = new URL(encodedCover, document.baseURI)
      return url.href + `?token=${userToken}&ts=${bookLastUpdate}`
    } catch (err) {
      console.error(err)
      return placeholder
    }
  }
}

export const actions = {
  // Return true if calling load
  load({ state, commit, rootState }) {
    if (!rootState.user || !rootState.user.user) {
      console.error('audiobooks/load - User not set')
      return false
    }

    var currentLibraryId = rootState.libraries.currentLibraryId

    if (currentLibraryId === state.loadedLibraryId) {
      // Don't load again if already loaded in the last 5 minutes
      var lastLoadDiff = Date.now() - state.lastLoad
      if (lastLoadDiff < 5 * 60 * 1000) {
        // Already up to date
        return false
      }
    }
    commit('setLoadedLibrary', currentLibraryId)

    this.$axios
      .$get(`/api/library/${currentLibraryId}/audiobooks`)
      .then((data) => {
        commit('set', data)
        commit('setLastLoad')

      })
      .catch((error) => {
        console.error('Failed', error)
        commit('set', [])
      })
    return true
  }
}

export const mutations = {
  setLoadedLibrary(state, val) {
    state.loadedLibraryId = val
  },
  setLastLoad(state) {
    state.lastLoad = Date.now()
  },
  setKeywordFilter(state, val) {
    state.keywordFilter = val
  },
  setSelectedSeries(state, val) {
    state.selectedSeries = val
  },
  setLibraryPage(state, val) {
    state.libraryPage = val
  },
  setSearchResults(state, val) {
    state.searchResults = val
    state.searchResultAudiobooks = val && val.audiobooks ? val.audiobooks.map(ab => ab.audiobook) : []
  },
  set(state, audiobooks) {
    // GENRES
    var genres = [...state.genres]
    audiobooks.forEach((ab) => {
      if (!ab.book) return
      genres = genres.concat(ab.book.genres)
    })
    state.genres = [...new Set(genres)] // Remove Duplicates
    state.genres.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)

    // TAGS
    var tags = []
    audiobooks.forEach((ab) => {
      tags = tags.concat(ab.tags)
    })
    state.tags = [...new Set(tags)] // Remove Duplicates
    state.tags.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)

    // SERIES
    var series = []
    audiobooks.forEach((ab) => {
      if (!ab.book || !ab.book.series || series.includes(ab.book.series)) return
      series.push(ab.book.series)
    })
    state.series = series
    state.series.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)

    state.audiobooks = audiobooks
    state.listeners.forEach((listener) => {
      listener.meth()
    })
  },
  addUpdate(state, audiobook) {
    if (state.loadedLibraryId && audiobook.libraryId !== state.loadedLibraryId) {
      console.warn('Invalid library', audiobook, 'loaded library', state.loadedLibraryId, '"')
      return
    }

    var index = state.audiobooks.findIndex(a => a.id === audiobook.id)
    var origAudiobook = null
    if (index >= 0) {
      origAudiobook = { ...state.audiobooks[index] }
      state.audiobooks.splice(index, 1, audiobook)
    } else {
      state.audiobooks.push(audiobook)
    }

    if (audiobook.book) {
      // GENRES
      var newGenres = []
      audiobook.book.genres.forEach((genre) => {
        if (!state.genres.includes(genre)) newGenres.push(genre)
      })
      if (newGenres.length) {
        state.genres = state.genres.concat(newGenres)
        state.genres.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
      }

      // SERIES
      if (audiobook.book.series && !state.series.includes(audiobook.book.series)) {
        state.series.push(audiobook.book.series)
        state.series.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
      }
      if (origAudiobook && origAudiobook.book && origAudiobook.book.series) {
        var isInAB = state.audiobooks.find(ab => ab.book && ab.book.series === origAudiobook.book.series)
        if (!isInAB) state.series = state.series.filter(series => series !== origAudiobook.book.series)
      }
    }

    // TAGS
    var newTags = []
    audiobook.tags.forEach((tag) => {
      if (!state.tags.includes(tag)) newTags.push(tag)
    })
    if (newTags.length) {
      state.tags = state.tags.concat(newTags)
      state.tags.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : 1)
    }

    state.listeners.forEach((listener) => {
      if (!listener.audiobookId || listener.audiobookId === audiobook.id) {
        listener.meth()
      }
    })
  },
  remove(state, audiobook) {
    state.audiobooks = state.audiobooks.filter(a => a.id !== audiobook.id)

    if (audiobook.book) {
      // GENRES
      audiobook.book.genres.forEach((genre) => {
        if (!STANDARD_GENRES.includes(genre)) {
          var isInOtherAB = state.audiobooks.find(ab => {
            return ab.book && ab.book.genres.includes(genre)
          })
          if (!isInOtherAB) {
            // Genre is not used by any other audiobook - remove it
            state.genres = state.genres.filter(g => g !== genre)
          }
        }
      })

      // SERIES
      if (audiobook.book.series) {
        var isInOtherAB = state.audiobooks.find(ab => ab.book && ab.book.series === audiobook.book.series)
        if (!isInOtherAB) {
          // Series not used in any other audiobook - remove it
          state.series = state.series.filter(s => s !== audiobook.book.series)
        }
      }
    }

    // TAGS
    audiobook.tags.forEach((tag) => {
      var isInOtherAB = state.audiobooks.find(ab => {
        return ab.tags.includes(tag)
      })
      if (!isInOtherAB) {
        // Tag is not used by any other audiobook - remove it
        state.tags = state.tags.filter(t => t !== tag)
      }
    })

    state.listeners.forEach((listener) => {
      if (!listener.audiobookId || listener.audiobookId === audiobook.id) {
        listener.meth()
      }
    })
  },
  addListener(state, listener) {
    var index = state.listeners.findIndex(l => l.id === listener.id)
    if (index >= 0) state.listeners.splice(index, 1, listener)
    else state.listeners.push(listener)
  },
  removeListener(state, listenerId) {
    state.listeners = state.listeners.filter(l => l.id !== listenerId)
  }
}