<template>
  <li
    :id="`${menuId}-${node.uid}`"
    role="menuitem"
    :tabindex="expandable ? -1 : undefined"
    :class="[
      'el-cascade-node',
      checkStrictly && 'is-selectable',
      inExpandingPath && 'in-active-path',
      inCheckedPath && 'in-checked-path',
      node.checked && 'is-active',
      !expandable && 'is-disabled'
    ]"
    @mouseenter="handleHoverExpand"
    @focus="handleHoverExpand"
    @click="handleClick"
  >
    <!-- prefix -->
    <el-checkbox
      v-if="multiple"
      :model-value="node.checked"
      :indeterminate="node.indeterminate"
      :disabled="isDisabled"
      @click.stop
      @update:model-value="handleSelectCheck"
    />
    <el-radio
      v-else-if="checkStrictly"
      :model-value="checkedNodeId"
      :value="node.uid"
      :disabled="isDisabled"
      @update:model-value="handleSelectCheck"
      @click.stop
    >
      <!--
        Add an empty element to avoid render label,
        do not use empty fragment here for https://github.com/vuejs/vue-next/pull/2485
      -->
      <span></span>
    </el-radio>
    <el-icon v-else-if="isLeaf && node.checked" class="el-cascade-node__prefix">
      <check />
    </el-icon>

    <!-- content -->
    <node-content />

    <!-- postfix -->
    <template v-if="!isLeaf">
      <el-icon v-if="node.loading" class="is-loading el-cascade-node__postfix">
        <loading />
      </el-icon>
      <el-icon v-else class="arrow-right el-cascade-node__postfix">
        <arrow-right />
      </el-icon>
    </template>
  </li>
</template>

<script lang="ts">
import { computed, defineComponent, inject } from 'vue'
import ElCheckbox from '@element-ultra/components/checkbox'
import ElRadio from '@element-ultra/components/radio'
import ElIcon from '@element-ultra/components/icon'
import { Check, Loading, ArrowRight } from 'icon-ultra'
import NodeContent from './node-content'
import { Cascade_PANEL_INJECTION_KEY } from './types'
import type { default as CascadeNode } from './node'

import type { PropType } from 'vue'

export default defineComponent({
  name: 'ElCascadeNode',

  components: {
    ElCheckbox,
    ElRadio,
    NodeContent,
    ElIcon,
    Check,
    Loading,
    ArrowRight
  },

  props: {
    node: {
      type: Object as PropType<CascadeNode>,
      required: true
    },
    menuId: String
  },

  emits: ['expand'],

  setup(props, { emit }) {
    const panel = inject(Cascade_PANEL_INJECTION_KEY)!

    const isHoverMenu = computed(() => panel.isHoverMenu)
    const multiple = computed(() => panel.config.multiple)
    const checkStrictly = computed(() => panel.config.checkStrictly)
    const checkedNodeId = computed(() => panel.checkedNodes[0]?.uid)

    const isDisabled = computed(() => props.node.isDisabled)
    const isLeaf = computed(() => props.node.isLeaf)
    const expandable = computed(
      () => (checkStrictly.value && !isLeaf.value) || !isDisabled.value
    )
    const inExpandingPath = computed(() => isInPath(panel.expandingNode!))
    // only useful in check-strictly mode
    const inCheckedPath = computed(
      () => checkStrictly.value && panel.checkedNodes.some(isInPath)
    )

    const isInPath = (node: CascadeNode) => {
      const { level, uid } = props.node
      return node?.pathNodes[level - 1]?.uid === uid
    }

    const doExpand = () => {
      if (inExpandingPath.value) return
      panel.expandNode(props.node)
    }

    const doCheck = (checked: boolean) => {
      const { node } = props
      if (checked === node.checked) return
      panel.handleCheckChange(node, checked)
    }

    const doLoad = () => {
      panel.lazyLoad(props.node, () => {
        if (!isLeaf.value) doExpand()
      })
    }

    const handleHoverExpand = (e: Event) => {
      if (!isHoverMenu.value) return
      handleExpand()
      !isLeaf.value && emit('expand', e)
    }

    const handleExpand = () => {
      const { node } = props
      // do not exclude leaf node because the menus expanded might have to reset
      if (!expandable.value || node.loading) return
      node.loaded ? doExpand() : doLoad()
    }

    const handleClick = () => {
      if (isHoverMenu.value && !isLeaf.value) return

      if (
        isLeaf.value &&
        !isDisabled.value &&
        !checkStrictly.value &&
        !multiple.value
      ) {
        handleCheck(true)
      } else {
        handleExpand()
      }
    }

    const handleSelectCheck = (checked: boolean) => {
      if (checkStrictly.value) {
        doCheck(checked)
        if (props.node.loaded) {
          doExpand()
        }
      } else {
        handleCheck(checked)
      }
    }

    const handleCheck = (checked: boolean) => {
      if (!props.node.loaded) {
        doLoad()
      } else {
        doCheck(checked)
        !checkStrictly.value && doExpand()
      }
    }

    return {
      panel,
      isHoverMenu,
      multiple,
      checkStrictly,
      checkedNodeId,
      isDisabled,
      isLeaf,
      expandable,
      inExpandingPath,
      inCheckedPath,
      handleHoverExpand,
      handleExpand,
      handleClick,
      handleCheck,
      handleSelectCheck
    }
  }
})
</script>
