import { Filter } from '../../models/filter.model';
import { State, Action, StateContext, Store } from '@ngxs/store';
import * as actions from '../actions/filter.actions';
import { FilterService } from '../../services/filter.service';
import { ProcessSelectedFilters } from '../../models/process-selected-filters.model';
import { FilterItem } from '../../models/filter-item.model';
import { removeItem } from '@ngxs/store/operators';
import { tap } from 'rxjs/operators';
import { AppState } from './app.state';
import { AddSearch } from '../actions/process.actions';
import { ProcessSelectors } from '../selectors/process.selectors';
import { UserStateSyncData } from '../../models/user-state-sync.model';
import { AppUserDataSync } from '../actions/app.actions';
import { FilterType } from '../../models/filter-type.enum';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';

export class FilterStateModel {
  filters: Filter[];
  processesSelectedFilters: ProcessSelectedFilters[];
  negativeProcessesSelectedFilters: ProcessSelectedFilters[];
  dropdownFilters: { [id: number]: Filter[] };
  negativeDropdownFilters: { [id: number]: Filter[] };
  filterType: { [id: number]: FilterType };
}

@State<FilterStateModel>({
  name: 'filters',
  defaults: {
    filters: [],
    processesSelectedFilters: [],
    negativeProcessesSelectedFilters: [],
    dropdownFilters: [],
    negativeDropdownFilters: [],
    filterType: [],
  },
})
@Injectable()
export class FilterState {
  constructor(private filterService: FilterService, private store: Store) {}

  @Action(actions.LoadFilters)
  load(ctx: StateContext<FilterStateModel>, { filterType }: actions.LoadFilters) {
    let filter$: Observable<Filter[]>;
    if (filterType === FilterType.Default) {
      filter$ = this.filterService.getFilters();
    }
    if (filterType === FilterType.Full) {
      filter$ = this.filterService.getFullFilter();
    }
    if (filterType === FilterType.Negative) {
      filter$ = this.filterService.getNegativeFilters();
    }
    return filter$.pipe(tap((filters: Filter[]) => this.loadFilterEffects(filters, ctx, filterType)));
  }

  loadFilterEffects(filters: Filter[], ctx: StateContext<FilterStateModel>, filterType: FilterType) {
    const state = ctx.getState();
    const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const dropdownFilters = filterType === FilterType.Negative ? state.negativeDropdownFilters : state.dropdownFilters;
    const existingFilterState = dropdownFilters[currentProcessId];
    let newFilterState = filters;
    if (existingFilterState && existingFilterState.length > 0 && newFilterState && newFilterState.length > 0) {
      newFilterState = newFilterState.map((t) => {
        const filterExistingState = existingFilterState.find((ex) => ex.id === t.id);
        if (filterExistingState) {
          return <Filter>{
            ...t,
            items: t.items.map((eit) => {
              const filterItemExistingState = filterExistingState.items.find((it) => it.id === eit.id);
              if (filterItemExistingState) {
                return <FilterItem>{
                  ...eit,
                  selected: filterItemExistingState.selected,
                };
              }
              return eit;
            }),
          };
        }
        return t;
      });
    }
    let items = <{ [id: number]: Filter[] }>{};
    if (dropdownFilters) {
      items = { ...dropdownFilters };
    }
    const itemExists = items[currentProcessId];
    if (!itemExists) {
      items = {
        ...dropdownFilters,
        [currentProcessId]: [...newFilterState.filter((fil: Filter) => fil.id !== 'cattype')],
      };
    } else {
      items[currentProcessId] = [...newFilterState.filter((fil: Filter) => fil.id !== 'cattype')];
    }
    ctx.patchState({
      filters: [...newFilterState],
      dropdownFilters: filterType === FilterType.Negative ? state.dropdownFilters : items,
      negativeDropdownFilters: filterType === FilterType.Negative ? items : state.negativeDropdownFilters,
    });
    this.syncApiState(
      [...newFilterState],
      state.processesSelectedFilters,
      state.negativeProcessesSelectedFilters,
      filterType === FilterType.Negative ? state.dropdownFilters : items,
      filterType === FilterType.Negative ? items : state.negativeDropdownFilters,
      state.filterType
    );
  }

