import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { NodeModal } from '../NodeModal'
import type { NodeData } from '@/types'

// ── Mock Shadcn Select with native <select> for testability ───────────────

vi.mock('@/components/ui/select', () => ({
  Select: ({ value, onValueChange, children }: {
    value?: string; onValueChange?: (v: string) => void; children: React.ReactNode
  }) => (
    <select value={value} onChange={(e) => onValueChange?.(e.target.value)}>
      {children}
    </select>
  ),
  SelectTrigger: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  SelectValue: () => null,
  SelectContent: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  SelectGroup: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  SelectLabel: () => null,
  SelectItem: ({ value, children }: { value: string; children: React.ReactNode }) => (
    <option value={value}>{children}</option>
  ),
  SelectSeparator: () => null,
}))

// ── Helpers ───────────────────────────────────────────────────────────────

function renderModal(props: Partial<Parameters<typeof NodeModal>[0]> = {}) {
  const onClose = vi.fn()
  const onSubmit = vi.fn()
  render(<NodeModal open onClose={onClose} onSubmit={onSubmit} {...props} />)
  return { onClose, onSubmit }
}

/** Get <select> elements in document order: [0]=Type, [1]=CheckMethod, [2]=BottomHandles */
function selects() { return screen.getAllByRole('combobox') as HTMLSelectElement[] }

const BASE: Partial<NodeData> = {
  type: 'server', label: 'My Server', hostname: 'server.lan',
  ip: '192.168.1.10', check_method: 'ping', services: [],
}

// ── Tests ─────────────────────────────────────────────────────────────────

