text: import merge from "lodash/merge";
  import mergeWith from "lodash/mergeWith";
  import {settings as defaultSettings} from "../config/default";
  import moment from "moment";
  import {normalizeListValues} from "./stuff";
  import {getWidgetForFieldOp} from "./ruleUtils";
  import clone from "clone";
  
  
  export const extendConfig = (config) => {
    //operators, defaultOperator - merge
    //widgetProps (including valueLabel, valuePlaceholder, hideOperator, operatorInlineLabel) - concrete by widget
  
    if (config.__extended) {
      return config;
    }
      
    config.settings = merge({}, defaultSettings, config.settings);
    config._fieldsCntByType = {};
    config._funcsCntByType = {};
  
    config.types = clone(config.types);
    _extendTypesConfig(config.types, config);
  
    config.fields = clone(config.fields);
    config.__fieldNames = {};
    _extendFieldsConfig(config.fields, config);
  
    config.funcs = clone(config.funcs);
    _extendFuncArgsConfig(config.funcs, config);
  
    moment.locale(config.settings.locale.moment);
  
    Object.defineProperty(config, "__extended", {
      enumerable: false,
      writable: false,
      value: true
    });
  
    return config;
  };
  
  function _extendTypesConfig(typesConfig, config) {
    for (let type in typesConfig) {
      let typeConfig = typesConfig[type];
      _extendTypeConfig(type, typeConfig, config);
    }
  }
  
  function _extendTypeConfig(type, typeConfig, config) {
    let operators = null, defaultOperator = null;
    typeConfig.mainWidget = typeConfig.mainWidget || Object.keys(typeConfig.widgets).filter(w => w != "field" && w != "func")[0];
    for (let widget in typeConfig.widgets) {
      let typeWidgetConfig = typeConfig.widgets[widget];
      if (typeWidgetConfig.operators) {
        if (!operators)
          operators = [];
              
        operators = operators.concat(typeWidgetConfig.operators.slice());
      }
      if (typeWidgetConfig.defaultOperator)
        defaultOperator = typeWidgetConfig.defaultOperator;
      if (widget == typeConfig.mainWidget) {
        typeWidgetConfig = merge({}, {widgetProps: typeConfig.mainWidgetProps || {}}, typeWidgetConfig);
      }
      typeConfig.widgets[widget] = typeWidgetConfig;
    }
    if (!typeConfig.valueSources)
      typeConfig.valueSources = Object.keys(config.settings.valueSourcesInfo);
    for (let valueSrc of typeConfig.valueSources) {
      if (valueSrc != "value" && !typeConfig.widgets[valueSrc]) {
        typeConfig.widgets[valueSrc] = {
        };
      }
    }
    if (!typeConfig.operators && operators)
      typeConfig.operators = Array.from(new Set(operators)); //unique
    if (!typeConfig.defaultOperator && defaultOperator)
      typeConfig.defaultOperator = defaultOperator;
  }
  
  function _extendFieldsConfig(subconfig, config, path = []) {
    for (let field in subconfig) {
      _extendFieldConfig(subconfig[field], config, [...path, field]);
      if (subconfig[field].subfields) {
        _extendFieldsConfig(subconfig[field].subfields, config, [...path, field]);
      }
    }
  }
  
  function _extendFuncArgsConfig(subconfig, config) {
    if (!subconfig) return;
    for (let funcKey in subconfig) {
      const funcDef = subconfig[funcKey];
      if (funcDef.returnType) {
        if (!config._funcsCntByType[funcDef.returnType])
          config._funcsCntByType[funcDef.returnType] = 0;
        config._funcsCntByType[funcDef.returnType]++;
      }
      for (let argKey in funcDef.args) {
        _extendFieldConfig(funcDef.args[argKey], config, null, true);
      }
  
      // isOptional can be only in the end
      if (funcDef.args) {
        const argKeys = Object.keys(funcDef.args);
        let tmpIsOptional = true;
        for (const argKey of argKeys.reverse()) {
          const argDef = funcDef.args[argKey];
          if (!tmpIsOptional && argDef.isOptional) {
            delete argDef.isOptional;
          }
          if (!argDef.isOptional)
            tmpIsOptional = false;
        }
      }
  
      if (funcDef.subfields) {
        _extendFuncArgsConfig(funcDef.subfields, config);
      }
    }
  }
  
  function _extendFieldConfig(fieldConfig, config, path = null, isFuncArg = false) {
    let operators = null, defaultOperator = null;
    const typeConfig = config.types[fieldConfig.type];
    const excludeOperators = fieldConfig.excludeOperators || [];
    if (fieldConfig.type != "!struct" && fieldConfig.type != "!group") {
      if (!typeConfig) {
        //console.warn(`No type config for ${fieldConfig.type}`);
        fieldConfig.disabled = true;
        return;
      }
      if (!isFuncArg) {
        if (!config._fieldsCntByType[fieldConfig.type])
          config._fieldsCntByType[fieldConfig.type] = 0;
        config._fieldsCntByType[fieldConfig.type]++;
      }
  
      if (!fieldConfig.widgets)
        fieldConfig.widgets = {};
      if (isFuncArg)
        fieldConfig._isFuncArg = true;
      fieldConfig.mainWidget = fieldConfig.mainWidget || typeConfig.mainWidget;
      fieldConfig.valueSources = fieldConfig.valueSources || typeConfig.valueSources;
      for (let widget in typeConfig.widgets) {
        let fieldWidgetConfig = fieldConfig.widgets[widget] || {};
        const typeWidgetConfig = typeConfig.widgets[widget] || {};
        if (!isFuncArg) {
          //todo: why I've excluded isFuncArg ?
          const shouldIncludeOperators = fieldConfig.preferWidgets && (widget == "field" || fieldConfig.preferWidgets.includes(widget)) || excludeOperators.length > 0;
          if (fieldWidgetConfig.operators) {
            if (!operators)
              operators = [];
            operators = operators.concat(fieldWidgetConfig.operators.filter(o => !excludeOperators.includes(o)));
          } else if (shouldIncludeOperators && typeWidgetConfig.operators) {
            if (!operators)
              operators = [];
            operators = operators.concat(typeWidgetConfig.operators.filter(o => !excludeOperators.includes(o)));
          }
          if (fieldWidgetConfig.defaultOperator)
            defaultOperator = fieldWidgetConfig.defaultOperator;
        }
  
        if (widget == fieldConfig.mainWidget) {
          fieldWidgetConfig = merge({}, {widgetProps: fieldConfig.mainWidgetProps || {}}, fieldWidgetConfig);
        }
        fieldConfig.widgets[widget] = fieldWidgetConfig;
      }
      if (!isFuncArg) {
        if (!fieldConfig.operators && operators)
          fieldConfig.operators = Array.from(new Set(operators));
        if (!fieldConfig.defaultOperator && defaultOperator)
          fieldConfig.defaultOperator = defaultOperator;
      }
  
      const keysToPutInFieldSettings = ["listValues", "allowCustomValues", "validateValue"];
      if (!fieldConfig.fieldSettings)
        fieldConfig.fieldSettings = {};
      for (const k of keysToPutInFieldSettings) {
        if (fieldConfig[k]) {
          fieldConfig.fieldSettings[k] = fieldConfig[k];
          delete fieldConfig[k];
        }
      }
  
      if (fieldConfig.fieldSettings.listValues) {
        fieldConfig.fieldSettings.listValues = normalizeListValues(fieldConfig.fieldSettings.listValues, fieldConfig.type, fieldConfig.fieldSettings);
      }
    }
  
    const computedFieldName = computeFieldName(config, path);
    if (computedFieldName) {
      fieldConfig.fieldName = computedFieldName;
    }
  
    if (path && fieldConfig.fieldName) {
      config.__fieldNames[fieldConfig.fieldName] = path;
    }
  }
  
  export const getFieldRawConfig = (config, field, fieldsKey = "fields", subfieldsKey = "subfields") => {
    if (!field)
      return null;
    const fieldSeparator = config.settings.fieldSeparator;
    //field = normalizeField(config, field);
    const parts = Array.isArray(field) ? field : field.split(fieldSeparator);
    const targetFields = config[fieldsKey];
    if (!targetFields)
      return null;
  
    let fields = targetFields;
    let fieldConfig = null;
    let path = [];
    for (let i = 0 ; i < parts.length ; i++) {
      const part = parts[i];
      path.push(part);
      const pathKey = path.join(fieldSeparator);
      fieldConfig = fields[pathKey];
      if (i < parts.length-1) {
        if (fieldConfig && fieldConfig[subfieldsKey]) {
          fields = fieldConfig[subfieldsKey];
          path = [];
        } else {
          fieldConfig = null;
        }
      }
    }
  
    return fieldConfig;
  };
  
  const computeFieldName = (config, path) => {
    if (!path)
      return null;
    const fieldSeparator = config.settings.fieldSeparator;
    let l = [...path], r = [], f, fConfig;
    while ((f = l.pop()) !== undefined && l.length > 0) {
      r.unshift(f);
      fConfig = getFieldRawConfig(config, l);
      if (fConfig.fieldName) {
        return [fConfig.fieldName, ...r].join(fieldSeparator);
      }
    }
    return null;
  };
  
  export const normalizeField = (config, field) => {
    const fieldSeparator = config.settings.fieldSeparator;
    const fieldStr = Array.isArray(field) ? field.join(fieldSeparator) : field;
    if (config.__fieldNames[fieldStr]) {
      return config.__fieldNames[fieldStr].join(fieldSeparator);
    }
    return fieldStr;
  };
  
  export const getFuncConfig = (config, func) => {
    if (!func)
      return null;
    const funcConfig = getFieldRawConfig(config, func, "funcs", "subfields");
    if (!funcConfig)
      return null; //throw new Error("Can't find func " + func + ", please check your config");
    return funcConfig;
  };
  
  export const getFuncArgConfig = (config, funcKey, argKey) => {
    const funcConfig = getFuncConfig(config, funcKey);
    if (!funcConfig)
      return null; //throw new Error(`Can't find func ${funcKey}, please check your config`);
    const argConfig = funcConfig.args && funcConfig.args[argKey] || null;
    if (!argConfig)
      return null; //throw new Error(`Can't find arg ${argKey} for func ${funcKey}, please check your config`);
  
    //merge, but don't merge operators (rewrite instead)
    const typeConfig = config.types[argConfig.type] || {};
    let ret = mergeWith({}, typeConfig, argConfig || {}, (objValue, srcValue, _key, _object, _source, _stack) => {
      if (Array.isArray(objValue)) {
        return srcValue;
      }
    });
  
    return ret;
  };
  
  export const getFieldConfig = (config, field) => {
    if (!field)
      return null;
    if (typeof field == "object" && !field.func && !!field.type)
      return field;
    if (typeof field == "object" && field.func && field.arg)
      return getFuncArgConfig(config, field.func, field.arg);
    const fieldConfig = getFieldRawConfig(config, field);
    if (!fieldConfig)
      return null; //throw new Error("Can't find field " + field + ", please check your config");
  
    //merge, but don't merge operators (rewrite instead)
    const typeConfig = config.types[fieldConfig.type] || {};
    let ret = mergeWith({}, typeConfig, fieldConfig || {}, (objValue, srcValue, _key, _object, _source, _stack) => {
      if (Array.isArray(objValue)) {
        return srcValue;
      }
    });
  
    return ret;
  };
  
  export const getOperatorConfig = (config, operator, field = null) => {
    if (!operator)
      return null;
    const opConfig = config.operators[operator];
    if (field) {
      const fieldConfig = getFieldConfig(config, field);
      const widget = getWidgetForFieldOp(config, field, operator);
      const widgetConfig = config.widgets[widget] || {};
      const fieldWidgetConfig = (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || {};
      const widgetOpProps = (widgetConfig.opProps || {})[operator];
      const fieldWidgetOpProps = (fieldWidgetConfig.opProps || {})[operator];
      const mergedOpConfig = merge({}, opConfig, widgetOpProps, fieldWidgetOpProps);
      return mergedOpConfig;
    } else {
      return opConfig;
    }
  };
  
  export const getFieldWidgetConfig = (config, field, operator, widget = null, valueSrc = null) => {
    if (!field)
      return null;
    if (!(operator || widget) && valueSrc != "const")
      return null;
    const fieldConfig = getFieldConfig(config, field);
    if (!widget)
      widget = getWidgetForFieldOp(config, field, operator, valueSrc);
    const widgetConfig = config.widgets[widget] || {};
    const fieldWidgetConfig = (fieldConfig && fieldConfig.widgets ? fieldConfig.widgets[widget] : {}) || {};
    const fieldWidgetProps = (fieldWidgetConfig.widgetProps || {});
    const valueFieldSettings = (valueSrc == "value" || !valueSrc) && fieldConfig && fieldConfig.fieldSettings || {}; // useful to take 'validateValue'
    const mergedConfig = merge({}, widgetConfig, fieldWidgetProps, valueFieldSettings);
    return mergedConfig;
  };
  