import through from 'through';
import tokenize from 'html-tokenize';
import pipe from 'multipipe';

var createExtractCritical = function createExtractCritical(cache) {
  return function (html) {
    // parse out ids from html
    // reconstruct css/rules/cache to pass
    var RGX = new RegExp(cache.key + "-([a-zA-Z0-9-_]+)", 'gm');
    var o = {
      html: html,
      ids: [],
      css: ''
    };
    var match;
    var ids = {};

    while ((match = RGX.exec(html)) !== null) {
      // $FlowFixMe
      if (ids[match[1]] === undefined) {
        // $FlowFixMe
        ids[match[1]] = true;
      }
    }

    o.ids = Object.keys(cache.inserted).filter(function (id) {
      if ((ids[id] !== undefined || cache.registered[cache.key + "-" + id] === undefined) && cache.inserted[id] !== true) {
        o.css += cache.inserted[id];
        return true;
      }
    });
    return o;
  };
};

var createExtractCriticalToChunks = function createExtractCriticalToChunks(cache) {
  return function (html) {
    // parse out ids from html
    // reconstruct css/rules/cache to pass
    var RGX = new RegExp(cache.key + "-([a-zA-Z0-9-_]+)", 'gm');
    var o = {
      html: html,
      styles: []
    };
    var match;
    var ids = {};

    while ((match = RGX.exec(html)) !== null) {
      // $FlowFixMe
      if (ids[match[1]] === undefined) {
        // $FlowFixMe
        ids[match[1]] = true;
      }
    }

    var regularCssIds = [];
    var regularCss = '';
    Object.keys(cache.inserted).forEach(function (id) {
      if ((ids[id] !== undefined || cache.registered[cache.key + "-" + id] === undefined) && cache.inserted[id] !== true) {
        if (cache.registered[cache.key + "-" + id]) {
          // regular css can be added in one style tag
          regularCssIds.push(id); // $FlowFixMe

          regularCss += cache.inserted[id];
        } else {
          // each global styles require a new entry so it can be independently flushed
          o.styles.push({
            key: cache.key + "-global",
            ids: [id],
            css: cache.inserted[id]
          });
        }
      }
    }); // make sure that regular css is added after the global styles

    o.styles.push({
      key: cache.key,
      ids: regularCssIds,
      css: regularCss
    });
    return o;
  };
};

function generateStyleTag(cssKey, ids, styles, nonceString) {
  return "<style data-emotion=\"" + cssKey + " " + ids + "\"" + nonceString + ">" + styles + "</style>";
}

var createRenderStylesToString = function createRenderStylesToString(cache, nonceString) {
  return function (html) {
    var inserted = cache.inserted,
        cssKey = cache.key,
        registered = cache.registered;
    var regex = new RegExp("<|" + cssKey + "-([a-zA-Z0-9-_]+)", 'gm');
    var seen = {};
    var result = '';
    var globalIds = '';
    var globalStyles = '';

    for (var id in inserted) {
      // eslint-disable-next-line no-prototype-builtins
      if (inserted.hasOwnProperty(id)) {
        var style = inserted[id];
        var key = cssKey + "-" + id;

        if (style !== true && registered[key] === undefined) {
          globalStyles += style;
          globalIds += " " + id;
        }
      }
    }

    if (globalStyles !== '') {
      result = generateStyleTag(cssKey, globalIds.substring(1), globalStyles, nonceString);
    }

    var ids = '';
    var styles = '';
    var lastInsertionPoint = 0;
    var match;

    while ((match = regex.exec(html)) !== null) {
      // $FlowFixMe
      if (match[0] === '<') {
        if (ids !== '') {
          result += generateStyleTag(cssKey, ids.substring(1), styles, nonceString);
          ids = '';
          styles = '';
        } // $FlowFixMe


        result += html.substring(lastInsertionPoint, match.index); // $FlowFixMe

        lastInsertionPoint = match.index;
        continue;
      } // $FlowFixMe


      var _id = match[1];
      var _style = inserted[_id];

      if (_style === true || _style === undefined || seen[_id]) {
        continue;
      }

      seen[_id] = true;
      styles += _style;
      ids += " " + _id;
    }

    result += html.substring(lastInsertionPoint);
    return result;
  };
};

var createRenderStylesToNodeStream = function createRenderStylesToNodeStream(cache, nonceString) {
  return function () {
    var insed = {};
    var tokenStream = tokenize();
    var inlineStream = through(function write(thing) {
      var type = thing[0],
          data = thing[1];

      if (type === 'open') {
        var css = '';
        var ids = {};
        var match;
        var fragment = data.toString();
        var regex = new RegExp(cache.key + "-([a-zA-Z0-9-_]+)", 'gm');

        while ((match = regex.exec(fragment)) !== null) {
          if (match !== null && insed[match[1]] === undefined) {
            ids[match[1]] = true;
          }
        }

        Object.keys(cache.inserted).forEach(function (id) {
          if (cache.inserted[id] !== true && insed[id] === undefined && (ids[id] === true || cache.registered[cache.key + "-" + id] === undefined && (ids[id] = true))) {
            insed[id] = true; // $FlowFixMe flow thinks emotion.caches.inserted[id] can be true even though it's checked earlier

            css += cache.inserted[id];
          }
        });

        if (css !== '') {
          this.queue("<style data-emotion=\"" + cache.key + " " + Object.keys(ids).join(' ') + "\"" + nonceString + ">" + css + "</style>");
        }
      }

      this.queue(data);
    }, function end() {
      this.queue(null);
    });
    return pipe(tokenStream, inlineStream);
  };
};

var createConstructStyleTagsFromChunks = function createConstructStyleTagsFromChunks(cache, nonceString) {
  return function (criticalData) {
    var styleTagsString = '';
    criticalData.styles.forEach(function (item) {
      styleTagsString += generateStyleTag(item.key, item.ids.join(' '), item.css, nonceString);
    });
    return styleTagsString;
  };
};

function createEmotionServer (cache) {
  if (cache.compat !== true) {
    // is this good? should we do this automatically?
    // this is only for when using the new apis (not emotion or create-emotion)
    cache.compat = true;
  }

  var nonceString = cache.nonce !== undefined ? " nonce=\"" + cache.nonce + "\"" : '';
  return {
    extractCritical: createExtractCritical(cache),
    extractCriticalToChunks: createExtractCriticalToChunks(cache),
    renderStylesToString: createRenderStylesToString(cache, nonceString),
    renderStylesToNodeStream: createRenderStylesToNodeStream(cache, nonceString),
    constructStyleTagsFromChunks: createConstructStyleTagsFromChunks(cache, nonceString)
  };
}

export { createEmotionServer as default };
