// @ts-nocheck
import '../../fixtures/window';
import { set, delayObxTick, delay } from '../../utils';
import { Editor } from '@ali/lowcode-editor-core';
import { Project } from '../../../src/project/project';
import { DocumentModel } from '../../../src/document/document-model';
import {
  isRootNode,
  Node,
  isNode,
  comparePosition,
  contains,
  insertChild,
  insertChildren,
  PositionNO,
} from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer';
import formSchema from '../../fixtures/schema/form';
import divMetadata from '../../fixtures/component-metadata/div';
import dialogMetadata from '../../fixtures/component-metadata/dialog';
import btnMetadata from '../../fixtures/component-metadata/button';
import formMetadata from '../../fixtures/component-metadata/form';
import otherMeta from '../../fixtures/component-metadata/other';
import pageMetadata from '../../fixtures/component-metadata/page';
import rootHeaderMetadata from '../../fixtures/component-metadata/root-header';
import rootContentMetadata from '../../fixtures/component-metadata/root-content';
import rootFooterMetadata from '../../fixtures/component-metadata/root-footer';

describe('Node 方法测试', () => {
  let editor: Editor;
  let designer: Designer;
  let project: Project;
  let doc: DocumentModel;

  beforeEach(() => {
    editor = new Editor();
    designer = new Designer({ editor });
    project = designer.project;
    doc = new DocumentModel(project, formSchema);
  });

  afterEach(() => {
    project.unload();
    designer.purge();
    editor = null;
    designer = null;
    project = null;
  });

  it('condition group', () => {});

  it('getExtraProp / setExtraProp', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    expect(firstBtn.getExtraProp('non-existing', false)).toBeNull();

    firstBtn.setExtraProp('xxx', '1111');
    expect(firstBtn.getExtraProp('xxx', false).getValue()).toBe('1111');
  });

  it('import(leaf)', () => {
    const form = doc.getNode('node_k1ow3cbo');
    form.insert({ componentName: 'Leaf', children: '111' });

    const leaf = form.getChildren().get(2);
    expect(leaf.getPropValue('children')).toBe('111');

    leaf.import({ componentName: 'Leaf', children: '222' });
    expect(leaf.getPropValue('children')).toBe('222');

    leaf.import({ componentName: 'Leaf', children: { type: 'JSExpression', value: 'state.x' } });
    expect(leaf.getPropValue('children')).toEqual({ type: 'JSExpression', value: 'state.x' });
  });

  it('hasCondition', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    firstBtn.getExtraProp('condition')?.setValue(undefined);
    expect(firstBtn.hasCondition()).toBeFalsy();

    firstBtn.getExtraProp('condition')?.setValue(null);
    expect(firstBtn.hasCondition()).toBeFalsy();

    firstBtn.getExtraProp('condition')?.setValue(true);
    expect(firstBtn.hasCondition()).toBeFalsy();

    firstBtn.getExtraProp('condition')?.setValue('');
    expect(firstBtn.hasCondition()).toBeFalsy();

    firstBtn.getExtraProp('condition')?.setValue(1);
    expect(firstBtn.hasCondition()).toBeTruthy();

    firstBtn.getExtraProp('condition')?.setValue(false);
    expect(firstBtn.hasCondition()).toBeTruthy();
  });

  it('hasLoop', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    expect(firstBtn.hasLoop()).toBeFalsy();

    // 这里必须用 add，因为 hasLoop 实现的跳过了 stash
    firstBtn.props.add([1, 2], '___loop___');
    expect(firstBtn.hasLoop()).toBeTruthy();

    firstBtn.getExtraProp('loop')?.setValue({ type: 'JSExpression', value: 'state.a' });
    expect(firstBtn.hasLoop()).toBeTruthy();

    firstBtn.getExtraProp('loop')?.setValue(1);
    expect(firstBtn.hasLoop()).toBeFalsy();
  });

  describe('getSuitablePlace', () => {
    it('root，子节点中有容器节点', () => {
      designer.createComponentMeta(pageMetadata);
      designer.createComponentMeta(rootHeaderMetadata);
      designer.createComponentMeta(rootContentMetadata);
      designer.createComponentMeta(rootFooterMetadata);

      const rootHeaderMeta = designer.getComponentMeta('RootHeader');
      set(rootHeaderMeta, 'prototype.options.canDropIn', true);

      let o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
      expect(o).toEqual({
        container: doc.getNode('node_k1ow3cba'),
        ref: 1,
      });

      set(rootHeaderMeta, 'prototype.options.canDropIn', () => true);
      o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
      expect(o).toEqual({
        container: doc.getNode('node_k1ow3cba'),
        ref: 1,
      });
    });

    it('root，直接子节点中无容器节点，自身支持放入子节点', () => {
      designer.createComponentMeta(pageMetadata);

      let o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);

      const pageMeta = designer.getComponentMeta('Page');
      set(pageMeta, 'prototype.options.canDropIn', () => true);

      expect(o).toEqual({
        container: doc.rootNode,
        ref: 1,
      });

      set(pageMeta, 'prototype.options.canDropIn', undefined);
      o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
      expect(o).toEqual({
        container: doc.rootNode,
        ref: 1,
      });

      set(pageMeta, 'prototype.options.canDropIn', true);
      o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
      expect(o).toEqual({
        container: doc.rootNode,
        ref: 1,
      });
    });

    it('root，子节点中无容器节点，自己也不支持放入子节点', () => {
      designer.createComponentMeta(pageMetadata);

      let pageMeta = designer.getComponentMeta('Page');

      pageMeta = set(pageMeta, 'prototype.options.canDropIn', () => false);
      let o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
      expect(o).toBeNull();

      set(pageMeta, 'prototype.options.canDropIn', false);
      o = doc.rootNode?.getSuitablePlace(doc.getNode('form'), 1);
      expect(o).toBeNull();
    });

    it('放入模态节点', () => {
      designer.createComponentMeta(pageMetadata);
      designer.createComponentMeta(dialogMetadata);

      const dialog = doc.createNode({ componentName: 'Dialog' });

      const o = doc.rootNode?.getSuitablePlace(dialog, 1);
      expect(o.container).toBe(doc.rootNode);
      expect(o.ref).toBe(1);
    });

    it('包含 focusNode', () => {
      const o = doc.rootNode?.getSuitablePlace(doc.rootNode);
      expect(o.container).toBe(doc.rootNode);
    });

    it.skip('非 root 节点，不能放入子节点', () => {
      designer.createComponentMeta(formMetadata);
      designer.createComponentMeta(pageMetadata);

      // form 子节点以及自身都不能放入子节点
      const formMeta = designer.getComponentMeta('Form');
      set(formMeta, 'prototype.options.canDropIn', false);

      const pageMeta = designer.getComponentMeta('Page');
      set(pageMeta, 'prototype.options.canDropIn', () => true);

      const o = doc.getNode('form')!.getSuitablePlace(doc.getNode('node_k1ow3cbj'), { index: 1 });
      expect(o).toEqual({
        container: doc.rootNode,
        ref: { index: 1 },
      });
    });

    it('非 root 节点，能放入子节点', () => {
      designer.createComponentMeta(formMetadata);
      designer.createComponentMeta(pageMetadata);

      // form 子节点以及自身都不能放入子节点
      const formMeta = designer.getComponentMeta('Form');
      set(formMeta, 'prototype.options.canDropIn', true);

      const o = doc.getNode('form')!.getSuitablePlace(doc.getNode('node_k1ow3cbj'), 1);
      expect(o).toEqual({
        container: doc.getNode('form'),
        ref: 1,
      });
    });

    it('null', () => {
      expect(
        doc.rootNode?.getSuitablePlace.call({
          contains: () => false,
          isContainer: () => false,
          isRoot: () => false,
        }),
      ).toBeNull();
    });
  });

  it('removeChild / replaceWith / replaceChild', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    const form = doc.getNode('node_k1ow3cbo');

    // 不符合条件的节点直接返回
    expect(firstBtn.replaceChild(form, { componentName: 'Button', props: { x: 1 } })).toBe(form);

    firstBtn.select();
    firstBtn.parent?.replaceChild(firstBtn, { componentName: 'Button', props: { x: 1 } });

    expect(firstBtn.parent?.getChildren()?.size).toBe(2);
    expect(firstBtn.parent?.getChildren()?.get(0)?.getPropValue('x')).toBe(1);

    const secondBtn = doc.getNode('node_k1ow3cbp')!;
    secondBtn.replaceWith({ componentName: 'Button', props: { y: 1 } });
    expect(firstBtn.parent?.getChildren()?.size).toBe(2);
    expect(firstBtn.parent?.getChildren()?.get(1)?.getPropValue('y')).toBe(1);
  });

  it('schema', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    const schema = firstBtn.schema;
    schema.props.size = 'large';
    firstBtn.schema = schema;

    expect(firstBtn.getPropValue('size')).toBe('large');
  });

  describe('插入相关方法', () => {
    it('insertBefore / onChildrenChange', () => {
      const firstBtn = doc.getNode('node_k1ow3cbn')!;
      const secondBtn = doc.getNode('node_k1ow3cbp')!;
      const btnParent = firstBtn.parent!;
      const mockFn = jest.fn();
      const off = btnParent.onChildrenChange(mockFn);

      // Node 实例
      btnParent.insertBefore(new Node(doc, { componentName: 'Button', props: { a: 1 } }), firstBtn);
      expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ a: 1 });
      expect(mockFn).toHaveBeenCalledTimes(1);

      // TODO: 暂时不支持，后面补上
      // // NodeSchema
      // btnParent.insertBefore({ componentName: 'Button', props: { b: 1 } }, firstBtn);
      // expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ b: 1 });
      // expect(mockFn).toHaveBeenCalledTimes(2);

      // // getComponentName
      // btnParent.insertBefore({ getComponentName: () => 'Button', props: { c: 1 } }, firstBtn);
      // expect(btnParent.children.get(0)?.getProps().export().props).toEqual({ c: 1 });
      // expect(mockFn).toHaveBeenCalledTimes(3);
    });

    it('insertAfter / onChildrenChange', () => {
      const firstBtn = doc.getNode('node_k1ow3cbn')!;
      const secondBtn = doc.getNode('node_k1ow3cbp')!;
      const btnParent = firstBtn.parent!;
      const mockFn = jest.fn();
      const off = btnParent.onChildrenChange(mockFn);

      // Node 实例
      btnParent.insertAfter(new Node(doc, { componentName: 'Button', props: { a: 1 } }), firstBtn);
      expect(btnParent.children.get(1)?.getProps().export().props).toEqual({ a: 1 });
      expect(mockFn).toHaveBeenCalledTimes(1);

      // NodeSchema
      btnParent.insertAfter({ componentName: 'Button', props: { b: 1 } }, firstBtn);
      expect(btnParent.children.get(1)?.getProps().export().props).toEqual({ b: 1 });
      expect(mockFn).toHaveBeenCalledTimes(2);

      // getComponentName
      btnParent.insertAfter({ getComponentName: () => 'Button' }, firstBtn);
      expect(btnParent.children.get(1)?.getProps().export().props).toEqual({});
      expect(mockFn).toHaveBeenCalledTimes(3);
    });
  });

  it('setVisible / getVisible / onVisibleChange', () => {
    const mockFn = jest.fn();
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    const off = firstBtn.onVisibleChange(mockFn);
    firstBtn.setVisible(true);
    expect(firstBtn.getVisible()).toBeTruthy();
    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).toHaveBeenCalledWith(true);

    firstBtn.setVisible(false);

    expect(firstBtn.getVisible()).toBeFalsy();
    expect(mockFn).toHaveBeenCalledTimes(2);
    expect(mockFn).toHaveBeenCalledWith(false);

    off();
    mockFn.mockClear();
    firstBtn.setVisible(true);
    expect(mockFn).not.toHaveBeenCalled();
  });

  it('RGL / getRGL', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    firstBtn.isRGLContainer = true;
    expect(firstBtn.isRGLContainer).toBeTruthy();

    const rgl = firstBtn.getRGL();
    expect(rgl.isContainerNode).toBeFalsy();
    expect(rgl.isEmptyNode).toBeTruthy();
    expect(rgl.isRGLContainerNode).toBeTruthy();
    expect(rgl.isRGLNode).toBeFalsy();
    expect(rgl.isRGL).toBeTruthy();
  });

  it('onPropChange', () => {
    const mockFn = jest.fn();
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    const off = firstBtn.onPropChange(mockFn);

    firstBtn.setPropValue('x', 1);
    expect(mockFn).toHaveBeenCalledTimes(1);
    firstBtn.setPropValue('x', 2);
    expect(mockFn).toHaveBeenCalledTimes(2);

    off();
    mockFn.mockClear();
    firstBtn.setPropValue('x', 3);
    expect(mockFn).not.toHaveBeenCalled();
  });

  it('addSlot / unlinkSlot / removeSlot', () => {});

  it('setProps', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    const secondBtn = doc.getNode('node_k1ow3cbp')!;

    firstBtn.setProps(secondBtn.getProps());
    expect(firstBtn.getProps()).toBe(secondBtn.getProps());
  });

  it('experimental initials / autoruns', async () => {
    designer.createComponentMeta(pageMetadata);

    const pageMeta = designer.getComponentMeta('Page');
    const autorunMockFn = jest.fn();
    set(pageMeta, '_transformedMetadata.experimental.autoruns', [
      { name: 'a', autorun: autorunMockFn },
    ]);
    const initialChildrenMockFn = jest.fn();
    set(pageMeta, '_transformedMetadata.experimental.initialChildren', initialChildrenMockFn);
    doc.createNode({ componentName: 'Page', props: { a: 1 } });

    expect(autorunMockFn).toHaveBeenCalled();
    expect(initialChildrenMockFn).toHaveBeenCalled();

    set(pageMeta, '_transformedMetadata.experimental.initialChildren', {});
    doc.createNode({ componentName: 'Page', props: { a: 1 } });
    expect(autorunMockFn).toHaveBeenCalledTimes(2);
  });

  it('isValidComponent', () => {
    designer.createComponentMeta(divMetadata);
    expect(doc.getNode('node_k1ow3cbo')?.isValidComponent()).toBeTruthy();
    expect(doc.getNode('form')?.isValidComponent()).toBeFalsy();
  });

  it('title', () => {
    designer.createComponentMeta(btnMetadata);
    const btn = doc.getNode('node_k1ow3cbn');
    // 从 componentMeta 中获取到 title 值
    expect(btn.title).toEqual({ type: 'i18n', 'zh-CN': '按钮', 'en-US': 'Button' } );
    // 从 extraProp 中获取值
    btn.setExtraProp('title', 'hello button');
    expect(btn.title).toBe('hello button');

    // btn.props.deleteKey('___title___');
    // 从 componentMeta descriptor 指向的 key 获取 title
    // btn.setPropValue('xTitle', 'title from descriptor')
    // expect(btn.title).toBe('title from descriptor');
  });

  it('isEmpty / getIndex / getIcon', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    // expect(firstBtn.children).toBeNull();
    expect(firstBtn.isEmpty()).toBeTruthy();
    expect(firstBtn.index).toBe(0);
    expect(firstBtn.getIndex()).toBe(0);
    expect(typeof firstBtn.getIcon()).toBe('function');
    expect(doc.getNode('page')!.index).toBe(-1);
  });

  it('schema / toData / export', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    expect(firstBtn.toData().componentName).toBe('Button');
  });

  it('internalSetParent / internalSetWillPurge', () => {
    const firstChild = doc.rootNode?.getChildren()?.get(0);
    firstChild?.internalSetParent(doc.rootNode);

    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    firstBtn.internalSetWillPurge();
    // expect(firstBtn.parent).();

    expect(firstBtn.hasSlots()).toBeFalsy();
  });

  it('prevSibling / nextSibling', () => {
    // no parent
    const page = doc.getNode('page');
    expect(page?.nextSibling).toBeNull();
    expect(page?.prevSibling).toBeNull();

    // normal
    const firstBtn = doc.getNode('node_k1ow3cbn');
    const secondBtn = doc.getNode('node_k1ow3cbp');
    expect(firstBtn?.nextSibling).toBe(secondBtn);
    expect(secondBtn?.prevSibling).toBe(firstBtn);
    expect(secondBtn?.nextSibling).toBeNull();

    // index < 0
    firstBtn?.parent?.removeChild(firstBtn);
    expect(firstBtn?.nextSibling).toBeNull();
    expect(firstBtn?.prevSibling).toBeNull();
  });

  it('toString', () => {
    expect(doc.rootNode.toString()).toBe('page');
  });

  it('lock', () => {
    const form = doc.getNode('node_k1ow3cbo');
    expect(form.isLocked).toBeFalsy();
    form.lock(true);
    expect(form.isLocked).toBeTruthy();
    form.lock(false);
    expect(form.isLocked).toBeFalsy();
    form.lock();
    expect(form.isLocked).toBeTruthy();
  });

  it('didDropIn / didDropOut', () => {
    const form = doc.getNode('node_k1ow3cbo');
    designer.createComponentMeta(divMetadata);
    const callbacks = form.componentMeta.getMetadata().experimental?.callbacks;
    const fn1 = callbacks.onNodeAdd = jest.fn();
    const fn2 = callbacks.onNodeRemove = jest.fn();
    const textField = doc.getNode('node_k1ow3cc9');
    form.didDropIn(textField);
    expect(fn1).toHaveBeenCalledWith(textField, form);

    form.didDropOut(textField);
    expect(fn2).toHaveBeenCalledWith(textField, form);
  });

  it('hover', () => {
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    firstBtn.hover(true);
    expect(doc.designer.detecting.current).toBe(firstBtn);
    firstBtn.hover(false);
    expect(doc.designer.detecting.current).toBeNull();
    firstBtn.hover();
    expect(doc.designer.detecting.current).toBe(firstBtn);
  });

  it('getRect', () => {
    const root = doc.rootNode!;
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    expect(root.getRect()).toBeNull();
    expect(firstBtn.getRect()).toBeNull();

    doc.project.mountSimulator({
      computeRect: () => ({ x: 2, y: 2 }),
      viewport: {
        contentBounds: { x: 1, y: 1 },
      },
    });

    expect(root.getRect()).toEqual({ x: 1, y: 1 });
    expect(firstBtn.getRect()).toEqual({ x: 2, y: 2 });
  });

  it('isRootNode / isRoot / isNode', () => {
    expect(isRootNode(doc.rootNode)).toBeTruthy();
    expect(isNode(doc.rootNode)).toBeTruthy();
  });

  it('contains / comparePosition', () => {
    const page = doc.getNode('page')!;
    const content = doc.getNode('node_k1ow3cbb')!;
    const firstBtn = doc.getNode('node_k1ow3cbn')!;
    const secondBtn = doc.getNode('node_k1ow3cbp')!;
    const firstCard = doc.getNode('node_k1ow3cbj')!;
    expect(contains(firstBtn, firstBtn)).toBeTruthy();
    expect(contains(firstBtn, secondBtn)).toBeFalsy();
    expect(contains(firstBtn, page)).toBeFalsy();
    expect(contains(firstBtn, content)).toBeFalsy();
    expect(contains(firstCard, firstBtn)).toBeFalsy();

    expect(comparePosition(firstBtn, secondBtn)).toBe(PositionNO.BeforeOrAfter);
    expect(firstBtn.comparePosition(firstBtn)).toBe(PositionNO.TheSame);
    expect(comparePosition(firstBtn, firstBtn)).toBe(PositionNO.TheSame);
    expect(comparePosition(firstBtn, firstBtn.parent)).toBe(PositionNO.ContainedBy);
    expect(comparePosition(firstBtn.parent, firstBtn)).toBe(PositionNO.Contains);
    expect(comparePosition(firstCard, firstBtn)).toBe(PositionNO.BeforeOrAfter);
    expect(comparePosition(firstBtn, firstCard)).toBe(PositionNO.BeforeOrAfter);
  });

  it('getZLevelTop', () => {});
  it('propsData', () => {
    expect(new Node(doc, { componentName: 'Leaf' }).propsData).toBeNull();
    expect(new Node(doc, { componentName: 'Fragment' }).propsData).toBeNull();
  });

  describe('deprecated methods', () => {
    it('setStatus / getStatus', () => {
      const root = doc.rootNode!;
      root.setStatus('xxx', true);

      root.setStatus('locking', true);
      root.setStatus('pseudo', true);
      root.setStatus('inPlaceEditing', true);

      expect(root.getStatus('locking')).toBeTruthy();
      expect(root.getStatus('pseudo')).toBeTruthy();
      expect(root.getStatus('inPlaceEditing')).toBeTruthy();
      expect(root.getStatus()).toEqual({
        locking: true,
        pseudo: true,
        inPlaceEditing: true,
      });
    });

    it('getPage', () => {
      expect(doc.rootNode?.getPage()).toBe(doc);
    });

    it('getDOMNode', () => {
      const root = doc.rootNode!;
      const firstBtn = doc.getNode('node_k1ow3cbn')!;

      doc.project.mountSimulator({
        findDOMNodes: () => [{ x: 1, y: 1 }],
        getComponentInstances: (node) => {
          if (node.componentName === 'Page') {
            return [];
          }
          return [{}];
        },
      });

      expect(root.getDOMNode()).toBeUndefined();
      expect(firstBtn.getDOMNode()).toEqual({ x: 1, y: 1 });
    });

    it('registerAddon / getAddonData', () => {
      const page = doc.getNode('page')!;
      page.registerAddon('a', () => 'prop a');
      expect(page.getAddonData('a')).toBe('prop a');
      expect(page.getAddonData('b')).toBeUndefined();

      expect(page.export().a).toBe('prop a');
    });

    it('getPrototype / setPrototype', () => {
      const page = doc.getNode('page')!;
      page.setPrototype({ a: 1 });
      expect(page.getPrototype()).toEqual({ a: 1 });
    });
  });
});
