/**
 * Dropdown — B 端下拉面板基础组件
 *
 * 统一视觉：圆角 8px、shadow-lg、py-1、项 hover bg-fill、与触发器间距 4px。
 * 列表行布局统一为：[前导选中] [图标?] [文案+描述?] [尾部选中?]
 *
 * 推荐 API（列表级）：
 *   mode — 'select' | 'action'
 *   selectionIndicator — 'row-highlight' | 'leading-check' | 'leading-checkbox' | 'trailing-check' | 'none'
 *   showIcon — true | false | 'auto'（开启后按 label 语义自动匹配 Icon；任一项有 icon 则整列对齐）
 *   showDesc — true | false | 'auto'（任一项有 desc 则双行布局 + 宽面板）
 *
 * 兼容 API：itemVariant 六种 preset 别名（内部映射到上述配置）
 *
 * @prop {boolean} [open]
 * @prop {boolean} [defaultOpen=false]
 * @prop {Function} [onOpenChange]
 * @prop {'auto'|'top'|'bottom'} [placement='auto']
 * @prop {number} [gap=4]
 * @prop {number} [maxHeight=240]
 * @prop {'select'|'action'} [mode]
 * @prop {'row-highlight'|'leading-check'|'leading-checkbox'|'trailing-check'|'none'} [selectionIndicator]
 * @prop {boolean|'auto'} [showIcon='auto']
 * @prop {boolean|'auto'} [showDesc='auto']
 * @prop {string} [itemVariant] — 兼容 preset：listbox / listbox-check / listbox-checkbox / menu / menu-radio / menu-rich
 */

import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useId,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
  Children,
  cloneElement,
  isValidElement,
} from 'react';
import { createPortal } from 'react-dom';
import Icon from './Icon';
import Tooltip from './Tooltip';
import {
  enrichDropdownItems,
  itemHasDropdownIconSignal,
  resolveDropdownItemIcon,
} from './dropdown-icon-resolver';
import { TFDS_Z_DROPDOWN_PANEL } from '../shared/overlay-z-index';

/* ── 布局常量 ── */
const DEFAULT_GAP = 4;
const DEFAULT_MAX_HEIGHT = 240;
const DEFAULT_Z_INDEX = TFDS_Z_DROPDOWN_PANEL;
export const DROPDOWN_PANEL_MIN_WIDTH = 150;
export const DROPDOWN_PANEL_MAX_WIDTH = 300;

/* ── 兼容 preset ── */
export const DROPDOWN_VARIANT_PRESETS = {
  listbox: { mode: 'select', selectionIndicator: 'row-highlight', showIcon: false, showDesc: false },
  'listbox-check': { mode: 'select', selectionIndicator: 'leading-check', showIcon: false, showDesc: false },
  'listbox-checkbox': { mode: 'select', selectionIndicator: 'leading-checkbox', showIcon: false, showDesc: false },
  menu: { mode: 'select', selectionIndicator: 'none', showIcon: 'auto', showDesc: false },
  'menu-radio': { mode: 'select', selectionIndicator: 'leading-check', showIcon: 'auto', showDesc: false },
  'menu-rich': { mode: 'select', selectionIndicator: 'leading-check', showIcon: 'auto', showDesc: 'auto' },
};

const LEGACY_LISTBOX_VARIANTS = new Set(['listbox', 'listbox-check', 'listbox-checkbox']);

const PANEL_MOTION_MS = 150;

/* ── 面板壳层 ── */
const PANEL_SHELL = [
  'rounded-md border border-solid border-border-default',
  'bg-surface shadow-lg',
  'py-1 overflow-y-auto overflow-x-hidden',
  '[font-family:inherit]',
].join(' ');

const PANEL_MOTION_BASE = 'transition-[opacity,transform] tfds-motion-ui-fast';

function getPanelMotionClasses(placement, isVisible) {
  const origin = placement === 'top' ? 'origin-bottom' : 'origin-top';
  const closedMotion = placement === 'top'
    ? 'translate-y-1 scale-[0.98] opacity-0 pointer-events-none'
    : '-translate-y-1 scale-[0.98] opacity-0 pointer-events-none';
  const openMotion = 'translate-y-0 scale-100 opacity-100';

  return [
    PANEL_MOTION_BASE,
    origin,
    isVisible ? openMotion : closedMotion,
  ].join(' ');
}

