import { Ref, ref, onMounted, onUnmounted, watch, createApp } from 'vue';
import useCore from './useCore';
import {
  Engine,
  ComponentProp,
  ComponentDescription,
  ComponentSlot,
  IFile,
  INode,
  IFileSchema,
  EVENT_OUTLINE_CHANGE,
  EVENT_OUTLINE_HOVER,
  EVENT_OUTLINE_SELECTED,
  EVENT_COMPONENETS_DRAG_END,
  EVENT_COMPONENETS_DRAG_START,
  EVENT_RENDERER_COMPLETE,
  BLOCK_NAME,
  PAGE_NAME,
  Block,
  Node,
  Assets,
  Document
} from '@vtj/engine';
import { useResizeObserver } from '@vueuse/core';
import SlotPicker from '../parts/SlotPicker.vue';
import { ElMessageBox, ElMessage } from 'element-plus';

interface IHover {
  node: INode;
  el: HTMLElement;
  rect: DOMRect;
}

interface ISelected extends IHover {
  path: Array<INode>;
}

interface IDropping extends IHover {
  type: 'left' | 'right' | 'top' | 'bottom' | 'inner';
}

/**
 * 鼠标经过组件
 */
const hover = ref<IHover | null>(null);
/**
 * 选中组件
 */
const selected = ref<ISelected | null>(null);
/**
 * 可拖拽放置的区域
 */
const dropping = ref<IDropping | null>(null);

/**
 * 正在拖拽的组件描述
 */
const draggingComponent = ref<ComponentDescription | null>(null);

/**
 * 模拟器iframe的document
 */
const iframeDocument = ref();

function createSlotDialog(slots: ComponentSlot[]) {
  return new Promise((resolve, reject) => {
    const dialog = createApp(SlotPicker, {
      slots: slots,
      onClose: () => {
        dialog.unmount();
        reject(null);
      },
      onClick: (slot: ComponentSlot) => {
        dialog.unmount();
        resolve(slot);
      }
    });
    dialog.mount(document.createElement('div'));
  });
}

function isRoot(node: INode) {
  return node.root === node;
}

function setHover(node: INode, el?: HTMLElement) {
  const _el = el || findElByNode(node);
  const cur: IHover = {
    node,
    el: _el as HTMLElement,
    rect: (_el as HTMLElement)?.getBoundingClientRect()
  };
  hover.value = cur as any;
}

function getDropType(rect: DOMRect, x: number, y: number) {
  const { left, top, width, height } = rect;
  if (x >= left && x <= left + width * 0.2) {
    return 'left';
  }
  if (x >= left + width * 0.8 && x <= left + width) {
    return 'right';
  }
  if (y >= top && y <= top + height * 0.2) {
    return 'top';
  }
  if (y >= top + height * 0.8 && y <= top + height) {
    return 'bottom';
  }
  if (
    x >= left + width * 0.2 &&
    x <= left + width * 0.8 &&
    y >= top + height * 0.2 &&
    y <= top + height * 0.8
  ) {
    return 'inner';
  }
}

function setDropping(e: DragEvent, node: INode, el?: HTMLElement) {
  const _el = el || findElByNode(node);
  const rect = (_el as HTMLElement).getBoundingClientRect();
  const type = isRoot(node) ? 'inner' : getDropType(rect, e.clientX, e.clientY);
  if (!type) {
    dropping.value = null;
    return;
  }
  const desc = draggingComponent.value as any;
  if (
    (type === 'inner' && node.allowDrop(desc)) ||
    (type !== 'inner' && node.parent?.allowDrop(desc))
  ) {
    dropping.value = {
      node,
      el: _el as HTMLElement,
      rect,
      type: type
    } as any;
  } else {
    dropping.value = null;
  }
  hover.value = null;
}

function setSelected(node: INode, el?: HTMLElement, path?: Array<INode>) {
  const _el = el || findElByNode(node);
  const _path = path || findPathByNode(node);
  const cur: ISelected = {
    node,
    el: _el as HTMLElement,
    rect: (_el as HTMLElement)?.getBoundingClientRect(),
    path: _path
  };
  selected.value = cur as any;
}

