import {
  Checkbox,
  CloseButton,
  Combobox,
  Flex,
  getTreeExpandedState,
  Input,
  InputProps,
  Popover,
  Radio,
  RenderTreeNodePayload,
  Stack,
  Text,
  TextInput,
  TreeNodeData,
  useTree,
} from "@mantine/core"
import { useDisclosure } from "@mantine/hooks"
import { IconChevronUp, IconSearch, IconX } from "@tabler/icons-react"
import React, { useEffect, useMemo, useState } from "react"
import classes from "./VirtualTreeSelect.module.css"
import { VirtualizedTree } from "./VirtualizedTree"
import {
  buildTreeValueMap,
  filterTreeData_dfs,
  findNodesByValues,
  getDescendantLeafValues,
  getLabelsByValues,
  getStateFromDataByName,
} from "./libs/util"

interface BaseVirtualTreeSelectProps extends Omit<
  InputProps,
  "value" | "defaultValue" | "onChange"
> {
  data: TreeNodeData[]
  placeholder?: string
  closeOnSelect?: boolean
  label?: React.ReactNode
}

interface SingleVirtualTreeSelectProps extends BaseVirtualTreeSelectProps {
  mode?: "single"
  value?: string | null
  onChange?: (value: string | null, node: TreeNodeData | null) => void
}

interface MultipleVirtualTreeSelectProps extends BaseVirtualTreeSelectProps {
  mode: "multiple"
  value?: string[]
  onChange?: (value: string[], nodes: TreeNodeData[]) => void
}

type VirtualTreeSelectProps =
  | SingleVirtualTreeSelectProps
  | MultipleVirtualTreeSelectProps

const areSameValues = (left: string[], right: string[]) =>
  left.length === right.length &&
  left.every((value, index) => value === right[index])

