import {
  Children,
  isValidElement,
  useCallback,
  useState,
  type ComponentType,
  type ReactElement,
  type ReactNode,
} from 'react'
import {
  Accordion,
  AccordionDetails,
  AccordionSummary,
  Box,
  LinearProgress,
  Typography,
  type SvgIconProps,
  type SxProps,
  type Theme,
} from '@mui/material'
import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material'
import { SmartTooltip } from '../../components/smart-tooltip/smart-tooltip'
import { useWidget, useWidgetId } from '../stores'
import { Actions, Options } from './widget-actions'
import { styles } from './style'
import { DEFAULT_WRAPPER_LABELS, type WrapperLabels } from './labels'

export interface WrapperProps {
  title: string
  /**
   * Controlled collapsed state. When supplied, the wrapper is fully
   * controlled — the consumer is responsible for updating it via
   * `onCollapseChange`. Leave undefined to use the uncontrolled mode,
   * which seeds internal state from `defaultCollapsed`.
   */
  collapsed?: boolean
  /**
   * Fires whenever the user interacts to toggle the wrapper, with the
   * NEXT collapsed value. Called for both controlled and uncontrolled
   * usage — consumers can subscribe to user-driven toggles even when
   * letting the wrapper manage its own state.
   */
  onCollapseChange?: (collapsed: boolean) => void
  /**
   * Initial collapsed state when the wrapper is uncontrolled (i.e.
   * `collapsed` is undefined). Ignored when `collapsed` is supplied.
   */
  defaultCollapsed?: boolean
  disabled?: boolean
  /**
   * Visual variant. `'elevation'` (default) renders a borderless shell;
   * `'outlined'` adds a 1px divider border around the card.
   */
  variant?: 'outlined' | 'elevation'
  labels?: Partial<WrapperLabels>
  /**
   * Icon shown at the right edge of the summary row. Accordion rotates it
   * 180° automatically when the wrapper expands. Default: `ExpandMoreIcon`.
   */
  expandIcon?: ComponentType<SvgIconProps>
  iconProps?: SvgIconProps
  sx?: SxProps<Theme>
  children: ReactNode
}

/**
 * Widget shell — title + collapsible body, built on MUI `<Accordion>` for
 * native click-to-toggle on the summary header and a smooth open/close
 * transition. UX matches v1 `<WrapperUI>`:
 *
 *   • hover-fade action buttons (always visible on coarse-pointer devices)
 *   • multi-line title clamp (`WebkitLineClamp: 2`, word-break) + SmartTooltip
 *     when the text overflows
 *   • top-edge `<LinearProgress>` driven by the widget store's `isFetching`
 *   • chevron at the right end (rotated by Accordion based on expanded state)
 *
 * Children are partitioned by component identity: `<Actions>` and `<Options>`
 * render inside the summary row; everything else (`<Content>`, `<Footer>`,
 * loose nodes) renders inside the details panel.
 */
export function Wrapper({
  title,
  collapsed,
  onCollapseChange,
  defaultCollapsed = false,
  disabled = false,
  variant = 'elevation',
  labels,
  expandIcon: ExpandIcon = ExpandMoreIcon,
  iconProps,
  sx,
  children,
}: WrapperProps) {
  const id = useWidgetId()
  const isFetching = useWidget(id, (s) => s.isFetching)
  // Controlled vs uncontrolled: when the consumer passes `collapsed`, they
  // own the state; otherwise we keep an internal copy seeded from
  // `defaultCollapsed`. `internalCollapsed` is still tracked in controlled
  // mode so a consumer can drop the controlled prop later without losing
  // the last-known position.
  const isControlled = collapsed !== undefined
  const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed)
  const effectiveCollapsed = isControlled ? collapsed : internalCollapsed
  const handleAccordionToggle = useCallback(
    (_: unknown, expanded: boolean) => {
      if (disabled) return
      const next = !expanded
      onCollapseChange?.(next)
      setInternalCollapsed(next)
    },
    [disabled, onCollapseChange],
  )
  const _labels = { ...DEFAULT_WRAPPER_LABELS, ...labels }

  const { summary: summarySlots, details: detailsSlots } =
    partitionChildren(children)

  const ariaLabel = effectiveCollapsed ? _labels.expand : _labels.collapse

  return (
    <Accordion
      data-collapsed={effectiveCollapsed ? 'true' : undefined}
      expanded={!effectiveCollapsed}
      onChange={handleAccordionToggle}
      disableGutters
      elevation={0}
      variant={variant}
      square
      sx={{
        ...styles.root,
        ...sx,
      }}
    >
      {isFetching ? (
        <LinearProgress sx={styles.loading} color='primary' />
      ) : null}

      <AccordionSummary
        expandIcon={
          disabled ? null : <ExpandIcon fontSize='small' {...iconProps} />
        }
        aria-label={ariaLabel}
        aria-disabled={disabled || undefined}
        sx={{
          ...styles.summary,
          ...(disabled ? styles.summaryDisabled : null),
        }}
      >
        <SmartTooltip title={title}>
          {({ ref }) => (
            <Box sx={styles.titleCell}>
              <Typography
                ref={ref}
                variant='subtitle1'
                sx={styles.titleText}
                textTransform='none'
              >
                {title}
              </Typography>
            </Box>
          )}
        </SmartTooltip>
        {summarySlots}
      </AccordionSummary>
      <AccordionDetails sx={styles.detail}>{detailsSlots}</AccordionDetails>
    </Accordion>
  )
}

/**
 * Splits children by component identity: `<Actions>` and `<Options>` belong
 * in the summary row, everything else in the details panel. Identity
 * comparison works because both slot components are imported from the same
 * module that consumers use.
 */
function partitionChildren(children: ReactNode): {
  summary: ReactNode[]
  details: ReactNode[]
} {
  const summary: ReactNode[] = []
  const details: ReactNode[] = []
  for (const child of Children.toArray(children)) {
    if (isSlot(child, Actions) || isSlot(child, Options)) {
      summary.push(child)
    } else {
      details.push(child)
    }
  }
  return { summary, details }
}

function isSlot(node: ReactNode, type: unknown): node is ReactElement {
  return isValidElement(node) && node.type === type
}