  @Action(actions.ResetFilters)
  resetFilters(ctx: StateContext<FilterStateModel>) {
    const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    return this.filterService.getFilters().pipe(
      tap((filters: Filter[]) => {
        const dropdownFilters = ctx.getState().dropdownFilters;
        let items = <{ [id: number]: Filter[] }>{};
        if (dropdownFilters) {
          items = { ...dropdownFilters };
        }
        const itemExists = items[currentProcessId];
        if (!itemExists) {
          items = { ...dropdownFilters, [currentProcessId]: [] };
        } else {
          items[currentProcessId] = [];
        }

        const negativeDropdownFilters = ctx.getState().negativeDropdownFilters;
        let neagtiveItems = <{ [id: number]: Filter[] }>{};
        if (negativeDropdownFilters) {
          neagtiveItems = { ...negativeDropdownFilters };
        }
        const neagtiveItemsExists = neagtiveItems[currentProcessId];
        if (!neagtiveItemsExists) {
          neagtiveItems = {
            ...negativeDropdownFilters,
            [currentProcessId]: [],
          };
        } else {
          neagtiveItems[currentProcessId] = [];
        }

        const filterTypes = ctx.getState().filterType;
        let updatedFilterTypes: { [id: number]: FilterType };
        if (filterTypes) {
          updatedFilterTypes = { ...filterTypes };
        }

        const filterTypeExists = updatedFilterTypes[currentProcessId];
        if (!filterTypeExists) {
          updatedFilterTypes = {
            ...filterTypes,
            [currentProcessId]: FilterType.Default,
          };
        } else {
          updatedFilterTypes[currentProcessId] = FilterType.Default;
        }

        const processSelectedFilters = ctx.getState().processesSelectedFilters;
        const updatedProcessesSelectedFilters = [
          ...processSelectedFilters.map((processFilter) => {
            if (processFilter.processId === currentProcessId) {
              return {
                ...processFilter,
                dropdownFilters: [],
                selectedFilters: [],
              };
            }
            return processFilter;
          }),
        ];

        const negativeProcessSelectedFilters = ctx.getState().negativeProcessesSelectedFilters;
        const updatedNegativeProcessesSelectedFilters = [
          ...negativeProcessSelectedFilters.map((processFilter) => {
            if (processFilter.processId === currentProcessId) {
              return {
                ...processFilter,
                dropdownFilters: [],
                selectedFilters: [],
              };
            }
            return processFilter;
          }),
        ];
        ctx.patchState({
          filters: [...filters],
          dropdownFilters: items,
          negativeDropdownFilters: neagtiveItems,
          processesSelectedFilters: updatedProcessesSelectedFilters,
          negativeProcessesSelectedFilters: updatedNegativeProcessesSelectedFilters,
        });
        this.syncApiState(
          [...filters],
          updatedProcessesSelectedFilters,
          updatedProcessesSelectedFilters,
          items,
          neagtiveItems,
          updatedFilterTypes
        );
      })
    );
  }

  @Action(actions.SelectFilterById)
  selectFilterById(ctx: StateContext<FilterStateModel>, { id }: actions.SelectFilterById) {
    const state = ctx.getState();
    const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const filterType = state.filterType[currentProcessId];
    const filters =
      filterType === FilterType.Negative ? state.negativeDropdownFilters[currentProcessId] : state.dropdownFilters[currentProcessId];
    return this.filterService.selectFilterById(filters, id).pipe(
      tap((f: Filter[]) => {
        const dropdownFilters = filterType === FilterType.Negative ? state.negativeDropdownFilters : state.dropdownFilters;
        let items = <{ [id: number]: Filter[] }>{};
        if (dropdownFilters) {
          items = { ...dropdownFilters };
        }
        const itemExists = items[currentProcessId];
        if (!itemExists) {
          items = { ...dropdownFilters, [currentProcessId]: f };
        } else {
          items[currentProcessId] = f;
        }
        ctx.patchState({
          dropdownFilters: filterType === FilterType.Negative ? state.dropdownFilters : items,
          negativeDropdownFilters: filterType === FilterType.Negative ? items : state.negativeDropdownFilters,
        });
        this.syncApiState(
          state.filters,
          state.processesSelectedFilters,
          state.negativeProcessesSelectedFilters,
          filterType === FilterType.Negative ? state.dropdownFilters : items,
          filterType === FilterType.Negative ? items : state.negativeDropdownFilters,
          state.filterType
        );
      })
    );
  }