function updateRect(entries: any) {
  setTimeout(() => {
    if (selected.value) {
      const parentNode = selected.value.el?.parentNode;
      // 如果节点已经重新创建创建了，需要重置selected
      if (!parentNode) {
        setSelected(selected.value.node as any);
      } else {
        const rect = selected.value.el.getBoundingClientRect();
        // 如果获取不到 rect，重新设置 selected, 图表组件会出现更新节点后获取不到的情况
        if (!rect.width && !rect.height) {
          setSelected(selected.value.node as any);
        } else {
          selected.value.rect = rect;
        }
      }
    }
    if (hover.value && hover.value.el) {
      hover.value.rect = hover.value.el.getBoundingClientRect();
    }
  }, 100);
}

function findVtjElement(path: Array<HTMLElement>) {
  return path.find((el) => !!(el as any).__vtj__);
}

function findNodePath(file: IFile, path: Array<HTMLElement>) {
  const vtjElements = path.filter((el) => !!(el as any).__vtj__);
  return vtjElements.map((el) => findNode(file, el));
}

function findElByNode(node: INode) {
  if (!iframeDocument.value) return;
  const list: Array<HTMLElement> = Array.from(
    iframeDocument.value.querySelectorAll('*')
  );
  return list.find((el) => (el as any).__vtj__ === node.id);
}

function findPathByNode(node: INode) {
  const path: INode[] = [];
  let current = node;
  while (current.parent && current.parent !== current) {
    current = current.parent;
    if (current !== node) {
      path.unshift(current);
    }
  }
  return path.reverse();
}

function findNode(file: IFile, el?: HTMLElement | null): INode {
  if (!el) return file;
  const id = (el as any).__vtj__;
  if (file.id === id) return file;
  return file.nodeMap[id] as INode;
}

function getDefaultProps(props: ComponentProp[] = []) {
  return props.reduce((result: any, prop: ComponentProp) => {
    result[prop.name] = prop.defaultValue;
    return result;
  }, {} as Record<string, any>);
}

function allowDrop(this: Engine, e: DragEvent) {
  e.preventDefault();
  const { project } = this;
  const current = project.current;
  if (!current) return;
  const el = findVtjElement((e as any).path);
  if (!el) return;
  const node = findNode(current as IFile, el);
  setDropping(e, node, el);
}

export function doDrop(
  assets: Assets,
  parent: INode | null,
  callback: (s?: ComponentSlot) => void
) {
  const componentMap = assets.componentMap || {};
  if (!parent) return callback();
  let slots = componentMap[parent.name]?.slots;
  if (!slots) return callback();
  slots = slots.map((n) => {
    return typeof n === 'string' ? { name: n } : n;
  });
  createSlotDialog(slots as ComponentSlot[]).then((slot) => {
    callback(slot as ComponentSlot);
  });
}

function handleDrop(this: Engine, e: DragEvent) {
  e.preventDefault();
  const { assets, project } = this;
  const current = project.current;
  if (!current) return;
  const data = draggingComponent.value as any;
  if (!data) return;
  // 放置页面模板
  if (data.name === PAGE_NAME) {
    if (current.name === BLOCK_NAME) {
      ElMessage.warning({
        message: '当前画布的是区块，不能放置页面模板'
      });
      return;
    }

    ElMessageBox.confirm(
      '放置的页面模板，当前的画布将重置，是否继续？',
      '提示',
      { type: 'warning' }
    ).then(() => {
      (current as Document).reset(data);
      dropping.value = null;
    });
    return;
  }

  const el = findVtjElement((e as any).path);
  if (!el) return;
  const node = findNode(current as IFile, el);
  let schema: IFileSchema;
  if (data.name === BLOCK_NAME) {
    schema = data;
  } else {
    const props = getDefaultProps(data.props);
    schema = {
      name: data.name,
      props,
      ...data.snippet
    };
  }

  const type = dropping.value?.type;
  if (!type) return;
  current.registerComponent(assets, schema);
  if (type === 'inner') {
    doDrop(assets, node, (slot) => {
      Object.assign(schema, { slotName: slot?.name || '' });
      node.appendSchema(schema);
    });
  } else {
    doDrop(assets, node.parent, (slot) => {
      Object.assign(schema, { slotName: slot?.name || '' });
      const newNode =
        schema.name === BLOCK_NAME
          ? new Block(schema, node.parent as any)
          : new Node(schema, node.parent as any);
      switch (type) {
        case 'left':
        case 'top':
          node.parent?.insertBefore(newNode, node);
          break;
        case 'right':
        case 'bottom':
          node.parent?.insertAfter(newNode, node);
          break;
      }
    });
  }
  dropping.value = null;
}

