const _ = require('lodash');

// This middleware component trims the results array by granularity when
// FallbackQuery was used.  FallbackQuery is used for inputs like
// `1090 N Charlotte St, Lancaster, PA` where the address may not exist and
// we must fall back to trying `Lancaster, PA`.  If the address does exist then
// FallbackQuery will return results for:
// - address+city+state
// - city+state
// - state
//
// Because the address matched, we're not interested in city+state or state, so
// this component removes results that aren't the most granular.

// layers in increasing order of granularity
// some places where this list differs in order from trimByGranularity:
// - because house number and street are in a single field, street hits must be considered
//   more important than addresses due to how ES matches
// - country outranks dependency, this was done to ensure that "country=United States" doesn't
//   bump up US dependencies containing "United States" above the country
// - retain both borough and locality results if both exist for when city=Manhattan is
//   supplied we want to retain borough=Manhattan and city=Manhattan results
const layers = [
  'venue',
  'address',
  'street',
  'postalcode',
  'neighbourhood',
  ['borough', 'locality'],
  'localadmin',
  'county',
  'macrocounty',
  'region',
  'macroregion',
  'country',
  'dependency'
];

// these layers are strictly used to drive one special case:
//   - when there was a borough explicitly supplied
// for example, if the user passed borough=manhattan and city=new york
// then we want to preserve just boroughs if they're most granular and throw away
// city results.  In the usual case where no borough is passed, the city value
// is looked up as a borough in the off chance that the user passed
// city=Manhattan
const explicit_borough_layers = [
  'venue',
  'address',
  'street',
  'postalcode',
  'neighbourhood',
  'borough',
  'locality',
  'localadmin',
  'county',
  'macrocounty',
  'region',
  'macroregion',
  'country',
  'dependency'
];

// this helper method returns `true` if every result has a matched_query
//  starting with `fallback.`
function isFallbackQuery(results) {
  return results.every(function(result) {
    return result.hasOwnProperty('_matched_queries') &&
            !_.isEmpty(result._matched_queries) &&
            _.startsWith(result._matched_queries[0], 'fallback.');
  });
}

function hasRecordsAtLayers(results, layer) {
  return results.some( (result) => {
    if (_.isArray(layer)) {
      return layer.some( (sublayer) => {
        return result._matched_queries[0] === 'fallback.' + sublayer;
      });
    } else {
      return result._matched_queries[0] === 'fallback.' + layer;
    }

  });
}

function retainRecordsAtLayers(results, layer) {
  return results.filter( (result) => {
    if (_.isArray(layer)) {
      return layer.some( (sublayer) => {
        return result._matched_queries[0] === 'fallback.' + sublayer;
      });
    }
    else {
      return result._matched_queries[0] === 'fallback.' + layer;
    }

  });
}

function getLayers(parsed_text) {
  if (parsed_text && parsed_text.hasOwnProperty('borough')) {
    return explicit_borough_layers;
  }
  return layers;
}

function setup() {
 return function trim(req, res, next) {
   // don't do anything if there are no results or there are non-fallback.* named queries
   // there should never be a mixture of fallback.* and non-fallback.* named queries
   if (_.isUndefined(res.data) || !isFallbackQuery(res.data)) {
     return next();
   }

   const layers = getLayers(req.clean.parsed_text);

   // start at the most granular possible layer.  if there are results at a layer
   // then remove everything not at that layer.
   layers.forEach( (layer) => {
     if (hasRecordsAtLayers(res.data, layer )) {
       res.data = retainRecordsAtLayers(res.data, layer);
     }
   });

   next();
 };
}

module.exports = setup;