import {
  ComputedQuery,
  Filter,
  FiltersMap,
  fixDomainForBI,
  PaginationModeRef,
  parseFqdn,
  PickerState,
  prefetchTranslations,
  RawFilters,
  selectionStatusWixPatternsPreset,
  withoutDefaults,
} from '@wix/bex-core';
import { PromiseState, reactive } from '@wix/bex-utils';
import { UsePickerModalParams } from './usePickerModal';
import { MultipleSelection } from '../../components/MaxSelection';
import { OnSelectOptions, RawFiltersParams } from './UsePickerModalParamsBase';
import { CustomModalContent } from '../customModalContent';
import mapValues from 'lodash/mapValues';
import some from 'lodash/some';
import {
  loadStart,
  pickerPickerOpenButton,
  pickerPickModalCtaButtonClickOrImmediateCta,
} from '@wix/bex-core/bi';
import type { PickerContentState } from '../../state';

export class PickerModalState<T, F extends FiltersMap> {
  readonly multipleRef = {} as { current: MultipleSelection | undefined };
  readonly pickerContentRef = {} as {
    current:
      | {
          onCreateNew?: (items?: T[]) => unknown;
        }
      | undefined;
  };
  readonly onSelectRef = {} as {
    current: (items: T[], options: OnSelectOptions<T>) => unknown;
  };
  readonly disableAutoSelectionRef = {} as { current: boolean | undefined };

  readonly bi;
  readonly _isModalOpen;
  readonly _isCustomContentVisible = reactive(false);
  readonly _customContent = reactive<CustomModalContent<T> | undefined>(
    undefined,
  );
  readonly _isOpenButtonClicked = reactive(false);

  readonly fetchFirstPage;
  readonly fetchPickerComponent;
  readonly fetchInitialProps;

  readonly minContentHeight;
  readonly shouldFilterNotAffectTotalCount;
  readonly queryName;
  readonly fetchData;
  readonly limit;
  readonly _paginationModeRef = {} as PaginationModeRef;
  readonly events;
  readonly filters;
  readonly environment;

  constructor(params: UsePickerModalParams<T, F>) {
    const {
      onSelect,
      errorMonitor,
      biLoggerFactory,
      environment,
      queryName,
      isOpenInitially = false,
      skipFirstPagePrefetch,
    } = params;

    this.minContentHeight = params.minContentHeight ?? 300;
    this.queryName = params.queryName;
    this.fetchData = params.fetchData;
    this.limit = params.limit;
    this._paginationModeRef.current = params.paginationMode;
    this.events = params.events;
    this.filters = params.filters ?? ({} as Partial<RawFiltersParams<F>>);
    this.shouldFilterNotAffectTotalCount =
      params.shouldFilterNotAffectTotalCount ?? (() => false);

    this.environment = environment;
    this.bi = biLoggerFactory()
      .updateDefaults({
        ...(environment.providerType === 'bm' && {
          _hostingPlatform: 'business-manager',
        }),
        artifactId: environment.artifactId,
        componentName: environment.componentName,
        cairoVersion: environment.uiLibVersion,
        ...parseFqdn(queryName),
        type: 'modal',
        componentType: 'Picker Modal',
      })
      .logger();

    this._isModalOpen = reactive(isOpenInitially);
    this.onSelectRef = { current: onSelect };

    const onPrefetchPromiseError = (err: unknown) => {
      errorMonitor.captureException(err);
      this.events?.onError?.({
        err,
        isOnline: true,
      });
    };

    this.fetchFirstPage = new PromiseState({
      onError: onPrefetchPromiseError,
      fn: async () => {
        const prefetchComputedQuery = ({
          filters: queryFilters,
        }: {
          filters: RawFilters<F>;
        }): ComputedQuery<F> => {
          const hasActiveFilters = some(
            queryFilters,
            (filter) => filter?.initialValue != null,
          );
          return {
            page: 1,
            limit: params.limit ?? 100,
            offset: 0,
            cursor: null,
            search: undefined,
            rawSearch: '',
            filters: queryFilters,
            rawFilters: queryFilters,
            hasActiveFilters,
            hasNonPersistentActiveFilters: hasActiveFilters,
          };
        };

        const firstPagePromise = !skipFirstPagePrefetch
          ? this.fetchData(
              prefetchComputedQuery({
                filters: mapValues(this.filters, (filter) =>
                  filter != null ? filter.initialValue : filter,
                ) as ComputedQuery<F & { search: Filter<string> }>['filters'],
              }),
            )
          : undefined;

        const [firstPageResult] = await Promise.all([firstPagePromise]);

        return {
          firstPageResult,
        };
      },
    });
    this.fetchPickerComponent = new PromiseState({
      onError: onPrefetchPromiseError,
      fn: async () => {
        return (await params.pickerContentComponent()).default;
      },
    });
    this.fetchInitialProps = new PromiseState({
      onError: onPrefetchPromiseError,
      fn: async () => {
        const [cairoMessages] = await Promise.all([
          prefetchTranslations({ errorMonitor })(environment.language).catch(
            (e) => {
              console.error(e);
              // Fallback to `PickerModalContent` -> `WixPatternsContainer` i18n init
              return {};
            },
          ),
          params.fetchInitialProps?.(),
        ]);

        return { cairoMessages };
      },
    });
  }

  get isCustomContentVisible() {
    return this._isCustomContentVisible.value;
  }

  get isModalOpen() {
    return this._isModalOpen.value;
  }

  get paginationMode() {
    return this._paginationModeRef.current;
  }

  set paginationMode(value: 'offset' | 'cursor' | undefined) {
    this._paginationModeRef.current = value;
  }

