import type { ChildrenList } from '@furystack/shades'
import { createComponent, Shade } from '@furystack/shades'
import { buildTransition, cssVariableTheme } from '../../services/css-variable-theme.js'
import type { FlattenedTreeNode, TreeService } from '../../services/tree-service.js'
import { Icon } from '../icons/icon.js'
import { chevronRight } from '../icons/icon-definitions.js'
import type { TreeItemState } from './tree.js'

export type TreeItemProps<T> = {
  item: T
  treeService: TreeService<T>
  nodeInfo: FlattenedTreeNode<T>
  isNew: boolean
  renderItem: (item: T, state: TreeItemState) => JSX.Element
  renderIcon?: (item: T, isExpanded: boolean) => JSX.Element
  onActivate?: (item: T) => void
}

const INDENT_PX = 20
const EXPAND_ICON_WIDTH = 20

export const TreeItem: <T>(props: TreeItemProps<T>, children: ChildrenList) => JSX.Element<any> = Shade({
  customElementName: 'shade-tree-item',
  css: {
    display: 'flex',
    fontFamily: cssVariableTheme.typography.fontFamily,
    alignItems: 'center',
    cursor: 'default',
    userSelect: 'none',
    padding: `${cssVariableTheme.spacing.xs} ${cssVariableTheme.spacing.sm}`,
    gap: '6px',
    transition: buildTransition(
      ['opacity', cssVariableTheme.transitions.duration.fast, 'ease-out'],
      ['transform', cssVariableTheme.transitions.duration.fast, 'ease-out'],
      ['background-color', cssVariableTheme.transitions.duration.fast, 'ease'],
      ['box-shadow', cssVariableTheme.transitions.duration.fast, cssVariableTheme.transitions.easing.easeInOut],
    ),
    borderLeft: '3px solid transparent',
    '&[data-animate-in]': {
      opacity: '0',
      transform: 'translateY(-6px)',
    },
    '&:not([data-selected]):hover': {
      backgroundColor: cssVariableTheme.action.hoverBackground,
    },
    '&[data-selected]': {
      backgroundColor: cssVariableTheme.action.selectedBackground,
      borderLeft: `3px solid ${cssVariableTheme.palette.primary.main}`,
    },
    '&[data-focused]': {
      boxShadow: `0 0 0 2px ${cssVariableTheme.palette.primary.main} inset`,
    },
  },
  render: ({ props, useObservable, useHostProps, useRef, useState }) => {
    const { item, treeService, nodeInfo, isNew, renderItem, renderIcon, onActivate } = props
    const { level, hasChildren, isExpanded } = nodeInfo

    const [selection] = useObservable('selection', treeService.selection)
    const [focusedItem] = useObservable('focusedItem', treeService.focusedItem)

    const [isAnimatingIn, setIsAnimatingIn] = useState('isAnimatingIn', isNew)
    if (isNew) {
      requestAnimationFrame(() => {
        requestAnimationFrame(() => setIsAnimatingIn(false))
      })
    }

    const isFocused = focusedItem === item
    const isSelected = selection.includes(item)

    useHostProps({
      tabIndex: isFocused ? 0 : -1,
      'data-spatial-nav-target': '',
      role: 'treeitem',
      'aria-level': (level + 1).toString(),
      'aria-selected': isSelected.toString(),
      ...(hasChildren ? { 'aria-expanded': isExpanded.toString() } : {}),
      onfocus: () => {
        if (treeService.focusedItem.getValue() !== item) {
          treeService.focusedItem.setValue(item)
        }
        if (!treeService.hasFocus.getValue()) {
          treeService.hasFocus.setValue(true)
        }
      },
      onclick: (ev: MouseEvent) => {
        treeService.handleItemClick(item, ev)
      },
      ondblclick: () => {
        treeService.handleItemDoubleClick(item)
        if (!hasChildren) {
          onActivate?.(item)
        }
      },
      ...(isAnimatingIn ? { 'data-animate-in': '' } : {}),
      ...(isSelected ? { 'data-selected': '' } : {}),
      ...(isFocused ? { 'data-focused': '' } : {}),
    })

    const wrapperRef = useRef<HTMLElement>('wrapper')

    if (isFocused) {
      queueMicrotask(() => {
        const el = wrapperRef.current
        if (!el) return
        const hostEl = el.closest('shade-tree-item') as HTMLElement
        if (!hostEl) return

        if (document.activeElement !== hostEl) {
          hostEl.focus({ preventScroll: true })
        }

        const scrollContainer = el.closest('shade-tree') as HTMLElement
        if (scrollContainer) {
          const containerRect = scrollContainer.getBoundingClientRect()
          const itemRect = hostEl.getBoundingClientRect()
          const itemTopInContainer = itemRect.top - containerRect.top
          const itemBottomInContainer = itemRect.bottom - containerRect.top

          if (itemTopInContainer < 0) {
            scrollContainer.scrollTo({
              top: scrollContainer.scrollTop + itemTopInContainer,
              behavior: 'instant',
            })
          } else if (itemBottomInContainer > scrollContainer.clientHeight) {
            scrollContainer.scrollTo({
              top: scrollContainer.scrollTop + (itemBottomInContainer - scrollContainer.clientHeight),
              behavior: 'instant',
            })
          }
        }
      })
    }
    const state: TreeItemState = { isFocused, isSelected, level, hasChildren, isExpanded }

    const handleExpandClick = (ev: MouseEvent) => {
      ev.stopPropagation()
      treeService.toggleExpanded(item)
    }

    return (
      <span ref={wrapperRef} style={{ display: 'contents' }}>
        <span style={{ width: `${level * INDENT_PX}px`, flexShrink: '0' }} />
        <span
          className="tree-item-expand"
          style={{
            width: `${EXPAND_ICON_WIDTH}px`,
            flexShrink: '0',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            cursor: hasChildren ? 'pointer' : 'default',
          }}
          onclick={hasChildren ? handleExpandClick : undefined}
        >
          {hasChildren ? (
            <span
              style={{
                display: 'inline-flex',
                transition: 'transform 0.2s ease',
                transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
              }}
            >
              <Icon icon={chevronRight} size={14} />
            </span>
          ) : (
            ''
          )}
        </span>
        {renderIcon && <span className="tree-item-icon">{renderIcon(item, isExpanded)}</span>}
        <span className="tree-item-content" style={{ flex: '1' }}>
          {renderItem(item, state)}
        </span>
      </span>
    )
  },
})
