import * as _ from 'lodash';

function simpleAccessor(lens: string, def: string = '') {
  return {
    get: (li: any) => {
      let val = _.get<any>(li, lens, def);
      return val;
    },
    set: (li: any, value: string) => {
      let lexItem = _.cloneDeep(li);
      let ret = _.set<any>(lexItem, lens, value);
      return ret;
    }
  };
}

function simpleAttrAccessor(attrPred: any) {
  let { attribVal, attribKey, pred, lens } = attrPred;
  let def = (value: any) => ({ _: value, $: { [attribKey]: attribVal } });
  return {
    get: (li: any) => {
      let allProps = _.get<any>(li, lens, def(''));
      let prop = _.filter<any>(allProps, (m) => {
        return pred(m);
      });
      let mcls = _.get<any>(prop, '[0]._', '');
      return mcls;
    },
    set: (li: any, value: string) => {
      let lexItem = _.cloneDeep(li);
      let allProps = _.get<any>(lexItem, lens, []);
      if (allProps.length > 0) {
        let keepProps = _.filter<any>(allProps, (m) => {
          return !pred(m);
        });
        _.set(lexItem, lens, _.concat(keepProps, [def(value)]));
        // if (prop.length > 0) {
        //   _.set(prop, '[0]._', value);
        // } else {
        //
        // }
      } else {
        _.set(lexItem, lens, [def(value)]);
      }
      return lexItem;
    }
  };
}

function listAttrAccessor(attrPred: any) {
  let { pred, lens } = attrPred;
  return {
    get: (li: any) => {
      let def: any = [];
      let morphProps = _.get<any>(li, lens, def);
      let morphExps = _.filter<any>(morphProps, (m) => {
        return pred(m);
      });
      let mEs = morphExps.map((me) => {
        return _.get<any>(me, '_', me);
      });
      return mEs;
    },
    set: (li: any, value: any) => {
      let lexItem = _.cloneDeep(li);
      let allProps = _.get<any>(lexItem, lens, []);
      if (allProps.length > 0) {
        let keepProps = _.filter<any>(allProps, (m) => {
          return !pred(m);
        });
        _.set(lexItem, lens, _.concat(keepProps, value));
      } else {
        _.set(lexItem, lens, value);
      }
      return lexItem;
    }
  };
}

function propListAttrAccessor(attrPred: any) {
  let { attribKey, pred, lens } = attrPred;
  return {
    get: (li: any) => {
      let def: any = [];
      let morphProps = _.get<any>(li, lens, def);
      let morphExps = _.filter<any>(morphProps, (m) => {
        return pred(m);
      });
      let mEs = morphExps.map((me) => {
        let value = _.get<any>(me, '_', me);
        let key = _.get<any>(me, '$.' + attribKey, '');
        return { value, key };
      });
      return mEs;
    },
    set: (li: any, value: any) => {
      let lexItem = _.cloneDeep(li);
      let allProps = _.get<any>(lexItem, lens, []);
      let setValue = value.map((v: any) => {
        let ret = {};
        _.set(ret, '$.form', v.key);
        _.set(ret, '_', v.value);
        return ret;
      });
      if (allProps.length > 0) {
        let keepProps = _.filter<any>(allProps, (m) => {
          return !pred(m);
        });
        _.set(lexItem, lens, _.concat(keepProps, setValue));
      } else {
        _.set(lexItem, lens, setValue);
      }
      return lexItem;
    }
  };
}

const attrPredGen = (lens: string, key: string, val: string, comp: any) => {
  const pred = (m: any) => comp(_.get<any>(m, '$.' + key, ''), val);
  return { lens, attribKey: key, attribVal: val, pred };
};

