import _ from 'lodash'
import { boolMerge, buildClause } from './utils'
import queryBuilder from './query-builder'
import aggregationBuilder from './aggregation-builder'

export default function filterBuilder () {
  let filter = {}

  function addMinimumShouldMatch(str) {
    const shouldClause = _.get(filter, 'bool.should')
    if (shouldClause && shouldClause.length > 1) {
      filter.bool['minimum_should_match'] = str
    }
  }

  function makeFilter (boolType, filterType, ...args) {
    const nested = {}
    if (_.isFunction(_.last(args))) {
      const nestedCallback = args.pop()
      const nestedResult = nestedCallback(
        Object.assign(
          {},
          queryBuilder(),
          filterBuilder(),
          aggregationBuilder()
        )
      )
      if (nestedResult.hasQuery()) {
        nested.query = nestedResult.getQuery()
      }
      if (nestedResult.hasFilter()) {
        nested.filter = nestedResult.getFilter()
      }
      if (nestedResult.hasAggregations()) {
        nested.aggs = nestedResult.getAggregations()
      }
    }

    filter = boolMerge(
      {[filterType]: Object.assign(buildClause(...args), nested)},
      filter,
      boolType
    )
  }

  return {
    /**
     * Add a filter clause to the query body.
     *
     * @param  {string}        type    Filter type.
     * @param  {string|Object} field   Field to filter or complete filter
     *                                 clause.
     * @param  {string|Object} value   Filter term or inner clause.
     * @param  {Object}        options (optional) Additional options for the
     *                                 filter clause.
     * @param  {Function}      [nest]  (optional) A function used to define
     *                                 sub-filters as children. This _must_ be
     *                                 the last argument.
     *
     * @return {bodybuilder} Builder.
     *
     * @example
     * bodybuilder()
     *   .filter('term', 'user', 'kimchy')
     *   .build()
     */
    filter (...args) {
      makeFilter('and', ...args)
      return this
    },

    /**
     * Alias for `filter`.
     *
     * @return {bodybuilder} Builder.
     */
    andFilter (...args) {
      return this.filter(...args)
    },

    /**
     * Alias for `filter`.
     *
     * @return {bodybuilder} Builder.
     */
    addFilter (...args) {
      return this.filter(...args)
    },

    /**
     * Add a "should" filter to the query body.
     *
     * Same arguments as `filter`.
     *
     * @return {bodybuilder} Builder.
     */
    orFilter (...args) {
      makeFilter('or', ...args)
      return this
    },

    /**
     * Add a "must_not" filter to the query body.
     *
     * Same arguments as `filter`.
     *
     * @return {bodybuilder} Builder.
     */
    notFilter (...args) {
      makeFilter('not', ...args)
      return this
    },

    /**
     * Set the `minimum_should_match` property on a bool filter with more than
     * one `should` clause.
     *
     * @param  {any} param  minimum_should_match parameter. For possible values
     *                      see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match.html
     * @return {bodybuilder} Builder.
     */
    filterMinimumShouldMatch (param) {
      addMinimumShouldMatch(param)
      return this
    },

    getFilter () {
      return filter
    },

    hasFilter () {
      return !!_.size(filter)
    }
  }
}

@@ -28,19 +28,8 @@
 rt %7B
- boolMerge,
  bui
@@ -208,20 +208,61 @@
 t filter
+s
  = %7B
+%0A    and: %5B%5D,%0A    or: %5B%5D,%0A    not: %5B%5D%0A  
 %7D%0A%0A  fun
@@ -304,128 +304,16 @@
     
-const shouldClause = _.get(filter, 'bool.should')%0A    if (shouldClause && shouldClause.length %3E 1) %7B%0A      filter.bool%5B'
+filters.
 mini
@@ -332,23 +332,15 @@
 atch
-'%5D
  = str%0A
-    %7D%0A
   %7D%0A
@@ -991,20 +991,24 @@
 lter
- = 
+s%5B
 bool
-Merge
+Type%5D.push
 (%0A  
@@ -1074,38 +1074,8 @@
 ed)%7D
-,%0A      filter,%0A      boolType
 %0A   
@@ -3275,22 +3275,55 @@
  return 
-filter
+this.hasFilter() ? toBool(filters) : %7B%7D
 %0A    %7D,%0A
@@ -3361,31 +3361,844 @@
 n !!
-_.size(filter)%0A    %7D%0A  %7D
+(filters.and.length %7C%7C filters.or.length %7C%7C filters.not.length)%0A    %7D%0A  %7D%0A%7D%0A%0Afunction toBool (filters) %7B%0A  const unwrapped = %7B%0A    must: unwrap(filters.and),%0A    should: unwrap(filters.or),%0A    must_not: unwrap(filters.not),%0A    minimum_should_match: filters.minimum_should_match%0A  %7D%0A%0A  if (%0A    filters.and.length === 1 &&%0A    !unwrapped.should &&%0A    !unwrapped.must_not%0A  ) %7B%0A    return unwrapped.must%0A  %7D%0A%0A  const cleaned = %7B%7D%0A%0A  if (unwrapped.must) %7B%0A    cleaned.must = unwrapped.must%0A  %7D%0A  if (unwrapped.should)%C2%A0%7B%0A    cleaned.should = filters.or%0A  %7D%0A  if (unwrapped.must_not) %7B%0A    cleaned.must_not = filters.not%0A  %7D%0A  if (unwrapped.minimum_should_match) %7B%0A    cleaned.minimum_should_match = unwrapped.minimum_should_match%0A  %7D%0A%0A  return %7B%0A    bool: cleaned%0A  %7D%0A%7D%0A%0Afunction unwrap (arr) %7B%0A  return arr.length %3E 1 ? arr : _.last(arr)
 %0A%7D%0A

allow nesting of filters