<template>
  <div
    v-if="!schema.hidden"
    v-bind="$attrs"
    ref="nodeRef"
    :style="getStyle"
    :class="schema.type === 'page' ? '' : 'ignore-click widget-node'"
    :widget-id="schema.id"
    :widget-type="schema.type"
    @click="handleSelect"
    @contextmenu.stop="handleContextmenu"
    :ignore-drag="ignoreDrag && !schema.props?.flags?.includes('LV_OBJ_FLAG_IGNORE_LAYOUT')"
  >
    <!-- 控件 -->
    <component
      v-bind="widgetProps"
      :is="widget"
      :style="{ 'pointer-events': schema.type === 'page' ? 'none' : '' }"
    />
    <div
      v-if="schema.children"
      class="absolute left-0 top-0 w-full h-full"
      :style="{ ...layoutStyle, ...dropStyle }"
    >
      <!-- 子节点 -->
      <NodeItem
        v-for="(child, index) in children"
        :key="child.id"
        :ignore-drag="ignoreChildrenDrag"
        :zIndex="schema.children.length - Number(index)"
        :schema="child"
        :rootContainer="nodeRef!"
        :style="childStyle"
      />
    </div>
  </div>
  <!-- 右键菜单 -->
  <ContextMenu
    ref="contextMenuRef"
    :id="schema.id"
    :virtualRef="triggerRef"
    :widgetType="schema.type === 'page' ? 'page' : 'widget'"
  />
</template>

<script setup lang="ts">
import type { BaseWidget } from '@/types/baseWidget'
import type { Page } from '@/types/page'
import type { StageState } from './type'
import type { CSSProperties } from 'vue'

import { computed, ref, inject, watch, nextTick, onBeforeUnmount } from 'vue'
import { useDrop, useMouse } from 'vue-hooks-plus'
import { createWidget } from '@/model'
import LvglWidgets from '@/lvgl-widgets'
import { useProjectStore } from '@/store/modules/project'
import { useAppStore } from '@/store/modules/app'
import { useActionStore } from '@/store/modules/action'
import { has, isEmpty, get } from 'lodash-es'

import ContextMenu from './ContextMenu.vue'
import { getAddWidgetIndex, isDescendant } from '@/utils'
import { klona } from 'klona'

defineOptions({
  name: 'NodeItem'
})

const page = inject<Page>('page')
const pageState = inject<StageState>('state')

const props = defineProps<{
  // 父级容器 拖拽缩放设置
  rootContainer: HTMLElement
  // 传入节点模型数据
  schema: BaseWidget | Page
  // 传入样式 如页面样式
  style?: CSSProperties
  // 层级
  zIndex?: number
  // 是否可拖拽
  ignoreDrag?: boolean
}>()

const projectStore = useProjectStore()
const appStore = useAppStore()
const actionStore = useActionStore()
// 获取lvgl vue控件
const widget = computed(() => LvglWidgets[props.schema.type]?.component)
// 节点容器放置ref
const nodeRef = ref<HTMLDivElement>()
// 当前层级
const zIndex = ref(props.zIndex)

// 放置效果样式
const dropStyle = ref<CSSProperties>({})

// 子节点
// 默认为子节点 当子节点为可切换容器时 根据activeIndex展示下一级节点
const children = computed(() => {
  const { schema } = props

  return has(schema.props, 'activeIndex')
    ? get(schema.children, schema.props.activeIndex)?.children || []
    : schema.children
})

// 控件属性
const widgetProps = computed(() => {
  const { schema } = props

  return {
    ...(schema?.props || {}),
    part: schema?.part,
    state: schema?.state,
    styles: schema?.style,
    id: schema?.id,
    // 处理可切换子节点容器属性
    ...(has(schema.props, 'activeIndex') && schema.children ? { children: schema.children } : {})
  }
})

// 忽略拖拽
const ignoreChildrenDrag = computed(() => {
  const { schema } = props
  return schema.props?.layout === 'flex'
})

