import { UniDriver } from '@wix/wix-ui-test-utils/unidriver';
import { baseUniDriverFactory } from '../../unidriver';
import { SledPage } from '../../sledPage';

const { fireEvent = null } =
  process.env.NODE_ENV === 'test'
    ? (() => {
        try {
          return require('@testing-library/react');
        } catch {}
        return null;
      })() ||
      (() => {
        try {
          // eslint-disable-next-line import/no-extraneous-dependencies
          return require('@testing-library/dom');
        } catch {}
        return null;
      })()
    : {};

export function DragAndDropUniDriver(base: UniDriver) {
  return {
    ...baseUniDriverFactory(base),
    escapeDrag: async (
      source: UniDriver,
      { key: code = 'Escape' }: { key?: string } = {},
    ) => {
      const sourceNative = await source.getNative();

      const { page } = sourceNative as { page?: SledPage['page'] };

      if (page?.evaluate) {
      } else if (process.env.NODE_ENV === 'test') {
        fireEvent.keyDown(sourceNative, {
          code,
        });
      }
    },
    startKeyboardDrag: async (source: UniDriver) => {
      const sourceNative = await source.getNative();

      const { page } = sourceNative as { page?: SledPage['page'] };

      if (page?.evaluate) {
      } else if (fireEvent) {
        fireEvent.keyDown(sourceNative, {
          code: 'Enter',
        });
      }
    },
    dragOverKeyboard: async (target: UniDriver) => {
      const targetNative = await target.getNative();

      const { page } = targetNative as { page?: SledPage['page'] };

      if (page?.evaluate) {
      } else if (fireEvent) {
        fireEvent.keyDown(targetNative, {
          code: 'ArrowDown',
        });
      }
    },
    endKeyboardDrag: async (target: UniDriver) => {
      const targetNative = await target.getNative();

      const { page } = targetNative as { page?: SledPage['page'] };

      if (page?.evaluate) {
      } else if (fireEvent) {
        return fireEvent.keyDown(targetNative, {
          code: 'Enter',
        });
      }
    },
    dragStart: async (
      source: UniDriver,
      options: { page?: SledPage['page']; x?: number; y?: number } = {},
    ) => {
      const sourceNative = await source.getNative();

      const page: SledPage['page'] | null = options.page ?? sourceNative.page;

      if (page?.evaluate) {
        const sourceBox = await sourceNative.element.boundingBox();

        await page.mouse.move(
          options.x ?? sourceBox.x + sourceBox.width / 2,
          options.y ?? sourceBox.y + sourceBox.height / 2,
        );

        await page.mouse.down();
      } else if (fireEvent) {
        fireEvent.pointerDown(sourceNative);
      }
    },
    dragMove: async () => {
      await document.dispatchEvent(new Event('mousemove'));
    },
    dragEnd: async (
      target: UniDriver,
      options: { page?: SledPage['page'] } = {},
    ) => {
      const targetNative = await target.getNative();
      const page: SledPage['page'] | null = options.page ?? targetNative.page;

      if (page?.evaluate) {
        await page?.mouse.up();
      } else if (fireEvent) {
        fireEvent.pointerUp(targetNative);
        await document.dispatchEvent(new Event('mouseup'));
      }
    },
    dragOver: async (
      source: UniDriver,
      target: UniDriver,
      {
        dragPointRatio = { x: 2, y: 2 },
        dropPointRatio = { x: 2, y: 2 },
        steps = 3,
      }: {
        dragPointRatio?: { x: number; y: number };
        dropPointRatio?: { x: number; y: number };
        steps?: number;
      } = {},
    ) => {
      const sourceRow = await source.getNative();
      const targetRow = await target.getNative();

      if (source.type === 'puppeteer') {
        const { page } = sourceRow as { page: SledPage['page'] };

        await source.hover();

        const sourceBox = await sourceRow.element.boundingBox();
        const targetBox = await targetRow.element.boundingBox();

        await page.mouse.move(
          sourceBox.x + sourceBox.width / dragPointRatio.x,
          sourceBox.y + sourceBox.height / dragPointRatio.y,
        );

        await page.mouse.down();
        await new Promise((resolve) => setTimeout(resolve, 100));

        await page.mouse.move(
          targetBox.x + targetBox.width / dropPointRatio.x,
          targetBox.y + targetBox.height / dropPointRatio.y,
          { steps },
        );

        await new Promise((resolve) => setTimeout(resolve, 100));
        await page.mouse.up();
        await new Promise((resolve) => setTimeout(resolve, 100)); // wait for drop animation to complete
      } else if (fireEvent) {
        fireEvent.pointerDown(sourceRow);
        document.dispatchEvent(new Event('mousemove'));
        fireEvent.pointerUp(targetRow);
      }
    },
    moveOver: async (
      target: UniDriver,
      options: {
        page?: SledPage['page'];
        pixels?: number;
        delta?: { x?: number };
      } = {},
    ) => {
      const targetRow = await target.getNative();

      const page = options.page ?? targetRow.page;

      const pixels = options.pixels ?? 0;

      if (page?.evaluate) {
        const targetBox = await targetRow.element.boundingBox();

        for (let i = 0; i < 2; i++) {
          await page.mouse.move(
            Math.max(options.delta?.x ?? targetBox.x - pixels, 0),
            Math.max(targetBox.y + targetBox.height - pixels, 0),
          );
        }
      } else if (fireEvent) {
        fireEvent.mouseMove(targetRow, {
          clientX: options.delta?.x ?? 0,
        });
      }
    },

    moveAny: async (
      source: UniDriver,
      options?: {
        page?: SledPage['page'];
        x?: number;
        y?: number;
        steps?: number;
        delay?: number;
        vertical?: boolean;
      },
    ) => {
      const sourceNative = await source.getNative();

      const page: SledPage['page'] | undefined =
        options?.page ?? sourceNative.page;

      if (page?.evaluate) {
        const sourceBox = await sourceNative.element.boundingBox();
        const steps = options?.steps ?? 3;
        for (let i = steps; i >= 0; i--) {
          await page.mouse.move(
            (options?.x ?? sourceBox.x + sourceBox.width / 2) - i * 10,
            (options?.y ?? sourceBox.y + sourceBox.height / 2) -
              (options?.vertical ? i * 10 : 0),
          );
          await new Promise((resolve) =>
            setTimeout(resolve, options?.delay ?? 2000),
          );
        }
      }
    },
  };
}
