import {
  getValueByPath,
  isEmptyStringOrNotDefined,
  searchString,
} from '@/helpers/string.helper';
import { Filter, SavedFilter } from '@/models';
import { computed, onMounted, Ref, ref, unref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useUserPref } from '@/composables/userPref.composable';
import _cloneDeep from 'lodash/cloneDeep';
import {
  handleValueInSearchParams,
  writeLocation,
} from '@/helpers/location.helper';
import {
  dateFilterCompare,
  dateIsBetween,
  isBetweenFilter,
  numberIsBetween,
} from '@/composables/search.helper';

const FILTER_KEY = 'bms-ui-filter';
const FILTER_SEARCH_KEY = `${FILTER_KEY}-search`;

export const useSearch = (
  persistent: boolean | Ref<boolean> = true,
  defaultFilters: Filter[] | Ref<Filter[]> = [],
  defaultFiltersOpened: boolean | Ref<boolean> = false,
) => {
  const route = useRoute();
  const userPref = useUserPref();
  const reflect = ref<boolean>(unref(persistent));
  const search = ref<string>('');
  const filters = ref<Filter[]>(unref(defaultFilters));
  const isFilterVisible = ref<boolean>(unref(defaultFiltersOpened));
  const isMounting = ref<boolean>(true);

  const toggleFilters = () => {
    isFilterVisible.value = !isFilterVisible.value;
  };

  const numberOfActiveFilters = computed(
    () =>
      filters.value?.filter(
        (f) => f.value !== '' && f.value !== null && f.value !== undefined,
      ).length,
  );

  const hydrateFiltersFromUrl = (requestedFilters: Filter[]): Filter[] => {
    const searchParams = new URLSearchParams(window.location.search);

    requestedFilters.forEach((filterOption) => {
      const savedFilterName = convertFilterKeyForPath(filterOption);
      let savedValue: string | boolean | null =
        searchParams.get(savedFilterName);
      if (savedValue) {
        isFilterVisible.value = true;
        if (filterOption.type === 'boolean') {
          savedValue = savedValue === 'true';
        }
        if (isBetweenFilter(filterOption)) {
          const betweenValue = (savedValue as string).split('|');
          filterOption.valueFrom = betweenValue[0];
          filterOption.valueTo = betweenValue[1] ? betweenValue[1] : '';
        } else {
          filterOption.value = savedValue;
        }
      }
    });

    const filtersOpened = searchParams.get('bms-ui-filters-open');
    if (filtersOpened === 'false') {
      isFilterVisible.value = false;
    } else if (filtersOpened === 'true') {
      isFilterVisible.value = true;
    }
    return requestedFilters;
  };

  const hydrateFiltersFromLocalStorage = (
    requestedFilters: Filter[],
  ): Filter[] => {
    if (userPref) {
      requestedFilters.forEach((filterOption) => {
        const savedFilterName = convertFilterKeyForPath(filterOption);
        const savedFilterPref = userPref.getUserPref(
          savedFilterName,
          route.path,
        );
        if (savedFilterPref !== undefined) {
          filterOption.value = savedFilterPref;
        }
      });
    }
    return requestedFilters;
  };

  const hasFiltersInUrl = (): boolean => {
    return (
      Array.from(new URLSearchParams(window.location.search).keys()).filter(
        (param: string) => param.includes(FILTER_KEY),
      ).length > 0
    );
  };

  const initFilters = (): void => {
    const requestedFilters = _cloneDeep(filters.value);
    let hydratedFilters = requestedFilters;
    if (reflect.value) {
      hydratedFilters = hasFiltersInUrl()
        ? hydrateFiltersFromUrl(requestedFilters)
        : hydrateFiltersFromLocalStorage(requestedFilters);
    }
    filters.value = hydratedFilters.map((filter) => {
      if (filter.value === undefined) {
        filter.value = null;
      }
      return filter;
    });
  };

  const filterItems = (items: any[]): any[] => {
    const defaultTextFilterFunction = (
      item: any,
      value: any,
      target: string,
    ) => {
      return searchString(getValueByPath(item, target) + '', value + '');
    };
    return items.filter((item) => {
      return filters.value.every((filter) => {
        if (isBetweenFilter(filter)) {
          if (
            (!getfilterValue(filter, 'valueFrom') &&
              !getfilterValue(filter, 'valueTo')) ||
            (!filter.customFilter && item[filter.key] === undefined)
          ) {
            return true;
          } else {
            try {
              if (filter.type === 'betweenNumber') {
                return numberIsBetween(item[filter.key], filter);
              } else {
                return dateIsBetween(item[filter.key], filter);
              }
            } catch (e: any) {
              return true;
            }
          }
        }
        if (isEmptyStringOrNotDefined(filter.value)) {
          return true;
        }
        if (filter.type === 'inputDate') {
          const applyFilter = filter.customFilter
            ? filter.customFilter
            : dateFilterCompare;
          return applyFilter(item[filter.key], filter, true);
        }

        const applyFilter = filter.customFilter
          ? filter.customFilter
          : defaultTextFilterFunction;
        return applyFilter(item, filter.value, filter.key);
      });
    });
  };

  const reflectSearchToUserPref = (str: string): void => {
    userPref.saveUserPref(FILTER_SEARCH_KEY, str, route.path);
  };

  const reflectFiltersToUserPref = (filters: Filter[]): void => {
    filters.forEach((filter) => {
      const savedFilterName = convertFilterKeyForPath(filter);
      userPref.saveUserPref(savedFilterName, filter.value, route.path);
    });
  };

  watch(search, () => {
    if (reflect.value) {
      reflectSearchToPath(search.value, FILTER_SEARCH_KEY);
      reflectSearchToUserPref(search.value);
    }
  });

  watch(
    () => filters,
    () => {
      if (reflect.value && filters.value) {
        reflectFiltersToPath(filters.value);
        reflectFiltersToUserPref(filters.value);
      }
    },
    { deep: true },
  );

  const initSearch = () => {
    if (reflect.value) {
      const searchParams = new URLSearchParams(window.location.search);

      if (searchParams.get(FILTER_SEARCH_KEY)) {
        search.value = searchParams.get(FILTER_SEARCH_KEY) || '';
      }
      if (!search.value && !hasFiltersInUrl()) {
        search.value =
          userPref.getUserPref(FILTER_SEARCH_KEY, route.path) || '';
      }
    }
  };

  const resetAllFilters = () => {
    isMounting.value = true;
    initSearch();
    initFilters();
    isMounting.value = false;
  };

  onMounted(() => {
    resetAllFilters();
  });

  const isSavedFilterActive = (savedFilter: SavedFilter): boolean => {
    return savedFilter.filters.every((saveF) => {
      const targetFilter = filters.value.find((f) => f.key === saveF.target);
      if (!targetFilter) {
        return false;
      }
      return (
        targetFilter.value === saveF.value ||
        (targetFilter.value === '' && saveF.value === null) ||
        (targetFilter.value === null && saveF.value === '')
      );
    });
  };

  const resetFilterValue = (filter: Filter) => {
    let resetFilter = { ...filter };
    const valueKeys = ['value', 'valueFrom', 'valueTo'];
    valueKeys.forEach((key) => {
      if (
        resetFilter[key as keyof Filter] !== null &&
        resetFilter[key as keyof Filter] !== undefined
      ) {
        resetFilter[key as keyof Filter] = null;
      }
    });
    return resetFilter;
  };

  const resetFilters = () => {
    filters.value = filters.value.map((filter) => {
      return resetFilterValue(filter);
    });
  };

  const selectSavedFilter = (savedFilter: SavedFilter) => {
    if (isSavedFilterActive(savedFilter)) {
      resetFilters();
    } else {
      filters.value.forEach((filter) => {
        const foundSavedFilter = savedFilter.filters.find(
          (f) => filter.key === f.target,
        );
        if (foundSavedFilter) {
          filter.value = foundSavedFilter.value;
        }
      });
    }
  };

  const updateFiltersFromProps = (updatedFilters: Filter[]) => {
    filters.value = filters.value.map((filter) => {
      const updatedFilter = updatedFilters.find((f) => f.key === filter.key);

      if (updatedFilter) {
        let mergedFilter = { ...filter };

        if (updatedFilter.autocompleteOptions)
          mergedFilter.autocompleteOptions = updatedFilter.autocompleteOptions;
        if (updatedFilter.selectOptions)
          mergedFilter.selectOptions = updatedFilter.selectOptions;

        const value = filter.value || filter.valueFrom || filter.valueTo;

        const isValueValidSelectOption =
          updatedFilter.type === 'select' &&
          updatedFilter.selectOptions?.find((option) => option.value === value);

        const isValueValidAutocompleteOption =
          updatedFilter.type === 'autocomplete' &&
          updatedFilter.autocompleteOptions?.find((option) => option === value);

        if (
          value &&
          !isValueValidSelectOption &&
          !isValueValidAutocompleteOption
        ) {
          mergedFilter = resetFilterValue(mergedFilter);
        }

        return mergedFilter;
      } else {
        return filter;
      }
    });
  };

  return {
    search,
    reflect,
    filters,
    isFilterVisible,
    numberOfActiveFilters,
    isMounting,
    toggleFilters,
    filterItems,
    isSavedFilterActive,
    resetFilters,
    selectSavedFilter,
    resetAllFilters,
    updateFiltersFromProps,
  };
};

export const reflectFiltersToPath = (filters: Filter[]): void => {
  let searchParams = new URLSearchParams(window.location.search);
  searchParams = filters.reduce((acc, filter) => {
    const key = convertFilterKeyForPath(filter);
    let value;
    if (isBetweenFilter(filter)) {
      const from = getfilterValue(filter, 'valueFrom').toString();
      const to = getfilterValue(filter, 'valueTo').toString();
      // escape "|" separator if end the string
      value = `${from}|${to}`.replace(/\|$/g, '');
    } else {
      value = getfilterValue(filter).toString();
    }
    return handleValueInSearchParams(acc, key, value);
  }, searchParams);
  writeLocation(searchParams);
};

const convertFilterKeyForPath = (filter: Filter): string => {
  return `${FILTER_KEY}-${filter.key}`;
};

const getfilterValue = (filter: Filter, key: string = 'value'): string => {
  return filter[key as keyof Filter] === null ||
    filter[key as keyof Filter] === undefined
    ? ''
    : filter[key as keyof Filter];
};

export const reflectSearchToPath = (searchValue: string, key: string): void => {
  let searchParams = new URLSearchParams(window.location.search);
  searchParams = handleValueInSearchParams(searchParams, key, searchValue);
  writeLocation(searchParams);
};
