import { action, computed, makeObservable, observable, reaction } from 'mobx';
import {
  addEventListener,
  CollectionState,
  FiltersMap,
  Sort,
  SortOrder,
  withoutDefaults,
  WixPatternsContainer,
} from '@wix/bex-core';
import { ToolbarCollectionState } from './ToolbarCollectionState';
import { MultiLevelSortingStateDragAndDrop } from './MultiLevelSortingDragAndDrop';
import { Column } from '../model';
import type { ReactNode } from 'react';
import { cairoFiltersPanelUsed } from '@wix/bex-core/bi';
import { v4 as uuid } from 'uuid';

export interface SortableColumn {
  key: string;
  columnId: string;
  name: ReactNode;
  direction: SortOrder;
}

export interface MultiLevelSortingStateParams<T, F extends FiltersMap> {
  readonly table: ToolbarCollectionState<T, F>;
  readonly container: WixPatternsContainer;
  readonly modalsContainerRef: {
    current: HTMLElement | null | undefined;
  };
}

export class MultiLevelSortingState<T, F extends FiltersMap> {
  table: ToolbarCollectionState<T, F>;
  container;
  sortingCollection;
  dragAndDrop;

  orderedSortableColumns = observable.array<SortableColumn>();

  touchedColumnId = '';
  lastManuallyAddedColumnId: string | null = null;

  constructor(props: MultiLevelSortingStateParams<T, F>) {
    this.table = props.table;
    this.container = props.container;

    this.sortingCollection = new CollectionState<SortableColumn>({
      ...this.container,
      events: {},
      queryName: [
        this.container.environment.componentId,
        this.table.collection.queryName,
        'multiLevelSorting',
      ]
        .filter(Boolean)
        .join('/'),
      fetchData: async () => {
        return {
          items: this.orderedSortableColumns,
        };
      },
      filters: {},
      itemKey: (item) => item.key,
      itemName: (item) => String(item.name),
    });

    this.dragAndDrop = new MultiLevelSortingStateDragAndDrop({
      multiLevelSortingState: this,
      modalsContainerRef: props.modalsContainerRef,
    });

    makeObservable(this, {
      hasAvailableSortingColumns: computed,
      sortListSize: computed,
      sortableColumnsMap: computed,
      sortableColumns: computed,
      availableColumns: computed,
      addSort: action,
      removeSort: action,
      changeSortOrder: action,
      replaceSort: action,
    });
  }

  get sortableColumns() {
    return this.table.columns.filter((column) => column.sortable);
  }

  get sortableColumnsMap() {
    return new Map(this.sortableColumns.map((column) => [column.id, column]));
  }

  get hasAvailableSortingColumns() {
    return this.sortableColumns.length - this.orderedSortableColumns.length > 0;
  }

  get sortListSize() {
    return this.orderedSortableColumns.length;
  }

  get availableColumns() {
    const { orderedSortableColumns, sortableColumns } = this;

    return new Set(
      sortableColumns
        .filter((column) =>
          orderedSortableColumns.every(
            (orderedSortableColumn) =>
              orderedSortableColumn.columnId !== column.id,
          ),
        )
        .map((sortableColumn) => sortableColumn.id),
    );
  }

  get currentSortOrder() {
    return this.orderedSortableColumns.map(({ columnId, direction }) => ({
      fieldName: columnId,
      order: direction,
    }));
  }

  get reportBI() {
    return this.table.reportBi;
  }

  private getNextAvailableSortableColumn() {
    for (const column of this.sortableColumns) {
      if (this.availableColumns.has(column.id)) {
        return column;
      }
    }

    return null;
  }

  private toSortQuery(): Sort[] {
    return this.orderedSortableColumns.map(({ columnId, direction }) => ({
      field: columnId,
      direction,
    }));
  }

  private isDifferentFromSortState() {
    const {
      table: {
        collection: {
          query: {
            sort: { value: sortStateValue },
          },
        },
      },
      orderedSortableColumns,
    } = this;

    if (sortStateValue.length !== orderedSortableColumns.length) {
      return true;
    }

    for (let i = 0; i < orderedSortableColumns.length; i++) {
      if (
        sortStateValue[i].direction !== orderedSortableColumns[i].direction ||
        sortStateValue[i].field !== orderedSortableColumns[i].columnId
      ) {
        return true;
      }
    }

    return false;
  }

