import { keys, isUndefined } from 'underscore';
import Property from './PropertyComposite';
import Layers from './Layers';

const VALUES_REG = /,(?![^\(]*\))/;
const PARTS_REG = /\s(?![^(]*\))/;

export default Property.extend({
  defaults: {
    ...Property.prototype.defaults,
    // Array of layers (which contain properties)
    layers: [],

    // The separator used to join layer values
    layerSeparator: ', ',

    // Prepend new layers in the list
    prepend: 0,

    // Layer preview
    preview: 0,

    // Parse single layer value string
    parseLayer: null,

    // Current selected layer
    selectedLayer: null,
  },

  initialize(props = {}, opts = {}) {
    Property.callParentInit(Property, this, props, opts);
    const layers = this.get('layers');
    const layersColl = new Layers(layers);
    layersColl.property = this;
    layersColl.properties = this.get('properties');
    this.set('layers', layersColl, { silent: true });
    this.on('change:selectedLayer', this.__upSelected);
    Property.callInit(this, props, opts);
  },

  __upSelected() {
    if (!this.__hasCustom()) return;
    const sm = this.em.get('StyleManager');
    sm.__trgEv(sm.events.layerSelect, { property: this });
  },

  _up(props, opts = {}) {
    const { __layers = [], ...rest } = props;
    const layers = __layers.map(values => ({ values }));
    this.getLayers().reset(layers);
    console.log('_up from stack', this.get('property'), { layers, rest, opts, currValue: this.getFullValue() });
    return Property.prototype._up.call(this, rest, opts);
  },

  __parseValue(value) {
    const result = this.parseValue(value);
    result.__layers = value
      .split(VALUES_REG)
      .map(v => v.trim())
      .map(v => this.__parseLayer(v))
      .filter(Boolean);

    return result;
  },

  __parseLayer(value) {
    const parseFn = this.get('parseLayer');
    const values = value.split(PARTS_REG);
    return parseFn ? parseFn({ value, values }) : values;
  },

  addLayer(props = {}) {
    return this.get('layers').push({ properties: [] });
  },

  /**
   * Select layer
   * @param {[Layer]} layer
   */
  selectLayer(layer) {
    return this.set('selectedLayer', layer);
  },

  /**
   * Get selected layer
   * @returns {[Layer] | null}
   */
  getSelectLayer() {
    return this.get('selectedLayer');
  },

  /**
   * Get style object from layer values
   * @param {[Layer]} layer
   */
  getStyleFromLayer(layer) {
    const sep = this.get('separator');
    const values = layer.getValues();
    const result = this.getProperties().map(prop => {
      const name = prop.getName();
      const val = values[name];
      const value = isUndefined(val) ? prop.getDefaultValue() : val;
      return { name, value };
    });

    return this.get('detached')
      ? result.reduce((acc, item) => {
          acc[item.name] = item.value;
          return acc;
        }, {})
      : {
          [this.getName()]: result.map(r => r.value).join(sep),
        };
  },

  __getFullValue() {
    if (this.get('detached')) return '';
    const name = this.getName();

    return this.getLayers()
      .map(l => this.getStyleFromLayer(l))
      .map(s => s[name])
      .filter(Boolean)
      .map(v => v?.trim())
      .join(this.get('layerSeparator'));
  },

  getLayers() {
    return this.get('layers');
  },

  getCurrentLayer() {
    return this.getLayers().filter(layer => layer.get('active'))[0];
  },

  getFullValue() {
    return this.get('detached') ? '' : this.get('layers').getFullValue();
  },

  getValueFromStyle(styles = {}) {
    const layers = this.getLayers().getLayersFromStyle(styles);
    return new Layers(layers).getFullValue();
  },

  clearValue() {
    this.getLayers().reset();
    return Property.prototype.clearValue.apply(this, arguments);
  },

  getValueFromTarget(target) {
    const { detached, property, properties } = this.attributes;
    const style = target.getStyle();
    const validStyles = {};

    properties.forEach(prop => {
      const name = prop.get('property');
      const value = style[name];
      if (value) validStyles[name] = value;
    });

    return !detached ? style[property] : keys(validStyles).length ? validStyles : '';
  },

  /**
   * This method allows to customize layers returned from the target
   * @param  {Object} target
   * @return {Array} Should return an array of layers
   * @example
   * // return example
   * [
   *  {
   *    properties: [
   *      { property: 'width', ... }
   *      { property: 'height', ... }
   *    ]
   *  }
   * ]
   */
  getLayersFromTarget(target) {
    return;
  },
});

@@ -1145,24 +1145,362 @@
 eManager');%0A
+    const selected = this.getSelectLayer();%0A    const values = selected?.getValues();%0A%0A    // Update properties by layer value%0A    values &&%0A      this.getProperties().forEach(prop =%3E %7B%0A        const name = prop.getName();%0A        const value = values%5Bname%5D;%0A        !isUndefined(value) && prop.upValue(value, %7B __up: true %7D);%0A      %7D);%0A%0A
     sm.__trg

Update properties on layer change