const AT_RULE_NESTED = 'At-rule nested in pseudo selector';

const DEFAULT_CONFIG = {
  prefix: '.', // e.g.: enforce css classes
  delimiter: '_',
  salt: '',
  hashSelector: false,
  appendHash: true,
};

const getMergedConfig = (config, defaults) => {
  const currentConfig = {};
  Object.keys(defaults).forEach(key => {
    currentConfig[key] = config.hasOwnProperty(key) ? config[key] : defaults[key];
  });
  return currentConfig;
};

const createProperty = (property, value) => `${property}:${value};`;
const createRuleSet = (selector, block) => (block !== '' ? `${selector}{${block}}` : '');
const convertCamelCase = input => input.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
const isPseudoSelector = property => property[0] === ':';
const isAtRule = property => property[0] === '@';

const renderProperties = (selector, atRuleNotAllowed) => (
  Object.keys(selector).map(property => {
    if (atRuleNotAllowed && isAtRule(property)) {
      throw new Error(AT_RULE_NESTED);
    }
    return createProperty(
      convertCamelCase(property),
      selector[property]
    );
  })
);

const renderPseudoSelectors = (pseudoSelectors, element) => (
  Object.keys(pseudoSelectors).map(selector => createRuleSet(
    element + selector,
    renderProperties(pseudoSelectors[selector], true)
  ))
);

const renderAtRules = (atRules, selector, config, selectorsMap) => (
  Object.keys(atRules).map(rule => createRuleSet(
    rule,
    render({ [selector]: atRules[rule] }, config, selectorsMap).style
  ))
);

const prepareBlock = block => {
  const pseudoSelectors = {};
  const atRules = {};
  const properties = {};

  Object.keys(block).forEach(property => {
    if (isPseudoSelector(property)) {
      pseudoSelectors[property] = block[property];
    } else if (isAtRule(property)) {
      atRules[property] = block[property];
    } else {
      properties[property] = block[property];
    }
  });

  return {
    properties,
    pseudoSelectors,
    atRules,
  };
};

const createDJB2 = str => {
  let i = str.length;
  let hash = 5381;
  while (i) hash = hash * 33 ^ str.charCodeAt(--i);
  return hash >>> 0;
};

const renderSelector = (selector, config, selectorsMap) => {
  const { prefix, hashSelector, appendHash, delimiter, salt } = config;
  const hashed = hashSelector ? createDJB2(selector + salt) : selector;
  const appended = appendHash ? `${hashed}${delimiter}${createDJB2(hashed + salt)}` : hashed;
  const delimited = `${delimiter}${appended}`;
  selectorsMap[selector] = delimited;
  return `${prefix}${delimited}`;
};

function render(obj, config, selectorsMap) {
  const style = Object.keys(obj).map(selector => {
    const { properties, pseudoSelectors, atRules } = prepareBlock(obj[selector]);
    const renderedSelector = renderSelector(selector, config, selectorsMap);
    return (
      createRuleSet(renderedSelector, renderProperties(properties).join('')) +
      renderPseudoSelectors(pseudoSelectors, renderedSelector).join('') +
      renderAtRules(atRules, selector, config, selectorsMap)
    );
  }).join('');

  return {
    style,
    selectors: selectorsMap,
  };
}

const create = (baseConfig = {}) => {
  const mergedBaseConfig = getMergedConfig(baseConfig, DEFAULT_CONFIG);
  const styles = [];

  return {
    add: (styleObj, config = {}) => {
      const currentConfig = getMergedConfig(config, mergedBaseConfig);
      const { style, selectors } = render(styleObj, currentConfig, {});
      styles.push(style);
      return selectors;
    },
    render: () => styles.join(''),
  };
};

module.exports = {
  create,
};

@@ -687,116 +687,8 @@
 e();
-%0Aconst isPseudoSelector = property =%3E property%5B0%5D === ':';%0Aconst isAtRule = property =%3E property%5B0%5D === '@';
 %0A%0Aco
@@ -814,34 +814,35 @@
 owed && 
-isAtRule(property)
+property%5B0%5D === '@'
 ) %7B%0A    
@@ -1447,59 +1447,35 @@
 nst 
-pseudoSelectors = %7B%7D;%0A  const atRules = %7B%7D;%0A  const
+types = %7B ':': %7B%7D, '@': %7B%7D,
  pro
@@ -1481,23 +1481,23 @@
 operties
- =
+:
  %7B%7D
+ %7D
 ;
-%0A
 %0A  Objec
@@ -1540,202 +1540,75 @@
     
-if (isPseudoSelector(property)) %7B%0A      pseudoSelectors%5Bproperty%5D = block%5Bproperty%5D;%0A    %7D else if (isAtRule(property)) %7B%0A      atRules%5Bproperty%5D = block%5Bproperty%5D;%0A    %7D else %7B%0A      properties
+const current = types%5Bproperty%5B0%5D%5D %7C%7C types.properties;%0A    current
 %5Bpro
@@ -1637,21 +1637,14 @@
 y%5D;%0A
-    %7D%0A
   %7D);%0A
-%0A
   re
@@ -1652,63 +1652,13 @@
 urn 
-%7B%0A    properties,%0A    pseudoSelectors,%0A    atRules,%0A  %7D
+types
 ;%0A%7D;
@@ -2337,48 +2337,13 @@
 nst 
-%7B properties, pseudoSelectors, atRules %7D
+block
  = p
@@ -2515,16 +2515,22 @@
 perties(
+block.
 properti
@@ -2573,31 +2573,26 @@
 lectors(
-pseudoSelectors
+block%5B':'%5D
 , render
@@ -2634,23 +2634,26 @@
 AtRules(
-atRules
+block%5B'@'%5D
 , select

Refactor prepare block