  getAvailableOptions(columnId: string) {
    return this.sortableColumns
      .filter((column) => {
        return column.id === columnId || this.availableColumns.has(column.id);
      })
      .map((column) => ({
        id: column.id,
        value:
          (typeof column.title === 'string' && column.title) ||
          column.name ||
          column.id,
      }));
  }

  addSort({
    column,
    order,
    manual = false,
  }: { column?: Column; order?: SortOrder; manual?: boolean } = {}) {
    const nextColumn = column ?? this.getNextAvailableSortableColumn();

    if (nextColumn) {
      const sortableColumn = {
        key: uuid(),
        columnId: nextColumn.id,
        name: nextColumn.title || nextColumn.name || nextColumn.id,
        direction: order ?? 'asc',
      };

      this.touchedColumnId = sortableColumn.columnId;
      this.orderedSortableColumns.push(sortableColumn);

      if (manual) {
        this.lastManuallyAddedColumnId = sortableColumn.columnId;
      }
    }
  }

  removeSort(columnId: string) {
    const index = this.orderedSortableColumns.findIndex(
      (sortableColumn) => sortableColumn.columnId === columnId,
    );

    if (index !== -1) {
      this.touchedColumnId = columnId;
      this.orderedSortableColumns.splice(index, 1);
    }
  }

  changeSortOrder(sortableColumnId: string, direction: SortOrder) {
    const { orderedSortableColumns } = this;

    const index = orderedSortableColumns.findIndex(
      ({ columnId }) => columnId === sortableColumnId,
    );

    if (index !== -1) {
      this.touchedColumnId = sortableColumnId;
      orderedSortableColumns[index].direction = direction;
    }
  }

  replaceSort(oldColumnId: string, newColumnId: string) {
    const { orderedSortableColumns } = this;

    const index = orderedSortableColumns.findIndex(
      ({ columnId }) => oldColumnId === columnId,
    );

    if (index !== -1) {
      this.touchedColumnId = newColumnId;
      orderedSortableColumns[index].columnId = newColumnId;
      orderedSortableColumns[index].name =
        this.sortableColumnsMap.get(newColumnId)!.name;
    }
  }

  onSortStateChange = action(() => {
    const {
      table: {
        collection: { query },
      },
    } = this;

    this.orderedSortableColumns.clear();

    for (const sort of query.sort.value) {
      const column = this.sortableColumnsMap.get(sort.field);

      if (column) {
        this.addSort({
          column,
          order: sort.direction,
        });
      }
    }
  });

  sort() {
    const { collection } = this.table;
    const { query, emitter } = collection;

    emitter.emit('beforeSortStart', {
      origin: 'SidePanel',
    });

    query.sort.setSortQuery(this.toSortQuery());

    emitter.emit('sortStart', {
      col: { id: this.touchedColumnId },
    });

    collection.scheduleSort({ clearResult: true });
  }

  reportSidePanelOpen() {
    this.reportBI(
      withoutDefaults(cairoFiltersPanelUsed)({
        currentSortOrder: JSON.stringify(this.currentSortOrder),
        numSortedColumns: this.sortListSize,
        isOpened: true,
        feature: 'Sort',
        origin: 'Sort CTA',
      }),
    );
  }

  reportSidePanelClose({
    origin = 'X Button',
  }: { origin?: 'X Button' | 'Sort CTA' } = {}) {
    this.reportBI(
      withoutDefaults(cairoFiltersPanelUsed)({
        currentSortOrder: JSON.stringify(this.currentSortOrder),
        numSortedColumns: this.sortListSize,
        isOpened: false,
        feature: 'Sort',
        origin,
      }),
    );
  }

  init() {
    const {
      table: {
        collection: { query },
      },
      sortingCollection,
    } = this;

    this.onSortStateChange();

    const disposers = [
      reaction(
        () => this.orderedSortableColumns.map((item) => ({ ...item })),
        () => {
          if (!this.isDifferentFromSortState()) {
            return;
          }

          sortingCollection.refreshAllPages();
          this.sort();
        },
      ),
      addEventListener(query.sort.events, 'change', this.onSortStateChange),
      this.sortingCollection.init(),
      this.dragAndDrop.init(),
    ];

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