import {
  Component,
  ChangeDetectionStrategy,
  Input,
  EventEmitter,
  Output,
  ViewChildren,
  QueryList,
  ChangeDetectorRef,
  AfterViewInit,
  Renderer2,
  ElementRef,
} from '@angular/core';
import { Filter, SelectFilterOption } from '../../models';
import { cloneFilter } from '../../utils';
import { FilterResetStrategy } from './defs';

@Component({
  selector: 'app-selected-filter-options',
  templateUrl: './selected-filter-options.component.html',
  styleUrls: ['./selected-filter-options.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SelectedFilterOptionsComponent implements AfterViewInit {
  @Input() value: Filter[];
  @Input() module: 'Customer' | 'Branch' = 'Branch';
  @Input() resetStrategy: FilterResetStrategy = 'all';
  @Input() showResetButton = true;

  @Output() filterChanged = new EventEmitter<Filter[]>();
  @Output() reset = new EventEmitter<void>();

  @ViewChildren('filteredItem') items: QueryList<ElementRef>;

  collapsedStateNumberOfSelectedFiltersShown = 3;
  isCollapsed = true;

  constructor(private cdr: ChangeDetectorRef, private renderer: Renderer2) {}

  ngAfterViewInit() {
    this.items.changes.subscribe((_) => {
      this.cdr.detectChanges();

      this.updateCollapsed(this.isCollapsed);
    });

    setTimeout(() => this.cdr.detectChanges(), 0);
  }

  updateCollapsed(isCollapsed: boolean) {
    this.isCollapsed = isCollapsed;

    this.items.forEach((item, index) => {
      if (this.isCollapsed && index >= this.collapsedStateNumberOfSelectedFiltersShown) {
        this.renderer.addClass(item.nativeElement, 'collapsed');
      } else {
        this.renderer.removeClass(item.nativeElement, 'collapsed');
      }
    });

    this.cdr.detectChanges();
  }

  resetFilters() {
    switch (this.resetStrategy) {
      case 'default':
        this.filterChanged.emit(this.resetToDefault(this.value));
        break;

      case 'all':
        this.filterChanged.emit(this.deselectAllFilters(this.value));
        break;
    }

    this.reset.emit();
  }

  clearFilter(filter: SelectFilterOption) {
    let clonedFilter = this.value.map(cloneFilter);
    const deselect = (sf: SelectFilterOption) => {
      if (sf.name === filter.name) {
        return this.deselect(sf);
      }

      if (Array.isArray(sf.options)) {
        deselectIfMatch(sf.options);
      }
    };

    function deselectIfMatch(selectFilterOptions: SelectFilterOption[]) {
      selectFilterOptions.forEach((option) => {
        if (option.name === filter.name) {
          deselect(option);
        }

        if (Array.isArray(option.options)) {
          deselectIfMatch(option.options);
        }
      });
    }

    clonedFilter = clonedFilter
      .map((f) => {
        if (Array.isArray(f.options)) {
          f.options = f.options.map((o) => {
            deselect(o);

            return o;
          });
        }

        return f;
      })
      .map(cloneFilter);

    this.filterChanged.emit(clonedFilter);
  }

  deselectAllFilters(filters: Filter[]): Filter[] {
    return filters.reduce((acc, curr) => {
      if (Array.isArray(curr.options)) {
        curr.options.forEach((option) => this.deselect(option));
      }
      return acc.concat({ ...curr });
    }, []);
  }

  resetToDefault(filters: Filter[]): Filter[] {
    const updatedFilters = filters.map((filter) => {
      let updatedOptions = filter.options || [];
      if (filter.options) {
        updatedOptions = filter.options.map((f) => ({
          ...this.resetToDefaultValue(f),
        }));
      }

      return { ...filter, options: updatedOptions };
    });

    return updatedFilters;
  }

  private deselectAllMatches(filter: SelectFilterOption, match?: string) {
    if (filter.name === match) {
      filter.selected = false;
      return filter.options.forEach((option) => this.deselect(option));
    }

    if (this.hasNestedOptions(filter)) {
      filter.options.forEach((option) => option.name === match && this.deselectAllMatches(option));
    }
  }

  private deselect(filter: SelectFilterOption, match?: string): SelectFilterOption {
    if (this.hasNestedOptions(filter)) {
      filter.options.forEach((option) => this.deselect(option));
    }

    if (filter.name === match || !match) {
      filter.selected = false;
    }
    return filter;
  }

  private resetToDefaultValue(filter: SelectFilterOption): SelectFilterOption {
    return {
      ...filter,
      selected: filter.initial_selected_state,
      options: [...(filter.options ? filter.options.map((option) => this.resetToDefaultValue(option)) : [])],
    };
  }

  private hasNestedOptions(filter: SelectFilterOption): boolean {
    return filter && Array.isArray(filter.options) && !!filter.options.length;
  }
}
