import Backbone from 'backbone';
import {
  isEmpty,
  isArray,
  isString,
  each,
  includes,
  extend,
  flatten
} from 'underscore';

let Component;

const getIdsToKeep = prev => {
  const pr = prev || [];
  return pr.map(comp => comp.getId());
};

export default Backbone.Collection.extend({
  initialize(models, opt = {}) {
    this.opt = opt;
    this.listenTo(this, 'add', this.onAdd);
    this.listenTo(this, 'remove', this.removeChildren);
    this.listenTo(this, 'reset', this.resetChildren);
    this.config = opt.config;
    this.em = opt.em;
    this.domc = opt.domc;
  },

  resetChildren(models, opts = {}) {
    const coll = this;
    const prev = opts.previousModels || [];
    const toRemove = prev.filter(prev => !models.get(prev.cid));
    opts.keepIds = getIdsToKeep(prev);
    toRemove.forEach(md => this.removeChildren(md, coll, opts));
    models.each(model => this.onAdd(model));
  },

  removeChildren(removed, coll, opts = {}) {
    // Removing a parent component can cause this function
    // to be called with an already removed child element
    if (!removed) {
      return;
    }

    const { domc, em } = this;
    const allByID = domc ? domc.allById() : {};

    if (!opts.temporary) {
      // Remove the component from the global list
      const id = removed.getId();
      const sels = em.get('SelectorManager').getAll();
      const rules = em.get('CssComposer').getAll();
      const canRemoveStyle = (opts.keepIds || []).indexOf(id) < 0;
      delete allByID[id];

      // Remove all component related styles
      const rulesRemoved = canRemoveStyle
        ? rules.remove(
            rules.filter(r => r.getSelectors().getFullString() === `#${id}`)
          )
        : [];

      // Clean selectors
      sels.remove(rulesRemoved.map(rule => rule.getSelectors().at(0)));

      if (!removed.opt.temporary) {
        const cm = em.get('Commands');
        const hasSign = removed.get('style-signature');
        const optStyle = { target: removed };
        hasSign && cm.run('core:component-style-clear', optStyle);
        removed.removed();
        em.trigger('component:remove', removed);
      }

      const inner = removed.components();
      inner.forEach(it => this.removeChildren(it, coll, opts));
      // removed.empty(opts);
    }

    // Remove stuff registered in DomComponents.handleChanges
    const inner = removed.components();
    const um = em.get('UndoManager');
    em.stopListening(inner);
    em.stopListening(removed);
    em.stopListening(removed.get('classes'));
    um.remove(removed);
    um.remove(inner);
  },

  model(attrs, options) {
    const { opt } = options.collection;
    const { em } = opt;
    let model;
    const df = em.get('DomComponents').componentTypes;
    options.em = em;
    options.config = opt.config;
    options.componentTypes = df;
    options.domc = opt.domc;

    for (let it = 0; it < df.length; it++) {
      const dfId = df[it].id;
      if (dfId == attrs.type) {
        model = df[it].model;
        break;
      }
    }

    // If no model found, get the default one
    if (!model) {
      model = df[df.length - 1].model;
      em &&
        attrs.type &&
        em.logWarning(`Component type '${attrs.type}' not found`, {
          attrs,
          options
        });
    }

    return new model(attrs, options);
  },

  parseString(value, opt = {}) {
    const { em } = this;
    const { domc } = this.opt;
    const cssc = em.get('CssComposer');
    const parsed = em.get('Parser').parseHtml(value);
    // We need this to avoid duplicate IDs
    if (!Component) Component = require('./Component').default;
    Component.checkId(parsed.html, parsed.css, domc.componentsById, opt);

    if (parsed.css && cssc && !opt.temporary) {
      cssc.addCollection(parsed.css, {
        ...opt,
        extend: 1
      });
    }

    return parsed.html;
  },

  add(models, opt = {}) {
    opt.keepIds = getIdsToKeep(opt.previousModels);

    if (isString(models)) {
      models = this.parseString(models, opt);
    } else if (isArray(models)) {
      models.forEach((item, index) => {
        if (isString(item)) {
          const nodes = this.parseString(item, opt);
          models[index] = isArray(nodes) && !nodes.length ? null : nodes;
        }
      });
    }

    const isMult = isArray(models);
    models = (isMult ? models : [models])
      .filter(i => i)
      .map(model => this.processDef(model));
    models = isMult ? flatten(models, 1) : models[0];

    return Backbone.Collection.prototype.add.apply(this, [models, opt]);
  },

  /**
   * Process component definition.
   */
  processDef(mdl) {
    // Avoid processing Models
    if (mdl.cid && mdl.ccid) return mdl;
    const { em, config = {} } = this;
    const { processor } = config;
    let model = mdl;

    if (processor) {
      model = { ...model }; // Avoid 'Cannot delete property ...'
      const modelPr = processor(model);
      if (modelPr) {
        each(model, (val, key) => delete model[key]);
        extend(model, modelPr);
      }
    }

    // React JSX preset
    if (model.$$typeof && typeof model.props == 'object') {
      model = { ...model };
      model.props = { ...model.props };
      const domc = em.get('DomComponents');
      const parser = em.get('Parser');
      const { parserHtml } = parser;

      each(model, (value, key) => {
        if (!includes(['props', 'type'], key)) delete model[key];
      });
      const { props } = model;
      const comps = props.children;
      delete props.children;
      delete model.props;
      const res = parserHtml.splitPropsFromAttr(props);
      model.attributes = res.attrs;

      if (comps) {
        model.components = comps;
      }
      if (!model.type) {
        model.type = 'textnode';
      } else if (!domc.getType(model.type)) {
        model.tagName = model.type;
        delete model.type;
      }

      extend(model, res.props);
    }

    return model;
  },

  onAdd(model, c, opts = {}) {
    const { domc, em } = this;
    const style = model.getStyle();
    const avoidInline = em && em.getConfig('avoidInlineStyle');
    domc && domc.Component.ensureInList(model);

    if (
      !isEmpty(style) &&
      !avoidInline &&
      em &&
      em.get &&
      em.getConfig('forceClass') &&
      !opts.temporary
    ) {
      const name = model.cid;
      const rule = em.get('CssComposer').setClassRule(name, style);
      model.setStyle({});
      model.addClass(name);
    }
  }
});

@@ -758,40 +758,125 @@
     
-opts.keepIds = getIdsToKeep(prev
+const newIds = models.map(i =%3E i.getId());%0A    opts.keepIds = getIdsToKeep(prev).filter(pr =%3E newIds.indexOf(pr) %3E= 0
 );%0A 

Clean removed styles from text component