import { useCallback, useEffect, useState, useMemo } from 'react';
import { useDebounceValue } from '../../hooks/use-debounce';
function mergeState(target, source, keys) {
  const result = {
    ...target,
    ...source
  };
  keys.forEach(key => {
    if (key in result) {
      const targetValue = target[key] || {};
      const sourceValue = source[key] || {};
      result[key] = {
        ...targetValue,
        ...sourceValue
      };
    }
  });
  return result;
}
const baseState = {
  sorting: [],
  columnSizing: {},
  pinned: {},
  visible: {},
  selected: {},
  expandedRows: {},
  columnOrder: [],
  search: '',
  pagination: {
    pageIndex: 0,
    pageSize: 20
  }
};
const getStorageKey = (tableId, version) => `table-react-${tableId}-version-${version}`;
function clearOldLocalStorageData(tableId, version) {
  const regex = /^table-react-(.+)-version-(.+)$/;
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i) ?? '';
    const result = key.match(regex);
    if (result) {
      const [, matchedTableId, matchedVersion] = result;
      if (matchedTableId === tableId && matchedVersion !== version.toString()) {
        // eslint-disable-next-line no-console
        console.log(`[TableReact] Removing old table key: ${key}`);
        localStorage.removeItem(key);
      }
    }
  }

  // this will remove old data before localStorage key was changed (it didn't have "version" suffix)
  // can be removed after some time
  const oldFormatKey = `table-react-${tableId}-${version}`;
  if (localStorage.getItem(oldFormatKey)) {
    // eslint-disable-next-line no-console
    console.log(`[TableReact] Removing deprecated table key format: ${oldFormatKey}`);
    localStorage.removeItem(oldFormatKey);
  }
}
function getSavedState(tableId, version, notPersistentKeys) {
  const state = localStorage.getItem(getStorageKey(tableId, version));
  try {
    if (state) {
      const parsedState = JSON.parse(state);
      notPersistentKeys.forEach(key => {
        const keys = key.split('.');
        const lastKey = keys.pop();
        const parent = keys.reduce((obj, curr) => obj?.[curr], parsedState);
        delete parent?.[lastKey];
      });
      return parsedState;
    }
    return {};
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
  } catch (e) {
    return {};
  }
}
function saveState(tableId, version, state) {
  localStorage.setItem(getStorageKey(tableId, version), JSON.stringify(state));
}
export function useTableReact({
  tableId,
  version,
  debounceSearchDelay = 300,
  defaultState = {},
  notPersistentKeys = ['pagination.pageIndex', 'search', 'filter', 'expandedRows', 'selected'],
  deepMergeKeys = ['pagination']
}) {
  const newState = {
    ...baseState,
    ...defaultState
  };
  const [startState] = useState(() => {
    const savedData = mergeState(newState, getSavedState(tableId, version, notPersistentKeys), deepMergeKeys);
    return {
      ...baseState,
      ...savedData
    };
  });
  const [sorting, setSorting] = useState(startState.sorting);
  const [touched, setTouched] = useState(startState.touched);
  const [search, setSearchValue] = useState(startState.search);
  const debounceSearch = useDebounceValue(search, debounceSearchDelay);
  const [expandedRows, setExpanded] = useState(startState.expandedRows);
  const [selected, setSelected] = useState(startState.selected);
  const [visible, setVisible] = useState(startState.visible);
  const [pinned, setPinned] = useState(startState.pinned);
  const [pagination, setPagination] = useState(startState.pagination);
  const [filters, setFiltersValue] = useState(startState.filters);
  const [columnSizing, setColumnSizing] = useState(startState.columnSizing);
  const [columnOrder, setColumnOrder] = useState(startState.columnOrder);
  const setFilters = useCallback(val => {
    setPagination(data => ({
      ...data,
      pageIndex: 0
    }));
    setFiltersValue(val);
  }, [setPagination, setFiltersValue]);
  const setSearch = useCallback(val => {
    setPagination(data => ({
      ...data,
      pageIndex: 0
    }));
    setSearchValue(val);
  }, [setPagination, setFiltersValue]);
  const reset = () => {
    localStorage.removeItem(getStorageKey(tableId, version));
    setSorting(newState.sorting);
    setSearch(newState.search);
    setVisible(newState.visible);
    setPinned(newState.pinned);
    setPagination(newState.pagination);
    setColumnSizing(newState.columnSizing);
    setColumnOrder(newState.columnOrder);
    setTouched(undefined);
    setSelected({});
  };

  // wipes out all localstorage data with the same tableId but different versions
  useEffect(() => clearOldLocalStorageData(tableId, version), []);
  useEffect(() => {
    const timer = setTimeout(() => {
      saveState(tableId, version, {
        sorting,
        visible,
        pagination,
        pinned,
        search,
        columnSizing,
        selected,
        expandedRows,
        filters,
        columnOrder,
        touched
      });
    }, 200);
    return () => timer ? clearTimeout(timer) : undefined;
  }, [sorting, visible, pagination, pinned, search, columnSizing, filters, selected, expandedRows, columnOrder, touched]);
  const state = useMemo(() => ({
    sorting,
    visible,
    pagination,
    pinned,
    search,
    defaultColumnSizing: columnSizing,
    filters,
    selected,
    expandedRows,
    columnOrder,
    touched,
    debounceSearch
  }), [sorting, visible, pagination, pinned, search, columnSizing, filters, selected, expandedRows, columnOrder, touched, debounceSearch]);
  const handlers = useMemo(() => ({
    onVisibilityChange: setVisible,
    onFiltersChange: setFilters,
    onSortingChange: setSorting,
    onPinningChange: setPinned,
    onSearchChange: setSearch,
    onRowSelectionChange: setSelected,
    onExpandedChange: setExpanded,
    onPaginationChange: setPagination,
    onColumnSizingChange: setColumnSizing,
    onColumnOrderChange: setColumnOrder,
    onTouchedChange: setTouched
  }), [setVisible, setFilters, setSorting, setPinned, setSearch, setSelected, setExpanded, setPagination, setColumnSizing, setColumnOrder, setTouched]);
  return {
    state,
    handlers,
    reset,
    defaultState: newState
  };
}