import {
  FiltersMap,
  KeyedItem,
  QueryState,
  syncWithSharedQuery,
  TaskState,
} from '@wix/bex-core';
import {
  action,
  computed,
  makeObservable,
  observable,
  runInAction,
} from 'mobx';
import { NestedCollectionState } from './NestedCollectionState';
import { NestedCollectionLevelDescriptor } from './NestedCollectionLevelDescriptor';
import { NodeCollection } from './types';
import { createNestedOptimisticActions } from './createNestedOptimisticActions';

export interface NestedCollectionNodeStateBaseParams<T, F extends FiltersMap> {
  readonly levelDescriptor: NestedCollectionLevelDescriptor<T, F>;
  readonly notifyAll?: boolean;
}

export interface NestedCollectionNodeStateParams<T, F extends FiltersMap>
  extends NestedCollectionNodeStateBaseParams<T, F> {
  readonly nestedCollection: NestedCollectionState;
  readonly parentNode: NestedCollectionNodeState<any, any> | null;
  readonly parent: KeyedItem<any> | null;
  readonly nodeCollection?: NodeCollection<T, F>;
}

export class NestedCollectionNodeState<T, F extends FiltersMap> {
  readonly nestedCollection;
  readonly collection;
  readonly levelDescriptor: NestedCollectionLevelDescriptor<T, F>;
  readonly parent;
  readonly parentNode;
  readonly parentFilter;

  readonly parentKeyToChildrenMap = observable.map<
    string,
    NestedCollectionNodeState<any, any>
  >([], {
    deep: false,
  });

  _expanded = false;
  _hovered = false;

  _notifyAll;

  readonly initTask = new TaskState();

  constructor(params: NestedCollectionNodeStateParams<T, F>) {
    const { nestedCollection } = params;
    this.nestedCollection = nestedCollection;
    this.levelDescriptor = params.levelDescriptor;
    this.parent = params.parent;
    this.parentNode = params.parentNode;
    this._notifyAll = params.notifyAll;

    runInAction(() => {
      this._expanded =
        !nestedCollection.isFetchAllAborted && nestedCollection.expandAll;
    });

    const nestedOptimisticActionsState = {
      parentId: this.parent?.id ?? 'null',
      sequences: nestedCollection.sequences,
      query: nestedCollection.query as QueryState<F>,
      levelDescriptor: this.levelDescriptor,
    };

    const { collection, parentFilter } =
      params.nodeCollection ??
      this.levelDescriptor.createCollection(nestedOptimisticActionsState);

    createNestedOptimisticActions(this.nestedCollection.container, collection, {
      ...nestedOptimisticActionsState,
      parentFilter,
    });

    if (!nestedCollection.isFetchAllAborted) {
      collection.queryObserver.setOptions({
        ...collection.queryObserver.options,
        staleTime: Infinity,
      });
    }

    this.collection = collection;
    this.parentFilter = parentFilter;

    const {
      parent,
      nestedCollection: { query: sharedQuery },
    } = this;

    const { query } = collection;

    syncWithSharedQuery(query, sharedQuery);

    if (parent != null && parentFilter != null) {
      parentFilter.persistent = true;
      parentFilter.setValue([parent.key]);
    }

    makeObservable(this, {
      init: action,
      _toggleExpand: action,
      _changeHover: action,
      _expanded: observable.ref,
      _hovered: observable.ref,
      hoveredOrParentHovered: computed,
    });
  }

  async _toggleExpand() {
    this._expanded = !this._expanded;
    if (this._expanded) {
      try {
        await this.collection.initialFetch();
      } catch (e) {
        console.error(e);
      }
    }
  }

  get hoveredOrParentHovered(): boolean {
    return this._hovered || !!this.parentNode?.hoveredOrParentHovered;
  }

  _changeHover(hovered: boolean) {
    this._hovered = hovered;
  }

  init({ skipCollectionInit }: { skipCollectionInit?: boolean } = {}) {
    const { initTask, collection, nestedCollection } = this;

    if (collection._optimisticActions) {
      nestedCollection.events.on(
        'dispose',
        collection._optimisticActions.init(),
      );
    }

    const disposers = [
      !skipCollectionInit
        ? collection.init({
            skipInitialFetch:
              nestedCollection.isFetchAllAborted === true && !this._expanded,
            skipFilterListeners: true,
          })
        : undefined,
    ];

    initTask.runOnce(async () => {
      collection.initTask.runOnce();
      await collection.initTask.status.promise;
    });

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

  _createNode(
    parent: KeyedItem<any>,
    params: NestedCollectionNodeStateBaseParams<any, any>,
  ) {
    const { nestedCollection } = this;

    return new NestedCollectionNodeState({
      parent,
      parentNode: this,
      nestedCollection,
      levelDescriptor: params.levelDescriptor,
      notifyAll: params.notifyAll,
    });
  }
}
