import {
  ArrayField,
  WixPatternsContainer,
  KeyedItem,
  SelectState,
  ToggleState,
} from '@wix/bex-core';
import { action, computed, makeObservable, reaction } from 'mobx';
import { EventEmitter } from '@wix/bex-core/events';
import { FilterToolbarState } from '../providers';

export interface DropdownSelectStateParams<V> {
  container: WixPatternsContainer;
  toolbar: FilterToolbarState;
  select?: SelectState<KeyedItem<V>>;
  filter: ArrayField<V>;
}

export class DropdownSelectState<V> {
  readonly container;
  readonly toolbar;
  readonly select: SelectState<KeyedItem<V>>;
  readonly _filter: DropdownSelectStateParams<V>['filter'];

  readonly dropdown = new ToggleState();

  readonly events = new EventEmitter();

  constructor(params: DropdownSelectStateParams<V>) {
    this.container = params.container;
    this.toolbar = params.toolbar;
    this.select =
      params.select ??
      new SelectState<KeyedItem<V>>({
        keyGetter: (value) => value.key,
      });
    this._filter = params.filter;

    makeObservable(this, {
      filterKeyedItems: computed,
      filterKeyedItemsMap: computed,
      finalFilterKeyedItems: computed,
      finalFilterKeyedItemsMap: computed,
      selectedItems: computed({
        keepAlive: true,
      }),
      set: action,
      delete: action,
      deleteKey: action,
      init: action.bound,
      open: action.bound,
      clear: action.bound,
      syncSelectedItems: action.bound,
      syncSelectedItemsIfChanged: action.bound,
    });

    this.syncSelectedItems();
  }

  get filterKeyedItemsMap() {
    return new Map<string, KeyedItem<V>>(
      this.filterKeyedItems.map((e) => [e.key, e]),
    );
  }

  get filter(): ArrayField<V> {
    return this.toolbar.getPendingFilter(this._filter);
  }

  get hasPendingFilter() {
    return this._filter !== this.filter;
  }

  toKeyedItem = (item: V, index: number) => {
    const { filter } = this;

    const key = filter.itemKey(item);
    return {
      id: key,
      key,
      item,
      index,
      indexWithinPage: index,
      pageIndex: 0,
    };
  };

  get finalFilterKeyedItems() {
    const { _filter } = this;
    return _filter.value.map(this.toKeyedItem);
  }

  get finalFilterKeyedItemsMap() {
    return new Map<string, KeyedItem<V>>(
      this.finalFilterKeyedItems.map((e) => [e.key, e]),
    );
  }

  get filterKeyedItems() {
    const { filter } = this;
    return filter.value.map(this.toKeyedItem);
  }

  syncSelectedItems() {
    const { select, events } = this;
    select.clear();
    select.finalizeMany(this.filterKeyedItems);
    events.emit('syncSelectedItems');
  }

  syncSelectedItemsIfChanged() {
    const { selectedItems, filter } = this;
    if (filter.value !== selectedItems) {
      this.syncSelectedItems();
    }
  }

  get selectedItems() {
    const { select } = this;
    return select.selectedValues.map(({ item }) => item);
  }

  set(item: KeyedItem<V>) {
    const { select } = this;
    select.set(item);
    this.refreshIfChanged({ actionType: 'add', clickedValueKey: item.key });
  }

  setSingle(item: KeyedItem<V>) {
    const { select } = this;
    select.clear();
    this.set(item);
  }

  clear() {
    const { select } = this;
    select.clear();
    this.refreshIfChanged({ actionType: 'remove', clickedValueKey: '' });
  }

  delete(item: KeyedItem<V>) {
    const { select } = this;
    select.delete(item);
    this.refreshIfChanged({ actionType: 'remove', clickedValueKey: item.key });
  }

  deleteKey(key: string) {
    const { select } = this;
    select.deleteKey(key);
    this.refreshIfChanged({ actionType: 'remove', clickedValueKey: key });
  }

  refreshIfChanged(
    options: { actionType?: 'add' | 'remove'; clickedValueKey?: string } = {},
  ) {
    const { selectedItems, filter } = this;

    if (filter == null) {
      return;
    }

    if (filter.value === selectedItems) {
      return;
    }

    filter.setValue(selectedItems, {
      emitEvents: ['change', 'beforeRefresh', 'refresh'],
      ...options,
    });
  }

  close() {
    const { dropdown, events } = this;
    dropdown.setOff();
    events.emit('close');
  }

  open() {
    const { dropdown, events } = this;
    dropdown.setOn();
    events.emit('open');
  }

  closeAndChangeSelectedItems() {
    this.close();
    const { selectedItems, filter } = this;

    if (filter.value === selectedItems) {
      return;
    }

    filter.setValue(selectedItems, {
      emitEvents: ['change', 'beforeRefresh', 'refresh'],
      actionType: 'add',
    });
  }

  init() {
    const { filter } = this;

    const disposers = [
      reaction(() => filter?.value, this.syncSelectedItemsIfChanged),
    ];

    return () => {
      disposers.forEach((d) => d());
    };
  }
}