// 组件样式
const getStyle = computed((): CSSProperties => {
  const { style = {}, schema } = props

  const other: CSSProperties = {}
  // 显示所有控件边框
  if (pageState?.showBorder && schema.type !== 'page') {
    other.border = '1px dashed #000'
    other.transform = `translate(${schema.props.x - 1}px, ${schema.props.y - 1}px)`
  }

  let rotate = ''
  // 存在旋转属性
  if (schema.props?.rotation) {
    rotate = `rotate(${schema.props.rotation}deg)`
    if (schema.props?.pivot) {
      other.transformOrigin = `${schema.props.pivot.x}px ${schema.props.pivot.y}px`
    }

    if (other.transform) {
      other.transform += ` ${rotate}`
    }
  }

  let scale = 1
  // 存在缩放属性且打开
  if (schema.props?.openScale) {
    scale = (schema.props?.scale ?? 256) / 256
  }

  let parentStyle = klona(style)

  // 忽略父级布局
  if (schema.props.flags?.includes('LV_OBJ_FLAG_IGNORE_LAYOUT')) {
    delete parentStyle.position
    delete parentStyle.transform
  }

  return {
    position: 'absolute',
    left: 0,
    top: 0,
    transform: `translate(${schema.props.x}px, ${schema.props.y}px) ${rotate}`,
    width: schema.props?.width * scale + 'px',
    height: schema.props?.height * scale + 'px',
    zIndex: zIndex.value,
    ...parentStyle,
    ...other
    // ...dropStyle.value
  }
})

/**
 * 布局样式
 */
const layoutStyle = ref<CSSProperties>({})
watch(
  () => props.schema.props,
  async () => {
    await nextTick()
    const { schema } = props
    let style: CSSProperties = {}
    layoutStyle.value = {}

    if (schema.props?.layout === 'flex' && schema.props?.flex) {
      const { flex } = schema.props
      const directionMap = {
        row: 'row',
        column: 'column',
        'row-wrap': 'row',
        'column-wrap': 'column',
        'row-reverse': 'row-reverse',
        'column-reverse': 'column-reverse',
        'row-wrap-reverse': 'row-reverse',
        'column-wrap-reverse': 'column-reverse'
      }
      style = {
        display: 'flex',
        overflow: 'hidden',
        flexDirection: directionMap?.[flex?.direction],
        flexWrap: flex?.direction?.includes('wrap') ? 'wrap' : 'nowrap',
        justifyContent: flex?.mainAxisAlign,
        alignItems: flex?.crossAxisAlign,
        alignContent: flex?.trackAxisAlign,
        'row-gap': flex?.rowGap ? `${flex.rowGap}px` : 0,
        'column-gap': flex?.columnGap ? `${flex.columnGap}px` : 0
      }
    }

    // 获取子节点容器样式
    const getChildStyle = LvglWidgets[props.schema.type]?.getChildStyle
    if (typeof getChildStyle === 'function') {
      style = {
        ...style,
        ...getChildStyle(props.schema)
      }
    }
    layoutStyle.value = style
  },
  {
    deep: true,
    immediate: true
  }
)

/**
 * 子元素样式
 */
const childStyle = computed((): CSSProperties => {
  const { schema } = props
  // flex布局,子元素移除transform,
  if (schema.props?.layout === 'flex') {
    return {
      transform: 'none',
      position: 'relative',
      flexShrink: 0
    }
  }
  return {}
})

// 拖拽放置事件处理
useDrop(nodeRef, {
  // 元素放置
  onDom: (content, event) => {
    const { schema } = props

    dropStyle.value = {}
    event?.stopPropagation()
    if (!content || !schema?.children) return

    // 创建控件
    const { offsetX = 0, offsetY = 0 } = event || {}
    const index = getAddWidgetIndex(page!, content?.key)
    const newWidget = createWidget(content, index, !!content?.id)
    newWidget.props.x = offsetX
    newWidget.props.y = offsetY

    // 添加到前面
    if (has(schema.props, 'activeIndex')) {
      const parent = get(schema.children, schema.props.activeIndex)
      parent?.children?.push(newWidget)
    } else {
      schema.children?.push(newWidget)
    }

    projectStore.setSelectWidgets([newWidget])
  },
  onDragOver: () => {
    if (props.schema.type !== 'page' && props.schema.children) {
      dropStyle.value = {
        // 放置阴影效果
        boxShadow: '0px 0px 10px #00fcfc'
      }
    }
  },
  onDragLeave: () => {
    dropStyle.value = {}
  }
})

// 监听鼠标位置
const nodeState = useMouse(nodeRef)
// 可放置标识
let dropFlag = false
let offsetX = 0
let offsetY = 0
let mouseupTarget: Element | null = null

const handleDropMouseup = (e: Event) => {
  const mouseEvent = e as MouseEvent
  offsetX = mouseEvent.offsetX
  offsetY = mouseEvent.offsetY
  mouseupTarget = null
}

const clearDropMouseupListener = () => {
  if (!mouseupTarget) return

  mouseupTarget.removeEventListener('mouseup', handleDropMouseup, true)
  mouseupTarget = null
}

