import {
  WixPatternsContainer,
  CollectionState,
  FiltersMap,
} from '@wix/bex-core';
import { computed, makeObservable, observable } from 'mobx';
import {
  ToolbarCollectionState,
  ToolbarCollectionStateParamsInitParams,
} from './ToolbarCollectionState';
import { CollectionDragAndDropState } from '../components/DragAndDrop';
import { RectState } from './RectState';
import { GridSizePreset } from './gridSizePresets';
import { ResponsiveColumnsState } from './ResponsiveColumnsState';
import { GridParentState } from './GridParentState';
import type { Preset } from '../components/Grid/Grid.types';

export type LayoutType = 'grid' | 'list';

export interface GridBaseStateParams<T, F extends FiltersMap> {
  readonly collection: CollectionState<T, F>;
  readonly container: WixPatternsContainer;
  readonly toolbar: ToolbarCollectionState<any, Partial<F>>;
  readonly sizePreset: GridSizePreset;
  readonly range?: { start: number; length: number };
  readonly layoutType: LayoutType | undefined;
  readonly parentState: GridParentState<T, F>;
  readonly draggable?: 'none' | 'draggable';
}

export interface GridBaseStateInitParams
  extends ToolbarCollectionStateParamsInitParams {}

export class GridBaseState<T, F extends FiltersMap> {
  readonly toolbar;
  readonly container;
  readonly collection;
  readonly _containerRectState;
  readonly _responsiveColumnsState;
  readonly parentState;
  _sizePreset;
  layoutType;

  trailingItemsCount = 0;
  minItemWidth = 196;
  smallCardWidthThreshold = 200;
  preset: Preset = 'title';
  _draggable: 'none' | 'draggable' = 'none';
  _localLimit: number | null | undefined;
  _localStart: number;
  forceRenderIndexes: number[] | undefined = undefined;

  collectionDragAndDropState: CollectionDragAndDropState<T, F> | null = null;

  get _dnd() {
    return this.collectionDragAndDropState?.dnd;
  }

  constructor(params: GridBaseStateParams<T, F>) {
    this.container = params.container;
    this.collection = params.collection;
    this.parentState = params.parentState;
    this.layoutType = params.layoutType ?? 'grid';
    this._containerRectState = new RectState({
      container: this.container,
    });
    this._draggable = params.draggable ?? 'none';
    this.toolbar = params.toolbar as ToolbarCollectionState<
      unknown,
      Partial<F>
    >;
    this._responsiveColumnsState = new ResponsiveColumnsState({
      gridState: this,
      minItemWidth: this.minItemWidth,
      rectState: this._containerRectState,
    });
    this._sizePreset = params.sizePreset;
    this._localLimit = params.range?.length;
    this._localStart = params.range?.start ?? 0;

    makeObservable(this, {
      trailingItemsCount: observable.ref,
      forceRenderIndexes: observable.ref,
      preset: observable.ref,
      _draggable: observable.ref,
      draggable: computed,
      itemsToRenderCount: computed,
      itemsToRender: computed,
      rowCount: computed,
      isInteracting: computed,
      calcGridProps: computed,
      columnsCount: computed,
      _sizeProps: computed,
      itemKeyRecyclerOverride: computed,
      _localLimit: observable.ref,
      _localStart: observable.ref,
      itemsContentWidth: computed,
      containerPaddingTop: computed,
      gap: computed,
      _sizePreset: observable.ref,
      layoutType: observable.ref,
    });
  }

  get sizePreset() {
    return this.parentState._sizePreset ?? this._sizePreset;
  }

  get imagePlacement() {
    return this.sizePreset.imagePlacement;
  }

  get gap() {
    const { layoutType } = this;
    if (layoutType === 'list') {
      return 12;
    }

    return 24;
  }

  get containerPaddingTop() {
    const { layoutType } = this;
    if (layoutType === 'list') {
      return 0;
    }

    return this.toolbar.contentPadding;
  }

  get aspectRatio() {
    return this.sizePreset.aspectRatio;
  }

  get footerHeights() {
    return this.sizePreset.footerHeights;
  }

  get itemsContentWidth() {
    return this._responsiveColumnsState.itemsContentWidth;
  }

  get draggable() {
    if (this._dnd?.isDisabled) {
      return 'none' as const;
    }
    return this._draggable;
  }

  get window() {
    return this.container.window;
  }