  @Action(actions.UnselectFilterById)
  unselectFilterById(ctx: StateContext<FilterStateModel>, { id }: actions.UnselectFilterById) {
    const state = ctx.getState();
    const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const filterType = state.filterType[currentProcessId];
    const filters =
      filterType === FilterType.Negative ? state.negativeDropdownFilters[currentProcessId] : state.dropdownFilters[currentProcessId];
    return this.filterService.unselectFilterById(filters, id).pipe(
      tap((f: Filter[]) => {
        const dropdownFilters = filterType === FilterType.Negative ? state.negativeDropdownFilters : state.dropdownFilters;
        let items = <{ [id: number]: Filter[] }>{};
        if (dropdownFilters) {
          items = { ...dropdownFilters };
        }
        const itemExists = items[currentProcessId];
        if (!itemExists) {
          items = { ...dropdownFilters, [currentProcessId]: f };
        } else {
          items[currentProcessId] = f;
        }
        ctx.patchState({
          dropdownFilters: filterType === FilterType.Negative ? state.dropdownFilters : items,
          negativeDropdownFilters: filterType === FilterType.Negative ? items : state.negativeDropdownFilters,
        });
        this.syncApiState(
          state.filters,
          state.processesSelectedFilters,
          state.negativeProcessesSelectedFilters,
          filterType === FilterType.Negative ? state.dropdownFilters : items,
          filterType === FilterType.Negative ? items : state.negativeDropdownFilters,
          state.filterType
        );
      })
    );
  }

  @Action(actions.ToggleFilterItemById)
  toggleItemById(ctx: StateContext<FilterStateModel>, { id }: actions.ToggleFilterItemById) {
    const state = ctx.getState();
    const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const filterType = state.filterType[currentProcessId];
    const filters =
      filterType === FilterType.Negative ? state.negativeDropdownFilters[currentProcessId] : state.dropdownFilters[currentProcessId];
    return this.filterService.toggleFilterItemsById(filters, id).pipe(
      tap((f: Filter[]) => {
        const dropdownFilters = filterType === FilterType.Negative ? state.negativeDropdownFilters : state.dropdownFilters;
        let items = <{ [id: number]: Filter[] }>{};
        if (dropdownFilters) {
          items = { ...dropdownFilters };
        }
        const itemExists = items[currentProcessId];
        if (!itemExists) {
          items = { ...dropdownFilters, [currentProcessId]: f };
        } else {
          items[currentProcessId] = f;
        }
        ctx.patchState({
          dropdownFilters: filterType === FilterType.Negative ? state.dropdownFilters : items,
          negativeDropdownFilters: filterType === FilterType.Negative ? items : state.negativeDropdownFilters,
        });
        this.syncApiState(
          state.filters,
          state.processesSelectedFilters,
          state.negativeProcessesSelectedFilters,
          filterType === FilterType.Negative ? state.dropdownFilters : items,
          filterType === FilterType.Negative ? items : state.negativeDropdownFilters,
          state.filterType
        );
      })
    );
  }

  @Action(actions.ToggleFilterItemByName)
  toggleItemByName(ctx: StateContext<FilterStateModel>, { name }: actions.ToggleFilterItemByName) {
    const state = ctx.getState();
    const currentProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const filterType = state.filterType[currentProcessId];
    const filters =
      filterType === FilterType.Negative ? state.negativeDropdownFilters[currentProcessId] : state.dropdownFilters[currentProcessId];
    return this.filterService.toggleFilterItemsByName(filters, name).pipe(
      tap((f: Filter[]) => {
        let search = this.store.selectSnapshot(ProcessSelectors.getSearch);
        if (search) {
          search = { ...search, fitlers: f };
          this.store.dispatch(new AddSearch(search));
        }
        const dropdownFilters = filterType === FilterType.Negative ? state.negativeDropdownFilters : state.dropdownFilters;
        let items = <{ [id: number]: Filter[] }>{};
        if (dropdownFilters) {
          items = { ...dropdownFilters };
        }
        const itemExists = items[currentProcessId];
        if (!itemExists) {
          items = { ...dropdownFilters, [currentProcessId]: f };
        } else {
          items[currentProcessId] = f;
        }
        const updatedDropDownFilter = filterType === FilterType.Negative ? state.dropdownFilters : items;
        const updatedNegativeDropdownFilters = filterType === FilterType.Negative ? items : state.negativeDropdownFilters;
        ctx.patchState({
          dropdownFilters: updatedDropDownFilter,
          negativeDropdownFilters: updatedNegativeDropdownFilters,
        });
        this.syncApiState(
          state.filters,
          state.processesSelectedFilters,
          state.negativeProcessesSelectedFilters,
          updatedDropDownFilter,
          updatedNegativeDropdownFilters,
          state.filterType
        );
      })
    );
  }