const bindDropMouseupListener = (target?: Element) => {
  if (!target || mouseupTarget === target) return

  clearDropMouseupListener()
  mouseupTarget = target
  target.addEventListener('mouseup', handleDropMouseup, {
    once: true,
    capture: true
  })
}

onBeforeUnmount(() => {
  clearDropMouseupListener()
})

watch(nodeState, (state) => {
  const { schema } = props
  if (schema?.children && schema.type !== 'page') {
    const { elementW, elementH, elementX, elementY, clientX, clientY } = state

    // 停止拖拽且允许放置时剪切到容器内
    if (dropFlag && !appStore.draging) {
      dropFlag = false
      clearDropMouseupListener()
      // 放置到容器内
      // 获取在容器的真实位置
      const scale = elementW / schema.props.width
      const x = elementX / scale - offsetX
      const y = elementY / scale - offsetY

      projectStore.activeWidgets[0].props.x = x
      projectStore.activeWidgets[0].props.y = y

      const node = klona(projectStore.activeWidgets[0])
      // 删除原来的节点
      actionStore.onDeleteById(node.id)
      // 添加到前面
      if (has(schema.props, 'activeIndex')) {
        const parent = get(schema.children, schema.props.activeIndex)
        parent?.children?.push(node)
      } else {
        schema.children?.push(node)
      }
      projectStore.activeWidgets = [node]
    }
    // 鼠标在节点内部
    // 处于节点拖拽状态
    // 当前选中节点只有一个
    // 且非自身节点
    // 非自身后代节点
    // 非页面节点
    if (
      elementX > 0 &&
      elementX < elementW &&
      elementY > 0 &&
      elementY < elementH &&
      appStore.draging &&
      projectStore.activeWidgets.length === 1 &&
      projectStore.activeWidgets[0].id !== schema.id &&
      !isDescendant(schema, projectStore.activeWidgets[0].id) &&
      schema.type !== 'page'
    ) {
      // 获取当前鼠标位置全部控件节点元素
      const elements = document
        .elementsFromPoint(clientX, clientY)
        ?.filter((el) => el.className?.includes?.('widget-node'))
      // 获取第一个元素不是自身的元素
      const targetEl = elements.find((el) => {
        const id = el.attributes.getNamedItem('widget-id')?.value
        return id !== projectStore.activeWidget?.id
      })
      const targetId = targetEl?.attributes.getNamedItem('widget-id')?.value

      if (targetId === schema.id) {
        // 最上层的容器
        dropStyle.value = {
          // 放置阴影效果
          boxShadow: '0px 0px 10px #00fcfc'
        }
        dropFlag = true
        bindDropMouseupListener(elements[0])
      } else if (!isEmpty(dropStyle.value)) {
        dropFlag = false
        clearDropMouseupListener()
        // 容器外
        dropStyle.value = {}
      }
    } else if (!isEmpty(dropStyle.value)) {
      dropFlag = false
      clearDropMouseupListener()
      // 容器外
      dropStyle.value = {}
    }
  }
})

// 选择节点
const handleSelect = (e: MouseEvent) => {
  if (props.schema.type !== 'page') {
    e.stopPropagation()
    // 判断当前是否按住ctrl
    if (e.ctrlKey) {
      projectStore.activeWidgets.push(props.schema as BaseWidget)
    } else {
      projectStore.setSelectWidgets([props.schema as BaseWidget])
    }
  } else {
    projectStore.setSelectWidgets([])
  }
  // 关闭右键菜单
  contextMenuRef.value?.handleClose()
}

/******************************右键菜单*********************************/
const position = ref({
  top: 0,
  left: 0,
  bottom: 0,
  right: 0
} as DOMRect)

const contextMenuRef = ref<InstanceType<typeof ContextMenu>>()

const triggerRef = ref({
  getBoundingClientRect: () => position.value
})

// 处理右键菜单
const handleContextmenu = (event: MouseEvent) => {
  const { clientX, clientY } = event
  position.value = DOMRect.fromRect({
    x: clientX,
    y: clientY
  })
  event.preventDefault()
  contextMenuRef.value?.handleOpen()

  // 没选中当前节点时 右键选中节点
  if (!projectStore.activeWidgets.map((item) => item.id).includes(props.schema.id)) {
    if (props.schema.type !== 'page') {
      // 判断当前是否按住ctrl
      if (event.ctrlKey) {
        projectStore.activeWidgets.push(props.schema as BaseWidget)
      } else {
        projectStore.setSelectWidgets([props.schema as BaseWidget])
      }
    }
  }
}
</script>
