text: 'use strict';
  
  var utils = require('./utils');
  
  module.exports = function (braces, options) {
    braces.compiler
  
    /**
     * bos
     */
  
    .set('bos', function () {
      if (this.output) return;
      this.ast.queue = isEscaped(this.ast) ? [this.ast.val] : [];
      this.ast.count = 1;
    })
  
    /**
     * Square brackets
     */
  
    .set('bracket', function (node) {
      var close = node.close;
      var open = !node.escaped ? '[' : '\\[';
      var negated = node.negated;
      var inner = node.inner;
  
      inner = inner.replace(/\\(?=[\\\w]|$)/g, '\\\\');
      if (inner === ']-') {
        inner = '\\]\\-';
      }
  
      if (negated && inner.indexOf('.') === -1) {
        inner += '.';
      }
      if (negated && inner.indexOf('/') === -1) {
        inner += '/';
      }
  
      var val = open + negated + inner + close;
      var queue = node.parent.queue;
      var last = utils.arrayify(queue.pop());
  
      queue.push(utils.join(last, val));
      queue.push.apply(queue, []);
    })
  
    /**
     * Brace
     */
  
    .set('brace', function (node) {
      node.queue = isEscaped(node) ? [node.val] : [];
      node.count = 1;
      return this.mapVisit(node.nodes);
    })
  
    /**
     * Open
     */
  
    .set('brace.open', function (node) {
      node.parent.open = node.val;
    })
  
    /**
     * Inner
     */
  
    .set('text', function (node) {
      var queue = node.parent.queue;
      var escaped = node.escaped;
      var segs = [node.val];
  
      if (node.optimize === false) {
        options = utils.extend({}, options, { optimize: false });
      }
  
      if (node.multiplier > 1) {
        node.parent.count *= node.multiplier;
      }
  
      if (options.quantifiers === true && utils.isQuantifier(node.val)) {
        escaped = true;
      } else if (node.val.length > 1) {
        if (isType(node.parent, 'brace') && !isEscaped(node)) {
          var expanded = utils.expand(node.val, options);
          segs = expanded.segs;
  
          if (expanded.isOptimized) {
            node.parent.isOptimized = true;
          }
  
          // if nothing was expanded, we probably have a literal brace
          if (!segs.length) {
            var val = expanded.val || node.val;
            if (options.unescape !== false) {
              // unescape unexpanded brace sequence/set separators
              val = val.replace(/\\([,.])/g, '$1');
              // strip quotes
              val = val.replace(/["'`]/g, '');
            }
  
            segs = [val];
            escaped = true;
          }
        }
      } else if (node.val === ',') {
        if (options.expand) {
          node.parent.queue.push(['']);
          segs = [''];
        } else {
          segs = ['|'];
        }
      } else {
        escaped = true;
      }
  
      if (escaped && isType(node.parent, 'brace')) {
        if (node.parent.nodes.length <= 4 && node.parent.count === 1) {
          node.parent.escaped = true;
        } else if (node.parent.length <= 3) {
          node.parent.escaped = true;
        }
      }
  
      if (!hasQueue(node.parent)) {
        node.parent.queue = segs;
        return;
      }
  
      var last = utils.arrayify(queue.pop());
      if (node.parent.count > 1 && options.expand) {
        last = multiply(last, node.parent.count);
        node.parent.count = 1;
      }
  
      queue.push(utils.join(utils.flatten(last), segs.shift()));
      queue.push.apply(queue, segs);
    })
  
    /**
     * Close
     */
  
    .set('brace.close', function (node) {
      var queue = node.parent.queue;
      var prev = node.parent.parent;
      var last = prev.queue.pop();
      var open = node.parent.open;
      var close = node.val;
  
      if (open && close && isOptimized(node, options)) {
        open = '(';
        close = ')';
      }
  
      // if a close brace exists, and the previous segment is one character
      // don't wrap the result in braces or parens
      var ele = utils.last(queue);
      if (node.parent.count > 1 && options.expand) {
        ele = multiply(queue.pop(), node.parent.count);
        node.parent.count = 1;
        queue.push(ele);
      }
  
      if (close && typeof ele === 'string' && ele.length === 1) {
        open = '';
        close = '';
      }
  
      if ((isLiteralBrace(node, options) || noInner(node)) && !node.parent.hasEmpty) {
        queue.push(utils.join(open, queue.pop() || ''));
        queue = utils.flatten(utils.join(queue, close));
      }
  
      if (typeof last === 'undefined') {
        prev.queue = [queue];
      } else {
        prev.queue.push(utils.flatten(utils.join(last, queue)));
      }
    })
  
    /**
     * eos
     */
  
    .set('eos', function (node) {
      if (this.input) return;
  
      if (options.optimize !== false) {
        this.output = utils.last(utils.flatten(this.ast.queue));
      } else if (Array.isArray(utils.last(this.ast.queue))) {
        this.output = utils.flatten(this.ast.queue.pop());
      } else {
        this.output = utils.flatten(this.ast.queue);
      }
  
      if (node.parent.count > 1 && options.expand) {
        this.output = multiply(this.output, node.parent.count);
      }
  
      this.output = utils.arrayify(this.output);
      this.ast.queue = [];
    });
  };
  
  /**
   * Multiply the segments in the current brace level
   */
  
  function multiply(queue, n, options) {
    return utils.flatten(utils.repeat(utils.arrayify(queue), n));
  }
  
  /**
   * Return true if `node` is escaped
   */
  
  function isEscaped(node) {
    return node.escaped === true;
  }
  
  /**
   * Returns true if regex parens should be used for sets. If the parent `type`
   * is not `brace`, then we're on a root node, which means we should never
   * expand segments and open/close braces should be `{}` (since this indicates
   * a brace is missing from the set)
   */
  
  function isOptimized(node, options) {
    if (node.parent.isOptimized) return true;
    return isType(node.parent, 'brace') && !isEscaped(node.parent) && options.expand !== true;
  }
  
  /**
   * Returns true if the value in `node` should be wrapped in a literal brace.
   * @return {Boolean}
   */
  
  function isLiteralBrace(node, options) {
    return isEscaped(node.parent) || options.optimize !== false;
  }
  
  /**
   * Returns true if the given `node` does not have an inner value.
   * @return {Boolean}
   */
  
  function noInner(node, type) {
    if (node.parent.queue.length === 1) {
      return true;
    }
    var nodes = node.parent.nodes;
    return nodes.length === 3 && isType(nodes[0], 'brace.open') && !isType(nodes[1], 'text') && isType(nodes[2], 'brace.close');
  }
  
  /**
   * Returns true if the given `node` is the given `type`
   * @return {Boolean}
   */
  
  function isType(node, type) {
    return typeof node !== 'undefined' && node.type === type;
  }
  
  /**
   * Returns true if the given `node` has a non-empty queue.
   * @return {Boolean}
   */
  
  function hasQueue(node) {
    return Array.isArray(node.queue) && node.queue.length;
  }