/** 面板 mount + 入场/退场：open 变 false 时保留 DOM 直至过渡结束 */
function useDropdownPanelPresence(open) {
  const [shouldRender, setShouldRender] = useState(open);
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    if (open) {
      setShouldRender(true);
      let raf2 = 0;
      const raf1 = requestAnimationFrame(() => {
        raf2 = requestAnimationFrame(() => setIsVisible(true));
      });
      return () => {
        cancelAnimationFrame(raf1);
        if (raf2) cancelAnimationFrame(raf2);
      };
    }
    setIsVisible(false);
    return undefined;
  }, [open]);

  useEffect(() => {
    if (open || !shouldRender || isVisible) return undefined;
    const timer = window.setTimeout(() => setShouldRender(false), PANEL_MOTION_MS + 50);
    return () => clearTimeout(timer);
  }, [open, shouldRender, isVisible]);

  const handleTransitionEnd = useCallback((event) => {
    if (event.target !== event.currentTarget) return;
    if (open || isVisible) return;
    setShouldRender(false);
  }, [open, isVisible]);

  return { shouldRender, isVisible, handleTransitionEnd };
}

const LIST_INSET_X = 'px-1';

const ITEM_ROW = [
  'w-full rounded-sm px-3 py-2 cursor-pointer text-left',
  'text-sm leading-5 text-foreground',
  'transition-colors tfds-motion-ui-instant',
  'hover:bg-fill',
].join(' ');

const ITEM_ROW_FLEX = 'flex min-h-9 min-w-0 gap-2';
const ITEM_ROW_ACTION_FOCUS = 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-200';
const ITEM_SELECTED = 'bg-brand-50 text-brand-500 hover:bg-brand-50';
const ITEM_CHECK_LEADING = 'inline-flex size-4 shrink-0 items-center justify-center text-brand-500';
const ITEM_ICON_SLOT = 'inline-flex size-4 shrink-0 items-center justify-center text-foreground-muted';
const ITEM_DISABLED = 'cursor-not-allowed opacity-50 pointer-events-none text-foreground-disabled hover:bg-transparent';
const ITEM_DANGER = 'text-red-600';
const ITEM_HIGHLIGHT = 'bg-fill';

const CHECKBOX_BOX = [
  'flex size-4 shrink-0 items-center justify-center rounded-sm border border-solid',
  'transition-colors tfds-motion-ui-fast',
].join(' ');
const CHECKBOX_OFF = 'border-foreground-muted bg-transparent';
const CHECKBOX_ON = 'border-brand-500 bg-brand-500 text-white';

const DropdownListContext = createContext(null);

function getValueKey(value) {
  return value === null || value === undefined ? '' : String(value);
}

function itemHasIcon(item) {
  return Boolean(item?.iconName || item?.icon);
}

function itemHasDesc(item) {
  return Boolean(item?.desc);
}

function resolveAutoFlag(flag, items, detector) {
  if (flag === true || flag === 'true') return true;
  if (flag === false || flag === 'false') return false;
  if (!Array.isArray(items) || items.length === 0) return false;
  return items.some(detector);
}

/** 合并 preset / 显式 props / items 推断，得到列表级配置 */
export function resolveListConfig({
  itemVariant,
  mode,
  selectionIndicator,
  showIcon,
  showDesc,
  trailingCheckTone,
  items,
} = {}) {
  const preset = itemVariant ? DROPDOWN_VARIANT_PRESETS[itemVariant] || {} : {};

  const resolvedMode = mode ?? preset.mode ?? 'select';
  const resolvedIndicatorRaw = selectionIndicator ?? preset.selectionIndicator ?? 'row-highlight';
  const resolvedIndicator = resolvedIndicatorRaw === 'trailing-check' ? 'leading-check' : resolvedIndicatorRaw;
  const resolvedShowIcon = resolveAutoFlag(showIcon ?? preset.showIcon ?? 'auto', items, itemHasDropdownIconSignal);
  const resolvedShowDesc = resolveAutoFlag(showDesc ?? preset.showDesc ?? 'auto', items, itemHasDesc);
  const resolvedTrailingTone = trailingCheckTone ?? preset.trailingCheckTone ?? 'brand';

  return {
    mode: resolvedMode,
    selectionIndicator: resolvedIndicator,
    showIcon: resolvedShowIcon,
    showDesc: resolvedShowDesc,
    trailingCheckTone: resolvedTrailingTone,
    itemVariant,
  };
}

