import _ from 'lodash';
import { getContent, clone, getContentList } from 'store/utils/helper';
import { config } from 'config/general';
import qs from 'query-string';
import unflatten from 'unflatten';
import uniqid from 'uniqid';

export const fieldsToList = (fields: Record<string, any> | any[]) => {
  if (typeof fields === 'object') {
    const list = [];

    const keys = Object.keys(fields);
    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      list.push({
        [key]: fields[key],
      });
    }
    return list;
  }
  return fields;
};

export const handleVarInString = (
  object: Record<string, any>,
  title: string,
  otherOptions: Record<string, any>,
) => {
  let result = title;

  const lg = otherOptions.lg;

  if (typeof title === 'string' && title.includes('{')) {
    const splittedTitle = _.words(title, /\{(.*?)\}/g || otherOptions.regex);

    splittedTitle.forEach(element => {
      const variable = element
        .replace('{', '')
        .replace('}', '')
        .split(':');

      // handle list
      let val = _.get(object, variable[0]) || _.get(object, variable[1]) || '...';

      if (lg && val?.[lg]) val = val[lg];

      if (otherOptions && lg && otherOptions.content) {
        const content = otherOptions.content;
        if (variable.length > 1 && content && lg) {
          val =
            getContent(content, `${variable[1]}.${val}`, lg) ||
            getContent(content, `${variable[0]}.${val}`, lg) ||
            getContentList(content, variable[1], val, lg) ||
            getContentList(content, variable[0], val, lg) ||
            val;
        }
      }
      if (val && result) {
        result = result.replace(element, val);
      }
    });
  }

  return result;
};

export const getPageTitle = (modelOptions: {}, data, otherOptions) => {
  const pageType = otherOptions.type;

  const title = modelOptions.title;

  let name;

  if (pageType === 'detail') name = modelOptions.titleDetail;
  if (pageType === 'update') name = modelOptions.titleUpdate || 'Update';
  if (pageType === 'create') name = modelOptions.titleCreate || 'Add';

  let pageTitle;

  if (title && name) {
    pageTitle = `${title} - ${name}`;
  } else {
    pageTitle = title || name;
  }

  const result = handleVarInString(data, pageTitle, otherOptions);

  return result;
};

export const getLinkWithVariables = (link: string, data: Record<string, any>) => {
  let result = link.split('/');

  result.forEach(element => {
    if (element.includes(':')) {
      const index = result.indexOf(element);
      let variable = element.replace(':', '');
      variable = _.get(data, variable);

      result[index] = variable;
    }
  });

  result = result.join('/');

  return result;
};

/*
CMS helpers
*/
const computePageCompletion = (item, stats) => {
  if (!item) return false;
  const locked = item.locked;
  const lockedValue = item.lockedValue;
  let toComplete = item.toComplete;
  if (!item.type) return false;
  if (item.type === 'object' || item.type === 'Object') {
    toComplete = false;
    if (!item.content) return false;
    const values = Object.values(item.content);
    for (let i = 0; i < values.length; i++) {
      const elem = values[i];
      if (computePageCompletion(elem, stats)) {
        toComplete = true;
      }
    }
  }
  if (item.type === 'Array' || item.type === 'array') {
    toComplete = false;
    if (!item.content) return false;
    for (let i = 0; i < item.content.length; i++) {
      const elem = item.content[i];
      if (computePageCompletion(elem, stats)) {
        toComplete = true;
      }
    }
  } else if (stats) {
    stats.base++;
    if (toComplete) stats.toComplete++;
  }
  item.toComplete = toComplete;
  item.locked = locked || false;
  item.lockedValue = lockedValue || false;
  return toComplete;
};

export const handleCompletion = pageContent => {
  const stats = {
    base: 0,
    toComplete: 0,
  };

  const items = Object.values(pageContent);
  items.map(item => computePageCompletion(item, stats));

  return stats;
};

export const handlePagesCompletion = pages => {
  pages.map(page => {
    page.stats = handleCompletion(page.content);
    page.toComplete = page.stats.toComplete > 0;

    return page.toComplete;
  });
};

