/**
 * Module dependencies
 */

var assert = require('assert');
var util = require('util');
var _ = require('@sailshq/lodash');
var flaverr = require('flaverr');
var getModel = require('../../ontology/get-model');
var normalizeConstraint = require('./normalize-constraint');


/**
 * Module constants
 */

// Predicate operators
var PREDICATE_OPERATOR_KINDS = [
  'or',
  'and'
];


/**
 * normalizeWhereClause()
 *
 * Validate and normalize the `where` clause, rejecting any obviously-unsupported
 * usage, and tolerating certain backwards-compatible things.
 *
 * ------------------------------------------------------------------------------------------
 * @param  {Ref} whereClause
 *         A hypothetically well-formed `where` clause from a Waterline criteria.
 *         (i.e. in a "stage 1 query")
 *         > WARNING:
 *         > IN SOME CASES (BUT NOT ALL!), THE PROVIDED VALUE WILL
 *         > UNDERGO DESTRUCTIVE, IN-PLACE CHANGES JUST BY PASSING IT
 *         > IN TO THIS UTILITY.
 *
 * @param {String} modelIdentity
 *        The identity of the model this `where` clause is referring to (e.g. "pet" or "user")
 *        > Useful for looking up the Waterline model and accessing its attribute definitions.
 *
 * @param {Ref} orm
 *        The Waterline ORM instance.
 *        > Useful for accessing the model definitions.
 *
 * @param {Dictionary?} meta
 *        The contents of the `meta` query key, if one was provided.
 *        > Useful for propagating query options to low-level utilities like this one.
 *
 * ------------------------------------------------------------------------------------------
 * @returns {Dictionary}
 *          The successfully-normalized `where` clause, ready for use in a stage 2 query.
 *          > Note that the originally provided `where` clause MAY ALSO HAVE BEEN
 *          > MUTATED IN PLACE!
 * ------------------------------------------------------------------------------------------
 * @throws {Error} If it encounters irrecoverable problems or unsupported usage in
 *                 the provided `where` clause.
 *         @property {String} code
 *                   - E_WHERE_CLAUSE_UNUSABLE
 *
 *
 * @throws {Error} If the `where` clause indicates that it should never match anything.
 *         @property {String} code
 *                   - E_WOULD_RESULT_IN_NOTHING
 *
 *
 * @throws {Error} If anything else unexpected occurs.
 */
