/**
 * Drives `Legend.Sortable`'s onDragEnd through the shared dnd-kit mock
 * harness (DndContext passthrough exposing its props) against the **real**
 * legend store — asserting the committed `moveGroup` / `moveLayer` effects
 * and the cross-kind/cross-bucket guards.
 */
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import {
  dndKitCoreMock,
  dndKitSortableMock,
  lastDndContextProps,
  resetDndKitMocks,
} from '../../../../tests/mocks/dnd-kit'

vi.mock('@dnd-kit/core', () => dndKitCoreMock())
vi.mock('@dnd-kit/sortable', () => dndKitSortableMock())

import { LegendProvider } from '../../provider'
import { LegendGroup } from '../legend-group/legend-group'
import { LegendRow } from '../legend-row/legend-row'
import { LegendItem } from '../legend-item/legend-item'
import { LegendSortable } from './legend-sortable'
import {
  clearAllLegendStores,
  getLegendStore,
  selectLayersByGroup,
  selectOrderedGroups,
  type LegendVariable,
} from '../../stores'

const cat: LegendVariable = {
  type: 'category',
  items: [{ label: 'A', color: '#123456' }],
}

const layers = [
  { id: 'a', name: 'Alpha', groupId: 'g1', variables: [cat] },
  { id: 'b', name: 'Beta', groupId: 'g1', variables: [cat] },
  { id: 'c', name: 'Gamma', groupId: 'g2', variables: [cat] },
  { id: 'x', name: 'Loose X', variables: [cat] },
  { id: 'y', name: 'Loose Y', variables: [cat] },
]
const groups = [
  { id: 'g1', label: 'One' },
  { id: 'g2', label: 'Two' },
]

beforeEach(() => resetDndKitMocks())
afterEach(() => clearAllLegendStores())

function renderSortable(id: string, sortable = true) {
  const tree = (
    <>
      {groups.map((g) => (
        <LegendGroup key={g.id} groupId={g.id}>
          {layers
            .filter((l) => l.groupId === g.id)
            .map((l) => (
              <LegendRow key={l.id} layerId={l.id}>
                <LegendItem />
              </LegendRow>
            ))}
        </LegendGroup>
      ))}
      {layers
        .filter((l) => !l.groupId)
        .map((l) => (
          <LegendRow key={l.id} layerId={l.id}>
            <LegendItem />
          </LegendRow>
        ))}
    </>
  )
  return render(
    <LegendProvider id={id} layers={layers} groups={groups}>
      {sortable ? <LegendSortable>{tree}</LegendSortable> : tree}
    </LegendProvider>,
  )
}

const dragEnd = (activeId: string, overId: string | null) =>
  lastDndContextProps().onDragEnd?.({
    active: { id: activeId },
    over: overId ? { id: overId } : null,
  })

describe('<LegendSortable> opt-in', () => {
  it('without the wrapper there are no drag handles', () => {
    renderSortable('plain', false)
    expect(screen.queryAllByLabelText('Reorder')).toHaveLength(0)
  })

  it('with the wrapper every group and row exposes its handle', () => {
    renderSortable('handles')
    // 2 groups + 5 rows.
    expect(screen.getAllByLabelText('Reorder')).toHaveLength(7)
  })
})

describe('<LegendSortable> onDragEnd commits', () => {
  it('group over group → moveGroup', () => {
    renderSortable('dnd-groups')
    dragEnd('group:g2', 'group:g1')
    expect(
      selectOrderedGroups(getLegendStore('dnd-groups').getState()).map(
        (g) => g.id,
      ),
    ).toEqual(['g2', 'g1'])
  })

  it('layer over layer in the same bucket → moveLayer', () => {
    renderSortable('dnd-rows')
    dragEnd('layer:b', 'layer:a')
    expect(
      selectLayersByGroup(getLegendStore('dnd-rows').getState(), 'g1').map(
        (l) => l.id,
      ),
    ).toEqual(['b', 'a'])

    // Ungrouped bucket too.
    dragEnd('layer:y', 'layer:x')
    expect(
      selectLayersByGroup(getLegendStore('dnd-rows').getState(), undefined).map(
        (l) => l.id,
      ),
    ).toEqual(['y', 'x'])
  })

  it('cross-kind and cross-bucket drops are ignored (snap back)', () => {
    renderSortable('dnd-guards')
    const before = getLegendStore('dnd-guards').getState().layers
    const beforeGroups = getLegendStore('dnd-guards').getState().groups

    dragEnd('group:g1', 'layer:x') // group over a row
    dragEnd('layer:a', 'group:g2') // row over a group
    dragEnd('layer:a', 'layer:c') // row over another group's row
    dragEnd('layer:a', 'layer:x') // grouped row over an ungrouped one
    dragEnd('layer:a', null) // dropped outside

    expect(getLegendStore('dnd-guards').getState().layers).toBe(before)
    expect(getLegendStore('dnd-guards').getState().groups).toBe(beforeGroups)
  })
})
