import { Box, type BoxProps, HoverCard, type HoverCardProps } from '@tidbcloud/uikit'
import React from 'react'

type HoverCardTargetProps = React.ComponentProps<typeof HoverCard.Target>
type HoverCardDropdownProps = React.ComponentProps<typeof HoverCard.Dropdown>

/**
 * A constrained subset of `ReactNode` that is suitable for hover-hint content.
 *
 * Why not `ReactNode`?
 * - `ReactNode` includes `boolean`/`undefined`, which render nothing and often come from `cond && ...`.
 *   For hint content, we prefer explicit `null` when you want "no hint".
 * - `ReactNode` includes generic Iterables; we intentionally only support arrays here to avoid
 *   accidentally consuming Iterators/Generators during "empty content" checks.
 */
export type HoverHintContent = React.ReactElement | string | number | null | readonly HoverHintContent[]

export interface HoverHintReason {
  /**
   * Unique id for this reason.
   *
   * This is a stable identifier for debugging/analytics and for distinguishing
   * reasons. It is NOT the React element `key` prop.
   */
  id?: string
  /**
   * Whether this reason applies (usually means the target should be disabled).
   */
  when: boolean
  /**
   * Content to render in the hover hint dropdown.
   *
   * If `null` (or an empty string after trimming),
   * the hint will not be rendered.
   */
  content: HoverHintContent
  /**
   * Props passed to `HoverCard`.
   */
  hoverCardProps?: Omit<HoverCardProps, 'children' | 'disabled'>
  /**
   * Props passed to `HoverCard.Target`.
   */
  hoverCardTargetProps?: Omit<HoverCardTargetProps, 'children' | 'ref'>
  /**
   * Props passed to `HoverCard.Dropdown`.
   */
  hoverCardDropdownProps?: Omit<HoverCardDropdownProps, 'children'>
  /**
   * Props passed to the wrapper element inside `HoverCard.Target`.
   *
   * This wrapper exists because disabled elements (e.g. `<button disabled />`)
   * typically do not fire hover events, while the wrapper can.
   */
  targetWrapperProps?: Omit<BoxProps, 'children'>
}

type ReasonHoverHintChildrenRenderFn = (params: {
  /**
   * Whether any reason matches (`reasons.some(r => r.when)`).
   *
   * You can use this value to set the target `disabled` state.
   *
   * Note: `ReasonHoverHint` does NOT disable the child automatically.
   */
  matched: boolean
  /**
   * The active reason used to render the hover hint dropdown.
   *
   * It's the first reason (by array order) that matches and has non-empty content.
   * If `undefined`, no hint dropdown will be rendered.
   */
  activeReason?: HoverHintReason
}) => React.ReactNode

interface ReasonHoverHintProps {
  /**
   * Reasons ordered by priority. The first matching reason with `when=true`
   * and non-null `content` will be used as the active hint content.
   */
  reasons: HoverHintReason[]
  /**
   * Target element.
   *
   * **Use render-prop when you want to control `disabled` state based on reasons:**
   * ```tsx
   * {({ matched }) => <Button disabled={matched}>...</Button>}
   * ```
   *
   * Use static children when you only want to show hints without controlling state.
   */
  children: React.ReactNode | ReasonHoverHintChildrenRenderFn
}

const isEmptyContent = (content: React.ReactNode): boolean => {
  if (content === null || content === undefined || typeof content === 'boolean') {
    return true
  }

  if (typeof content === 'string') {
    return content.trim() === ''
  }

  // Arrays are a very common ReactNode form (e.g. `items.map(...)`).
  // Empty arrays (or arrays of empty nodes) render nothing, so treat them as empty hint content.
  if (Array.isArray(content)) {
    return content.length === 0 || content.every(isEmptyContent)
  }

  // NOTE: ReactNode also allows generic Iterables (e.g. Set, Map.values(), generators).
  // We intentionally do NOT iterate unknown iterables here because iterating an Iterator/Generator
  // would consume it and change what React renders.
  return false
}

/**
 * A hover hint component that conditionally shows hints based on reasons.
 *
 * ⚠️ **IMPORTANT**: This component does **NOT** automatically disable children.
 * You **MUST** manually handle the disabled state using the render-prop pattern.
 *
 * @example
 * // ✅ Correct: Using render-prop to control disabled state
 * <ReasonHoverHint reasons={[accessDeniedReason, clusterNotReadyReason]}>
 *   {({ matched }) => (
 *     <Button disabled={matched} onClick={handleClick}>
 *       Submit
 *     </Button>
 *   )}
 * </ReasonHoverHint>
 *
 * @example
 * // ❌ Wrong: Button will be clickable even when reasons match
 * <ReasonHoverHint reasons={[accessDeniedReason]}>
 *   <Button onClick={handleClick}>Submit</Button>
 * </ReasonHoverHint>
 *
 * @example
 * // ✅ Also correct: Static children when you only want to show hints (no disable needed)
 * <ReasonHoverHint reasons={[infoReason]}>
 *   <Text>Some informational text</Text>
 * </ReasonHoverHint>
 */
export const ReasonHoverHint = ({ reasons, children }: ReasonHoverHintProps) => {
  const matched = reasons.some((r) => r.when)
  const activeReason = reasons.find((r) => r.when && !isEmptyContent(r.content))

  const renderedChildren = typeof children === 'function' ? children({ matched, activeReason }) : children

  if (isEmptyContent(renderedChildren)) {
    return null
  }

  if (!activeReason) {
    return <>{renderedChildren}</>
  }

  return (
    <HoverCard withArrow withinPortal shadow="md" {...activeReason.hoverCardProps}>
      <HoverCard.Target {...activeReason.hoverCardTargetProps}>
        <Box sx={{ display: 'inline-block' }} {...activeReason.targetWrapperProps}>
          {renderedChildren}
        </Box>
      </HoverCard.Target>
      <HoverCard.Dropdown {...activeReason.hoverCardDropdownProps}>{activeReason.content}</HoverCard.Dropdown>
    </HoverCard>
  )
}