describe('NodeModal', () => {

  // ── Visibility ────────────────────────────────────────────────────────

  it('renders nothing when closed', () => {
    const { container } = render(<NodeModal open={false} onClose={vi.fn()} onSubmit={vi.fn()} />)
    expect(container.querySelector('[role="dialog"]')).toBeNull()
  })

  it('renders form fields when open', () => {
    renderModal()
    expect(screen.getByPlaceholderText('My Server')).toBeDefined()
    expect(screen.getByText('Add Node')).toBeDefined()
  })

  it('shows "Add" button for default title', () => {
    renderModal()
    expect(screen.getByRole('button', { name: 'Add' })).toBeDefined()
  })

  it('shows "Save" button when title is Edit Node', () => {
    renderModal({ title: 'Edit Node' })
    expect(screen.getByRole('button', { name: 'Save' })).toBeDefined()
  })

  it('pre-fills form from initial prop', () => {
    renderModal({ initial: BASE })
    expect((screen.getByPlaceholderText('My Server') as HTMLInputElement).value).toBe('My Server')
    expect((screen.getByPlaceholderText('server.lan') as HTMLInputElement).value).toBe('server.lan')
    expect((screen.getByPlaceholderText('192.168.1.x, 2001:db8::1') as HTMLInputElement).value).toBe('192.168.1.10')
  })

  // ── Cancel ────────────────────────────────────────────────────────────

  it('calls onClose when Cancel is clicked', () => {
    const { onClose } = renderModal()
    fireEvent.click(screen.getByRole('button', { name: 'Cancel' }))
    expect(onClose).toHaveBeenCalledOnce()
  })

  // ── Delete confirm ────────────────────────────────────────────────────

  it('deletes and closes when Delete confirm is accepted', () => {
    const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true)
    const { onClose, onSubmit } = renderModal({ title: 'Edit Node', initial: BASE })
    fireEvent.click(screen.getByRole('button', { name: 'Delete' }))
    expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({ _delete: true }))
    expect(onClose).toHaveBeenCalledOnce()
    confirmSpy.mockRestore()
  })

  // Regression: bare-if without braces used to call onClose() unconditionally,
  // closing the modal even when the user cancelled the confirm dialog.
  it('does not delete or close when Delete confirm is cancelled', () => {
    const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false)
    const { onClose, onSubmit } = renderModal({ title: 'Edit Node', initial: BASE })
    fireEvent.click(screen.getByRole('button', { name: 'Delete' }))
    expect(onSubmit).not.toHaveBeenCalled()
    expect(onClose).not.toHaveBeenCalled()
    confirmSpy.mockRestore()
  })

  // ── Custom-style shortcut ─────────────────────────────────────────────

  it('shows the type-style shortcut and calls onEditTypeStyle with the node type', () => {
    const onEditTypeStyle = vi.fn()
    renderModal({ initial: BASE, onEditTypeStyle })
    const link = screen.getByRole('button', { name: /style for all nodes on the canvas/i })
    fireEvent.click(link)
    expect(onEditTypeStyle).toHaveBeenCalledWith('server')
  })

  it('omits the shortcut when onEditTypeStyle is not provided', () => {
    renderModal({ initial: BASE })
    expect(screen.queryByText(/style for all nodes on the canvas/i)).toBeNull()
  })

  // ── Label validation ──────────────────────────────────────────────────

  it('blocks submit and shows error when label is empty', () => {
    const { onSubmit } = renderModal()
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect(onSubmit).not.toHaveBeenCalled()
    expect(screen.getByText('Label is required')).toBeDefined()
  })

  it('blocks submit when label is whitespace only', () => {
    const { onSubmit } = renderModal()
    fireEvent.change(screen.getByPlaceholderText('My Server'), { target: { value: '   ' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect(onSubmit).not.toHaveBeenCalled()
  })

  it('clears label error when user starts typing', () => {
    renderModal()
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    fireEvent.change(screen.getByPlaceholderText('My Server'), { target: { value: 'x' } })
    expect(screen.queryByText('Label is required')).toBeNull()
  })

  // ── Form submission ───────────────────────────────────────────────────

  it('calls onSubmit and onClose with form data on valid submit', () => {
    const { onSubmit, onClose } = renderModal({ initial: BASE })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect(onSubmit).toHaveBeenCalledOnce()
    expect(onClose).toHaveBeenCalledOnce()
    const data = onSubmit.mock.calls[0][0] as Partial<NodeData>
    expect(data.label).toBe('My Server')
    expect(data.type).toBe('server')
  })

  it('submits updated hostname, IP and notes', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.change(screen.getByPlaceholderText('server.lan'), { target: { value: 'nas.local' } })
    fireEvent.change(screen.getByPlaceholderText('192.168.1.x, 2001:db8::1'), { target: { value: '10.0.0.1' } })
    fireEvent.change(screen.getByPlaceholderText('Optional notes'), { target: { value: 'rack A' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    const data = onSubmit.mock.calls[0][0] as Partial<NodeData>
    expect(data.hostname).toBe('nas.local')
    expect(data.ip).toBe('10.0.0.1')
    expect(data.notes).toBe('rack A')
  })

  it('resets form values when reopened in Add mode', () => {
    const onClose = vi.fn()
    const onSubmit = vi.fn()

    const { rerender } = render(<NodeModal key="open-1" open onClose={onClose} onSubmit={onSubmit} />)
    fireEvent.change(screen.getByPlaceholderText('My Server'), { target: { value: 'Temp Node' } })
    fireEvent.change(screen.getByPlaceholderText('server.lan'), { target: { value: 'temp.local' } })

    rerender(<NodeModal key="closed" open={false} onClose={onClose} onSubmit={onSubmit} />)
    rerender(<NodeModal key="open-2" open onClose={onClose} onSubmit={onSubmit} />)

    expect((screen.getByPlaceholderText('My Server') as HTMLInputElement).value).toBe('')
    expect((screen.getByPlaceholderText('server.lan') as HTMLInputElement).value).toBe('')
  })

  it('submits check_target', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.change(screen.getByPlaceholderText('http://...'), { target: { value: 'http://192.168.1.10:8080' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).check_target).toBe('http://192.168.1.10:8080')
  })

  // ── Type selector ─────────────────────────────────────────────────────

  it('pre-fills type from initial', () => {
    renderModal({ initial: { ...BASE, type: 'router' } })
    expect(selects()[0].value).toBe('router')
  })

  it('changes type and submits it', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.change(selects()[0], { target: { value: 'nas' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).type).toBe('nas')
  })

  it('offers Z-Wave node types and submits one', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.change(selects()[0], { target: { value: 'zwave_enddevice' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).type).toBe('zwave_enddevice')
  })

  it('forces check_method to none when a Z-Wave type is selected', () => {
    const { onSubmit } = renderModal({ initial: { ...BASE, check_method: 'ping' } })
    fireEvent.change(selects()[0], { target: { value: 'zwave_coordinator' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).check_method).toBe('none')
  })

  // ── Check method ──────────────────────────────────────────────────────

  it('pre-fills check_method from initial', () => {
    renderModal({ initial: { ...BASE, check_method: 'http' } })
    expect(selects()[1].value).toBe('http')
  })

  it('changes check_method and submits it', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.change(selects()[1], { target: { value: 'ssh' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).check_method).toBe('ssh')
  })

  // ── Icon picker ───────────────────────────────────────────────────────

  it('shows "Default" label when no custom icon', () => {
    renderModal({ initial: BASE })
    expect(screen.getByText('Default')).toBeDefined()
  })

  it('opens icon picker on trigger button click', () => {
    renderModal({ initial: BASE })
    expect(screen.queryByPlaceholderText('Search icons…')).toBeNull()
    fireEvent.click(screen.getByText('Default'))
    expect(screen.getByPlaceholderText('Search icons…')).toBeDefined()
  })

  it('closes picker and shows icon label after selecting an icon', () => {
    renderModal({ initial: BASE })
    fireEvent.click(screen.getByText('Default'))
    fireEvent.click(screen.getByTitle('Database (SQL/NoSQL)'))
    expect(screen.queryByPlaceholderText('Search icons…')).toBeNull()
    expect(screen.getByText('Database (SQL/NoSQL)')).toBeDefined()
  })

  it('submits custom_icon key after picking', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.click(screen.getByText('Default'))
    fireEvent.click(screen.getByTitle('Database (SQL/NoSQL)'))
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).custom_icon).toBe('database')
  })

  it('shows Reset button when custom_icon is set', () => {
    renderModal({ initial: { ...BASE, custom_icon: 'database' } })
    expect(screen.getByRole('button', { name: /Reset/i })).toBeDefined()
  })

  it('hides Reset button when no custom_icon', () => {
    renderModal({ initial: BASE })
    expect(screen.queryByRole('button', { name: /Reset/i })).toBeNull()
  })

  it('resets custom_icon and shows Default on Reset click', () => {
    renderModal({ initial: { ...BASE, custom_icon: 'database' } })
    fireEvent.click(screen.getByRole('button', { name: /Reset/i }))
    expect(screen.getByText('Default')).toBeDefined()
  })

  it('filters icons by search query', () => {
    renderModal({ initial: BASE })
    fireEvent.click(screen.getByText('Default'))
    fireEvent.change(screen.getByPlaceholderText('Search icons…'), { target: { value: 'grafana' } })
    expect(screen.getByTitle('Grafana / Kibana')).toBeDefined()
    expect(screen.queryByTitle('Router')).toBeNull()
  })

  // ── Container mode ─────────────────────────────────────────────────────

  const containerModeTypes = ['proxmox', 'vm', 'lxc', 'docker_host'] as const
  const nonContainerModeTypes = ['isp', 'router', 'switch', 'server', 'nas', 'ap', 'printer', 'iot', 'camera', 'cpl', 'computer', 'generic', 'docker_container', 'groupRect', 'group'] as const

  it.each(containerModeTypes)('shows Container Mode toggle for %s type', (type) => {
    renderModal({ initial: { ...BASE, type } })
    expect(screen.getByText('Container Mode')).toBeDefined()
  })

  it.each(nonContainerModeTypes)('hides Container Mode for %s type', (type) => {
    renderModal({ initial: { ...BASE, type } })
    expect(screen.queryByText('Container Mode')).toBeNull()
  })

  it('toggles container_mode on click', () => {
    const { onSubmit } = renderModal({ initial: { ...BASE, type: 'proxmox', container_mode: true } })
    fireEvent.click(screen.getByRole('switch', { name: 'Container Mode' }))
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).container_mode).toBe(false)
  })

  // ── Show services toggle (modal-only) ───────────────────────────────

  it('shows Show Services toggle for regular nodes', () => {
    renderModal({ initial: BASE })
    expect(screen.getByText('Show Services')).toBeDefined()
    expect(screen.getByRole('switch', { name: 'Show Services' })).toBeDefined()
  })

  it('hides Show Services toggle for groupRect', () => {
    renderModal({ initial: { ...BASE, type: 'groupRect' } })
    expect(screen.queryByText('Show Services')).toBeNull()
  })

  it('submits custom_colors.show_services=true when toggled on', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.click(screen.getByRole('switch', { name: 'Show Services' }))
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    const data = onSubmit.mock.calls[0][0] as Partial<NodeData>
    expect(data.custom_colors?.show_services).toBe(true)
  })

  it('keeps default colors hint visible when Show Services is toggled on', () => {
    renderModal({ initial: BASE })
    fireEvent.click(screen.getByRole('switch', { name: 'Show Services' }))
    expect(screen.getByText(/Using default colors for/)).toBeDefined()
  })

  it('does not show Appearance reset when only Show Services is set', () => {
    renderModal({ initial: BASE })
    fireEvent.click(screen.getByRole('switch', { name: 'Show Services' }))
    expect(screen.queryByText('Reset to defaults')).toBeNull()
  })

  // ── Parent Container selector ─────────────────────────────────────────

  it('does not render Parent Container for non-child types', () => {
    renderModal({
      initial: BASE,
      parentCandidates: [{ id: 'p1', label: 'Proxmox', type: 'proxmox' }],
    })
    expect(screen.queryByText('Parent Container')).toBeNull()
  })

  it('does not render Parent Container when no valid candidates exist', () => {
    renderModal({
      initial: { ...BASE, type: 'docker_container' },
      parentCandidates: [],
    })
    expect(screen.queryByText('Parent Container')).toBeNull()
  })

  it('renders Parent Container for docker_container when docker_host candidate exists', () => {
    renderModal({
      initial: { ...BASE, type: 'docker_container' },
      parentCandidates: [{ id: 'dh1', label: 'Docker Host', type: 'docker_host' }],
    })
    expect(screen.getByText('Parent Container')).toBeDefined()
  })

  it('renders Parent Container for docker_container when only an LXC candidate exists', () => {
    renderModal({
      initial: { ...BASE, type: 'docker_container' },
      parentCandidates: [{ id: 'lxc1', label: 'My LXC', type: 'lxc' }],
    })
    expect(screen.getByText('Parent Container')).toBeDefined()
  })

  it('renders Parent Container for lxc when proxmox candidate exists', () => {
    renderModal({
      initial: { ...BASE, type: 'lxc' },
      parentCandidates: [{ id: 'px1', label: 'PVE', type: 'proxmox' }],
    })
    expect(screen.getByText('Parent Container')).toBeDefined()
  })

  it('renders Parent Container for a plain node when a container-mode candidate exists', () => {
    renderModal({
      initial: { ...BASE, type: 'server' },
      parentCandidates: [{ id: 'px1', label: 'PVE', type: 'proxmox', container_mode: true }],
    })
    expect(screen.getByText('Parent Container')).toBeDefined()
  })

  it('still hides Parent Container for a plain node when the candidate is not in container mode', () => {
    renderModal({
      initial: { ...BASE, type: 'server' },
      parentCandidates: [{ id: 'px1', label: 'PVE', type: 'proxmox', container_mode: false }],
    })
    expect(screen.queryByText('Parent Container')).toBeNull()
  })

  it('renders Parent Container for an already-nested plain node so it can be detached', () => {
    renderModal({
      initial: { ...BASE, type: 'server', parent_id: 'px1' },
      parentCandidates: [{ id: 'px1', label: 'PVE', type: 'proxmox', container_mode: true }],
    })
    expect(screen.getByText('Parent Container')).toBeDefined()
  })

  it('keeps a container-mode parent_id on submit for a plain node', () => {
    const { onSubmit } = renderModal({
      initial: { ...BASE, type: 'server', parent_id: 'px1' },
      parentCandidates: [{ id: 'px1', label: 'PVE', type: 'proxmox', container_mode: true }],
    })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).parent_id).toBe('px1')
  })

  it('drops a parent_id that is not a valid container on submit', () => {
    const { onSubmit } = renderModal({
      initial: { ...BASE, type: 'server', parent_id: 'px1' },
      parentCandidates: [{ id: 'px1', label: 'PVE', type: 'proxmox', container_mode: false }],
    })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).parent_id).toBeUndefined()
  })

  // ── Appearance ────────────────────────────────────────────────────────

  it('renders 3 color swatch labels (border, background, icon)', () => {
    renderModal({ initial: BASE })
    expect(screen.getByText('border')).toBeDefined()
    expect(screen.getByText('background')).toBeDefined()
    expect(screen.getByText('icon')).toBeDefined()
  })

  it('shows default colors hint when no custom_colors', () => {
    renderModal({ initial: BASE })
    expect(screen.getByText(/Using default colors for/)).toBeDefined()
  })

  it('shows Reset to defaults when custom_colors are set', () => {
    renderModal({ initial: { ...BASE, custom_colors: { border: '#ff0000' } } })
    expect(screen.getByText('Reset to defaults')).toBeDefined()
  })

  it('resets custom_colors on Reset to defaults click', () => {
    renderModal({ initial: { ...BASE, custom_colors: { border: '#ff0000' } } })
    fireEvent.click(screen.getByText('Reset to defaults'))
    expect(screen.queryByText('Reset to defaults')).toBeNull()
    expect(screen.getByText(/Using default colors for/)).toBeDefined()
  })

  // ── Connection points per side (issue #243) ────────────────────────────

  it('shows the Connection Points section for server type', () => {
    renderModal({ initial: BASE })
    expect(screen.getByText('Connection Points')).toBeDefined()
    expect(screen.getByLabelText('Top connection points')).toBeDefined()
    expect(screen.getByLabelText('Right connection points')).toBeDefined()
    expect(screen.getByLabelText('Bottom connection points')).toBeDefined()
    expect(screen.getByLabelText('Left connection points')).toBeDefined()
  })

  it('hides Connection Points for groupRect', () => {
    renderModal({ initial: { ...BASE, type: 'groupRect' } })
    expect(screen.queryByText('Connection Points')).toBeNull()
  })

  it('hides Connection Points for group', () => {
    renderModal({ initial: { ...BASE, type: 'group' } })
    expect(screen.queryByText('Connection Points')).toBeNull()
  })

  it('defaults top/bottom to 1 and left/right to 0', () => {
    renderModal({ initial: BASE })
    expect((screen.getByLabelText('Top connection points') as HTMLInputElement).value).toBe('1')
    expect((screen.getByLabelText('Bottom connection points') as HTMLInputElement).value).toBe('1')
    expect((screen.getByLabelText('Left connection points') as HTMLInputElement).value).toBe('0')
    expect((screen.getByLabelText('Right connection points') as HTMLInputElement).value).toBe('0')
  })

  it('pre-fills each side from initial', () => {
    renderModal({ initial: { ...BASE, top_handles: 2, bottom_handles: 3, left_handles: 4, right_handles: 1 } })
    expect((screen.getByLabelText('Top connection points') as HTMLInputElement).value).toBe('2')
    expect((screen.getByLabelText('Bottom connection points') as HTMLInputElement).value).toBe('3')
    expect((screen.getByLabelText('Left connection points') as HTMLInputElement).value).toBe('4')
    expect((screen.getByLabelText('Right connection points') as HTMLInputElement).value).toBe('1')
  })

  it('submits per-side counts edited via the number inputs', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.change(screen.getByLabelText('Bottom connection points'), { target: { value: '12' } })
    fireEvent.change(screen.getByLabelText('Left connection points'), { target: { value: '3' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    const payload = onSubmit.mock.calls[0][0] as Partial<NodeData>
    expect(payload.bottom_handles).toBe(12)
    expect(payload.left_handles).toBe(3)
    expect(payload.top_handles).toBe(1)
    expect(payload.right_handles).toBe(0)
  })

  it('increments a side with the + stepper button', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.click(screen.getByRole('button', { name: 'Increase Right connection points' }))
    fireEvent.click(screen.getByRole('button', { name: 'Increase Right connection points' }))
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).right_handles).toBe(2)
  })

  it('disables the − button at the side minimum (0 for left/right, 1 for top/bottom)', () => {
    renderModal({ initial: BASE })
    expect((screen.getByRole('button', { name: 'Decrease Left connection points' }) as HTMLButtonElement).disabled).toBe(true)
    expect((screen.getByRole('button', { name: 'Decrease Top connection points' }) as HTMLButtonElement).disabled).toBe(true)
  })

  it('left/right min is 0, top/bottom min is 1; max is 64 everywhere', () => {
    renderModal({ initial: BASE })
    expect((screen.getByLabelText('Bottom connection points') as HTMLInputElement).min).toBe('1')
    expect((screen.getByLabelText('Left connection points') as HTMLInputElement).min).toBe('0')
    for (const label of ['Top', 'Right', 'Bottom', 'Left']) {
      expect((screen.getByLabelText(`${label} connection points`) as HTMLInputElement).max).toBe('64')
    }
  })

  it('supports the full range up to 64 (issue #20)', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    fireEvent.change(screen.getByLabelText('Bottom connection points'), { target: { value: '52' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).bottom_handles).toBe(52)
  })

  it('clamps pre-filled out-of-range values into range', () => {
    renderModal({ initial: { ...BASE, bottom_handles: 9999 } })
    expect((screen.getByLabelText('Bottom connection points') as HTMLInputElement).value).toBe('64')
  })

  it('toggles show_port_numbers and submits it (issue #20)', () => {
    const { onSubmit } = renderModal({ initial: BASE })
    const toggle = screen.getByLabelText('Toggle port numbers')
    expect(toggle.getAttribute('aria-checked')).toBe('false')
    fireEvent.click(toggle)
    expect(toggle.getAttribute('aria-checked')).toBe('true')
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).show_port_numbers).toBe(true)
  })

  // ── Zigbee nodes ──────────────────────────────────────────────────────

  const zigbeeTypes = ['zigbee_coordinator', 'zigbee_router', 'zigbee_enddevice'] as const

  it.each(zigbeeTypes)('hides Check Method for %s type', (type) => {
    renderModal({ initial: { ...BASE, type } })
    expect(screen.queryByText('Check Method')).toBeNull()
  })

  it.each(zigbeeTypes)('hides Check Target for %s type', (type) => {
    renderModal({ initial: { ...BASE, type } })
    expect(screen.queryByText('Check Target')).toBeNull()
  })

  it.each(zigbeeTypes)('submits check_method=none for %s type', (type) => {
    const { onSubmit } = renderModal({ initial: { ...BASE, type, label: 'Zigbee Node' } })
    fireEvent.click(screen.getByRole('button', { name: 'Add' }))
    expect((onSubmit.mock.calls[0][0] as Partial<NodeData>).check_method).toBe('none')
  })
})