function normalizeSelectedSet(selectedValue, multiple) {
  if (multiple) {
    const list = Array.isArray(selectedValue) ? selectedValue : selectedValue == null ? [] : [selectedValue];
    return new Set(list.map(getValueKey));
  }
  if (Array.isArray(selectedValue)) {
    return new Set(selectedValue.length ? [getValueKey(selectedValue[0])] : []);
  }
  return new Set(selectedValue == null || selectedValue === '' ? [] : [getValueKey(selectedValue)]);
}

function isItemSelected(selectedSet, value) {
  return selectedSet.has(getValueKey(value));
}

function resolveListRole(listConfig, listRole) {
  if (listRole) return listRole;
  return listConfig.mode === 'select' ? 'listbox' : 'menu';
}

function resolvePanelLayout(listConfig, matchTriggerWidthProp) {
  void listConfig;
  const matchTriggerWidth = matchTriggerWidthProp === true;

  return {
    matchTriggerWidth,
    minWidth: DROPDOWN_PANEL_MIN_WIDTH,
    maxWidth: DROPDOWN_PANEL_MAX_WIDTH,
  };
}

function measurePanelPosition(triggerEl, panelEl, {
  gap,
  maxHeight,
  minWidth,
  maxWidth,
  matchTriggerWidth,
  placement,
  panelHeight,
  align = 'start',
}) {
  const rect = triggerEl.getBoundingClientRect();
  const spaceBelow = window.innerHeight - rect.bottom - gap;
  const spaceAbove = rect.top - gap;
  const measuredHeight = panelEl?.offsetHeight || 0;
  const estimatedHeight = Math.max(80, Math.min(maxHeight, panelHeight ?? maxHeight));
  const contentHeight = measuredHeight > 0 ? measuredHeight : estimatedHeight;
  const flipThreshold = Math.min(Math.max(contentHeight, 80), 200);

  let resolvedPlacement = placement;
  if (placement === 'auto') {
    resolvedPlacement = spaceBelow >= flipThreshold ? 'bottom' : 'top';
  }

  let top;
  let resolvedMaxHeight;
  if (resolvedPlacement === 'bottom') {
    top = rect.bottom + gap;
    resolvedMaxHeight = Math.min(maxHeight, spaceBelow - 4);
  } else {
    resolvedMaxHeight = Math.min(maxHeight, spaceAbove - 4);
    const visibleHeight = Math.min(contentHeight, Math.max(80, resolvedMaxHeight));
    top = rect.top - visibleHeight - gap;
  }

  const width = matchTriggerWidth ? rect.width : undefined;
  const panelWidth = panelEl?.offsetWidth
    || (typeof width === 'number' ? width : undefined)
    || minWidth
    || DROPDOWN_PANEL_MIN_WIDTH;
  let left = rect.left;
  if (align === 'end') {
    left = rect.right - panelWidth;
  }

  return {
    top: Math.max(8, top),
    left: Math.max(8, left),
    width,
    minWidth: matchTriggerWidth ? undefined : (minWidth ?? DROPDOWN_PANEL_MIN_WIDTH),
    maxWidth: matchTriggerWidth ? undefined : (maxWidth ?? DROPDOWN_PANEL_MAX_WIDTH),
    maxHeight: Math.max(80, resolvedMaxHeight),
    placement: resolvedPlacement,
  };
}