const fieldMetaMap = {
  label: {
    type: 'text',
    ...simpleAccessor('label[0]'),
  },
  unl: { type: 'text', ...simpleAccessor('unl[0]'), },
  synset: { type: 'text', ...simpleAccessor('lexprops[0].wnsynset[0]'), },
  guid: { type: 'text', ...simpleAccessor('guid[0]'), },
  pos: { type: 'select', ...simpleAccessor('pos[0]'), },
  image: { type: 'preview', ...simpleAccessor('image[0]'), },
  relations: { type: 'text', ...simpleAccessor('relations[0]'), },
  frame: {
    type: 'select',
    ...simpleAttrAccessor(attrPredGen(
      'syntacticprops[0].property',
      'id',
      'frame',
      _.isEqual
    )),
  },
  cat: {
    type: 'select',
    ...simpleAttrAccessor(attrPredGen(
      'uiprops[0].property',
      'id',
      'cat',
      _.isEqual
    )),
  },
  subcat: {
    type: 'select',
    ...simpleAttrAccessor(attrPredGen(
      'uiprops[0].property',
      'id',
      'subcat',
      _.isEqual
    )),
  },
  morphclass: {
    type: 'select',
    ...simpleAttrAccessor(attrPredGen(
      'lexprops[0].morphology[0].morph',
      'form',
      'morphclass',
      _.isEqual
    ))
  },
  morphexceptions: {
    type: 'proplist',
    ...propListAttrAccessor(attrPredGen(
      'lexprops[0].morphology[0].morph',
      'form',
      'morphclass',
      _.negate(_.isEqual)
    ))
  },
  stats: { type: 'text', ...simpleAccessor('stats[0].property[0]._'), },
  lang: {
    type: 'select',
    options: ['en', 'es'], ...simpleAccessor('$.id', 'en'),
  },
  syntacticprops: {
    type: 'list',
    ...listAttrAccessor(attrPredGen(
      'syntacticprops[0].property',
      'id',
      'frame',
      _.negate(_.isEqual)
    ))
  },
  groups: {
    type: 'list',
    ...listAttrAccessor(attrPredGen(
      'groups[0].property',
      'id',
      'frame',
      _.negate(_.isEqual)
    ))
  },
};

function getSelectOptions(field: string, entries: any) {
  let allOpts = entries.map((q: any) => {
    return fieldMetaMap[field].get(q);
  });
  switch (fieldMetaMap[field].type) {
    case 'select':
      return _.uniq(allOpts);
    case 'list':
      return _.uniq(_.flatten(allOpts));
    case 'proplist':
      return _.uniq(_.map(_.flatten(allOpts), (v) => _.get<any>(v, 'key')));
    default:
      return [];
  }
}

export const xmlToEntries = (xmlData: any) => {
  let allEntries = _.chain(xmlData)
    .get<any>('document.lexicon[0].item')
    .flatMap((o: any) => _.chain(o)
      .get<any>('entry')
      .map((p: any) => _.chain(p)
        .get<any>('lang[0]')
        .set('guid[0]', o.$.guid)
        .value())
      .value()
    )
    .value();
  let langReducer = ((result: any, q: any) => {
    let lang = fieldMetaMap.lang.get(q);
    (result[lang] || (result[lang] = [])).push(q);
    return result;
  });
  let langEntries = _.reduce(allEntries, langReducer, {});
  let langs = _.keys(langEntries);
  let selectableFields = ['select', 'list', 'proplist'];
  let selectFields = _.fromPairs(langs.map((lang) => {
    let langOpts = _.fromPairs(_.keys(fieldMetaMap).filter((s) => {
      return _.includes(selectableFields, fieldMetaMap[s].type);
    }).map((s) => {
      let entries = _.get<any>(langEntries, lang, 'en');
      // let allOpts = entries.map((q: any) => {
      //   return fieldMetaMap[s].get(q);
      // });
      // let select = _.isEqual(fieldMetaMap[s].type, 'select');
      // let selectOptions = select ? _.uniq(allOpts) : _.uniq(_.flatten(allOpts));
      return [s, getSelectOptions(s, entries)];
    }));
    return [lang, langOpts];
  }));
  return ({
    allEntries, selectFields, fieldMetaMap
  });
};
