<script setup lang="ts">
import type { CheckboxChangeEvent } from 'ant-design-vue/es/checkbox/interface';
import type { DataNode } from 'ant-design-vue/es/tree';
import type { CheckInfo } from 'ant-design-vue/es/vc-tree/props';

import type { PropType, SetupContext } from 'vue';

import { computed, nextTick, onMounted, ref, useSlots, watch } from 'vue';

import { findGroupParentIds, treeToList } from '@vben/utils';

import { Checkbox, Tree } from 'ant-design-vue';
import { uniq } from 'lodash-es';

/** 需要禁止透传 */
defineOptions({ inheritAttrs: false });

const props = defineProps({
  checkStrictly: {
    default: true,
    type: Boolean,
  },
  expandAllOnInit: {
    default: false,
    type: Boolean,
  },
  fieldNames: {
    default: () => ({ key: 'id', title: 'name' }),
    type: Object as PropType<{ key: string; title: string }>,
  },
  /** 点击节点关联/独立时 清空已勾选的节点 */
  resetOnStrictlyChange: {
    default: true,
    type: Boolean,
  },
  treeData: {
    default: () => [],
    type: Array as PropType<DataNode[]>,
  },
});
const emit = defineEmits<{ checkStrictlyChange: [boolean] }>();

const expandStatus = ref(false);
const selectAllStatus = ref(false);

/**
 * 后台的这个字段跟antd/ele是反的
 * 组件库这个字段代表不关联
 * 后台这个代表关联
 */
const innerCheckedStrictly = computed(() => {
  return !props.checkStrictly;
});

const associationText = computed(() => {
  return props.checkStrictly ? '父子节点关联' : '父子节点独立';
});

/**
 * 这个只用于界面显示
 * 关联情况下 只会有最末尾的节点被选中
 */
const checkedKeys = defineModel('value', {
  default: () => [],
  type: Array as PropType<(number | string)[]>,
});
// 所有节点的ID
const allKeys = computed(() => {
  const idField = props.fieldNames.key;
  return treeToList(props.treeData).map((item: any) => item[idField]);
});

/** 已经选择的所有节点  包括子/父节点 用于提交 */
const checkedRealKeys = ref<(number | string)[]>([]);

/**
 * 取第一次的menuTree id 设置到checkedMenuKeys
 * 主要为了解决没有任何修改 直接点击保存的情况
 *
 * length为0情况(即新增时候没有勾选节点) 勾选这里会延迟触发 节点会拼接上父节点 导致ID重复
 */
const stop = watch([checkedKeys, () => props.treeData], () => {
  if (
    props.checkStrictly &&
    checkedKeys.value.length > 0 &&
    props.treeData.length > 0
  ) {
    /** 找到父节点 添加上 */
    const parentIds = findGroupParentIds(
      props.treeData,
      checkedKeys.value as any,
      { id: props.fieldNames.key },
    );
    /**
     * uniq 解决上面的id重复问题
     */
    checkedRealKeys.value = uniq([...parentIds, ...checkedKeys.value]);
    stop();
  }
  if (!props.checkStrictly && checkedKeys.value.length > 0) {
    /** 节点独立 这里是全部的节点 */
    checkedRealKeys.value = checkedKeys.value;
    stop();
  }
});

/**
 *
 * @param checkedStateKeys 已经选中的子节点的ID
 * @param info info.halfCheckedKeys为父节点的ID
 */
type CheckedState<T = number | string> =
  | T[]
  | { checked: T[]; halfChecked: T[] };
function handleChecked(checkedStateKeys: CheckedState, info: CheckInfo) {
  // 数组的话为节点关联
  if (Array.isArray(checkedStateKeys)) {
    const halfCheckedKeys: number[] = (info.halfCheckedKeys || []) as number[];
    checkedRealKeys.value = [...halfCheckedKeys, ...checkedStateKeys];
  } else {
    checkedRealKeys.value = [...checkedStateKeys.checked];
    // fix: Invalid prop: type check failed for prop "value". Expected Array, got Object
    checkedKeys.value = [...checkedStateKeys.checked];
  }
}

function handleExpandChange(e: CheckboxChangeEvent) {
  // 这个用于展示
  checkedKeys.value = e.target.checked ? allKeys.value : [];
  // 这个用于提交
  checkedRealKeys.value = e.target.checked ? allKeys.value : [];
}

const expandedKeys = ref<string[]>([]);
function handleExpandOrCollapseAll(e: CheckboxChangeEvent) {
  const expand = e.target.checked;
  expandedKeys.value = expand ? allKeys.value : [];
}

function handleCheckStrictlyChange(e: CheckboxChangeEvent) {
  emit('checkStrictlyChange', e.target.checked);
  if (props.resetOnStrictlyChange) {
    checkedKeys.value = [];
    checkedRealKeys.value = [];
  }
}

/**
 * 暴露方法来获取用于提交的全部节点
 * uniq去重(保险方案)
 */
defineExpose({
  getCheckedKeys: () => uniq(checkedRealKeys.value),
});

onMounted(async () => {
  if (props.expandAllOnInit) {
    await nextTick();
    expandedKeys.value = allKeys.value;
  }
});

const slots = useSlots() as SetupContext['slots'];
</script>

<template>
  <div class="bg-background w-full rounded-lg border-[1px] p-[12px]">
    <div class="flex items-center justify-between gap-2 border-b-[1px] pb-2">
      <div>
        <span>节点状态: </span>
        <span :class="[props.checkStrictly ? 'text-primary' : 'text-red-500']">
          {{ associationText }}
        </span>
      </div>
      <div>
        已选中
        <span class="text-primary mx-1 font-semibold">
          {{ checkedRealKeys.length }}
        </span>
        个节点
      </div>
    </div>
    <div
      class="flex flex-wrap items-center justify-between border-b-[1px] py-2"
    >
      <Checkbox
        v-model:checked="expandStatus"
        @change="handleExpandOrCollapseAll"
      >
        展开/折叠全部
      </Checkbox>
      <Checkbox v-model:checked="selectAllStatus" @change="handleExpandChange">
        全选/取消全选
      </Checkbox>
      <Checkbox :checked="checkStrictly" @change="handleCheckStrictlyChange">
        父子节点关联
      </Checkbox>
    </div>
    <div class="py-2">
      <Tree
        v-if="treeData.length > 0"
        v-model:check-strictly="innerCheckedStrictly"
        v-model:checked-keys="checkedKeys"
        v-model:expanded-keys="expandedKeys"
        :checkable="true"
        :field-names="fieldNames"
        :selectable="false"
        :tree-data="treeData"
        @check="handleChecked"
      >
        <template
          v-for="slotName in Object.keys(slots)"
          :key="slotName"
          #[slotName]="data"
        >
          <slot :name="slotName" v-bind="data ?? {}"></slot>
        </template>
      </Tree>
    </div>
  </div>
</template>
