/**
 * Webpack has bug for import loop, which is not the same behavior as ES module.
 * When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
 */
import type { ComputedRef, InjectionKey, PropType, ShallowRef } from 'vue'
import { computed, defineComponent, inject, provide, shallowRef } from 'vue'

import type { VueNode } from '../_util/type'
import type { DraggableConfig } from './Tree'
import type {
  DataEntity,
  Direction,
  DragNodeEvent,
  EventDataNode,
  FlattenNode,
  IconType,
  Key,
} from './interface'

export interface NodeMouseEventParams {
  event: MouseEvent
  node: EventDataNode
}
export interface NodeDragEventParams {
  event: DragEvent
  node: EventDataNode
}

export type NodeMouseEventHandler = (e: MouseEvent, node: EventDataNode) => void
export type NodeDragEventHandler = (
  e: DragEvent,
  node: DragNodeEvent,
  outsideTree?: boolean,
) => void

export interface TreeContextProps {
  prefixCls: string
  selectable: boolean
  showIcon: boolean
  icon: IconType
  switcherIcon: IconType
  draggable: DraggableConfig | false
  draggingNodeKey?: Key
  checkable: boolean
  customCheckable: () => any
  checkStrictly: boolean
  disabled: boolean
  keyEntities: Record<Key, DataEntity<any>>
  // for details see comment in Tree.state (Tree.tsx)
  dropLevelOffset?: number
  dropContainerKey: Key | null
  dropTargetKey: Key | null
  dropPosition: -1 | 0 | 1 | null
  indent: number | null
  dropIndicatorRender: (props: {
    dropPosition: -1 | 0 | 1
    dropLevelOffset: number
    indent: number | null
    prefixCls: string
    direction: Direction
  }) => VueNode
  dragOverNodeKey: Key | null
  dragging: boolean
  direction: Direction

  loadData: (treeNode: EventDataNode) => Promise<void>
  filterTreeNode: (treeNode: EventDataNode) => boolean

  onNodeClick: NodeMouseEventHandler
  onNodeDoubleClick: NodeMouseEventHandler
  onNodeExpand: NodeMouseEventHandler
  onNodeSelect: NodeMouseEventHandler
  onNodeCheck: (e: MouseEvent, treeNode: EventDataNode, checked: boolean) => void
  onNodeLoad: (treeNode: EventDataNode) => void
  onNodeMouseEnter: NodeMouseEventHandler
  onNodeMouseLeave: NodeMouseEventHandler
  onNodeContextMenu: NodeMouseEventHandler
  onNodeDragStart: NodeDragEventHandler
  onNodeDragEnter: NodeDragEventHandler
  onNodeDragOver: NodeDragEventHandler
  onNodeDragLeave: NodeDragEventHandler
  onNodeDragEnd: NodeDragEventHandler
  onNodeDrop: NodeDragEventHandler
  slots: {
    title?: (data: any) => any
    titleRender?: (data: any) => any
    [key: string]: ((...args: any[]) => any) | undefined
  }
}
const TreeContextKey: InjectionKey<ComputedRef<TreeContextProps>> = Symbol('TreeContextKey')

export const TreeContext = defineComponent({
  compatConfig: { MODE: 3 },
  name: 'TreeContext',
  props: {
    value: { type: Object as PropType<TreeContextProps> },
  },
  setup(props, { slots }) {
    provide(
      TreeContextKey,
      computed(() => props.value),
    )
    return () => slots.default?.()
  },
})

export const useInjectTreeContext = () => {
  return inject(
    TreeContextKey,
    computed(() => ({}) as TreeContextProps),
  )
}
interface KeysStateKeyType {
  expandedKeysSet: ComputedRef<Set<Key>>
  selectedKeysSet: ComputedRef<Set<Key>>
  loadedKeysSet: ComputedRef<Set<Key>>
  loadingKeysSet: ComputedRef<Set<Key>>
  checkedKeysSet: ComputedRef<Set<Key>>
  halfCheckedKeysSet: ComputedRef<Set<Key>>
  expandedKeys: ShallowRef<Key[]>
  selectedKeys: ShallowRef<Key[]>
  loadedKeys: ShallowRef<Key[]>
  loadingKeys: ShallowRef<Key[]>
  checkedKeys: ShallowRef<Key[]>
  halfCheckedKeys: ShallowRef<Key[]>
  flattenNodes: ShallowRef<FlattenNode[]>
}
const KeysStateKey: InjectionKey<KeysStateKeyType> = Symbol('KeysStateKey')
export const useProvideKeysState = (state: KeysStateKeyType) => {
  provide(KeysStateKey, state)
}

export const useInjectKeysState = () => {
  return inject(KeysStateKey, {
    expandedKeys: shallowRef<Key[]>([]),
    selectedKeys: shallowRef<Key[]>([]),
    loadedKeys: shallowRef<Key[]>([]),
    loadingKeys: shallowRef<Key[]>([]),
    checkedKeys: shallowRef<Key[]>([]),
    halfCheckedKeys: shallowRef<Key[]>([]),
    expandedKeysSet: computed<Set<Key>>(() => new Set()),
    selectedKeysSet: computed<Set<Key>>(() => new Set()),
    loadedKeysSet: computed<Set<Key>>(() => new Set()),
    loadingKeysSet: computed<Set<Key>>(() => new Set()),
    checkedKeysSet: computed<Set<Key>>(() => new Set()),
    halfCheckedKeysSet: computed<Set<Key>>(() => new Set()),
    flattenNodes: shallowRef<FlattenNode[]>([]),
  })
}