  prefetch = async () => {
    try {
      await Promise.all([
        this.fetchPickerComponent.once(),
        this.fetchFirstPage.once(),
        this.fetchInitialProps.once(),
      ]);
      return true;
    } catch (e) {
      console.error(e);
      return false;
    }
  };

  hover = () => {
    this.bi.report(
      pickerPickerOpenButton({
        action: 'hover',
        ...this._commonDynamicBiParams(),
      }),
    );
    this.prefetch();
  };

  onCreateNew = async (items?: T[]) => {
    const { pickerContentRef, _isCustomContentVisible } = this;

    pickerContentRef.current?.onCreateNew?.(items);

    if (_isCustomContentVisible.value) {
      _isCustomContentVisible.value = false;
    }
  };

  onPrimaryButtonClick = (
    state:
      | (PickerContentState<T, F> & {
          // Before multiple status refactor compat
          indeterminateMap?: { partialModeKeys?: string[] };
        })
      | PickerState<T, F>,
    additionalStepData?: unknown,
  ) => {
    const { picker, indeterminateMap } =
      'picker' in state
        ? state
        : { picker: state, indeterminateMap: undefined };

    const { collection } = picker;
    const { _isModalOpen, onSelectRef } = this;
    const {
      bulkSelect: {
        selectedValues,
        uncheckedValues,
        allSelected,
        indeterminateSet,
        select,
      },
    } = collection;
    _isModalOpen.value = false;
    const selectedCount = allSelected
      ? collection.result.total - uncheckedValues.length
      : selectedValues.length;

    onSelectRef.current(selectedValues, {
      uncheckedValues,
      isSelectAll: allSelected,
      selectedCount,
      // backwards compat with pikachu GA
      partiallySelectedIds: indeterminateSet
        ? Array.from(indeterminateSet)
        : indeterminateMap?.partialModeKeys ?? [],

      values: (select.toArray ?? []).map(({ value, status }) => ({
        status,
        value: value.item,
      })),
      defaultStatus: select.defaultStatus,
      additionalStepData,
    });
  };

  openModal = () => {
    const { _isOpenButtonClicked, _isModalOpen } = this;
    _isOpenButtonClicked.value = true;
    _isModalOpen.value = true;
  };

  closeModal = () => {
    const { _isModalOpen } = this;
    _isModalOpen.value = false;
  };

  openModalOrCTA = async () => {
    const { bi, _isOpenButtonClicked, fetchFirstPage, onSelectRef } = this;
    bi.report(
      pickerPickerOpenButton({
        action: 'click',
        ...this._commonDynamicBiParams(),
      }),
    );

    _isOpenButtonClicked.value = true;

    try {
      await fetchFirstPage.once();
      const { status } = fetchFirstPage;

      if (
        status.value.type === 'success' &&
        status.value.data.firstPageResult != null
      ) {
        const {
          firstPageResult: { total, items },
        } = status.value.data;

        if (!this.disableAutoSelectionRef.current && total === 1) {
          bi.report(
            pickerPickModalCtaButtonClickOrImmediateCta({
              isImmediate: true,
              itemsCnt: 1,
              listSize: total,
              filteredListSize: total,
              ...this._commonDynamicBiParams(),
            }),
          );
          onSelectRef.current(items, {
            uncheckedValues: [],
            isSelectAll: false,
            selectedCount: items.length,
            partiallySelectedIds: [],
            values: [],
            defaultStatus: selectionStatusWixPatternsPreset.checked,
          });
          return;
        }
      }
    } catch (e) {
      console.error(e);
    }

    this._isModalOpen.value = true;
  };

  get isOpenButtonClicked() {
    return this._isOpenButtonClicked.value;
  }

  get noAvailableItemsFound() {
    const { fetchFirstPage } = this;
    return (
      fetchFirstPage.status.value.type === 'success' &&
      fetchFirstPage.status.value.data.firstPageResult?.available === 0
    );
  }

  get showButtonLoadingIndication() {
    const { _isOpenButtonClicked, fetchFirstPage } = this;
    return (
      _isOpenButtonClicked.value &&
      fetchFirstPage.status.value.type === 'loading'
    );
  }

  get customContent() {
    return this._customContent.value;
  }

  _commonDynamicBiParams(): {} {
    return {
      maxItems: this.getMaxItems(),
    };
  }

  getMaxItems = () => {
    const { current: multiple } = this.multipleRef;
    return typeof multiple === 'number' && Number.isInteger(multiple)
      ? multiple
      : multiple
      ? undefined
      : 1;
  };

  onIsOpenChanged() {
    const { isModalOpen } = this;

    if (isModalOpen) {
      this.prefetch();
      this._reportModalLoaded();
    }
  }

  init() {
    const { bi } = this;

    bi.report(
      pickerPickerOpenButton({
        action: 'render',
        ...this._commonDynamicBiParams(),
      }),
    );

    // if the modal is already open, report startLoad BI, otherwise prefetch dependencies
    if (!this.isModalOpen) {
      // the timeout delay (500) indicates how soon (or late) we want to execute the callback, in case requestIdleCallback is not implemented
      const _requestIdleCallback =
        window.requestIdleCallback ??
        ((cb: () => void) => {
          return window.setTimeout(cb, 500);
        });

      const requestId = _requestIdleCallback(() => {
        this.fetchFirstPage.once();
      });

      return () => {
        if (window.cancelIdleCallback) {
          window.cancelIdleCallback(requestId);
        } else {
          window.clearTimeout(requestId);
        }
      };
    }

    return;
  }

  _reportModalLoaded() {
    this.bi.report(
      withoutDefaults(loadStart)({
        ...this._commonDynamicBiParams(),
        url: fixDomainForBI(window.location.href),
        featuresAvailability: JSON.stringify({}),
      }),
    );
  }
}