module.exports = function normalizeWhereClause(whereClause, modelIdentity, orm, meta) {

  // Look up the Waterline model for this query.
  // > This is so that we can reference the original model definition.
  var WLModel = getModel(modelIdentity, orm);


  //  ┌─┐┬ ┬┌─┐┌─┐┌─┐┬─┐┌┬┐  ╔╦╗╦ ╦╔╦╗╔═╗╔╦╗╔═╗  ╔═╗╦═╗╔═╗╔═╗  ┌┬┐┌─┐┌┬┐┌─┐  ┬┌─┌─┐┬ ┬
  //  └─┐│ │├─┘├─┘│ │├┬┘ │   ║║║║ ║ ║ ╠═╣ ║ ║╣   ╠═╣╠╦╝║ ╦╚═╗  │││├┤  │ ├─┤  ├┴┐├┤ └┬┘
  //  └─┘└─┘┴  ┴  └─┘┴└─ ┴   ╩ ╩╚═╝ ╩ ╩ ╩ ╩ ╚═╝  ╩ ╩╩╚═╚═╝╚═╝  ┴ ┴└─┘ ┴ ┴ ┴  ┴ ┴└─┘ ┴
  // Unless the `mutateArgs` meta key is enabled, deep-clone the entire `where` clause.
  if (!meta || !meta.mutateArgs) {
    whereClause = _.cloneDeep(whereClause);
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // FUTURE: Replace this naive implementation with something better.
    // (This isn't great because it can mess up things like Buffers... which you
    // shouldn't really be using in a `where` clause anyway, but still-- it makes
    // it way harder to figure out what's wrong when folks find themselves in that
    // situation.  It could also affect any weird custom constraints for `type:'ref'`
    // attrs.  And if the current approach were also used in valuesToSet, newRecord,
    // newRecords etc, it would matter even more.)
    //
    // The full list of query keys that need to be carefully checked:
    // • criteria
    // • populates
    // • newRecord
    // • newRecords
    // • valuesToSet
    // • targetRecordIds
    // • associatedIds
    //
    // The solution will probably mean distributing this deep clone behavior out
    // to the various places it's liable to come up.  In reality, this will be
    // more performant anyway, since we won't be unnecessarily cloning things like
    // big JSON values, etc.
    //
    // The important thing is that this should do shallow clones of deeply-nested
    // control structures like top level query key dictionaries, criteria clauses,
    // predicates/constraints/modifiers in `where` clauses, etc.
    //
    // > And remember:  Don't deep-clone functions.
    // > Note that, weirdly, it's fine to deep-clone dictionaries/arrays
    // > that contain nested functions (they just don't get cloned-- they're
    // > the same reference).  But if you try to deep-clone a function at the
    // > top level, it gets messed up.
    // >
    // > More background on this: https://trello.com/c/VLXPvyN5
    // > (Note that this behavior maintains backwards compatibility with Waterline <=0.12.)
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  }//ﬁ


  //  ╔╦╗╔═╗╔═╗╔═╗╦ ╦╦ ╔╦╗
  //   ║║║╣ ╠╣ ╠═╣║ ║║  ║
  //  ═╩╝╚═╝╚  ╩ ╩╚═╝╩═╝╩
  // If no `where` clause was provided, give it a default value.
  if (_.isUndefined(whereClause)) {
    whereClause = {};
  }//>-

  //  ╔═╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╦╔╗ ╦╦  ╦╔╦╗╦ ╦       (COMPATIBILITY)
  //  ║  ║ ║║║║╠═╝╠═╣ ║ ║╠╩╗║║  ║ ║ ╚╦╝
  //  ╚═╝╚═╝╩ ╩╩  ╩ ╩ ╩ ╩╚═╝╩╩═╝╩ ╩  ╩
  // COMPATIBILITY
  // If where is `null`, turn it into an empty dictionary.
  if (_.isNull(whereClause)) {

    console.warn();
    console.warn(
      'Deprecated: In previous versions of Waterline, the specified `where` '+'\n'+
      'clause (`null`) would match ALL records in this model (`'+modelIdentity+'`).  '+'\n'+
      'So for compatibility, that\'s what just happened.  If that is what you intended '+'\n'+
      'then, in the future, please pass in `{}` instead, or simply omit the `where` '+'\n'+
      'clause altogether-- both of which are more explicit and future-proof ways of '+'\n'+
      'doing the same thing.\n'+
      '\n'+
      '> Warning: This backwards compatibility will be removed\n'+
      '> in a future release of Sails/Waterline.  If this usage\n'+
      '> is left unchanged, then queries like this one will eventually \n'+
      '> fail with an error.'
    );
    console.warn();

    whereClause = {};
  }//>-



  //  ┌┐┌┌─┐┬─┐┌┬┐┌─┐┬  ┬┌─┐┌─┐  ╔═╗╦╔═╦  ╦  ┌─┐┬─┐  ╦╔╗╔  ┌─┐┬ ┬┌─┐┬─┐┌┬┐┬ ┬┌─┐┌┐┌┌┬┐
  //  ││││ │├┬┘│││├─┤│  │┌─┘├┤   ╠═╝╠╩╗╚╗╔╝  │ │├┬┘  ║║║║  └─┐├─┤│ │├┬┘ │ ├─┤├─┤│││ ││
  //  ┘└┘└─┘┴└─┴ ┴┴ ┴┴─┘┴└─┘└─┘  ╩  ╩ ╩ ╚╝   └─┘┴└─  ╩╝╚╝  └─┘┴ ┴└─┘┴└─ ┴ ┴ ┴┴ ┴┘└┘─┴┘
  //  ┌─    ┌─┐┌┬┐  ┌┬┐┬ ┬┌─┐  ┌┬┐┌─┐┌─┐  ┬  ┌─┐┬  ┬┌─┐┬    ┌─┐┌─┐  ╦ ╦╦ ╦╔═╗╦═╗╔═╗    ─┐
  //  │───  ├─┤ │    │ ├─┤├┤    │ │ │├─┘  │  ├┤ └┐┌┘├┤ │    │ │├┤   ║║║╠═╣║╣ ╠╦╝║╣   ───│
  //  └─    ┴ ┴ ┴    ┴ ┴ ┴└─┘   ┴ └─┘┴    ┴─┘└─┘ └┘ └─┘┴─┘  └─┘└    ╚╩╝╩ ╩╚═╝╩╚═╚═╝    ─┘
  //
  // If the `where` clause itself is an array, string, or number, then we'll
  // be able to understand it as a primary key, or as an array of primary key values.
  //
  // ```
  // where: [...]
  // ```
  //
  // ```
  // where: 'bar'
  // ```
  //
  // ```
  // where: 29
  // ```
  if (_.isArray(whereClause) || _.isNumber(whereClause) || _.isString(whereClause)) {

    var topLvlPkValuesOrPkValueInWhere = whereClause;

    // So expand that into the beginnings of a proper `where` dictionary.
    // (This will be further normalized throughout the rest of this file--
    //  this is just enough to get us to where we're working with a dictionary.)
    whereClause = {};
    whereClause[WLModel.primaryKey] = topLvlPkValuesOrPkValueInWhere;

  }//>-



  //  ┬  ┬┌─┐┬─┐┬┌─┐┬ ┬  ┌┬┐┬ ┬┌─┐┌┬┐  ┌┬┐┬ ┬┌─┐  ╦ ╦╦ ╦╔═╗╦═╗╔═╗  ┌─┐┬  ┌─┐┬ ┬┌─┐┌─┐
  //  └┐┌┘├┤ ├┬┘│├┤ └┬┘   │ ├─┤├─┤ │    │ ├─┤├┤   ║║║╠═╣║╣ ╠╦╝║╣   │  │  ├─┤│ │└─┐├┤
  //   └┘ └─┘┴└─┴└   ┴    ┴ ┴ ┴┴ ┴ ┴    ┴ ┴ ┴└─┘  ╚╩╝╩ ╩╚═╝╩╚═╚═╝  └─┘┴─┘┴ ┴└─┘└─┘└─┘
  //  ┬┌─┐  ┌┐┌┌─┐┬ ┬  ┌─┐  ╔╦╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗╦═╗╦ ╦
  //  │└─┐  ││││ ││││  ├─┤   ║║║║   ║ ║║ ║║║║╠═╣╠╦╝╚╦╝
  //  ┴└─┘  ┘└┘└─┘└┴┘  ┴ ┴  ═╩╝╩╚═╝ ╩ ╩╚═╝╝╚╝╩ ╩╩╚═ ╩
  // At this point, the `where` clause should be a dictionary.
  if (!_.isObject(whereClause) || _.isArray(whereClause) || _.isFunction(whereClause)) {
    throw flaverr('E_WHERE_CLAUSE_UNUSABLE', new Error(
      'If provided, `where` clause should be a dictionary.  But instead, got: '+
      util.inspect(whereClause, {depth:5})+''
    ));
  }//-•




  //    ██╗    ██████╗ ███████╗ ██████╗██╗   ██╗██████╗ ███████╗██╗ ██████╗ ███╗   ██╗    ██╗
  //   ██╔╝    ██╔══██╗██╔════╝██╔════╝██║   ██║██╔══██╗██╔════╝██║██╔═══██╗████╗  ██║    ╚██╗
  //  ██╔╝     ██████╔╝█████╗  ██║     ██║   ██║██████╔╝███████╗██║██║   ██║██╔██╗ ██║     ╚██╗
  //  ╚██╗     ██╔══██╗██╔══╝  ██║     ██║   ██║██╔══██╗╚════██║██║██║   ██║██║╚██╗██║     ██╔╝
  //   ╚██╗    ██║  ██║███████╗╚██████╗╚██████╔╝██║  ██║███████║██║╚██████╔╝██║ ╚████║    ██╔╝
  //    ╚═╝    ╚═╝  ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝  ╚═══╝    ╚═╝
  //      ███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗
  //      ╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝
  //  ┌┬┐┌─┐  ┌┬┐┬ ┬┌─┐  ╦═╗╔═╗╔═╗╦ ╦╦═╗╔═╗╦╦  ╦╔═╗  ╔═╗╦═╗╔═╗╦ ╦╦
  //   │││ │   │ ├─┤├┤   ╠╦╝║╣ ║  ║ ║╠╦╝╚═╗║╚╗╔╝║╣   ║  ╠╦╝╠═╣║║║║
  //  ─┴┘└─┘   ┴ ┴ ┴└─┘  ╩╚═╚═╝╚═╝╚═╝╩╚═╚═╝╩ ╚╝ ╚═╝  ╚═╝╩╚═╩ ╩╚╩╝╩═╝
  // Recursively iterate through the provided `where` clause, starting with the top level.
  //
  // > Note that we mutate the `where` clause IN PLACE here-- there is no return value
  // > from this self-calling recursive function.
  //
  //
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // EDGE CASES INVOLVING "VOID" AND "UNIVERSAL"
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // In order to provide the simplest possible interface for adapter implementors
  // (i.e. fully-normalized stage 2&3 queries, w/ the fewest possible numbers of
  // extraneous symbols) we need to handle certain edge cases in a special way.
  //
  // For example, an empty array of conjuncts/disjuncts is not EXACTLY invalid, per se.
  // Instead, what exactly it means depends on the circumstances:
  //
  // |-------------------------|-------------------|-------------------|-------------------|
  // | ||     Parent branch => | Parent is `and`   | Parent is `or`    | No parent         |
  // | \/ This branch          | (conjunct, `∩`)   | (disjunct, `∪`)   | (at top level)    |
  // |-------------------------|===================|===================|===================|
  // |                         |                   |                   |                   |
  // | `{ and: [] }`           | Rip out this      | Throw to indicate | Replace entire    |
  // | `{ ??: { nin: [] } }`   | conjunct.         | parent will match | `where` clause    |
  // | `{}`                    |                   | EVERYTHING.       | with `{}`.        |
  // |                         |                   |                   |                   |
  // | Ξ  : universal          | x ∩ Ξ = x         | x ∪ Ξ = Ξ         | Ξ                 |
  // | ("matches everything")  | <<identity>>      | <<annihilator>>   | (universal)       |
  // |-------------------------|-------------------|-------------------|-------------------|
  // |                         |                   |                   |                   |
  // | `{ or:  [] }`           | Throw to indicate | Rip out this      | Throw E_WOULD_... |
  // | `{ ??: { in: [] } }`    | parent will NEVER | disjunct.         | RESULT_IN_NOTHING |
  // |                         | match anything.   |                   | error to indicate |
  // |                         |                   |                   | that this query   |
  // |                         |                   |                   | is a no-op.       |
  // |                         |                   |                   |                   |
  // | Ø : void                | x ∩ Ø = Ø         | x ∪ Ø = x         | Ø                 |
  // | ("matches nothing")     | <<annihilator>>   | <<identity>>      | (void)            |
  // |-------------------------|-------------------|-------------------|-------------------|
  //
  // > For deeper reference, here are the boolean monotone laws:
  // > https://en.wikipedia.org/wiki/Boolean_algebra#Monotone_laws
  // >
  // > See also the "identity" and "domination" laws from fundamental set algebra:
  // > (the latter of which is roughly equivalent to the "annihilator" law from boolean algebra)
  // > https://en.wikipedia.org/wiki/Algebra_of_sets#Fundamentals
  //
  // Anyways, as it turns out, this is exactly how it should work for ANY universal/void
  // branch in the `where` clause.  So as you can see below, we use this strategy to handle
  // various edge cases involving `and`, `or`, `nin`, `in`, and `{}`.
  //
  // **There are some particular bits to notice in the implementation below:**
  // • If removing this conjunct/disjunct would cause the parent predicate operator to have
  //   NO items, then we recursively apply the normalization all the way back up the tree,
  //   until we hit the root.  That's taken care of above (in the place in the code where we
  //   make the recursive call).
  // • If there is no containing conjunct/disjunct (i.e. because we're at the top-level),
  //   then we'll either throw a E_WOULD_RESULT_IN_NOTHING error (if this is an `or`),
  //   or revert the criteria to `{}` so it matches everything (if this is an `and`).
  //   That gets taken care of below.
  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  //
  // With that, let's begin.
  try {

    // Initially invoke our self-calling, recursive function.
    (function _recursiveStep(branch, recursionDepth, parent, indexInParent){

      var MAX_RECURSION_DEPTH = 25;
      if (recursionDepth > MAX_RECURSION_DEPTH) {
        throw flaverr('E_WHERE_CLAUSE_UNUSABLE', new Error('This `where` clause seems to have a circular reference. Aborted automatically after reaching maximum recursion depth ('+MAX_RECURSION_DEPTH+').'));
      }//-•

      //-• IWMIH, we know that `branch` is a dictionary.
      // But that's about all we can trust.
      //
      // > In an already-fully-normalized `where` clause, we'd know that this dictionary
      // > would ALWAYS be a valid conjunct/disjunct.  But since we're doing the normalizing
      // > here, we have to be more forgiving-- both for usability and backwards-compatibility.


      //  ╔═╗╔╦╗╦═╗╦╔═╗  ╦╔═╔═╗╦ ╦╔═╗  ┬ ┬┬┌┬┐┬ ┬  ╦ ╦╔╗╔╔╦╗╔═╗╔═╗╦╔╗╔╔═╗╔╦╗  ┬─┐┬ ┬┌─┐
      //  ╚═╗ ║ ╠╦╝║╠═╝  ╠╩╗║╣ ╚╦╝╚═╗  ││││ │ ├─┤  ║ ║║║║ ║║║╣ ╠╣ ║║║║║╣  ║║  ├┬┘├─┤└─┐
      //  ╚═╝ ╩ ╩╚═╩╩    ╩ ╩╚═╝ ╩ ╚═╝  └┴┘┴ ┴ ┴ ┴  ╚═╝╝╚╝═╩╝╚═╝╚  ╩╝╚╝╚═╝═╩╝  ┴└─┴ ┴└─┘
      // Strip out any keys with undefined values.
      _.each(_.keys(branch), function (key){
        if (_.isUndefined(branch[key])) {
          delete branch[key];
        }
      });


      // Now count the keys.
      var origBranchKeys = _.keys(branch);

      //  ┬ ┬┌─┐┌┐┌┌┬┐┬  ┌─┐  ╔═╗╔╦╗╔═╗╔╦╗╦ ╦  ┬ ┬┬ ┬┌─┐┬─┐┌─┐  ┌─┐┬  ┌─┐┬ ┬┌─┐┌─┐
      //  ├─┤├─┤│││ │││  ├┤   ║╣ ║║║╠═╝ ║ ╚╦╝  │││├─┤├┤ ├┬┘├┤   │  │  ├─┤│ │└─┐├┤
      //  ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘  ╚═╝╩ ╩╩   ╩  ╩   └┴┘┴ ┴└─┘┴└─└─┘  └─┘┴─┘┴ ┴└─┘└─┘└─┘
      // If there are 0 keys...
      if (origBranchKeys.length === 0) {

        // An empty dictionary means that this branch is universal (Ξ).
        // That is, that it would match _everything_.
        //
        // So we'll throw a special signal indicating that to the previous recursive step.
        // (or to our `catch` statement below, if we're at the top level-- i.e. an empty `where` clause.)
        //
        // > Note that we could just `return` instead of throwing if we're at the top level.
        // > That's because it's a no-op and throwing would achieve exactly the same thing.
        // > Since this is a hot code path, we might consider doing that as a future optimization.
        throw flaverr('E_UNIVERSAL', new Error('`{}` would match everything'));

      }//-•



      //  ╔═╗╦═╗╔═╗╔═╗╔╦╗╦ ╦╦═╗╔═╗  ┌┐ ┬─┐┌─┐┌┐┌┌─┐┬ ┬
      //  ╠╣ ╠╦╝╠═╣║   ║ ║ ║╠╦╝║╣   ├┴┐├┬┘├─┤││││  ├─┤
      //  ╚  ╩╚═╩ ╩╚═╝ ╩ ╚═╝╩╚═╚═╝  └─┘┴└─┴ ┴┘└┘└─┘┴ ┴
      // Now we may need to denormalize (or "fracture") this branch.
      // This is to normalize it such that it has only one key, with a
      // predicate operator on the RHS.
      //
      // For example:
      // ```
      // {
      //   name: 'foo',
      //   age: 2323,
      //   createdAt: 23238828382,
      //   hobby: { contains: 'ball' }
      // }
      // ```
      // ==>
      // ```
      // {
      //   and: [
      //     { name: 'foo' },
      //     { age: 2323 }
      //     { createdAt: 23238828382 },
      //     { hobby: { contains: 'ball' } }
      //   ]
      // }
      // ```
      if (origBranchKeys.length > 1) {

        // Loop over each key in the original branch and build an array of conjuncts.
        var fracturedConjuncts = [];
        _.each(origBranchKeys, function (origKey){


          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          // For now, we don't log this warning.
          // It is still convenient to write criteria this way, and it's still
          // a bit too soon to determine whether we should be recommending a change.
          //
          // > NOTE: There are two sides to this, for sure.
          // > If you like this usage the way it is, please let @mikermcneil or
          // > @particlebanana know.
          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
          // // Check if this is a key for a predicate operator.
          // // e.g. the `or` in this example:
          // // ```
          // // {
          // //   age: { '>': 28 },
          // //   or: [
          // //     { name: { 'startsWith': 'Jon' } },
          // //     { name: { 'endsWith': 'Snow' } }
          // //   ]
          // // }
          // // ```
          // //
          // // If so, we'll still automatically map it.
          // // But also log a deprecation warning here, since it's more explicit to avoid
          // // using predicates within multi-facet shorthand (i.e. could have used an additional
          // // `and` predicate instead.)
          // //
          // if (_.contains(PREDICATE_OPERATOR_KINDS, origKey)) {
          //
          //   // console.warn();
          //   // console.warn(
          //   //   'Deprecated: Within a `where` clause, it tends to be better (and certainly '+'\n'+
          //   //   'more explicit) to use an `and` predicate when you need to group together '+'\n'+
          //   //   'constraints side by side with additional predicates (like `or`).  This was '+'\n'+
          //   //   'automatically normalized on your behalf for compatibility\'s sake, but please '+'\n'+
          //   //   'consider changing your usage in the future:'+'\n'+
          //   //   '```'+'\n'+
          //   //   util.inspect(branch, {depth:5})+'\n'+
          //   //   '```'+'\n'+
          //   //   '> Warning: This backwards compatibility may be removed\n'+
          //   //   '> in a future release of Sails/Waterline.  If this usage\n'+
          //   //   '> is left unchanged, then queries like this one may eventually \n'+
          //   //   '> fail with an error.'
          //   // );
          //   // console.warn();
          //
          // }//>-
          // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

          var conjunct = {};
          conjunct[origKey] = branch[origKey];
          fracturedConjuncts.push(conjunct);

        });//</ _.each() >


        // Change this branch so that it now contains a predicate consisting of
        // the conjuncts we built above.
        //
        // > Note that we change the branch in-place (on its parent) AND update
        // > our `branch` variable.  If the branch has no parent (i.e. top lvl),
        // > then we change the actual variable we're using instead.  This will
        // > change the return value from this utility.
        branch = {
          and: fracturedConjuncts
        };

        if (parent) {
          parent[indexInParent] = branch;
        }
        else {
          whereClause = branch;
        }

      }//>-  </if this branch was a multi-key dictionary>


      // --• IWMIH, then we know there is EXACTLY one key.
      var soleBranchKey = _.keys(branch)[0];


      //  ┬ ┬┌─┐┌┐┌┌┬┐┬  ┌─┐  ╔═╗╔═╗╔╗╔╔═╗╔╦╗╦═╗╔═╗╦╔╗╔╔╦╗
      //  ├─┤├─┤│││ │││  ├┤   ║  ║ ║║║║╚═╗ ║ ╠╦╝╠═╣║║║║ ║
      //  ┴ ┴┴ ┴┘└┘─┴┘┴─┘└─┘  ╚═╝╚═╝╝╚╝╚═╝ ╩ ╩╚═╩ ╩╩╝╚╝ ╩
      // If this key is NOT a predicate (`and`/`or`)...
      if (!_.contains(PREDICATE_OPERATOR_KINDS, soleBranchKey)) {

        // ...then we know we're dealing with a constraint.

        //  ╔═╗╦═╗╔═╗╔═╗╔╦╗╦ ╦╦═╗╔═╗  ┌─┐┌─┐┌┬┐┌─┐┬  ┌─┐─┐ ┬  ┌─┐┌─┐┌┐┌┌─┐┌┬┐┬─┐┌─┐┬┌┐┌┌┬┐
        //  ╠╣ ╠╦╝╠═╣║   ║ ║ ║╠╦╝║╣   │  │ ││││├─┘│  ├┤ ┌┴┬┘  │  │ ││││└─┐ │ ├┬┘├─┤││││ │
        //  ╚  ╩╚═╩ ╩╚═╝ ╩ ╚═╝╩╚═╚═╝  └─┘└─┘┴ ┴┴  ┴─┘└─┘┴ └─  └─┘└─┘┘└┘└─┘ ┴ ┴└─┴ ┴┴┘└┘ ┴
        //  ┌─  ┬┌─┐  ┬┌┬┐  ┬┌─┐  ┌┬┐┬ ┬┬ ┌┬┐┬   ┬┌─┌─┐┬ ┬  ─┐
        //  │   │├┤   │ │   │└─┐  ││││ ││  │ │───├┴┐├┤ └┬┘   │
        //  └─  ┴└    ┴ ┴   ┴└─┘  ┴ ┴└─┘┴─┘┴ ┴   ┴ ┴└─┘ ┴   ─┘
        // Before proceeding, we may need to fracture the RHS of this key.
        // (if it is a complex constraint w/ multiple keys-- like a "range" constraint)
        //
        // > This is to normalize it such that every complex constraint ONLY EVER has one key.
        // > In order to do this, we may need to reach up to our highest ancestral predicate.
        var isComplexConstraint = _.isObject(branch[soleBranchKey]) && !_.isArray(branch[soleBranchKey]) && !_.isFunction(branch[soleBranchKey]);
        // If this complex constraint has multiple keys...
        if (isComplexConstraint && _.keys(branch[soleBranchKey]).length > 1){

          // Then fracture it before proceeding.
          var complexConstraint = branch[soleBranchKey];

          // Loop over each modifier in the complex constraint and build an array of conjuncts.
          var fracturedModifierConjuncts = [];
          _.each(complexConstraint, function (modifier, modifierKind){
            var conjunct = {};
            conjunct[soleBranchKey] = {};
            conjunct[soleBranchKey][modifierKind] = modifier;
            fracturedModifierConjuncts.push(conjunct);
          });//</ _.each() key in the complex constraint>

          // Change this branch so that it now contains a predicate consisting of
          // the new conjuncts we just built for these modifiers.
          //
          // > Note that we change the branch in-place (on its parent) AND update
          // > our `branch` variable.  If the branch has no parent (i.e. top lvl),
          // > then we change the actual variable we're using instead.  This will
          // > change the return value from this utility.
          //
          branch = {
            and: fracturedModifierConjuncts
          };

          if (parent) {
            parent[indexInParent] = branch;
          }
          else {
            whereClause = branch;
          }

          // > Also note that we update the sole branch key variable.
          soleBranchKey = _.keys(branch)[0];

          // Now, since we know our branch is a predicate, we'll continue on.
          // (see predicate handling code below)

        }
        // Otherwise, we can go ahead and normalize the constraint, then bail.
        else {
          //  ╔╗╔╔═╗╦═╗╔╦╗╔═╗╦  ╦╔═╗╔═╗  ╔═╗╔═╗╔╗╔╔═╗╔╦╗╦═╗╔═╗╦╔╗╔╔╦╗
          //  ║║║║ ║╠╦╝║║║╠═╣║  ║╔═╝║╣   ║  ║ ║║║║╚═╗ ║ ╠╦╝╠═╣║║║║ ║
          //  ╝╚╝╚═╝╩╚═╩ ╩╩ ╩╩═╝╩╚═╝╚═╝  ╚═╝╚═╝╝╚╝╚═╝ ╩ ╩╚═╩ ╩╩╝╚╝ ╩
          // Normalize the constraint itself.
          // (note that this checks the RHS, but it also checks the key aka constraint target -- i.e. the attr name)
          try {
            branch[soleBranchKey] = normalizeConstraint(branch[soleBranchKey], soleBranchKey, modelIdentity, orm, meta);
          } catch (e) {
            switch (e.code) {

              case 'E_CONSTRAINT_NOT_USABLE':
                throw flaverr('E_WHERE_CLAUSE_UNUSABLE', new Error(
                  'Could not filter by `'+soleBranchKey+'`: '+ e.message
                ));

              case 'E_CONSTRAINT_WOULD_MATCH_EVERYTHING':
                throw flaverr('E_UNIVERSAL', e);

              case 'E_CONSTRAINT_WOULD_MATCH_NOTHING':
                throw flaverr('E_VOID', e);

              default:
                throw e;

            }
          }//</catch>

          // Then bail early.
          return;

        }//</ else (i.e. case where the constraint was good to go w/o needing any fracturing)>

      }//</ if the sole branch key is NOT a predicate >



      // >-• IWMIH, then we know that this branch's sole key is a predicate (`and`/`or`).
      // (If it isn't, then our code above has a bug.)
      assert(soleBranchKey === 'and' || soleBranchKey === 'or', 'Should never have made it here if the sole branch key is not `and` or `or`!');



      //  ██╗  ██╗ █████╗ ███╗   ██╗██████╗ ██╗     ███████╗
      //  ██║  ██║██╔══██╗████╗  ██║██╔══██╗██║     ██╔════╝
      //  ███████║███████║██╔██╗ ██║██║  ██║██║     █████╗
      //  ██╔══██║██╔══██║██║╚██╗██║██║  ██║██║     ██╔══╝
      //  ██║  ██║██║  ██║██║ ╚████║██████╔╝███████╗███████╗
      //  ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═════╝ ╚══════╝╚══════╝
      //
      //  ██████╗ ██████╗ ███████╗██████╗ ██╗ ██████╗ █████╗ ████████╗███████╗
      //  ██╔══██╗██╔══██╗██╔════╝██╔══██╗██║██╔════╝██╔══██╗╚══██╔══╝██╔════╝
      //  ██████╔╝██████╔╝█████╗  ██║  ██║██║██║     ███████║   ██║   █████╗
      //  ██╔═══╝ ██╔══██╗██╔══╝  ██║  ██║██║██║     ██╔══██║   ██║   ██╔══╝
      //  ██║     ██║  ██║███████╗██████╔╝██║╚██████╗██║  ██║   ██║   ███████╗
      //  ╚═╝     ╚═╝  ╚═╝╚══════╝╚═════╝ ╚═╝ ╚═════╝╚═╝  ╚═╝   ╚═╝   ╚══════╝
      //
      //
      // ```                      ```
      // {                        {
      //   or: [...]                and: [...]
      // }                        }
      // ```                      ```

      var conjunctsOrDisjuncts = branch[soleBranchKey];


      // RHS of a predicate must always be an array.
      if (!_.isArray(conjunctsOrDisjuncts)) {
        throw flaverr('E_WHERE_CLAUSE_UNUSABLE', new Error('Expected an array at `'+soleBranchKey+'`, but instead got: '+util.inspect(conjunctsOrDisjuncts,{depth: 5})+'\n(`and`/`or` should always be provided with an array on the right-hand side.)'));
      }//-•


      // Now loop over each conjunct or disjunct within this AND/OR predicate.
      // Along the way, track any that will need to be trimmed out.
      var indexesToRemove = [];
      _.each(conjunctsOrDisjuncts, function (conjunctOrDisjunct, i){

        // If conjunct/disjunct is `undefined`, trim it out and bail to the next one.
        if (conjunctsOrDisjuncts[i] === undefined) {
          indexesToRemove.push(i);
          return;
        }//•

        // Check that each conjunct/disjunct is a plain dictionary, no funny business.
        if (!_.isObject(conjunctOrDisjunct) || _.isArray(conjunctOrDisjunct) || _.isFunction(conjunctOrDisjunct)) {
          throw flaverr('E_WHERE_CLAUSE_UNUSABLE', new Error('Expected each item within an `and`/`or` predicate\'s array to be a dictionary (plain JavaScript object).  But instead, got: `'+util.inspect(conjunctOrDisjunct,{depth: 5})+'`'));
        }

        // Recursive call
        try {
          _recursiveStep(conjunctOrDisjunct, recursionDepth+1, conjunctsOrDisjuncts, i);
        } catch (e) {
          switch (e.code) {

            // If this conjunct or disjunct is universal (Ξ)...
            case 'E_UNIVERSAL':

              // If this item is a disjunct, then annihilate our branch by throwing this error
              // on up for the previous recursive step to take care of.
              // ```
              // x ∪ Ξ = Ξ
              // ```
              if (soleBranchKey === 'or') {
                throw e;
              }//-•

              // Otherwise, rip it out of the array.
              // ```
              // x ∩ Ξ = x
              // ```
              indexesToRemove.push(i);
              break;

            // If this conjunct or disjunct is void (Ø)...
            case 'E_VOID':

              // If this item is a conjunct, then annihilate our branch by throwing this error
              // on up for the previous recursive step to take care of.
              // ```
              // x ∩ Ø = Ø
              // ```
              if (soleBranchKey === 'and') {
                throw e;
              }//-•

              // Otherwise, rip it out of the array.
              // ```
              // x ∪ Ø = x
              // ```
              indexesToRemove.push(i);
              break;

            default:
              throw e;
          }
        }//</catch>

      });//</each conjunct or disjunct inside of predicate operator>


      // If any conjuncts/disjuncts were scheduled for removal above,
      // go ahead and take care of that now.
      if (indexesToRemove.length > 0) {
        for (var i = 0; i < indexesToRemove.length; i++) {
          var indexToRemove = indexesToRemove[i] - i;
          conjunctsOrDisjuncts.splice(indexToRemove, 1);
        }//</for>
      }//>-


      // If the array is NOT EMPTY, then this is the normal case, and we can go ahead and bail.
      if (conjunctsOrDisjuncts.length > 0) {
        return;
      }//-•



      // Otherwise, the predicate array is empty (e.g. `{ or: [] }` / `{ and: [] }`)
      //
      // For our purposes here, we just need to worry about signaling either "universal" or "void".
      // (see table above for more information).

      // If this branch is universal (i.e. matches everything / `{and: []}`)
      // ```
      // Ξ
      // ```
      if (soleBranchKey === 'and') {
        throw flaverr('E_UNIVERSAL', new Error('`{and: []}` with an empty array would match everything.'));
      }
      // Otherwise, this branch is void (i.e. matches nothing / `{or: []}`)
      // ```
      // Ø
      // ```
      else {
        throw flaverr('E_VOID', new Error('`{or: []}` with an empty array would match nothing.'));
      }

    })(whereClause, 0, undefined, undefined);
    //</self-invoking recursive function that kicked off our recursion with the `where` clause>

  } catch (e) {
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Note:
    // This `catch` block exists to handle top-level E_UNIVERSAL and E_VOID exceptions.
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    switch (e.code) {

      // If an E_UNIVERSAL exception made it all the way up here, then we know that
      // this `where` clause should match EVERYTHING.  So we set it to `{}`.
      case 'E_UNIVERSAL':
        whereClause = {};
        break;

      // If an E_VOID exception made it all the way up here, then we know that
      // this `where` clause would match NOTHING.  So we throw `E_WOULD_RESULT_IN_NOTHING`
      // to pass that message along.
      case 'E_VOID':
        throw flaverr('E_WOULD_RESULT_IN_NOTHING', new Error('Would match nothing'));

      default:
        throw e;
    }
  }//</catch>

  //          ███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗
  //          ╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝
  //
  //    ██╗    ██╗    ██████╗ ███████╗ ██████╗██╗   ██╗██████╗ ███████╗██╗ ██████╗ ███╗   ██╗    ██╗
  //   ██╔╝   ██╔╝    ██╔══██╗██╔════╝██╔════╝██║   ██║██╔══██╗██╔════╝██║██╔═══██╗████╗  ██║    ╚██╗
  //  ██╔╝   ██╔╝     ██████╔╝█████╗  ██║     ██║   ██║██████╔╝███████╗██║██║   ██║██╔██╗ ██║     ╚██╗
  //  ╚██╗  ██╔╝      ██╔══██╗██╔══╝  ██║     ██║   ██║██╔══██╗╚════██║██║██║   ██║██║╚██╗██║     ██╔╝
  //   ╚██╗██╔╝       ██║  ██║███████╗╚██████╗╚██████╔╝██║  ██║███████║██║╚██████╔╝██║ ╚████║    ██╔╝
  //    ╚═╝╚═╝        ╚═╝  ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝  ╚═══╝    ╚═╝
  //


  // Return the modified `where` clause.
  return whereClause;

};