  @Action(actions.AddSelectedFilter)
  addSelectedFilterToCurrentProcess(ctx: StateContext<FilterStateModel>, { payload }: actions.AddSelectedFilter) {
    const currenProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const state = ctx.getState();
    const filterType = state.filterType[currenProcessId];
    const currentProcessSelectedFilters =
      filterType === FilterType.Negative ? state.negativeProcessesSelectedFilters : state.processesSelectedFilters;
    const processExists = currentProcessSelectedFilters.find((t) => t.processId === currenProcessId);
    const updatedSelectedFilters = currentProcessSelectedFilters.map((item) => {
      if (item.processId === currenProcessId) {
        let filters = [];
        if (item.selectedFilters) {
          filters = [...item.selectedFilters.filter((f) => f.id !== payload.id), payload];
        } else {
          filters.push(payload);
        }
        return { ...item, selectedFilters: filters };
      }
      return item;
    });
    const processesSelectedFilters = <ProcessSelectedFilters>{
      processId: currenProcessId,
      selectedFilters: [payload],
    };
    let updatedProcessesSelectedFilters: ProcessSelectedFilters[];
    let updatedNegativeProcessesSelectedFilters: ProcessSelectedFilters[];
    if (processExists) {
      updatedProcessesSelectedFilters = filterType === FilterType.Negative ? state.processesSelectedFilters : updatedSelectedFilters;
      updatedNegativeProcessesSelectedFilters =
        filterType === FilterType.Negative ? updatedSelectedFilters : state.negativeProcessesSelectedFilters;
    } else {
      updatedProcessesSelectedFilters =
        filterType === FilterType.Negative ? state.processesSelectedFilters : [...state.processesSelectedFilters, processesSelectedFilters];
      updatedNegativeProcessesSelectedFilters =
        filterType === FilterType.Negative
          ? [...state.negativeProcessesSelectedFilters, processesSelectedFilters]
          : state.negativeProcessesSelectedFilters;
    }
    ctx.patchState({
      processesSelectedFilters: updatedProcessesSelectedFilters,
      negativeProcessesSelectedFilters: updatedNegativeProcessesSelectedFilters,
    });
    this.syncApiState(
      state.filters,
      updatedProcessesSelectedFilters,
      updatedNegativeProcessesSelectedFilters,
      state.dropdownFilters,
      state.negativeDropdownFilters,
      state.filterType
    );
  }

  @Action(actions.RemoveSelectedFilter)
  removeSelectedFilterFromCurrentProcess(ctx: StateContext<FilterStateModel>, { payload }: actions.RemoveSelectedFilter) {
    const state = ctx.getState();
    const currenProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const filterType = state.filterType[currenProcessId];
    const currentProcessSelectedFilters =
      filterType === FilterType.Negative ? state.negativeProcessesSelectedFilters : state.processesSelectedFilters;
    const updatedProcessesSelectedFilters = currentProcessSelectedFilters.map((processSelectedFilters) => {
      if (processSelectedFilters.processId === currenProcessId) {
        const updatedFilters = processSelectedFilters.selectedFilters.map((f) => {
          return {
            ...f,
            items: f.items.map((i) => {
              if (i.id === payload.id && i.selected === true) {
                return <FilterItem>{ ...i, selected: false };
              }
              return i;
            }),
          };
        });
        return { ...processSelectedFilters, selectedFilters: updatedFilters };
      }
      return processSelectedFilters;
    });
    ctx.patchState({
      processesSelectedFilters: filterType === FilterType.Negative ? state.processesSelectedFilters : updatedProcessesSelectedFilters,
      negativeProcessesSelectedFilters:
        filterType === FilterType.Negative ? updatedProcessesSelectedFilters : state.negativeProcessesSelectedFilters,
    });
    this.syncApiState(
      state.filters,
      filterType === FilterType.Negative ? state.processesSelectedFilters : updatedProcessesSelectedFilters,
      filterType === FilterType.Negative ? updatedProcessesSelectedFilters : state.negativeProcessesSelectedFilters,
      state.dropdownFilters,
      state.negativeDropdownFilters,
      state.filterType
    );
  }

