import {map} from 'rxjs/operators'
import {joinPath} from '../../util/searchUtils'
import {compact, toLower, flatten, uniq, flow, sortBy, union} from 'lodash'
import {removeDupes} from '../../util/draftUtils'
import {applyWeights} from './applyWeights'
import {tokenize} from '../common/tokenize'

const combinePaths = flow([flatten, union, compact])

const toGroqParams = terms =>
  terms.reduce((acc, term, i) => {
    acc[`t${i}`] = `${term}*` // "t" is short for term
    return acc
  }, {})

export function createWeightedSearch(types, client) {
  const searchSpec = types.map(type => {
    return {
      typeName: type.name,
      paths: type.__experimental_search.map(config => ({
        weight: config.weight,
        path: joinPath(config.path)
      }))
    }
  })

  const combinedSearchPaths = combinePaths(
    searchSpec.map(configForType => configForType.paths.map(opt => opt.path))
  )

  const selections = searchSpec.map(
    spec => `_type == "${spec.typeName}" => {${spec.paths.map((cfg, i) => `"w${i}": ${cfg.path}`)}}`
  )

  // this is the actual search function that takes the search string and returns the hits
  return function search(queryString, opts = {}) {
    const terms = uniq(compact(tokenize(toLower(queryString))))
    const constraints = terms
      .map((term, i) => combinedSearchPaths.map(joinedPath => `${joinedPath} match $t${i}`))
      .filter(constraint => constraint.length > 0)

    const filters = [
      '_type in $types',
      opts.includeDrafts === false && `!(_id in path('drafts.**'))`,
      ...constraints.map(constraint => `(${constraint.join('||')})`)
    ].filter(Boolean)

    const selection = selections.length > 0 ? `...select(${selections.join(',\n')})` : ''
    const query = `*[${filters.join('&&')}][0...$limit]{_type, _id, ${selection}}`

    return client.observable
      .fetch(query, {
        ...toGroqParams(terms),
        types: searchSpec.map(spec => spec.typeName),
        limit: 1000
      })
      .pipe(
        map(removeDupes),
        map(hits => applyWeights(searchSpec, hits, terms)),
        map(hits => sortBy(hits, hit => -hit.score)),
        map(hits => hits.slice(0, 100))
      )
  }
}

@@ -1,86 +1,53 @@
-import %7Bmap%7D from 'rxjs/operators'%0Aimport %7BjoinPath%7D from '../../util/searchUtils'
+/* eslint-disable import/prefer-default-export */
 %0Aimp
@@ -127,27 +127,59 @@
 import %7B
-removeDupes
+map%7D from 'rxjs/operators'%0Aimport %7BjoinPath
 %7D from '
@@ -189,21 +189,22 @@
 ../util/
-draft
+search
 Utils'%0Ai
@@ -214,41 +214,47 @@
 rt %7B
-applyWeights%7D from './applyWeight
+removeDupes%7D from '../../util/draftUtil
 s'%0Ai
@@ -295,16 +295,60 @@
 okenize'
+%0Aimport %7BapplyWeights%7D from './applyWeights'
 %0A%0Aconst 
@@ -590,19 +590,74 @@
 , client
-) %7B
+, options = %7B%7D) %7B%0A  const %7Bfilter, filterParams%7D = options
 %0A  const
@@ -1565,16 +1565,18 @@
 ype in $
+__
 types',%0A
@@ -1712,16 +1712,51 @@
 %7C%7C')%7D)%60)
+,%0A      filter ? %60($%7Bfilter%7D)%60 : ''
 %0A    %5D.f
@@ -1910,16 +1910,18 @@
 %7D%5D%5B0...$
+__
 limit%5D%7B_
@@ -2034,24 +2034,26 @@
 s),%0A        
+__
 types: searc
@@ -2094,16 +2094,18 @@
         
+__
 limit: 1
@@ -2107,16 +2107,49 @@
 it: 1000
+,%0A        ...(filterParams %7C%7C %7B%7D)
 %0A      %7D

Include additional filter + params if passed