  get itemsToRenderCount() {
    const { collection, trailingItemsCount, _localLimit, _localStart } = this;
    const {
      result: { keyedItems },
    } = collection;

    const itemsCount =
      _localLimit != null
        ? Math.min(_localLimit, keyedItems.length)
        : keyedItems.length;

    return collection.fetchedLastPage ||
      (_localLimit != null && _localStart + _localLimit < keyedItems.length)
      ? itemsCount + trailingItemsCount
      : itemsCount - Math.floor(itemsCount % this.columnsCount);
  }

  get reportBi() {
    return this.toolbar.reportBi;
  }

  get columnsCount() {
    return this.calcGridProps.columnCount;
  }

  get containerRect() {
    return this.toolbar.containerRect;
  }

  get rowCount() {
    const { itemsToRenderCount, columnsCount } = this;

    return Math.ceil(itemsToRenderCount / columnsCount);
  }

  get isInteracting() {
    return this._dnd?.isActive || this._dnd?.isDropping;
  }

  get itemsToRender(): null[] {
    const { itemsToRenderCount } = this;
    return new Array(itemsToRenderCount).fill(null);
  }

  getItemRenderTypeAt(index: number) {
    const { collection, itemsToRenderCount, _localStart } = this;
    const {
      result: { keyedItems },
    } = collection;

    if (index >= itemsToRenderCount) {
      return null;
    }

    const indexInCollection = index + _localStart;

    if (indexInCollection >= keyedItems.length) {
      return 'trailing';
    }

    return keyedItems[indexInCollection];
  }

  get errorMonitor() {
    return this.container.errorMonitor;
  }

  get _sizeProps() {
    const {
      aspectRatio,
      preset,
      draggable,
      _responsiveColumnsState: {
        gap,
        columnCount: responsiveColumnCount,
        itemsContentWidth: width,
        widthWithLastColumnGap,
      },
      layoutType,
    } = this;

    const columnCount = layoutType === 'list' ? 1 : responsiveColumnCount;

    const widthWithoutGaps = width - (columnCount - 1) * gap;
    const columnWidth = widthWithoutGaps / columnCount;

    const cardFooterSize = 'small' as const;

    const cardFooterHeight =
      this.footerHeights[preset][draggable][cardFooterSize];
    const cardTopImageHeight = columnWidth * aspectRatio;
    const rowHeight =
      cardTopImageHeight + (cardFooterHeight > 0 ? cardFooterHeight + 1 : 0);

    const columnWidthIncludingGap = widthWithLastColumnGap / columnCount;
    const rowHeightIncludingGap = rowHeight + gap;

    return {
      columnCount,
      columnWidth: () => columnWidth,
      columnWidthIncludingGap: () => columnWidthIncludingGap,
      rowHeight: () => rowHeight,
      rowHeightFixed: rowHeight,
      cardTopImageHeight,
      rowHeightIncludingGap: () => rowHeightIncludingGap,
      cardFooterHeight,
      cardFooterSize,
    };
  }

  get calcGridProps() {
    const {
      toolbar: { itemsContentHeight },
      _sizeProps: { rowHeightFixed },
    } = this;

    const overscanCount = Math.round(
      (itemsContentHeight / rowHeightFixed) * 1.5,
    );

    return {
      ...this._sizeProps,
      overscanCount,
    };
  }

  get getCollectionSnapshot() {
    return this.toolbar.getCollectionSnapshot;
  }

  onAddItemClick = (origin?: string) => {
    this.toolbar.toolbarBIReporter.onAddItemClick(origin);
  };

  onAddItemActionComplete = async (items: T[]) => {
    return this.toolbar.onAddItemActionComplete(items);
  };

  get itemKeyRecyclerOverride() {
    const { collection } = this;
    const {
      result: { keyedItems },
    } = collection;
    return (index: number) => {
      return keyedItems[index]?.key ?? index;
    };
  }

  get dragAndDropCategories() {
    return this.toolbar.dragAndDropCategories;
  }

  get listAttributes() {
    return {
      ...this._dnd?.nullAnnouncements.listAttributes?.(),
      role: 'list',
    };
  }

  _subscribe() {
    const {
      parentState: { _subGrids },
    } = this;

    if (!_subGrids) {
      return;
    }

    _subGrids.push(this);

    return () => {
      const i = _subGrids.indexOf(this);
      i !== -1 && _subGrids.splice(i, 1);
    };
  }

  init() {
    return this._subscribe();
  }
}