/*
CMS helpers
*/
const setContentNew = item => {
  if (!item) return;

  if (!item.type) return;
  if (item.type === 'object' || item.type === 'Object') {
    if (!item.content) return;
    const values = Object.values(item.content);
    for (let i = 0; i < values.length; i++) {
      const elem = values[i];
      setContentNew(elem);
    }
  }
  if (item.type === 'Array' || item.type === 'array') {
    if (!item.content) return;
    for (let i = 0; i < item.content.length; i++) {
      const elem = item.content[i];
      setContentNew(elem);
    }
  }
  item.toComplete = true;
};
export const setAllContentNew = pageContent => {
  const items = Object.values(pageContent);
  items.map(item => setContentNew(item));
  return pageContent;
};

/**
 * @param {boolean} options.forceClose - force item to be closed
 * @param {string} options.filter - filter to apply (hide or not elements)
 * @param {boolean} options.parentVisible - is parent visible or not
 */
const handlePageFilter = (current, options, data) => {
  options = options || {};
  const keys = Object.keys(current);

  let hasOneChildVisible = false;

  for (let i = 0; i < keys.length; i += 1) {
    const key = keys[i];
    const elem = current[key];

    if (typeof elem !== 'object') continue;

    const isOpen = options.forceClose ? false : elem.open || false;
    elem.open = isOpen;

    // No hide if no filter
    if (!options.filter) {
      elem.hide = false;
    } else {
      elem.hide = !options.parentVisible;
    }
    const regex = new RegExp(options.filter || '', 'i');

    const label = elem.label && elem.label.toString();
    elem.labelMatch = false;
    elem.valueMatch = [];
    // Check elem
    if ((key && key.match(regex)) || (label && label.match(regex))) {
      hasOneChildVisible = true;
      elem.hide = false;

      if (options.filter) {
        elem.labelMatch = true;
        data.match++;
      }
    }
    if (elem.content && (elem.type === 'Object' || elem.type === 'Array')) {
      // Check if child are visible

      if (
        handlePageFilter(
          elem.content,
          {
            ...options,
            parentVisible: !elem.hide,
          },
          data,
        )
      ) {
        hasOneChildVisible = true;
        elem.hide = false;
        // Open if filter and match, else preserve open status
        elem.open = options.filter ? true : isOpen; // !!filter;
      }
    } else if (elem.content) {
      const entries = Object.entries(elem.content);

      for (let j = 0; j < entries.length; j++) {
        const entry = entries[j];
        const contentKey = entry[0];
        let val = entry[1];

        val = typeof val === 'string' && val.toString();
        if (val && val.match(regex)) {
          hasOneChildVisible = true;
          elem.hide = false;
          // Open if filter and match, else preserve open status
          elem.open = options.filter ? true : isOpen;
          if (options.filter) {
            // keep the match on a string
            if (contentKey) elem.valueMatch.push(contentKey);
            data.match++;
          }
        }
      }
    }
  }
  return hasOneChildVisible;
};

export const handleFusion = (newCurrent, current) => {
  if (!current) return newCurrent;

  const keys = Object.keys(newCurrent);

  for (let i = 0; i < keys.length; i++) {
    const key = keys[i];
    const newElem = newCurrent[key];
    const elem = current[key];

    // this also add language value
    if (!elem) {
      // add new element
      current[key] = newElem;
    } else if (
      !elem.open &&
      newElem.content &&
      ['Object', 'object', 'Array', 'array'].indexOf(newElem.type) < 0
    ) {
      elem.toComplete = newElem.toComplete;
      // put new content and keep the rest
      elem.content = newElem.content;
    }
    // object or array
    else if (newElem.content) {
      // Check if child are visible
      handleFusion(newElem.content, elem.content);
    }
  }
  return current;
};

export const handlePageFusion = (newPage, currentPage) => {
  handleFusion(newPage.content, currentPage.content);

  return currentPage;
};

/**
 * @param {boolean} options.forceClose - force item to be closed
 * @param {string} options.filter - filter to apply (hide or not elements)
 */
export const handlePagesSearch = (draft, options) => {
  const list = draft[options.key] && draft[options.key].data;
  if (!list) return;
  options = options || {};

  const data = {
    totalMatch: 0,
  };
  const pages = clone(list);
  pages.forEach(page => {
    data.match = 0;
    page.hide = !handlePageFilter(page.content, options, data);

    page.filterMatch = data.match;
    data.totalMatch += data.match;
  });

  draft[options.key].data = pages;

  draft.search = {
    filter: options.filter,
    totalMatch: data.totalMatch,
  };
};
interface PageInfo {
  type: string;
  allParams: {};
  params: {};
  search: {};
  data: {};
  transformedData: {};
  fields: {};
  resourceOptions: {};
  subDataOptions: {};
  listDataOptions: {};
  actionOptions: {};
  modelOptions: {};
  dataOptions: {};
}

