import { useEffect, useRef } from 'react'
import { describe, it, expect } from 'vitest'
import { fireEvent, render, screen } from '@testing-library/react'
import { IconButton } from '@mui/material'
import StarIcon from '@mui/icons-material/Star'
import { Toolbox } from './toolbox'

function makeChildren(n: number) {
  return Array.from({ length: n }).map((_, i) => (
    <IconButton key={i} aria-label={`Tool ${i + 1}`} size='small'>
      <StarIcon fontSize='small' />
    </IconButton>
  ))
}

/**
 * Visible iff the item's Toolbox wrapper has `data-toolbox-item-visible`
 * set to `'true'`. Items always mount (single-mount via portal) —
 * visibility is the only thing that flips when the popper opens/closes.
 */
function isItemVisible(label: string): boolean {
  const el = screen.getByLabelText(label)
  const wrapper = el.closest('[data-toolbox-item-visible]')
  if (!wrapper) throw new Error(`No toolbox wrapper for "${label}"`)
  return wrapper.getAttribute('data-toolbox-item-visible') === 'true'
}

describe('<Toolbox>', () => {
  it('renders nothing when there are no children', () => {
    const { container } = render(<Toolbox>{null}</Toolbox>)
    expect(container.firstChild).toBeNull()
  })

  it('renders every child inline when visibleCount is omitted', () => {
    render(<Toolbox>{makeChildren(4)}</Toolbox>)
    for (let i = 1; i <= 4; i++) {
      expect(screen.getByLabelText(`Tool ${i}`)).toBeTruthy()
    }
  })

  it('renders every child inline when count <= visibleCount', () => {
    render(<Toolbox visibleCount={5}>{makeChildren(3)}</Toolbox>)
    expect(screen.queryByLabelText('More actions')).toBeNull()
    expect(screen.getByLabelText('Tool 1')).toBeTruthy()
    expect(screen.getByLabelText('Tool 3')).toBeTruthy()
  })

  it('closed with overflow → first N visible inline, rest in DOM but hidden', () => {
    render(<Toolbox visibleCount={2}>{makeChildren(5)}</Toolbox>)
    // Every item is mounted exactly once.
    for (let i = 1; i <= 5; i++) {
      expect(screen.getAllByLabelText(`Tool ${i}`).length).toBe(1)
    }
    // Visibility: first 2 visible, rest display:none.
    expect(isItemVisible('Tool 1')).toBe(true)
    expect(isItemVisible('Tool 2')).toBe(true)
    expect(isItemVisible('Tool 3')).toBe(false)
    expect(isItemVisible('Tool 4')).toBe(false)
    expect(isItemVisible('Tool 5')).toBe(false)
  })

  it('open with overflow → all items visible (popover) without remounting any of them', () => {
    let mountCount = 0
    function Counted() {
      const counted = useRef(false)
      useEffect(() => {
        if (counted.current) return
        counted.current = true
        mountCount += 1
      }, [])
      return (
        <IconButton aria-label='Counted' size='small'>
          <StarIcon fontSize='small' />
        </IconButton>
      )
    }
    render(
      <Toolbox visibleCount={1}>
        <IconButton aria-label='Visible' size='small' key='v'>
          <StarIcon fontSize='small' />
        </IconButton>
        <Counted key='c' />
      </Toolbox>,
    )
    expect(mountCount).toBe(1)
    expect(isItemVisible('Counted')).toBe(false)
    fireEvent.click(screen.getByLabelText('More actions'))
    // Still mounted exactly once — no remount on open.
    expect(mountCount).toBe(1)
    expect(isItemVisible('Counted')).toBe(true)
    fireEvent.click(screen.getByLabelText('Close'))
    // And still no remount on close.
    expect(mountCount).toBe(1)
    expect(isItemVisible('Counted')).toBe(false)
  })

  it('swaps the trigger label to "Close" when the Paper is open', () => {
    render(<Toolbox visibleCount={2}>{makeChildren(4)}</Toolbox>)
    const trigger = screen.getByLabelText('More actions')
    expect(trigger.getAttribute('aria-pressed')).toBe('false')
    fireEvent.click(trigger)
    // Same button now exposes the close label.
    expect(screen.getByLabelText('Close')).toBe(trigger)
    expect(trigger.getAttribute('aria-pressed')).toBe('true')
  })

  it('honors a custom trigger label', () => {
    render(
      <Toolbox visibleCount={1} labels={{ trigger: 'Extra' }}>
        {makeChildren(3)}
      </Toolbox>,
    )
    expect(screen.getByLabelText('Extra')).toBeTruthy()
  })

  it('keeps a hidden marker between visible items in the inline preview', () => {
    render(
      <Toolbox visibleCount={2}>
        <IconButton key='a' aria-label='Tool A' size='small'>
          <StarIcon fontSize='small' />
        </IconButton>
        <span data-toolbar-hidden key='divider' aria-label='Divider' />
        <IconButton key='b' aria-label='Tool B' size='small'>
          <StarIcon fontSize='small' />
        </IconButton>
        <IconButton key='c' aria-label='Tool C' size='small'>
          <StarIcon fontSize='small' />
        </IconButton>
      </Toolbox>,
    )
    // Every item is mounted exactly once. Inline visibility: A, divider, B
    // (divider sits between visible items); Tool C is hidden inline.
    expect(screen.getAllByLabelText('Tool A').length).toBe(1)
    expect(screen.getAllByLabelText('Divider').length).toBe(1)
    expect(screen.getAllByLabelText('Tool B').length).toBe(1)
    expect(screen.getAllByLabelText('Tool C').length).toBe(1)
    expect(isItemVisible('Tool A')).toBe(true)
    expect(isItemVisible('Divider')).toBe(true)
    expect(isItemVisible('Tool B')).toBe(true)
    expect(isItemVisible('Tool C')).toBe(false)
  })

  it('drops trailing hidden items past the visible budget — no orphan dividers inline', () => {
    render(
      <Toolbox visibleCount={2}>
        <IconButton key='a' aria-label='Tool A' size='small'>
          <StarIcon fontSize='small' />
        </IconButton>
        <IconButton key='b' aria-label='Tool B' size='small'>
          <StarIcon fontSize='small' />
        </IconButton>
        <span
          data-toolbar-hidden
          key='divider'
          data-testid='trailing-divider'
        />
        <IconButton key='c' aria-label='Tool C' size='small'>
          <StarIcon fontSize='small' />
        </IconButton>
      </Toolbox>,
    )
    // Inline visibility: A + B; the trailing divider is dropped from the
    // inline-visible set so it doesn't appear as an orphan separator.
    expect(isItemVisible('Tool A')).toBe(true)
    expect(isItemVisible('Tool B')).toBe(true)
    const divider = screen.getByTestId('trailing-divider')
    const wrapper = divider.closest('[data-toolbox-item-visible]')
    expect(wrapper?.getAttribute('data-toolbox-item-visible')).toBe('false')
    expect(isItemVisible('Tool C')).toBe(false)
  })

  it('marks the trigger active (className) while the Paper is open so the wrapper hover-fade keeps it visible', () => {
    render(<Toolbox visibleCount={2}>{makeChildren(4)}</Toolbox>)
    const trigger = screen.getByLabelText('More actions')
    expect(trigger.className).not.toMatch(/\bactive\b/)
    fireEvent.click(trigger)
    expect(trigger.className).toMatch(/\bactive\b/)
  })

  it('auto-closes and falls back to inline when visibleCount flips to undefined while the popper is open', () => {
    function Wrap({ count }: { count: number | undefined }) {
      return <Toolbox visibleCount={count}>{makeChildren(5)}</Toolbox>
    }
    const { rerender } = render(<Wrap count={2} />)
    fireEvent.click(screen.getByLabelText('More actions'))
    // Popper open, all items visible inside it.
    for (let i = 1; i <= 5; i++) {
      expect(isItemVisible(`Tool ${i}`)).toBe(true)
    }
    // Caller drops `visibleCount` (e.g. fullscreen opens). Overflow vanishes
    // and so does the popper — items should keep showing inline, *not* be
    // stranded in the detached paper node.
    rerender(<Wrap count={undefined} />)
    expect(screen.queryByLabelText('More actions')).toBeNull()
    expect(screen.queryByLabelText('Close')).toBeNull()
    for (let i = 1; i <= 5; i++) {
      expect(isItemVisible(`Tool ${i}`)).toBe(true)
    }
  })
})
