import {
  Box,
  Button,
  Checkbox,
  Divider,
  Group,
  Input,
  Popover,
  PopoverProps,
  Text,
  Flex,
  BoxProps,
  TextProps
} from '@tidbcloud/uikit'
import {
  IconChevronDown,
  IconChevronRight,
  IconChevronSelectorVertical
} from '@tidbcloud/uikit/icons'
import { useEffect, useMemo, useState } from 'react'

type SelectionProtectType = string | number

export interface TreeSelectOption<T extends SelectionProtectType = string> {
  label: string
  value: T
  disabled?: boolean
  isChecked?: boolean
  isLeaf?: boolean
  showChildren?: boolean
  children?: TreeSelectOption<T>[]
  renderSelectItem?: (options: TreeSelectOption<T>) => React.ReactNode
}

export interface TreeSelectProps<T extends SelectionProtectType = string> {
  value?: T[]
  options: TreeSelectOption<T>[]
  onChange: (value: T[]) => void
  disabled?: boolean
  popoverProps?: Omit<PopoverProps, 'children'>
  invalid?: boolean
  placeholder?: string
  renderSelectItem?: (options: TreeSelectOption<T>) => React.ReactNode
  allWithEmpty?: boolean
  emptyMessage?: string
  selectItemProps?: SelectItemWrapperProps
}

const checkAll = <T extends SelectionProtectType = string>(
  options: TreeSelectOption<T>[],
  isChecked: boolean
): TreeSelectOption<T>[] => {
  return options.map((v) => ({
    ...v,
    // skip disabled
    isChecked: v.disabled ? false : isChecked,
    children: v.children ? checkAll(v.children, isChecked) : undefined
  }))
}
const checkOptionsByValue = <T extends SelectionProtectType = string>(
  options: TreeSelectOption<T>[],
  value: T[]
): TreeSelectOption<T>[] => {
  // [] means check all
  if (!!options?.length && !value.length) {
    return checkAll(options, true)
  }
  return options.map((op) => {
    if (op.children?.length) {
      op.children = checkOptionsByValue(op.children, value)
    }
    return { ...op, isChecked: value.includes(op.value) }
  })
}
const flatAllLeaf = <T extends SelectionProtectType = string>(
  value: TreeSelectOption<T>[]
) =>
  value.reduce((prev, cur) => {
    if (cur.isLeaf && !cur.disabled) {
      prev.push(cur)
    }
    if (cur.children) {
      prev.push(...flatAllLeaf(cur.children))
    }
    return prev
  }, [] as TreeSelectOption<T>[])

export const TreeSelect = <T extends SelectionProtectType = string>({
  options,
  value,
  disabled,
  onChange,
  popoverProps,
  invalid,
  renderSelectItem,
  placeholder,
  allWithEmpty = true,
  emptyMessage = 'No data',
  selectItemProps
}: TreeSelectProps<T>) => {
  const [opened, setOpened] = useState(false)
  const [_options, setOptions] = useState<TreeSelectOption<T>[] | undefined>(
    undefined
  )
  const internalOptions = useMemo(() => {
    if (!!_options) {
      return _options
    }
    return options
  }, [options, _options])
  const flatOptions = useMemo(
    () => flatAllLeaf(internalOptions),
    [internalOptions]
  )
  const itemCount = flatOptions.length
  const isCheckAll = value && (!value.length || value.length === itemCount)
  const selectedTips =
    value === undefined
      ? ''
      : isCheckAll
      ? `All (${itemCount})`
      : `Checked ${value.length} (Total ${itemCount})`
  const everyInternalChecked = flatOptions.every((o) => o.isChecked)
  const someInternalChecked = flatOptions.some((o) => o.isChecked)
  const everInternalNotChecked = flatOptions.every((o) => !o.isChecked)

  const resetCheckedStatus = () =>
    setOptions(checkOptionsByValue(internalOptions, value || []))

  useEffect(() => {
    // value could be undefined
    setOptions(checkOptionsByValue(options, value || []))
  }, [options, value])

  return (
    <Popover
      position="bottom-end"
      {...popoverProps}
      opened={opened}
      onChange={setOpened}
      shadow="sm"
      onClose={resetCheckedStatus}
    >
      <Popover.Target>
        <Input
          styles={{
            input: {
              userSelect: 'none',
              height: '40px',
              lineHeight: '38px',
              paddingRight: '40px',
              ':hover': {
                cursor: disabled ? 'not-allowed' : 'pointer'
              },
              '::selection': {
                backgroundColor: 'transparent'
              }
            },
            section: {
              width: '40px',
              pointerEvents: 'none'
            }
          }}
          sx={{
            lineHeight: '1.55'
          }}
          value={selectedTips}
          disabled={disabled}
          placeholder={placeholder}
          readOnly
          rightSection={
            <IconChevronSelectorVertical
              size={16}
              color="var(--mantine-color-carbon-7)!important"
            />
          }
          onClick={() => setOpened((v) => !v)}
          data-invalid={invalid}
        />
      </Popover.Target>
      <Popover.Dropdown p={0}>
        {internalOptions.length ? (
          <SelectItem
            label={selectedTips}
            value="all"
            isLeaf={true}
            isChecked={everyInternalChecked}
            indeterminate={someInternalChecked && !everyInternalChecked}
            onChange={(v) => {
              setOptions(checkAll(internalOptions, v))
            }}
            {...selectItemProps}
          />
        ) : (
          <Flex my={16} justify="center">
            {emptyMessage}
          </Flex>
        )}
        <Divider color="carbon.3" />
        <Box mah={240} sx={{ overflow: 'auto' }}>
          <SelectItems<T>
            value={internalOptions}
            renderSelectItem={renderSelectItem}
            onChange={setOptions}
            {...selectItemProps}
          />
        </Box>
        <Divider color="carbon.3" />
        <Group p="md" gap={8} justify="flex-end">
          <Button
            variant="default"
            size="xs"
            onClick={() => {
              resetCheckedStatus()
              setOpened(false)
            }}
          >
            Cancel
          </Button>
          <Button
            size="xs"
            onClick={() => {
              const flatOptions = flatAllLeaf(internalOptions)
              const isCheckAll = flatOptions.every((n) => n.isChecked)
              onChange(
                isCheckAll && allWithEmpty
                  ? []
                  : flatOptions.filter((n) => n.isChecked).map((n) => n.value)
              )
              setOpened(false)
            }}
            disabled={everInternalNotChecked}
          >
            Apply
          </Button>
        </Group>
      </Popover.Dropdown>
    </Popover>
  )
}

