import _ from 'lodash';
import { concat } from './StringUtils';
import { Connection } from 'mystique/types/common';

export const lastElement = <T>(array: T[]): T | undefined => {
  const length = array?.length;
  return length ? array[length - 1] : undefined;
};

export const arraysLengthSum = (arr: any[], path: string) => {
  return (arr || []).reduce((total, i) => total + getIndex(i, path).length, 0);
};

export const getIndex = (obj: any, index: string) => {
  return index
    .split('.')
    .reduce((o, i) => o[i || ''] || {}, (obj || {}) as any);
};

export const recursiveFieldDecorator = (
  target: any,
  fieldDecorator: (target: any, key: string, path: string[]) => any,
  path: string[] = [],
) => {
  const proxyCache: Record<string, any> = {};
  const proxy = {
    set(target: any, key: string, value: any) {
      target[key] = value;
      delete proxyCache[key];
      return true;
    },
    get(target: any, key: string) {
      if (key === '__fieldDecorator' && !(key in target)) {
        return fieldDecorator;
      }
      // if "raw" value is requested, return the undecorated value.
      if (typeof key === 'string' && key.endsWith('__raw')) {
        // But first check the literal '<key>__raw' in case a) it actually exists, or b) (more likely) we have nested proxies
        return target[key] || target[key.substring(0, key.length - 5)];
      }

      let decoratedValue = fieldDecorator(target, key, path);
      if (decoratedValue === undefined) decoratedValue = target[key];

      if (typeof decoratedValue === 'object' && decoratedValue !== null) {
        const desc = Object.getOwnPropertyDescriptor(target, key);

        // don't proxy non-writables (e.g. prototypes)
        const preventReproxy = desc && !desc.writable && !desc.configurable;

        if (preventReproxy) {
          return decoratedValue;
        } else if (proxyCache[key]) {
          return proxyCache[key];
        } else {
          proxyCache[key] = recursiveFieldDecorator(
            decoratedValue,
            fieldDecorator,
            [...path, key],
          );
          return proxyCache[key];
        }
      }

      return decoratedValue;
    },
  };

  return window.Proxy ? new Proxy(target, proxy) : target;
};

export const objectSortByValues = (obj: any): any => {
  return Object.fromEntries(
    Object.entries(obj).sort((a, b) => {
      const left = typeof b[1] === 'number' ? b[1] : Number.NaN;
      const right = typeof a[1] === 'number' ? a[1] : Number.NaN;
      return left - right;
    }),
  );
};

export const arraysLengthSumByStatus = (
  arr: any[],
  path: string,
  ...status: string[]
): string =>
  (arr || [])
    .reduce((items, i) => items.concat(_.get(i, path)), [])
    .filter((el: any) => status.some((e: string) => e === el.node.status))
    ?.length ?? 0;

export const arraySumV2 = (arr: any[], path: string): string => {
  return (arr || []).reduce((total, i) => {
    return total + (_.get(i, path) || 0) || 0;
  }, 0);
};

export const innerArraysSum = (arr: any[], inner: string, element: string) => {
  return (arr || []).reduce((total, i) => {
    const sum = (_.get(i, inner) || []).reduce(
      (s1: any, j: any) => s1 + (_.get(j, element) || 0),
      0,
    );
    return total + sum;
  }, 0);
};

export const arraySumConditional = (
  arr: any[],
  path: string,
  match: string,
): string => {
  return (arr || [])
    .filter((el) => el.node.type === match)
    .reduce((total, i) => total + (_.get(i, path) || 0), 0);
};

export const concatArray = (arr: Connection<any>, sep: string): string => {
  const values = arr.edges.map((el) => {
    return _.get(el, 'node.name');
  });
  return concat(values, sep);
};

export const arraysSumTimeDiff = (
  arr: any[],
  timeFrom: string,
  timeTo: string,
) => {
  return (arr || []).reduce((total, i) => {
    return (
      total +
      (new Date(_.get(i, timeTo)).getTime() -
        new Date(_.get(i, timeFrom)).getTime())
    );
  }, 0);
};

export const cloneDeep = (obj: any): any => {
  if (obj['isCopy']) {
    return obj;
  }
  const newObj = _.cloneDeep(obj);
  newObj['isCopy'] = true;
  return newObj;
};

export const replaceOrAdd = (
  current: string | string[],
  value: string,
  isArrayField: boolean,
) => {
  if (isArrayField && current) {
    const currentArr = Array.isArray(current) ? current : [current];
    return [...currentArr.filter((i) => !(i === value)), value];
  } else {
    return value;
  }
};

export const arrayAnyEquals = (
  arr: any[],
  path: string,
  ...match: string[]
): boolean => {
  for (const value of arr) {
    if (match?.includes(_.get(value, path))) {
      return true;
    }
  }
  return false;
};

export const parseJsonOrReturnOriginal = (obj: any) => {
  try {
    return JSON.parse(obj);
  } catch (_) {
    return obj;
  }
};

export const isJSONObjEmpty = (obj: any) => {
  return Object.keys(obj).length === 0 && obj.constructor === Object;
};