  @Action(actions.DeleteSelectedFiltersForProcess)
  deleteSelectedFiltersForProcess(ctx: StateContext<FilterStateModel>, { payload }: actions.DeleteSelectedFiltersForProcess) {
    const state = ctx.getState();
    const currentProcessFilters = state.processesSelectedFilters;
    const currentNegativeProcessFilters = state.negativeProcessesSelectedFilters;
    const processesSelectedFilters = removeItem<ProcessSelectedFilters>((item) => item.processId === payload)(currentProcessFilters);
    const negativeProcessesSelectedFilters = removeItem<ProcessSelectedFilters>((item) => item.processId === payload)(
      currentNegativeProcessFilters
    );
    const dropdownFilters = ctx.getState().dropdownFilters;
    let items = <{ [id: number]: Filter[] }>{};
    if (dropdownFilters) {
      items = { ...dropdownFilters };
    }
    const itemExists = items[payload];
    if (itemExists) {
      delete items[payload];
    }
    const negativeDropdownFilters = ctx.getState().negativeDropdownFilters;
    let negativeItems = <{ [id: number]: Filter[] }>{};
    if (negativeDropdownFilters) {
      negativeItems = { ...negativeDropdownFilters };
    }
    const negativeItemExists = negativeItems[payload];
    if (negativeItemExists) {
      delete negativeItems[payload];
    }
    ctx.patchState({
      processesSelectedFilters,
      negativeProcessesSelectedFilters: negativeProcessesSelectedFilters,
      dropdownFilters: items,
      negativeDropdownFilters: negativeItems,
    });
    this.syncApiState(
      state.filters,
      processesSelectedFilters,
      negativeProcessesSelectedFilters,
      state.dropdownFilters,
      state.negativeDropdownFilters,
      state.filterType
    );
  }

  @Action(actions.SetFilterType)
  setFilterType(ctx: StateContext<FilterStateModel>, { filterType }: actions.SetFilterType) {
    const state = ctx.getState();
    const filterTypes = state.filterType;
    const currenProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const updatedFilterTypes = {
      ...filterTypes,
      [currenProcessId]: filterType,
    };
    ctx.patchState({ filterType: updatedFilterTypes });
    this.syncApiState(
      state.filters,
      state.processesSelectedFilters,
      state.negativeProcessesSelectedFilters,
      state.dropdownFilters,
      state.negativeDropdownFilters,
      state.filterType
    );
  }

  @Action(actions.ClearNegativeFilters)
  clearNegativeFilters(ctx: StateContext<FilterStateModel>) {
    const state = ctx.getState();
    const negativeProcessesSelectedFilters = state.negativeProcessesSelectedFilters;
    const negativeDropdownFilters = state.negativeDropdownFilters;
    const currenProcessId = this.store.selectSnapshot(AppState.getCurrentProcessId);
    const updatedNegativeProcessesSelectedFilters = removeItem<ProcessSelectedFilters>((item) => item.processId === currenProcessId)(
      negativeProcessesSelectedFilters
    );
    const updatedNegativeDropdownFilters = {
      ...negativeDropdownFilters,
      [currenProcessId]: undefined,
    };
    ctx.patchState({
      negativeProcessesSelectedFilters: updatedNegativeProcessesSelectedFilters,
      negativeDropdownFilters: updatedNegativeDropdownFilters,
    });
    this.syncApiState(
      state.filters,
      state.processesSelectedFilters,
      updatedNegativeProcessesSelectedFilters,
      state.dropdownFilters,
      negativeDropdownFilters,
      state.filterType
    );
  }

  @Action(actions.ReloadFiltersData)
  reloadFilters(
    ctx: StateContext<FilterStateModel>,
    {
      filters,
      processesSelectedFilters,
      negativeProcessesSelectedFilters,
      dropdownFilters,
      negativeDropdownFilters,
      filterType,
    }: actions.ReloadFiltersData
  ) {
    ctx.setState({
      filters: filters,
      processesSelectedFilters: processesSelectedFilters,
      negativeProcessesSelectedFilters: negativeProcessesSelectedFilters,
      dropdownFilters: dropdownFilters,
      negativeDropdownFilters: negativeDropdownFilters,
      filterType: filterType,
    });
  }

  private syncApiState(
    filters: Filter[],
    processesSelectedFilters: ProcessSelectedFilters[],
    negativeProcessesSelectedFilters: ProcessSelectedFilters[],
    dropdownFilters: { [id: number]: Filter[] },
    negativeDropdownFilters: { [id: number]: Filter[] },
    filterType: { [id: number]: FilterType }
  ) {
    const userSyncData: UserStateSyncData = {
      filters: filters,
      processesSelectedFilters: processesSelectedFilters,
      negativeProcessesSelectedFilters: negativeProcessesSelectedFilters,
      dropdownFilters: dropdownFilters,
      negativeDropdownFilters: negativeDropdownFilters,
      filterType: filterType,
    };
    const userData = JSON.stringify(userSyncData);
    this.store.dispatch(new AppUserDataSync(userData));
  }
}
