import { TextUniDriver } from '@wix/design-system/dist/testkit/unidriver';
import { InputWithOptionsUniDriver } from '@wix/design-system/dist/types/InputWithOptions/InputWithOptions.uni.driver';
import { UniDriver, waitFor } from '@wix/wix-ui-test-utils/unidriver';
import shuffle from 'lodash/shuffle';
import { isSled2, isSled3 } from '../../unidriver/nativeEnv';

export const CollectionDropdownFilterUniDriver = <
  T extends Omit<InputWithOptionsUniDriver, 'driver'>,
>(
  optionsDriver: T,
  dataHook: string,
  base: UniDriver,
  body: UniDriver,
) => {
  const waitForOptions = async (total = 1, timeout = 3000) => {
    await waitFor(
      async () =>
        (await optionsDriver.dropdownLayoutDriver.optionsLength()) >= total,
      timeout,
    );

    return optionsDriver.dropdownLayoutDriver.options();
  };

  const getDropdownLayoutBase = async () => {
    const contentDataHook = await base.attr('data-content-hook');
    if (contentDataHook) {
      return body.$(`[data-content-element="${contentDataHook}"]`);
    }

    const baseDataHook = await base.attr('data-hook');

    return body.$(`[data-hook="${baseDataHook}-content"]`);
  };

  const getOptions = async (ids: string[]) => {
    return ids.map(async (id) => {
      return optionsDriver.dropdownLayoutDriver.optionById(id);
    });
  };

  const getOptionsCount = () => {
    return optionsDriver.dropdownLayoutDriver.optionsLength();
  };

  const getAvailableOptionsIds = async () => {
    const options = await optionsDriver.dropdownLayoutDriver.options();

    const maybeIds = await Promise.all(
      options.map(async (option) => {
        const hook = await option.element().attr('data-hook');
        return hook?.replace(/^dropdown-item-/, '');
      }),
    );

    return maybeIds.filter((value): value is string => value != null);
  };

  const getInputPlaceholder = () => {
    return optionsDriver.inputDriver.getPlaceholder();
  };

  const isNoSearchResults = async () =>
    (await getDropdownLayoutBase()).$('[data-hook="empty-state"]').exists();

  const isErrorStateExists = async () =>
    (await getDropdownLayoutBase())
      .$('[data-hook="filter-error-state"]')
      .exists();

  const getEmptyStateText = async () =>
    TextUniDriver(
      (await getDropdownLayoutBase()).$('[data-hook="empty-state-text"]'),
      body,
    ).getText();

  const initialLoaderExists = async () =>
    (await getDropdownLayoutBase()).$('[data-hook="initial-loader"]').exists();
  const infiniteScrollLoaderExists = async () =>
    (await getDropdownLayoutBase())
      .$('[data-hook="dropdownLayout-loader"]')
      .exists();

  const clickOptions = async (ids: string[]) => {
    for (const id of ids) {
      await optionsDriver.dropdownLayoutDriver.clickAtOptionByDataHook(
        `dropdown-item-${id}`,
      );
    }

    return ids;
  };

  const existInFilter = async (dataHook: string) =>
    (await getDropdownLayoutBase()).$(`[data-hook="${dataHook}"]`).exists();

  const selectRandomOptions = async (
    count: number,
    options: { closeDropdown?: boolean } = {},
  ) => {
    await optionsDriver.inputDriver.click();

    const ids = await getRandomOptions(count);

    return selectOptions(ids, options);
  };

  const close = () => optionsDriver.dropdownLayoutDriver.mouseClickOutside();
  const clickInputClear = () => optionsDriver.inputDriver.clickClear();

  const getRandomOptions = async (count: number) => {
    await waitForOptions(count);

    const options = await optionsDriver.dropdownLayoutDriver.options();
    const maybeIds = await Promise.all(
      options.map(async (option) => {
        const dataHook = await option.element().attr('data-hook');
        return dataHook?.replace(/^dropdown-item-/, '');
      }),
    );

    const ids = maybeIds.filter((value): value is string => value != null);

    return shuffle(ids).slice(0, count);
  };

  const selectOptions = async (
    ids: string[],
    { closeDropdown = true }: { closeDropdown?: boolean } = {},
  ) => {
    await optionsDriver.inputDriver.click();

    await waitForOptions();
    const clickedIds = await clickOptions(ids);

    if (closeDropdown) {
      await close();
    }

    return clickedIds;
  };

  const selectOption = async (id: string) => {
    await optionsDriver.inputDriver.click();

    await waitForOptions();
    const clickedId = await clickOptions([id]);

    return clickedId[0] || [];
  };

  const hasDivider = async () => {
    return (await getAvailableOptionsIds()).some(
      (id) => id === 'promoted-section-divider',
    );
  };

  return {
    ...optionsDriver,
    waitForOptions,
    getOptions,
    clickOptions,
    close,
    clickInputClear,
    selectOptions,
    selectOption,
    selectRandomOptions,
    getRandomOptions,
    getOptionsCount,
    getAvailableOptionsIds,
    getInputPlaceholder,
    isNoSearchResults,
    getEmptyStateText,
    isErrorStateExists,
    initialLoaderExists,
    infiniteScrollLoaderExists,
    hasDivider,
    existInFilter,
    _getDropdownLayoutBase: getDropdownLayoutBase,
    scrollDown: async () => {
      const element = await base.getNative();

      const contentHook = await base.attr('data-content-hook');
      const baseDataHook = await base.attr('data-hook');
      const contentSelector = contentHook
        ? `[data-content-element="${contentHook}"]`
        : `[data-hook="${baseDataHook}-content"]`;

      // puppeteer
      if (isSled2(element)) {
        const { page } = element as { page: { evaluate: Function } };
        await page.evaluate(
          (_dataHook: string, _contentSelector: string) => {
            const e = document.querySelector(
              `[data-hook="${_dataHook}"] ${_contentSelector}`,
            )!;
            e.scrollBy({ behavior: 'smooth', top: e.scrollHeight });
          },
          dataHook,
          contentSelector,
        );
      }
      // playwright
      else if (isSled3(element)) {
        const baseLocator = (await base.unwrap()) as {
          locator: (selector: string) => {
            evaluate: (fn: (el: HTMLElement) => void) => Promise<void>;
          };
        };
        await baseLocator.locator(contentSelector).evaluate((el) => {
          el.scrollBy({ behavior: 'smooth', top: el.scrollHeight });
        });
      }
      // jsdom
      else {
        await (
          await base.$(contentSelector).getNative()
        ).dispatchEvent(new Event('scroll'));
      }
    },
  };
};
