import { deserializeWithContext, deserializeWithContextAndRun, FilterQueryParams, JsonObject, mergeObjects, tryToDeserialize } from '@win2win/shared';
import { cloneDeep, compact, isArray, isEqual, isNil, mapValues, merge, uniqueId, values } from 'lodash';
import { computed, reactive, ref, Ref, watch } from 'vue';
import { TOOLBAR_WIN_FILTERS } from './ToolbarWinFilters';
import { ComputedFilterConfig, FilterConfig, FiltersConfig } from './W2WToolbarConfig';
import { FILTER_STRATEGIES } from './filters_strategies';
import { standarizeDate as _standarizeDate } from './helpers';

// no borrar estas funciones. Se usan en la evaluación de filtros
const standarizeDate = _standarizeDate;

export function useFilters(filtersConfig: Ref<FiltersConfig>, model: Ref<FilterQueryParams>) {
  const filtersValue = reactive<JsonObject>({});
  const computedFilters = computed<ComputedFilterConfig[]>(() => compact(filtersConfig.value?.items?.map((filter) => buildFilter(filter)) || []));

  const buildFilter = (filterConfig: FilterConfig) => {
    const filter = cloneDeep(TOOLBAR_WIN_FILTERS[filterConfig.code]);
    if (!filter) return null;
    const { props = {}, show, apply, ...customParams } = filterConfig;
    const formatFn = tryToDeserialize(apply?.format, { standarizeDate }, '') || '';
    const _props = typeof props === 'string' ? deserializeWithContextAndRun(props, { filters: cloneDeep(filtersValue) }) : props;
    const onChange = customParams.onChange ? deserializeWithContext((customParams.onChange || '') as string, { filters: cloneDeep(filtersValue) }) : null;

    return {
      ...filter,
      ...customParams,
      apply: { ...(apply || {}), format: formatFn },
      show: show ?? true,
      props: merge({}, filter.props || {}, _props),
      id: uniqueId(),
      onChange,
    };
  };

  const hasFilters = computed(() => !!computedFilters.value.length);
  const showFilters = ref(false);
  const activeFilters = computed(() => {
    return values(filtersValue).filter((v) => !isNil(v) && (!isArray(v) || !!v.length)) || [];
  });

  const fromQueryParams = (params: any) => {
    const filters: Record<string, any> = {};

    Object.entries(params.byEqualCol || {}).forEach(([key, value]) => {
      filters[key] = value;
    });

    Object.entries(params.byNotEqualCol || {}).forEach(([key, value]) => {
      filters[key] = value;
    });

    Object.entries(params.byGreaterCol || {}).forEach(([key, value]) => {
      if (!filters[key]) filters[key] = {};
      filters[key].from = value;
    });

    Object.entries(params.byLessCol || {}).forEach(([key, value]) => {
      if (!filters[key]) filters[key] = {};
      filters[key].to = value;
    });

    Object.entries(params.byBetweenCol || {}).forEach(([key, value]: any) => {
      filters[key] = value ? { from: value.from, to: value.to } : null;
    });

    Object.entries(params.bycol || {}).forEach(([key, value]) => {
      filters[key] = value;
    });

    Object.entries(params.custom || {}).forEach(([key, value]) => {
      filters[key] = value;
    });

    return filters;
  };

  const clearFilters = () => {
    Object.assign(
      filtersValue,
      mapValues(filtersValue, () => null)
    );
  };

  watch(
    computedFilters,
    (filters) => {
      const newFilters = mergeObjects(
        filtersValue,
        filters.reduce((acc, filter) => {
          const values = fromQueryParams(model.value);
          const initVal = filter.apply?.key ? values[filter.apply?.key] : null;
          acc[filter.name] = initVal ?? filter.initValue;
          return acc;
        }, {})
      );
      if (!isEqual(newFilters, filtersValue)) {
        Object.assign(filtersValue, newFilters);
      }
    },
    {
      immediate: true,
      deep: true,
    }
  );

  const toQueryParams = (value: Record<string, any>) => {
    const params = {
      byEqualCol: {},
      byNotEqualCol: {},
      bycol: {},
      byGreaterCol: {},
      byLessCol: {},
      byBetweenCol: {},
      custom: {},
    };

    computedFilters.value.forEach((filter) => {
      const rawValue = value[filter.name];
      if (!filter.apply?.key) return;
      const type = filter.type || 'default';
      const { key, format } = filter.apply;
      const strategy = FILTER_STRATEGIES[type] || FILTER_STRATEGIES.default;
      strategy({ key, rawValue, format, params });
    });

    return params;
  };

  watch(
    filtersValue,
    (value) => {
      if (!model) return;
      model.value = mergeObjects(model.value || {}, toQueryParams(value));
    },
    { deep: true }
  );

  return { computedFilters, hasFilters, filtersValue, showFilters, activeFilters, clearFilters };
}