export const getPageInfo = (props, options, models, transformFunctions): string | PageInfo => {
  const {
    match: { params },
    location,
    detail,
  } = props;

  const search = qs.parse(location.search);

  const allParams = {
    ...search,
    ...params,
  };

  const { id, index, form, type, subData, listData, parentType, parentId } = allParams;

  const actionForm = form;
  if (!type) return 'Please provide a type';

  // Data options
  let resourceOptions = options[type];

  if (parentType) {
    resourceOptions = options[parentType];
    resourceOptions = resourceOptions[type];
  }

  if (!resourceOptions) return `No config found for the resource ${type}`;

  // Sub data options
  const actionOptions = resourceOptions[actionForm];
  const subDataOptions = resourceOptions[subData];
  const listDataOptions = resourceOptions[listData];

  if (actionForm && !actionOptions) {
    return `No config found for the sub form action ${actionForm} of the resource ${type}`;
  }
  if (subData && !subDataOptions) {
    return `No config found for the sub data ${subData} of the resource ${type}`;
  }
  if (listData && !listDataOptions) {
    return `No config found for the list data ${listData} of the resource ${type}`;
  }

  const dataOptions = actionOptions || subDataOptions || listDataOptions;

  const modelOptions = dataOptions || resourceOptions;

  if (actionForm && !actionOptions) {
    return `No action options found in model ${params.type} options for ${actionForm}`;
  }

  // TODO: everythng in the options
  // pageOptions = actionsOptions | resourceOptions
  // pageOptions.fields, ...

  // Fields of the form
  let fieldsModel = actionOptions && (actionOptions.model || actionOptions.fields);

  // subdata
  if (!fieldsModel && subDataOptions) fieldsModel = subDataOptions.model || subDataOptions.fields;
  // list data
  if (!fieldsModel && listDataOptions)
    fieldsModel = listDataOptions.model || listDataOptions.fields;
  if (!fieldsModel) fieldsModel = resourceOptions.model || resourceOptions.fields;
  if (!fieldsModel) fieldsModel = models && models[type];

  // Object data (if edit & no actionForm)
  let data = detail && detail.data;
  const resourceData = data;
  if (actionOptions) data = {};
  else if (subDataOptions) data = (data && data[subData]) || {};
  else if (listDataOptions && index) {
    data = data && data[listData];
    data = data && data[index];
  } else if (listDataOptions) {
    // new item of the list
    data = {};
  }

  // Transformed data (for legend)
  const transformFunction =
    modelOptions.transformFunction || (transformFunctions && transformFunctions[type]);
  const transformedData = data && transformFunction && transformFunction(data);

  return {
    // usefull params
    id,
    index,
    form,
    type,
    parentType,
    parentId,
    subData,
    listData,
    // search just in case
    search,
    // all params
    allParams,
    resourceData,
    data,
    dataLoading: detail?.loading,
    transformFunction,
    transformedData,
    fields: fieldsModel,
    // all options
    resourceOptions,
    subDataOptions,
    listDataOptions,
    actionOptions,
    modelOptions,
    dataOptions,
  };
};