function DropdownTruncatedText({
  text,
  className = '',
  block = false,
  triggerClassName = 'min-w-0 flex-1 overflow-hidden',
}) {
  const ref = useRef(null);
  const [overflow, setOverflow] = useState(false);

  const checkOverflow = useCallback(() => {
    const el = ref.current;
    if (!el) return;
    setOverflow(el.scrollWidth > el.clientWidth);
  }, []);

  useLayoutEffect(() => {
    checkOverflow();
    const el = ref.current;
    if (!el || typeof ResizeObserver === 'undefined') return undefined;
    const observer = new ResizeObserver(checkOverflow);
    observer.observe(el);
    return () => observer.disconnect();
  }, [checkOverflow, text]);

  if (!text) return null;

  const textClass = [
    block ? 'block w-full' : 'block w-full',
    'min-w-0 truncate',
    className,
  ].filter(Boolean).join(' ');

  const textNode = (
    <span ref={ref} className={textClass}>
      {text}
    </span>
  );

  if (!overflow) return textNode;

  return (
    <Tooltip content={text} placement="top" triggerClassName={triggerClassName}>
      {textNode}
    </Tooltip>
  );
}

function CheckIcon({ className = 'size-3' }) {
  return (
    <svg viewBox="0 0 12 12" fill="none" className={className} aria-hidden="true">
      <path d="M2.5 6.25L4.75 8.5L9.5 3.5" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function LargeCheckIcon() {
  return (
    <svg viewBox="0 0 16 16" fill="none" className="size-4" aria-hidden="true">
      <path d="M3.5 8.25L6.5 11.25L12.5 4.75" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function useMergedListConfig(overrides = {}) {
  const ctx = useContext(DropdownListContext);
  return useMemo(() => resolveListConfig({
    itemVariant: overrides.itemVariant ?? ctx?.itemVariant,
    mode: overrides.mode ?? ctx?.mode,
    selectionIndicator: overrides.selectionIndicator ?? ctx?.selectionIndicator,
    showIcon: overrides.showIcon ?? ctx?.showIcon,
    showDesc: overrides.showDesc ?? ctx?.showDesc,
    trailingCheckTone: overrides.trailingCheckTone ?? ctx?.trailingCheckTone,
    items: overrides.items ?? ctx?.items,
  }), [
    ctx,
    overrides.itemVariant,
    overrides.mode,
    overrides.selectionIndicator,
    overrides.showIcon,
    overrides.showDesc,
    overrides.trailingCheckTone,
    overrides.items,
  ]);
}

function renderLeadingIndicator(indicator, selected, disabled) {
  if (indicator === 'leading-check') {
    return (
      <span className={`${ITEM_CHECK_LEADING} ${selected ? 'opacity-100' : 'opacity-0'}`} aria-hidden="true">
        <LargeCheckIcon />
      </span>
    );
  }
  if (indicator === 'leading-checkbox') {
    return (
      <span className={[CHECKBOX_BOX, selected ? CHECKBOX_ON : CHECKBOX_OFF, disabled ? 'border-border-default bg-disabled' : ''].filter(Boolean).join(' ')}>
        {selected ? <CheckIcon /> : null}
      </span>
    );
  }
  return null;
}

function renderIconSlot(showIconColumn, icon, iconName, alignStart) {
  if (!showIconColumn) return null;
  const iconNode = icon ?? (iconName ? <Icon name={iconName} size="sm" color="currentColor" aria-hidden="true" /> : null);
  return (
    <span className={[ITEM_ICON_SLOT, alignStart ? 'mt-0.5' : ''].filter(Boolean).join(' ')}>
      {iconNode}
    </span>
  );
}

function renderTrailingIndicator(indicator, selected, tone, alignStart) {
  if (indicator !== 'trailing-check' || !selected) return null;
  const muted = tone === 'muted';
  return (
    <span className={[alignStart ? 'mt-0.5 shrink-0' : 'shrink-0'].filter(Boolean).join(' ')}>
      <Icon
        name="check-stroked"
        size={alignStart ? 'sm' : 'xs'}
        className={muted ? 'text-foreground-muted' : 'text-brand-500'}
        aria-hidden="true"
      />
    </span>
  );
}

function renderItemContent(content, desc, showDescLine, selected, indicator) {
  if (showDescLine && desc) {
    const labelSelected = selected && indicator === 'trailing-check';
    return (
      <span className="min-w-0 flex-1 overflow-hidden">
        <DropdownTruncatedText
          text={content}
          block
          className={labelSelected ? 'font-medium text-brand-500' : ''}
        />
        <DropdownTruncatedText
          text={desc}
          block
          className="text-xs leading-4 text-foreground-muted"
        />
      </span>
    );
  }
  return (
    <DropdownTruncatedText
      text={content}
      triggerClassName="min-w-0 flex-1 overflow-hidden"
    />
  );
}

/* ── Dropdown.Item ── */
export function DropdownItem({
  itemVariant,
  mode,
  selectionIndicator,
  showIcon,
  showDesc,
  trailingCheckTone,
  selected = false,
  disabled = false,
  danger = false,
  highlighted = false,
  label,
  desc,
  icon,
  iconName,
  value,
  children,
  className = '',
  onClick,
  onMouseDown,
  role,
  ...rest
}) {
  const listConfig = useMergedListConfig({
    itemVariant,
    mode,
    selectionIndicator,
    showIcon,
    showDesc,
    trailingCheckTone,
  });

  const content = children ?? label;
  const { mode: resolvedMode, selectionIndicator: indicator, showIcon: showIconColumn, showDesc: showDescColumn } = listConfig;
  const showDescLine = showDescColumn && Boolean(desc);
  const alignStart = showDescLine;
  const isSelectMode = resolvedMode === 'select';
  const useFlex = indicator !== 'row-highlight' || showIconColumn || showDescLine || !isSelectMode;

  const stateClass = [
    ITEM_ROW,
    useFlex ? ITEM_ROW_FLEX : '',
    alignStart ? 'items-start' : useFlex ? 'items-center' : '',
    !isSelectMode ? ITEM_ROW_ACTION_FOCUS : '',
    selected && !disabled && indicator === 'row-highlight' ? ITEM_SELECTED : '',
    highlighted && !selected && !disabled ? ITEM_HIGHLIGHT : '',
    danger && !disabled ? ITEM_DANGER : '',
    disabled ? ITEM_DISABLED : '',
    className,
  ].filter(Boolean).join(' ');

  const itemRole = role ?? (isSelectMode ? 'option' : 'menuitem');
  const Tag = isSelectMode ? 'div' : 'button';

  const inner = useFlex ? (
    <>
      {renderLeadingIndicator(indicator, selected, disabled)}
      {renderIconSlot(showIconColumn, icon, iconName, alignStart)}
      {renderItemContent(content, desc, showDescLine, selected, indicator)}
      {renderTrailingIndicator(indicator, selected, listConfig.trailingCheckTone, alignStart)}
    </>
  ) : (
    <DropdownTruncatedText text={content} triggerClassName="min-w-0 w-full overflow-hidden" />
  );

  return (
    <Tag
      type={Tag === 'button' ? 'button' : undefined}
      role={itemRole}
      aria-selected={isSelectMode ? selected : undefined}
      aria-checked={!isSelectMode && indicator === 'trailing-check' ? selected : undefined}
      aria-disabled={disabled || undefined}
      data-value={value !== undefined ? getValueKey(value) : undefined}
      className={stateClass}
      disabled={Tag === 'button' ? disabled : undefined}
      onMouseDown={onMouseDown}
      onClick={disabled ? undefined : onClick}
      {...rest}
    >
      {inner}
    </Tag>
  );
}

/* ── Dropdown.List ── */
export function DropdownList({
  variant,
  itemVariant,
  mode,
  selectionIndicator,
  showIcon,
  showDesc,
  trailingCheckTone,
  multiple = false,
  role,
  className = '',
  items,
  children,
  ...rest
}) {
  const listConfig = useMemo(() => resolveListConfig({
    itemVariant: itemVariant ?? variant,
    mode,
    selectionIndicator,
    showIcon,
    showDesc,
    trailingCheckTone,
    items,
  }), [itemVariant, variant, mode, selectionIndicator, showIcon, showDesc, trailingCheckTone, items]);

  const listRole = resolveListRole(listConfig, role);

  return (
    <DropdownListContext.Provider value={listConfig}>
      <div
        role={listRole}
        aria-multiselectable={listRole === 'listbox' && multiple ? 'true' : undefined}
        className={['flex flex-col', LIST_INSET_X, className].filter(Boolean).join(' ')}
        {...rest}
      >
        {Children.map(children, (child) => {
          if (!isValidElement(child)) return child;
          if (child.type === DropdownItem) {
            return cloneElement(child, {
              itemVariant: child.props.itemVariant ?? listConfig.itemVariant,
              mode: child.props.mode ?? listConfig.mode,
              selectionIndicator: child.props.selectionIndicator ?? listConfig.selectionIndicator,
              showIcon: child.props.showIcon ?? listConfig.showIcon,
              showDesc: child.props.showDesc ?? listConfig.showDesc,
              trailingCheckTone: child.props.trailingCheckTone ?? listConfig.trailingCheckTone,
            });
          }
          return child;
        })}
      </div>
    </DropdownListContext.Provider>
  );
}

export function DropdownEmpty({ children = '暂无数据', className = '' }) {
  return (
    <div className={['px-3 py-2 text-sm text-foreground-muted', className].filter(Boolean).join(' ')}>
      {children}
    </div>
  );
}

export function DropdownPanel({
  open,
  triggerRef,
  gap = DEFAULT_GAP,
  maxHeight = DEFAULT_MAX_HEIGHT,
  minWidth,
  maxWidth,
  matchTriggerWidth = false,
  placement = 'auto',
  panelAlign = 'start',
  portal = true,
  panelClassName = '',
  style,
  children,
  id,
  role,
  onMouseEnter,
  onMouseLeave,
}) {
  const panelRef = useRef(null);
  const { shouldRender, isVisible, handleTransitionEnd } = useDropdownPanelPresence(open);
  const [panelStyle, setPanelStyle] = useState({
    top: 0,
    left: 0,
    width: undefined,
    minWidth: minWidth ?? DROPDOWN_PANEL_MIN_WIDTH,
    maxWidth: maxWidth ?? DROPDOWN_PANEL_MAX_WIDTH,
    maxHeight,
    placement: 'bottom',
  });

  const updatePosition = useCallback(() => {
    if (!triggerRef?.current) return;
    setPanelStyle(measurePanelPosition(triggerRef.current, panelRef.current, {
      gap,
      maxHeight,
      minWidth: minWidth ?? DROPDOWN_PANEL_MIN_WIDTH,
      maxWidth: maxWidth ?? DROPDOWN_PANEL_MAX_WIDTH,
      matchTriggerWidth,
      placement,
      align: panelAlign,
    }));
  }, [triggerRef, gap, maxHeight, minWidth, maxWidth, matchTriggerWidth, placement, panelAlign]);

  useLayoutEffect(() => {
    if (!shouldRender) return undefined;
    updatePosition();
    const raf = requestAnimationFrame(updatePosition);
    const handle = () => updatePosition();
    window.addEventListener('resize', handle);
    window.addEventListener('scroll', handle, true);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', handle);
      window.removeEventListener('scroll', handle, true);
    };
  }, [shouldRender, updatePosition, children]);

  if (!shouldRender) return null;

  const panelNode = (
    <div
      ref={panelRef}
      id={id}
      role={role}
      data-tfds-component="Dropdown.Panel"
      data-placement={panelStyle.placement}
      data-state={isVisible ? 'open' : 'closed'}
      className={[
        PANEL_SHELL,
        getPanelMotionClasses(panelStyle.placement, isVisible),
        matchTriggerWidth ? '' : 'w-max',
        panelClassName,
      ].filter(Boolean).join(' ')}
      style={{
        position: 'fixed',
        top: panelStyle.top,
        left: panelStyle.left,
        width: panelStyle.width,
        minWidth: panelStyle.minWidth,
        maxWidth: panelStyle.maxWidth ?? style?.maxWidth,
        maxHeight: panelStyle.maxHeight,
        zIndex: DEFAULT_Z_INDEX,
        ...style,
      }}
      onTransitionEnd={handleTransitionEnd}
      onMouseEnter={onMouseEnter}
      onMouseLeave={onMouseLeave}
    >
      {children}
    </div>
  );

  if (portal && typeof document !== 'undefined') {
    return createPortal(panelNode, document.body);
  }
  return panelNode;
}

function renderDeclarativeItems({
  items,
  listConfig,
  selectedSet,
  multiple,
  onItemSelect,
  closeOnSelect,
  updateOpen,
}) {
  if (!items?.length) return <DropdownEmpty />;

  return items.map((item, index) => {
    const value = item.value ?? item.id ?? index;
    const selected = item.selected ?? isItemSelected(selectedSet, value);
    const handleClick = (event) => {
      item.onClick?.(item, event);
      onItemSelect?.(item, event);
      if (closeOnSelect && !multiple) updateOpen(false);
    };

    return (
      <DropdownItem
        key={item.id ?? getValueKey(value) ?? index}
        mode={listConfig.mode}
        selectionIndicator={listConfig.selectionIndicator}
        showIcon={listConfig.showIcon}
        showDesc={listConfig.showDesc}
        trailingCheckTone={listConfig.trailingCheckTone}
        value={value}
        label={item.label}
        desc={item.desc}
        icon={item.icon}
        iconName={item.iconName}
        selected={selected}
        disabled={item.disabled}
        danger={item.danger}
        onMouseDown={(event) => event.preventDefault()}
        onClick={handleClick}
      />
    );
  });
}

export default function Dropdown({
  open: openProp,
  defaultOpen = false,
  onOpenChange,
  placement = 'auto',
  panelAlign = 'start',
  gap = DEFAULT_GAP,
  maxHeight = DEFAULT_MAX_HEIGHT,
  minWidth: minWidthProp,
  matchTriggerWidth: matchTriggerWidthProp,
  portal = true,
  triggerMode = 'click',
  hoverOpenDelay = 0,
  hoverCloseDelay = 120,
  closeOnSelect: closeOnSelectProp,
  multiple = false,
  trigger,
  children,
  items,
  itemVariant,
  mode,
  selectionIndicator,
  showIcon,
  showDesc,
  trailingCheckTone,
  selectedValue,
  onItemSelect,
  emptyText = '暂无数据',
  listRole,
  className = '',
  panelClassName = '',
  style,
  ...rest
}) {
  const uid = useId();
  const listId = `${uid}-dropdown-list`;
  const triggerRef = useRef(null);
  const openTimer = useRef(null);
  const closeTimer = useRef(null);

  const isControlled = openProp !== undefined;
  const [innerOpen, setInnerOpen] = useState(defaultOpen);
  const open = isControlled ? openProp : innerOpen;

  const listConfig = useMemo(() => resolveListConfig({
    itemVariant,
    mode,
    selectionIndicator,
    showIcon,
    showDesc,
    trailingCheckTone,
    items,
  }), [itemVariant, mode, selectionIndicator, showIcon, showDesc, trailingCheckTone, items]);

  const enrichedItems = useMemo(() => {
    if (!items?.length || !listConfig.showIcon) return items;
    return enrichDropdownItems(items);
  }, [items, listConfig.showIcon]);

  const panelLayout = useMemo(
    () => resolvePanelLayout(listConfig, matchTriggerWidthProp),
    [listConfig, matchTriggerWidthProp],
  );

  const closeOnSelect = closeOnSelectProp ?? !multiple;
  const minWidth = minWidthProp ?? panelLayout.minWidth;
  const maxWidth = panelLayout.maxWidth;
  const resolvedListRole = resolveListRole(listConfig, listRole);
  const selectedSet = useMemo(() => normalizeSelectedSet(selectedValue, multiple), [selectedValue, multiple]);

  const updateOpen = useCallback((next) => {
    if (!isControlled) setInnerOpen(next);
    onOpenChange?.(next);
  }, [isControlled, onOpenChange]);

  const clearTimers = useCallback(() => {
    if (openTimer.current) clearTimeout(openTimer.current);
    if (closeTimer.current) clearTimeout(closeTimer.current);
    openTimer.current = null;
    closeTimer.current = null;
  }, []);

  const openPanel = useCallback(() => {
    clearTimers();
    if (triggerMode === 'hover' && hoverOpenDelay > 0) {
      openTimer.current = setTimeout(() => updateOpen(true), hoverOpenDelay);
      return;
    }
    updateOpen(true);
  }, [clearTimers, hoverOpenDelay, triggerMode, updateOpen]);

  const closePanel = useCallback(() => {
    clearTimers();
    if (triggerMode === 'hover') {
      closeTimer.current = setTimeout(() => updateOpen(false), hoverCloseDelay);
      return;
    }
    updateOpen(false);
  }, [clearTimers, hoverCloseDelay, triggerMode, updateOpen]);

  const togglePanel = useCallback(() => {
    updateOpen(!open);
  }, [open, updateOpen]);

  useEffect(() => () => clearTimers(), [clearTimers]);

  useEffect(() => {
    if (!open || triggerMode !== 'click') return undefined;

    const handlePointerDown = (event) => {
      const target = event.target;
      if (triggerRef.current?.contains(target)) return;
      const panelEl = document.getElementById(listId);
      if (panelEl?.contains(target)) return;
      updateOpen(false);
    };

    const handleKeyDown = (event) => {
      if (event.key === 'Escape') updateOpen(false);
    };

    document.addEventListener('mousedown', handlePointerDown);
    document.addEventListener('keydown', handleKeyDown);
    return () => {
      document.removeEventListener('mousedown', handlePointerDown);
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [open, listId, triggerMode, updateOpen]);

  const panelContent = children ?? (
    <DropdownList
      itemVariant={itemVariant}
      mode={listConfig.mode}
      selectionIndicator={listConfig.selectionIndicator}
      showIcon={listConfig.showIcon}
      showDesc={listConfig.showDesc}
      trailingCheckTone={listConfig.trailingCheckTone}
      multiple={multiple}
      items={enrichedItems}
      role={resolvedListRole}
    >
      {enrichedItems?.length
        ? renderDeclarativeItems({
          items: enrichedItems,
          listConfig,
          selectedSet,
          multiple,
          onItemSelect,
          closeOnSelect,
          updateOpen,
        })
        : <DropdownEmpty>{emptyText}</DropdownEmpty>}
    </DropdownList>
  );

  const triggerNode = isValidElement(trigger)
    ? cloneElement(trigger, {
      ref: (node) => {
        triggerRef.current = node;
        const { ref } = trigger;
        if (typeof ref === 'function') ref(node);
        else if (ref) ref.current = node;
      },
      'aria-expanded': open,
      'aria-haspopup': resolvedListRole,
      'aria-controls': open ? listId : undefined,
      onClick: (event) => {
        trigger.props.onClick?.(event);
        if (triggerMode === 'click') togglePanel();
      },
      onMouseEnter: (event) => {
        trigger.props.onMouseEnter?.(event);
        if (triggerMode === 'hover') openPanel();
      },
      onMouseLeave: (event) => {
        trigger.props.onMouseLeave?.(event);
        if (triggerMode === 'hover') closePanel();
      },
      onFocus: (event) => {
        trigger.props.onFocus?.(event);
        if (triggerMode === 'hover') openPanel();
      },
      onBlur: (event) => {
        trigger.props.onBlur?.(event);
        if (triggerMode === 'hover') closePanel();
      },
    })
    : trigger;

  return (
    <div
      className={['relative inline-flex', className].filter(Boolean).join(' ')}
      data-tfds-component="Dropdown"
      data-open={open || undefined}
      data-mode={listConfig.mode}
      data-selection-indicator={listConfig.selectionIndicator}
      data-variant={itemVariant || undefined}
      {...rest}
    >
      {triggerNode ? (
        <div
          ref={!isValidElement(trigger) ? triggerRef : undefined}
          className={!isValidElement(trigger) ? 'inline-flex' : undefined}
        >
          {triggerNode}
        </div>
      ) : null}

      <DropdownPanel
        open={open}
        triggerRef={triggerRef}
        gap={gap}
        maxHeight={maxHeight}
        minWidth={minWidth}
        maxWidth={maxWidth}
        matchTriggerWidth={panelLayout.matchTriggerWidth}
        placement={placement}
        panelAlign={panelAlign}
        portal={portal}
        panelClassName={panelClassName}
        id={listId}
        role={resolvedListRole}
        style={style}
        onMouseEnter={triggerMode === 'hover' ? openPanel : undefined}
        onMouseLeave={triggerMode === 'hover' ? closePanel : undefined}
      >
        {panelContent}
      </DropdownPanel>
    </div>
  );
}

Dropdown.Item = DropdownItem;
Dropdown.List = DropdownList;
Dropdown.Panel = DropdownPanel;
Dropdown.Empty = DropdownEmpty;
Dropdown.resolveListConfig = resolveListConfig;
Dropdown.resolveItemIcon = resolveDropdownItemIcon;
Dropdown.enrichItems = enrichDropdownItems;
Dropdown.VARIANT_PRESETS = DROPDOWN_VARIANT_PRESETS;