interface SelectItemsProps<T extends SelectionProtectType = string> {
  value: TreeSelectOption<T>[]
  onChange: (opts: TreeSelectOption<T>[]) => void
  level?: number
  renderSelectItem?: (options: TreeSelectOption<T>) => React.ReactNode
}

const SelectItems = <T extends SelectionProtectType = string>({
  value,
  level = 0,
  onChange,
  ...rest
}: SelectItemsProps<T>) => {
  return (
    <>
      {value.map((v, i) => {
        return (
          <SelectItemWithChildren<T>
            key={v.value}
            value={v}
            level={level}
            onChange={(v) =>
              onChange([...value.slice(0, i), v, ...value.slice(i + 1)])
            }
            onChildrenChange={(opts) => {
              const childChange = { ...v, children: opts }
              onChange([
                ...value.slice(0, i),
                childChange,
                ...value.slice(i + 1)
              ])
            }}
            {...rest}
          />
        )
      })}
    </>
  )
}

interface SelectItemWithChildrenProps<T extends SelectionProtectType = string> {
  value: TreeSelectOption<T>
  onChange: (v: TreeSelectOption<T>) => void
  onChildrenChange: (opts: TreeSelectOption<T>[]) => void
  level?: number
  renderSelectItem?: (options: SelectItemProps<T>) => React.ReactNode
  selectItemProps?: SelectItemWrapperProps
}

const SelectItemWithChildren = <T extends SelectionProtectType = string>({
  value,
  level = 0,
  onChange,
  onChildrenChange,
  renderSelectItem,
  ...rest
}: SelectItemWithChildrenProps<T>) => {
  const someChildrenChecked = flatAllLeaf(value.children || []).some(
    (c) => c.isChecked
  )
  const everyChildrenChecked = flatAllLeaf(value.children || []).every(
    (c) => c.isChecked
  )
  return (
    <>
      <SelectItem<T>
        {...value}
        level={level}
        showChildren={value.showChildren}
        setShowChildren={(v) => onChange({ ...value, showChildren: v })}
        someChildrenChecked={someChildrenChecked}
        everyChildrenChecked={everyChildrenChecked}
        onChange={(v) => {
          onChange({
            ...value,
            isChecked: v,
            children: value.isLeaf
              ? value.children
              : checkAll(value.children!, v)
          })
        }}
        renderSelectItem={renderSelectItem}
        {...rest}
      />
      {value.children && value.showChildren && (
        <SelectItems<T>
          value={value.children}
          level={level + 1}
          onChange={onChildrenChange}
          renderSelectItem={renderSelectItem}
          {...rest}
        />
      )}
    </>
  )
}

interface SelectItemWrapperProps extends Omit<BoxProps, 'children'> {
  textProps?: TextProps
}

interface SelectItemProps<T extends SelectionProtectType = string>
  extends TreeSelectOption<T>,
    SelectItemWrapperProps {
  onChange: (v: boolean) => void
  indeterminate?: boolean
  level?: number
  showChildren?: boolean
  setShowChildren?: (v: boolean) => void
  someChildrenChecked?: boolean
  everyChildrenChecked?: boolean
}

const SelectItem = <T extends SelectionProtectType = string>({
  label,
  indeterminate,
  level = 0,
  isLeaf,
  someChildrenChecked,
  everyChildrenChecked,
  showChildren,
  setShowChildren,
  isChecked,
  onChange,
  disabled,
  renderSelectItem,
  value,
  ...rest
}: SelectItemProps<T>) => {
  const { textProps, ...wrapperProps } = rest
  return (
    <Box pl={8 + 24 * level} {...wrapperProps}>
      <Group
        p="sm"
        gap={8}
        onClick={() => {
          isLeaf ? onChange(!isChecked) : setShowChildren?.(!showChildren)
        }}
        sx={{ cursor: 'pointer', userSelect: 'none' }}
      >
        <Checkbox
          indeterminate={
            (!isLeaf && someChildrenChecked && !everyChildrenChecked) ||
            indeterminate
          }
          checked={isLeaf ? isChecked : everyChildrenChecked}
          onClick={(e) => {
            e.stopPropagation()
            onChange(isLeaf ? !isChecked : !everyChildrenChecked!)
          }}
          disabled={disabled}
          readOnly
        />
        {!isLeaf &&
          (showChildren ? (
            <IconChevronDown size={16} />
          ) : (
            <IconChevronRight size={16} />
          ))}
        {!!renderSelectItem ? (
          renderSelectItem({ label, value, disabled })
        ) : (
          <Text truncate maw="80%" {...textProps}>
            {label}
          </Text>
        )}
      </Group>
    </Box>
  )
}