export const gqlConvert = (model, options) => {
  const handleModel = (current, output, prefix) => {
    const keys = Object.keys(current);

    for (let i = 0; i < keys.length; i += 1) {
      const key = keys[i];
      const elem = current[key];

      if ((elem && elem.type === 'separator') || !elem) {
        continue;
      }

      if (elem.type === 'list') {
        handleModel(elem.fields, output, `${prefix || ''}${elem.name || key}.`);
      }

      // section or column
      else if (elem.fields) {
        handleModel(elem.fields, output, elem.name ? `${prefix || ''}${elem.name}.` : prefix);
      } else if (elem.type) {
        if (elem.noQuery) {
          continue;
        }
        // field level
        if (elem.type === 'listData' && options) {
          const subModel = _.get(options, elem.model);
          if (subModel)
            handleModel(
              subModel.fields || subModel.model,
              output,
              `${prefix || ''}${subModel.resourceList}.`,
            );

          continue;
        }
        const str = (prefix || '') + (elem.name || elem.value || key);

        if (elem.extraFields) {
          output[str] = elem.extraFields;
        } else if (elem.type === 'multiPictures' || elem.type === 'multiFiles') {
          output[str] = {
            large: true,
            medium: true,
            title: true,
            fileType: true,
          };
        }
        // multi text
        else if (
          elem.type === 'multi' ||
          elem.type === 'multiwysiwyg' ||
          elem.type === 'multiarea' ||
          elem.type === 'multicode'
        ) {
          for (let index = 0; index < config.languages.length; index++) {
            const lang = config.languages[index];
            const lg = lang.short || lang.value;
            output[str] = {
              ...output[str],
              [lg]: true,
            };
          }
        } else if (elem.type === 'schedule') {
          if (elem.initialValue) {
            Object.keys(elem.initialValue).forEach(k => {
              output[str] = {
                ...output[str],
                [k]: true,
              };
            });
          }
        } else if (elem.type === 'picture') {
          output[str] = {
            large: true,
            medium: true,
            small: true,
          };
        } else if (elem.type === 'price') {
          output.price = true;
          output.vatClassId = true;
          output.currency = true;
        } else if (elem.type === 'file') {
          output[str] = {
            large: true,
            medium: true,
            small: true,
          };
        } else if (elem.type === 'geoloc') {
          output[str] = {
            formattedAddress: true,
            placeId: true,
            loc: {
              type: true,
              coordinates: true,
            },
          };
        } else {
          output[str] = true;
        }

        if (elem.id) {
          output[(prefix || '') + elem.id] = true;
        }
      }
    }
    return output;
  };

  const tmpModel = handleModel(model, {});
  const finalModel = unflatten(tmpModel);

  return finalModel;
};

export const filterUpdateData = (model, modelData) => {
  const handleModel = (current, output, data, prefix) => {
    const keys = Object.keys(current);

    for (let i = 0; i < keys.length; i += 1) {
      const key = keys[i];
      const elem = current[key];
      let updatable = true;
      if (elem.noUpdate) updatable = false;
      if (elem.noMutation) updatable = false;

      if (elem.type === 'list' && updatable) {
        const str = (prefix || '') + (elem.name || key);
        if (updatable && !elem.fields) {
          const val = _.get(data, str);
          if (val !== undefined && val !== null) _.set(output, str, val);
        } else if (elem.fields && updatable) {
          _.set(output, str, []);

          const currentItem = _.get(data, str);
          if (currentItem) {
            for (let j = 0; j < currentItem.length; j++) {
              const listData = handleModel(elem.fields, {}, currentItem[j]);
              const actual = _.get(output, str);
              if (actual) {
                actual.push(listData);
                _.set(output, str, actual);
              }
            }
          }
        }
      }
      // section or column
      else if (elem.fields) {
        if (updatable) handleModel(elem.fields, output, data, prefix);
      } else if (elem.type) {
        // multi text

        // field level
        const str = (prefix || '') + (elem.name || key);

        if (!updatable) {
          continue;
        }

        if (elem.type === 'price') {
          output.price = data.price;
          output.vatClassId = data.vatClassId;
          output.currency = data.currency;
        }

        if (elem.type === 'multi') {
          for (let index = 0; index < config.languages.length; index++) {
            const lang = config.languages[index];
            const lg = lang.short || lang.value;
            if (_.get(data, `${str}.${lg}`)) {
              _.set(output, str, { ..._.get(output, str), [lg]: _.get(data, `${str}.${lg}`) });
            }
          }
        } else {
          const val = _.get(data, str);
          if (val !== undefined && val !== null) _.set(output, str, val);
          if (
            val === null &&
            (elem.type === 'picture' || elem.type === 'file' || elem.type === 'select')
          ) {
            _.set(output, str, val);
          }
        }
      }
    }
    return output;
  };

  const filteredData = handleModel(model, {}, modelData);
  return filteredData;
};

export const insertInModel = (model, item, fieldId = '0', sectionId = '0') => {
  const modelCopy = clone(model);

  const keys = Object.keys(modelCopy);
  for (let i = 0; i < keys.length; i += 1) {
    const key = keys[i];
    const elem = modelCopy[key];

    if (key === sectionId) {
      const fieldKeys = Object.keys(elem.fields);
      for (let j = 0; j < fieldKeys.length; j += 1) {
        const fieldKey = fieldKeys[j];

        if (fieldKey === fieldId) {
          if (Array.isArray(item)) {
            for (let k = 0; k < item.length; k++) {
              const el = item[k];
              elem.fields.splice(j, 0, el);
            }
          } else {
            elem.fields.splice(j, 0, item);
          }
          break;
        }
      }
      break;
    }
  }

  return modelCopy;
};