function handleMouseOver(this: Engine, e: MouseEvent) {
  const { project } = this;
  const current = project.current;
  if (!current) return;
  const el = findVtjElement((e as any).path);
  if (!el) return;
  const node = findNode(current as IFile, el);
  setHover(node, el);
}

function handleMouseLeave(this: Engine, e: MouseEvent) {
  hover.value = null;
  dropping.value = null;
}

function handleClick(this: Engine, e: MouseEvent) {
  const { project } = this;
  const current = project.current;
  if (!current) return;
  const el = findVtjElement((e as any).path);
  if (!el) return;
  const node = findNode(current as IFile, el);
  const path = findNodePath(current as IFile, (e as any).path);
  path.splice(0, 1);
  setSelected(node, el, path);
  hover.value = null;
}

function getDomDocument(iframe: Ref<HTMLIFrameElement>) {
  const window = iframe.value?.contentWindow;
  return window?.document;
}

export default function useDesigner(iframe: Ref<HTMLIFrameElement>) {
  const { engine, project, emitter } = useCore();

  const _allowDrop = allowDrop.bind(engine);
  const _handleDrop = handleDrop.bind(engine);
  const _handleMouseOver = handleMouseOver.bind(engine);
  const _handleMouseLeave = handleMouseLeave.bind(engine);
  const _handleClick = handleClick.bind(engine);

  watch(
    () => project.current,
    (doc) => {
      if (doc) {
        const schema = doc.toSchema();
        engine.simulator.draw(schema as IFileSchema);
      }
      selected.value = null;
      hover.value = null;
    }
  );

  emitter.on(EVENT_OUTLINE_CHANGE, () => {
    selected.value = null;
    hover.value = null;
  });

  emitter.on(EVENT_COMPONENETS_DRAG_START, (item) => {
    selected.value = null;
    hover.value = null;
    draggingComponent.value = item as any;
  });

  emitter.on(EVENT_COMPONENETS_DRAG_END, () => {
    dropping.value = null;
    draggingComponent.value = null;
  });

  emitter.on(EVENT_OUTLINE_HOVER, (node) => {
    if (node) {
      setHover(node as INode);
    } else {
      hover.value = null;
    }
  });
  emitter.on(EVENT_OUTLINE_SELECTED, (node) => {
    if (node) {
      setSelected(node as INode);
    } else {
      selected.value = null;
    }
  });

  emitter.on(EVENT_RENDERER_COMPLETE, updateRect);

  useResizeObserver(iframe, updateRect);

  onMounted(() => {
    const document = getDomDocument(iframe);
    if (!document) return;
    iframeDocument.value = document;
    engine.simulator.init(iframe.value, () => {
      document.addEventListener('dragover', _allowDrop);
      document.addEventListener('drop', _handleDrop);
      document.addEventListener('mouseover', _handleMouseOver);
      document.addEventListener('mouseleave', _handleMouseLeave);
      document.addEventListener('click', _handleClick);
      document.addEventListener('scroll', updateRect);
      document.addEventListener('dragleave', _handleMouseLeave);
    });
  });

  onUnmounted(() => {
    const document = iframeDocument.value;
    if (!document) return;
    document.removeEventListener('dragover', _allowDrop);
    document.removeEventListener('drop', _handleDrop);
    document.removeEventListener('mouseover', _handleMouseOver);
    document.removeEventListener('mouseleave', _handleMouseLeave);
    document.removeEventListener('click', _handleClick);
    document.removeEventListener('scroll', updateRect);
    document.removeEventListener('dragleave', _handleMouseLeave);
    emitter.off(EVENT_OUTLINE_CHANGE);
    emitter.off(EVENT_OUTLINE_HOVER);
    emitter.off(EVENT_OUTLINE_SELECTED);
    emitter.off(EVENT_COMPONENETS_DRAG_START);
    emitter.off(EVENT_COMPONENETS_DRAG_END);
    emitter.off(EVENT_RENDERER_COMPLETE, updateRect);
    engine.simulator.dispose();
  });

  return {
    project,
    hover,
    dropping,
    selected,
    setHover,
    setSelected
  };
}

export function useSelected() {
  return {
    selected,
    setSelected
  };
}
