// @ts-ignore
import React from 'react';
import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep';
import '../fixtures/window';
import { Editor, globalContext } from '@ali/lowcode-editor-core';
import {
  AssetLevel,
  Asset,
  AssetList,
  assetBundle,
  assetItem,
  AssetType,
} from '@ali/lowcode-utils';
import {
  Dragon,
  isDragNodeObject,
  isDragNodeDataObject,
  isDragAnyObject,
  isLocateEvent,
  DragObjectType,
  isShaken,
  setShaken,
} from '../../src/designer/dragon';
import { Project } from '../../src/project/project';
import { Node } from '../../src/document/node/node';
import { Designer } from '../../src/designer/designer';
import { DocumentModel } from '../../src/document/document-model';
import formSchema from '../fixtures/schema/form';
import { getMockDocument, getMockWindow, getMockEvent, delayObxTick } from '../utils';
import { BuiltinSimulatorHost } from '../../src/builtin-simulator/host';
import { fireEvent } from '@testing-library/react';

describe('Host 测试', () => {
  let editor: Editor;
  let designer: Designer;
  let project: Project;
  let doc: DocumentModel;
  let host: BuiltinSimulatorHost;

  beforeAll(() => {
    editor = new Editor();
    !globalContext.has(Editor) && globalContext.register(editor, Editor);
  });

  beforeEach(() => {
    designer = new Designer({ editor });
    project = designer.project;
    doc = project.createDocument(formSchema);
    host = new BuiltinSimulatorHost(designer.project);
  });

  afterEach(() => {
    project.unload();
    project.mountSimulator(undefined);
    designer._componentMetasMap.clear();
    designer.purge();
    host.purge();
    designer = null;
    project = null;
    host = null;
  });

  describe('基础方法测试', () => {
    it('setProps / get / set', async () => {
      expect(host.currentDocument).toBe(designer.project.currentDocument);
      expect(host.renderEnv).toBe('default');
      expect(host.device).toBe('default');
      expect(host.deviceClassName).toBeUndefined();
      expect(host.requestHandlersMap).toBeNull();
      host.setProps({
        renderEnv: 'rax',
        device: 'mobile',
        deviceClassName: 'mobile-rocks',
        componentsAsset: [
          {
            type: AssetType.JSText,
            content: 'console.log(1)',
          },
          {
            type: AssetType.JSUrl,
            content: '//path/to/js',
          },
        ],
        theme: {
          type: AssetType.CSSText,
          content: '.theme {font-size: 50px;}',
        },
        requestHandlersMap: {},
      });
      expect(host.renderEnv).toBe('rax');
      expect(host.device).toBe('mobile');
      expect(host.deviceClassName).toBe('mobile-rocks');
      expect(host.componentsAsset).toEqual([
        {
          type: AssetType.JSText,
          content: 'console.log(1)',
        },
        {
          type: AssetType.JSUrl,
          content: '//path/to/js',
        },
      ]);
      expect(host.theme).toEqual({
        type: AssetType.CSSText,
        content: '.theme {font-size: 50px;}',
      });
      expect(host.componentsMap).toEqual(designer.componentsMap);
      expect(host.requestHandlersMap).toEqual({});

      host.set('renderEnv', 'vue');
      expect(host.renderEnv).toBe('vue');

      expect(host.getComponentContext).toThrow('Method not implemented.');
    });

    it('connect', () => {
      const mockFn = jest.fn();
      const mockRenderer = { isSimulatorRenderer: true };
      host.connect(mockRenderer, mockFn);
      expect(host.renderer).toEqual(mockRenderer);

      // await delayObxTick();
      expect(mockFn).toHaveBeenCalled();
    });

    it('mountViewport', () => {
      const mockBounds = {
        top: 10,
        bottom: 100,
        left: 10,
        right: 100,
      };
      host.mountViewport({
        getBoundingClientRect() {
          return mockBounds;
        },
      });
      expect(host.viewport.bounds).toEqual(mockBounds);
    });

    it('autorun', () => {
      const mockFn = jest.fn();
      host.autorun(mockFn);
      expect(mockFn).toHaveBeenCalled();
    });

    it('purge', () => {
      host.purge();
    });

    it('isEnter', () => {
      const mockBounds = {
        top: 10,
        bottom: 100,
        left: 10,
        right: 100,
      };
      host.mountViewport({
        getBoundingClientRect() {
          return mockBounds;
        },
      });
      expect(
        host.isEnter({
          globalX: 5,
          globalY: 50,
        }),
      ).toBeFalsy();
      expect(
        host.isEnter({
          globalX: 115,
          globalY: 50,
        }),
      ).toBeFalsy();
      expect(
        host.isEnter({
          globalX: 50,
          globalY: 50,
        }),
      ).toBeTruthy();
      expect(
        host.isEnter({
          globalX: 50,
          globalY: 5,
        }),
      ).toBeFalsy();
      expect(
        host.isEnter({
          globalX: 50,
          globalY: 150,
        }),
      ).toBeFalsy();
      expect(
        host.isEnter({
          globalX: 150,
          globalY: 150,
        }),
      ).toBeFalsy();
    });

    it('fixEvent', () => {
      expect(host.fixEvent({ fixed: true, clientX: 1 })).toEqual({ fixed: true, clientX: 1 });
    });

    it('findDOMNodes', () => {
      host.connect({
        findDOMNodes: () => {
          return null;
        },
      }, () => {});
      expect(host.findDOMNodes()).toBeNull();

      const mockElems = [document.createElement('div')];
      host.connect({
        findDOMNodes: () => {
          return mockElems;
        },
      }, () => {});
      expect(host.findDOMNodes({})).toBe(mockElems);
      expect(host.findDOMNodes({}, 'xxx')).toBeNull();
      expect(host.findDOMNodes({}, 'div')).toEqual(mockElems);
    });

    it('getClosestNodeInstance', () => {
      const mockFn = jest.fn(() => {
        return {
          node: {},
          nodeId: 'id',
          docId: 'docId',
        };
      });
      host.connect({
        getClosestNodeInstance: mockFn,
      }, () => {});
      expect(host.getClosestNodeInstance()).toEqual({
        node: {},
        nodeId: 'id',
        docId: 'docId',
      });
    });

    it('getNodeInstanceFromElement', () => {
      expect(host.getNodeInstanceFromElement()).toBeNull();
      host.getClosestNodeInstance = () => {
        return null;
      };
      expect(host.getNodeInstanceFromElement({})).toBeNull();
      host.getClosestNodeInstance = () => {
        return {
          docId: project.currentDocument.id,
          nodeId: 'xxx',
        };
      };
      expect(host.getNodeInstanceFromElement({})).toBeTruthy();
    });

    it('getDropContainer', () => {
      host.getNodeInstanceFromElement = () => {
        return {
          node: doc.rootNode,
        };
      };
      host.getDropContainer({
        target: {},
        dragObject: {
          type: DragObjectType.Node,
          nodes: [doc.getNode('page')],
        },
      });
    });

    it('getComponentInstances', () => {
      const mockNode = {
        document: { id: 'docId' },
      };
      host.instancesMap = {
        docId: {
          get() {
            return [{ comp: true }, { comp2: true }];
          },
        },
      };
      expect(host.getComponentInstances(mockNode))
        .toEqual([{ comp: true }, { comp2: true }]);

      const mockInst = { inst: true };
      host.getClosestNodeInstance = () => {
        return {
          instance: mockInst,
        };
      };
      expect(host.getComponentInstances(mockNode, { instance: mockInst }))
        .toEqual([{ comp: true }, { comp2: true }]);
    });

    it('setNativeSelection / setDraggingState / setCopyState / clearState', () => {
      const mockFn1 = jest.fn();
      const mockFn2 = jest.fn();
      const mockFn3 = jest.fn();
      const mockFn4 = jest.fn();
      host.connect({
        setNativeSelection: mockFn1,
        setDraggingState: mockFn2,
        setCopyState: mockFn3,
        clearState: mockFn4,
      }, () => {});
      host.setNativeSelection(true);
      expect(mockFn1).toHaveBeenCalledWith(true);
      host.setDraggingState(false);
      expect(mockFn2).toHaveBeenCalledWith(false);
      host.setCopyState(true);
      expect(mockFn3).toHaveBeenCalledWith(true);
      host.clearState();
      expect(mockFn4).toHaveBeenCalled();
    });

    it('sensorAvailable / deactiveSensor', () => {
      expect(host.sensorAvailable).toBeTruthy();
      host.deactiveSensor();
      expect(host.sensing).toBeFalsy();
    });

    it('getComponent', () => {
      host.connect({
        getComponent: () => {
          return {};
        },
      }, () => {});
      expect(host.getComponent()).toEqual({});
      expect(host.createComponent()).toBeNull();
      expect(host.setSuspense()).toBeFalsy();
    });

    it('setInstance', () => {
      host.instancesMap = {};
      host.setInstance('docId1', 'id1', [{}]);
      expect(host.instancesMap.docId1.get('id1')).toEqual([{}]);

      host.setInstance('docId1', 'id1', null);
      expect(host.instancesMap.docId1.get('id1')).toBeUndefined();
    });
  });

  describe('locate 方法', () => {
    beforeEach(() => {
      const mockBounds = {
        top: 10,
        bottom: 100,
        left: 10,
        right: 100,
      };
      host.mountViewport({
        getBoundingClientRect() {
          return mockBounds;
        },
      });
    });
    it('locate，没有 nodes', () => {
      expect(host.locate({
        dragObject: {
          type: DragObjectType.Node,
          nodes: [],
        },
      })).toBeUndefined();
    });
    it('locate，没有 document', () => {
      project.removeDocument(doc);
      expect(host.locate({
        dragObject: {
          type: DragObjectType.Node,
          nodes: [doc.getNode('page')],
        },
      })).toBeNull();
    });
    it('locate', () => {
      host.locate({
        dragObject: {
          type: DragObjectType.Node,
          nodes: [doc.getNode('page')],
        },
      });
    });
  });

  describe('事件测试', () => {
    it('setupDragAndClick', () => {});
    it('setupContextMenu', async () => {
      const mockDocument = getMockDocument();
      const mockWindow = getMockWindow(mockDocument);
      const mockIframe = {
        contentWindow: mockWindow,
        contentDocument: mockDocument,
        dispatchEvent() {},
      };

      host.set('library', [
        {
          package: '@ali/vc-deep',
          library: 'lib',
          urls: ['a.js', 'b.js'],
        },
      ]);

      host.componentsConsumer.consume(() => {});
      host.injectionConsumer.consume(() => {});
      await host.mountContentFrame(mockIframe);

      host.setupContextMenu();
      host.getNodeInstanceFromElement = () => {
        return {
          node: { componentMeta: { componentName: 'Button', getMetadata() { return {} } }, contains() {} },
        };
      };
      const mockFn = jest.fn();
      host.designer.editor.on('designer.builtinSimulator.contextmenu', mockFn);
      fireEvent.contextMenu(document, {});
      // TODO:
      // expect(mockFn).toHaveBeenCalledWith({ selected: 'Button' });
    });
  });

  it('事件测试', async () => {
    const mockDocument = getMockDocument();
    const mockWindow = getMockWindow(mockDocument);
    const mockIframe = {
      contentWindow: mockWindow,
      contentDocument: mockDocument,
      dispatchEvent() {},
    };

    // 非法分支测试
    host.mountContentFrame();
    expect(host._iframe).toBeUndefined();

    host.set('library', [
      {
        package: '@ali/vc-deep',
        library: 'lib',
        urls: ['a.js', 'b.js'],
      },
    ]);

    host.componentsConsumer.consume(() => {});
    host.injectionConsumer.consume(() => {});
    await host.mountContentFrame(mockIframe);

    expect(host.contentWindow).toBe(mockWindow);

    mockDocument.triggerEventListener(
      'mouseover',
      getMockEvent(mockDocument.createElement('div')),
      host,
    );
    mockDocument.triggerEventListener(
      'mouseleave',
      getMockEvent(mockDocument.createElement('div')),
      host,
    );
    mockDocument.triggerEventListener(
      'mousedown',
      getMockEvent(mockDocument.createElement('div')),
      host,
    );
    mockDocument.triggerEventListener(
      'mouseup',
      getMockEvent(mockDocument.createElement('div')),
      host,
    );
    mockDocument.triggerEventListener(
      'mousemove',
      getMockEvent(mockDocument.createElement('div')),
      host,
    );
    mockDocument.triggerEventListener('click', getMockEvent(document.createElement('input')), host);
    mockDocument.triggerEventListener(
      'dblclick',
      getMockEvent(mockDocument.createElement('div')),
      host,
    );
    mockDocument.triggerEventListener(
      'contextmenu',
      getMockEvent(mockDocument.createElement('div')),
      host,
    );
  });
});