export function VirtualTreeSelect(props: VirtualTreeSelectProps) {
  const {
    data,
    mode = "single",
    value,
    onChange,
    placeholder = "请选择...",
    closeOnSelect: closeOnSelectProp,
    label,
    ...inputProps
  } = props
  const [opened, { close, open, toggle }] = useDisclosure(false)
  const [searchName, setSearchName] = useState("")
  const closeOnSelect = closeOnSelectProp ?? mode === "single"

  const valueMap = useMemo(() => buildTreeValueMap(data), [data])

  const controlledValues = useMemo<string[]>(() => {
    if (mode === "multiple") {
      return Array.isArray(value) ? value : []
    }

    return typeof value === "string" ? [value] : []
  }, [mode, value])

  const filteredTreeData = useMemo(
    () => filterTreeData_dfs(data, searchName),
    [data, searchName]
  )

  const tree = useTree({
    initialExpandedState: getTreeExpandedState(data, []),
    initialSelectedState: mode === "single" ? controlledValues : [],
    initialCheckedState: mode === "multiple" ? controlledValues : [],
    multiple: mode === "multiple",
  })

  const currentValues =
    mode === "multiple" ? tree.checkedState : tree.selectedState

  const selectedLabels = useMemo(
    () => getLabelsByValues(valueMap, currentValues),
    [currentValues, valueMap]
  )

  const displayValue = useMemo(() => {
    if (mode === "multiple" && selectedLabels.length > 2) {
      return ""
    }

    return selectedLabels.join("、")
  }, [mode, selectedLabels])

  const shouldShowCountTag = mode === "multiple" && currentValues.length > 2
  const hasValue = currentValues.length > 0
  const triggerTitle = selectedLabels.join("、") || placeholder

  useEffect(() => {
    tree.initialize(data)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data])

  useEffect(() => {
    if (mode === "multiple") {
      if (!areSameValues(tree.checkedState, controlledValues)) {
        tree.setCheckedState(controlledValues)
      }

      if (tree.selectedState.length) {
        tree.setSelectedState([])
      }

      return
    }

    if (!areSameValues(tree.selectedState, controlledValues)) {
      tree.setSelectedState(controlledValues)
    }

    if (tree.checkedState.length) {
      tree.setCheckedState([])
    }
    // `useTree()` returns a new controller object every render; depending on it
    // here would retrigger syncs that only need to respond to controlled values.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    controlledValues,
    mode,
    tree.checkedState,
    tree.selectedState,
    tree.setCheckedState,
    tree.setSelectedState,
  ])

  useEffect(() => {
    if (filteredTreeData.length && searchName) {
      tree.setExpandedState(getStateFromDataByName(filteredTreeData))
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [filteredTreeData, searchName, tree.setExpandedState])

  const emitChange = (nextValues: string[]) => {
    if (mode === "multiple") {
      ;(onChange as MultipleVirtualTreeSelectProps["onChange"] | undefined)?.(
        nextValues,
        findNodesByValues(valueMap, nextValues)
      )
      return
    }

    const nextValue = nextValues[0] ?? null
    ;(onChange as SingleVirtualTreeSelectProps["onChange"] | undefined)?.(
      nextValue,
      nextValue ? (valueMap[nextValue]?.node ?? null) : null
    )
  }

  const handleSingleSelect = (nodeValue: string, hasChildren: boolean) => {
    if (hasChildren) {
      return
    }

    const nextValues = currentValues[0] === nodeValue ? [] : [nodeValue]
    tree.setSelectedState(nextValues)
    emitChange(nextValues)

    if (nextValues.length && closeOnSelect) {
      close()
    }
  }

  const handleMultipleToggle = (nodeValue: string) => {
    const leafValues = getDescendantLeafValues(data, nodeValue)

    if (!leafValues.length) {
      return
    }

    const nextValues = tree.isNodeChecked(nodeValue)
      ? tree.checkedState.filter((item) => !leafValues.includes(item))
      : Array.from(new Set([...tree.checkedState, ...leafValues]))

    tree.setCheckedState(nextValues)
    emitChange(nextValues)
  }

  const handleNodeSelect = (nodeValue: string, hasChildren: boolean) => {
    if (mode === "multiple") {
      handleMultipleToggle(nodeValue)
      return
    }

    handleSingleSelect(nodeValue, hasChildren)
  }

  const handleClear = () => {
    if (mode === "multiple") {
      tree.setCheckedState([])
      emitChange([])
      return
    }

    tree.setSelectedState([])
    emitChange([])
  }

  const Leaf = React.memo(
    ({
      node,
      expanded,
      hasChildren,
      level,
      selected,
      tree,
      elementProps,
    }: RenderTreeNodePayload) => {
      const checked =
        mode === "multiple" ? tree.isNodeChecked(node.value) : false
      const indeterminate =
        mode === "multiple" ? tree.isNodeIndeterminate(node.value) : false
      const active = mode === "multiple" ? checked || indeterminate : selected
      const selectable = mode === "multiple" || !hasChildren

      return (
        <Flex
          {...elementProps}
          className={classes.treeRow}
          data-active={active ? "true" : "false"}
          onClick={() => {}}
          gap={12}
          justify="space-between"
          flex={1}
          align="stretch"
          style={{
            ...elementProps.style,
            overflowX: "hidden",
            cursor: "default",
            height: 36,
            paddingInline: 0,
            backgroundColor: undefined,
          }}>
          <Flex
            className={classes.treeContent}
            align="center"
            gap={12}
            flex={1}
            // onClick={
            //   selectable
            //     ? () => handleNodeSelect(node.value, hasChildren)
            //     : undefined
            // }
            style={{
              cursor: selectable ? "pointer" : "default",
              minWidth: 0,
            }}>
            <span
              aria-hidden="true"
              className={classes.levelSpacer}
              style={{ width: `${level * 20}px` }}
            />
            {mode === "multiple" ? (
              <Checkbox.Indicator
                checked={checked}
                indeterminate={indeterminate}
                size="xs"
                radius="sm"
                onClick={(event) => {
                  event.stopPropagation()
                  handleNodeSelect(node.value, hasChildren)
                }}
              />
            ) : (
              <Radio.Indicator
                checked={selected}
                size="xs"
                onClick={
                  selectable
                    ? (event) => {
                        event.stopPropagation()
                        handleNodeSelect(node.value, hasChildren)
                      }
                    : undefined
                }
              />
            )}
            <Text
              lh="22px"
              size="15px"
              title={String(node.label)}
              style={{
                whiteSpace: "nowrap",
                overflow: "hidden",
                textOverflow: "ellipsis",
              }}>
              {node.label}
            </Text>
          </Flex>
          <Flex gap={24} align="center">
            <UnstyledChevronButton
              expanded={expanded}
              hasChildren={hasChildren}
              onClick={() => tree.toggleExpanded(node.value)}
            />
          </Flex>
        </Flex>
      )
    }
  )

  return (
    <Input.Wrapper label={label}>
      <div className={classes.root} data-opened={opened ? "true" : "false"}>
        <Popover
          opened={opened}
          onChange={(nextOpened) => {
            if (nextOpened) {
              open()
            } else {
              close()
            }
          }}
          width="target"
          position="bottom-start"
          shadow="md"
          keepMounted={false}>
          <Popover.Target>
            <Input
              component="button"
              type="button"
              pointer
              onClick={toggle}
              rightSection={
                <div className={classes.triggerActions}>
                  {hasValue ? (
                    <span
                      role="button"
                      aria-label="清空已选项"
                      className={classes.clearButton}
                      onMouseDown={(event) => {
                        event.preventDefault()
                        event.stopPropagation()
                      }}
                      onClick={(event) => {
                        event.preventDefault()
                        event.stopPropagation()
                        handleClear()
                      }}>
                      <IconX size={14} />
                    </span>
                  ) : null}
                  <Combobox.Chevron />
                </div>
              }
              rightSectionPointerEvents="all"
              rightSectionWidth={hasValue ? 54 : 30}
              title={triggerTitle}
              {...inputProps}>
              {shouldShowCountTag ? (
                <span className={classes.triggerCountTag}>
                  已选 {currentValues.length} 项
                </span>
              ) : (
                <span
                  className={`${classes.triggerText} ${
                    displayValue
                      ? classes.triggerValue
                      : classes.triggerPlaceholder
                  }`}>
                  {displayValue || placeholder}
                </span>
              )}
            </Input>
          </Popover.Target>
          <Popover.Dropdown className={classes.dropdown} p={0}>
            <div className={classes.searchArea}>
              <TextInput
                className={classes.searchField}
                radius="md"
                placeholder="搜索名称"
                value={searchName}
                onChange={(event) => {
                  setSearchName(event.currentTarget.value)
                }}
                rightSection={
                  searchName ? (
                    <CloseButton
                      size="1rem"
                      aria-label="Clear input"
                      onClick={() => {
                        setSearchName("")
                      }}
                      style={{
                        cursor: "pointer",
                      }}
                    />
                  ) : (
                    <IconSearch size="1rem" />
                  )
                }
              />
            </div>
            {filteredTreeData.length === 0 ? (
              <Stack
                className={classes.emptyState}
                flex={1}
                h="250px"
                justify="center"
                align="center">
                <Text c="dimmed">暂无数据</Text>
              </Stack>
            ) : (
              <div className={classes.treePanel}>
                <VirtualizedTree
                  height="450px"
                  tree={tree}
                  data={filteredTreeData}
                  levelOffset={30}
                  renderNode={(payload) => <Leaf {...payload} />}
                />
              </div>
            )}
          </Popover.Dropdown>
        </Popover>
      </div>
    </Input.Wrapper>
  )
}

function UnstyledChevronButton({
  expanded,
  hasChildren,
  onClick,
}: {
  expanded: boolean
  hasChildren: boolean
  onClick: () => void
}) {
  return (
    <button
      type="button"
      onClick={(event) => {
        event.stopPropagation()
        onClick()
      }}
      aria-label={expanded ? "收起节点" : "展开节点"}
      style={{
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        width: 16,
        height: 16,
        border: 0,
        padding: 0,
        background: "transparent",
        cursor: hasChildren ? "pointer" : "default",
        visibility: hasChildren ? "visible" : "hidden",
      }}>
      <IconChevronUp
        size={16}
        stroke={2.5}
        style={{
          transform: expanded ? "rotate(180deg)" : "rotate(90deg)",
        }}
      />
    </button>
  )
}
