/**
 * Kanban upgrade tests
 * @countermeasure/web-components/visualization/kanban
 *
 * These tests cover the new features being added to the Kanban component.
 * They are expected to FAIL because the implementation does not exist yet.
 */

import { describe, it, expect, vi } from 'vitest'
import { createContainer } from '../../../../test/utils/dom'
import { Kanban } from '../index'
import type {
  KanbanConfig,
  KanbanColumn,
  KanbanCard,
  KanbanAssignee,
  KanbanSwimlane,
  KanbanAction,
} from '../types'

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function baseColumns(): KanbanColumn[] {
  return [
    {
      id: 'todo',
      title: 'To Do',
      cards: [
        { id: 'card-1', title: 'First card', description: 'A description' },
        { id: 'card-2', title: 'Second card', priority: 'high' },
      ],
    },
    {
      id: 'doing',
      title: 'In Progress',
      cards: [{ id: 'card-3', title: 'Third card' }],
    },
    {
      id: 'done',
      title: 'Done',
      cards: [],
    },
  ]
}

function createKanban(overrides: Partial<KanbanConfig> = {}): {
  kanban: Kanban
  container: HTMLElement
} {
  const container = createContainer()
  const kanban = new Kanban({
    container,
    columns: baseColumns(),
    ...overrides,
  })
  return { kanban, container }
}

function requirePresent<T>(value: T | null | undefined, message: string): T {
  if (value === null || value === undefined) {
    throw new Error(message)
  }
  return value
}

function toLocalDateString(date: Date): string {
  const year = String(date.getFullYear())
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}

// ---------------------------------------------------------------------------
// 1. Rich Card Fields
// ---------------------------------------------------------------------------

describe('Rich Card Fields', () => {
  describe('issue row', () => {
    it('renders issue key element', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', issueKey: 'PROJ-123' }],
          },
        ],
      })

      const issueRow = container.querySelector('.cmm-kanban__card-issue')
      expect(issueRow).not.toBeNull()

      const keyEl = container.querySelector('.cmm-kanban__issue-key')
      expect(keyEl).not.toBeNull()
      expect(keyEl!.textContent).toBe('PROJ-123')
    })

    it('renders issue type with icon', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [
              {
                id: 'c1',
                title: 'Card',
                issueType: 'bug',
                issueTypeIcon: '\uD83D\uDC1B',
              },
            ],
          },
        ],
      })

      const typeEl = container.querySelector('.cmm-kanban__issue-type')
      expect(typeEl).not.toBeNull()
      expect(typeEl?.textContent).toContain('\uD83D\uDC1B')
    })

    it('renders story points badge', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', storyPoints: 5 }],
          },
        ],
      })

      const spEl = container.querySelector('.cmm-kanban__story-points')
      expect(spEl).not.toBeNull()
      expect(spEl!.textContent).toContain('5')
    })

    it('renders rank badge', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', rank: 3 }],
          },
        ],
      })

      const rankEl = container.querySelector('.cmm-kanban__rank')
      expect(rankEl).not.toBeNull()
      expect(rankEl!.textContent).toContain('3')
    })
  })

  describe('context chips', () => {
    it('renders epic name as context chip', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', epicName: 'My Epic' }],
          },
        ],
      })

      const contextRow = container.querySelector('.cmm-kanban__card-context')
      expect(contextRow).not.toBeNull()

      const epicChip = container.querySelector('.cmm-kanban__context-chip--epic')
      expect(epicChip).not.toBeNull()
      expect(epicChip!.textContent).toContain('My Epic')
    })

    it('renders sprint name as context chip', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', sprintName: 'Sprint 7' }],
          },
        ],
      })

      const sprintChip = container.querySelector('.cmm-kanban__context-chip--sprint')
      expect(sprintChip).not.toBeNull()
      expect(sprintChip!.textContent).toContain('Sprint 7')
    })

    it('renders parent label as context chip', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', parentId: 'parent-1', parentLabel: 'Parent Story' }],
          },
        ],
      })

      const parentChip = container.querySelector('.cmm-kanban__context-chip--parent')
      expect(parentChip).not.toBeNull()
      expect(parentChip?.textContent).toContain('Parent Story')
    })

    it('does not render context row when no context fields are provided', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Plain card' }],
          },
        ],
      })

      const contextRow = container.querySelector('.cmm-kanban__card-context')
      expect(contextRow).toBeNull()
    })
  })

  describe('progress bar', () => {
    it('renders progress bar based on subtask completion', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', subtaskCount: 10, subtaskCompleted: 6 }],
          },
        ],
      })

      const progressBar = container.querySelector('.cmm-kanban__card-progress')
      expect(progressBar).not.toBeNull()

      const fill = container.querySelector('.cmm-kanban__card-progress-fill') as HTMLElement
      expect(fill).not.toBeNull()
      expect(fill.style.width).toBe('60%')
    })

    it('renders progress bar from explicit completionPercentage', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', completionPercentage: 75 }],
          },
        ],
      })

      const fill = container.querySelector('.cmm-kanban__card-progress-fill') as HTMLElement
      expect(fill).not.toBeNull()
      expect(fill.style.width).toBe('75%')
    })
  })

  describe('due date', () => {
    it('renders due date element', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', dueDate: '2026-12-31' }],
          },
        ],
      })

      const dueEl = container.querySelector('.cmm-kanban__card-due')
      expect(dueEl).not.toBeNull()
    })

    it('applies overdue modifier for past dates', () => {
      const pastDate = new Date()
      pastDate.setDate(pastDate.getDate() - 5)

      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', dueDate: toLocalDateString(pastDate) }],
          },
        ],
      })

      const dueEl = requirePresent(
        container.querySelector('.cmm-kanban__card-due'),
        'expected overdue due date element'
      )
      expect(dueEl.classList.contains('cmm-kanban__card-due--overdue')).toBe(true)
    })

    it('applies today modifier for current date', () => {
      const today = toLocalDateString(new Date())

      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', dueDate: today }],
          },
        ],
      })

      const dueEl = requirePresent(
        container.querySelector('.cmm-kanban__card-due'),
        'expected today due date element'
      )
      expect(dueEl.classList.contains('cmm-kanban__card-due--today')).toBe(true)
    })

    it('applies soon modifier for dates within 3 days', () => {
      const soon = new Date()
      soon.setDate(soon.getDate() + 2)

      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', dueDate: toLocalDateString(soon) }],
          },
        ],
      })

      const dueEl = requirePresent(
        container.querySelector('.cmm-kanban__card-due'),
        'expected soon due date element'
      )
      expect(dueEl.classList.contains('cmm-kanban__card-due--soon')).toBe(true)
    })

    it('accepts Date objects for dueDate', () => {
      const futureDate = new Date('2027-06-15')

      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', dueDate: futureDate }],
          },
        ],
      })

      const dueEl = container.querySelector('.cmm-kanban__card-due')
      expect(dueEl).not.toBeNull()
    })
  })

  describe('stats (comments, attachments)', () => {
    it('renders comment count stat', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', commentCount: 4 }],
          },
        ],
      })

      const stats = container.querySelectorAll('.cmm-kanban__card-stat')
      expect(stats.length).toBeGreaterThanOrEqual(1)

      const commentStat = Array.from(stats).find(s => s.textContent?.includes('4'))
      expect(commentStat).not.toBeUndefined()
    })

    it('renders attachment count stat', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', attachmentCount: 2 }],
          },
        ],
      })

      const stats = container.querySelectorAll('.cmm-kanban__card-stat')
      expect(stats.length).toBeGreaterThanOrEqual(1)

      const attachStat = Array.from(stats).find(s => s.textContent?.includes('2'))
      expect(attachStat).not.toBeUndefined()
    })

    it('does not render stats when counts are zero or undefined', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', commentCount: 0 }],
          },
        ],
      })

      const stats = container.querySelectorAll('.cmm-kanban__card-stat')
      expect(stats.length).toBe(0)
    })
  })

  describe('assignees array', () => {
    const threeAssignees: KanbanAssignee[] = [
      { id: 'u1', name: 'Alice' },
      { id: 'u2', name: 'Bob', avatarUrl: 'https://example.com/bob.png' },
      { id: 'u3', name: 'Charlie', kind: 'team' },
    ]

    it('renders assignee avatars', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', assignees: threeAssignees }],
          },
        ],
      })

      const assigneesEl = container.querySelector('.cmm-kanban__card-assignees')
      expect(assigneesEl).not.toBeNull()

      const avatars = container.querySelectorAll('.cmm-kanban__card-avatar')
      expect(avatars.length).toBe(3)
    })

    it('limits visible avatars to maxAssigneeAvatars with overflow indicator', () => {
      const fiveAssignees: KanbanAssignee[] = [
        { id: 'u1', name: 'Alice' },
        { id: 'u2', name: 'Bob' },
        { id: 'u3', name: 'Charlie' },
        { id: 'u4', name: 'Diana' },
        { id: 'u5', name: 'Eve' },
      ]

      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', assignees: fiveAssignees }],
          },
        ],
      })

      const avatars = container.querySelectorAll('.cmm-kanban__card-avatar')
      // default maxAssigneeAvatars is 3
      expect(avatars.length).toBe(3)

      // overflow element shows +2
      const overflow = container.querySelector('.cmm-kanban__card-assignees-overflow')
      expect(overflow).not.toBeNull()
      expect(overflow?.textContent).toBe('+2')
    })

    it('respects custom maxAssigneeAvatars config', () => {
      const fourAssignees: KanbanAssignee[] = [
        { id: 'u1', name: 'Alice' },
        { id: 'u2', name: 'Bob' },
        { id: 'u3', name: 'Charlie' },
        { id: 'u4', name: 'Diana' },
      ]

      const { container } = createKanban({
        maxAssigneeAvatars: 2,
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', assignees: fourAssignees }],
          },
        ],
      })

      const avatars = container.querySelectorAll('.cmm-kanban__card-avatar')
      expect(avatars.length).toBe(2)

      const overflow = container.querySelector('.cmm-kanban__card-assignees-overflow')
      expect(overflow).not.toBeNull()
      expect(overflow?.textContent).toBe('+2')
    })
  })
})

// ---------------------------------------------------------------------------
// 2. Column Enhancements
// ---------------------------------------------------------------------------

describe('Column Enhancements', () => {
  describe('column icon', () => {
    it('renders icon in column header', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'To Do',
            cards: [],
            icon: '<svg><circle r="5"/></svg>',
          },
        ],
      })

      const iconEl = container.querySelector('.cmm-kanban__column-icon')
      expect(iconEl).not.toBeNull()
      expect(iconEl?.innerHTML).toContain('<svg')
    })
  })

  describe('collapsed state', () => {
    it('adds collapsed class when column is collapsed', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Done',
            cards: [{ id: 'c1', title: 'Card' }],
            collapsible: true,
            collapsed: true,
          },
        ],
      })

      const colEl = container.querySelector('[data-column-id="col"]')
      expect(colEl).not.toBeNull()
      expect(colEl!.classList.contains('cmm-kanban__column--collapsed')).toBe(true)
    })

    it('hides card list when collapsed', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Done',
            cards: [{ id: 'c1', title: 'Card' }],
            collapsible: true,
            collapsed: true,
          },
        ],
      })

      const cardList = container.querySelector('.cmm-kanban__card-list')
      // Card list should be hidden (display:none or not rendered)
      expect(
        cardList === null ||
          (cardList as HTMLElement).style.display === 'none' ||
          (cardList as HTMLElement).hidden
      ).toBe(true)
    })

    it('renders collapse toggle button when collapsible is true', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Done',
            cards: [],
            collapsible: true,
          },
        ],
      })

      const collapseBtn = container.querySelector('.cmm-kanban__column-collapse')
      expect(collapseBtn).not.toBeNull()
    })

    it('toggles collapsed state on button click and invokes callback', () => {
      const onCollapse = vi.fn()
      const { container } = createKanban({
        onColumnCollapse: onCollapse,
        columns: [
          {
            id: 'col',
            title: 'Done',
            cards: [],
            collapsible: true,
            collapsed: false,
          },
        ],
      })

      const collapseBtn = container.querySelector('.cmm-kanban__column-collapse') as HTMLElement
      expect(collapseBtn).not.toBeNull()

      collapseBtn.click()

      expect(onCollapse).toHaveBeenCalledWith('col', true)

      const colEl = container.querySelector('[data-column-id="col"]')
      expect(colEl!.classList.contains('cmm-kanban__column--collapsed')).toBe(true)
    })

    it('does not render collapse button when collapsible is false or undefined', () => {
      const { container } = createKanban({
        columns: [{ id: 'col', title: 'Col', cards: [] }],
      })

      const collapseBtn = container.querySelector('.cmm-kanban__column-collapse')
      expect(collapseBtn).toBeNull()
    })
  })

  describe('empty state', () => {
    it('renders empty state message when column has no cards', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Backlog',
            cards: [],
            emptyMessage: 'No items in this column',
          },
        ],
      })

      const emptyState = container.querySelector('.cmm-kanban__empty-state')
      expect(emptyState).not.toBeNull()
      expect(emptyState!.textContent).toContain('No items in this column')
    })

    it('does not render empty state when column has cards', () => {
      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card' }],
            emptyMessage: 'Nothing here',
          },
        ],
      })

      const emptyState = container.querySelector('.cmm-kanban__empty-state')
      expect(emptyState).toBeNull()
    })
  })

  describe('add button', () => {
    it('renders add button when addButton is true', () => {
      const { container } = createKanban({
        columns: [{ id: 'col', title: 'To Do', cards: [], addButton: true }],
      })

      const addBtn = container.querySelector('.cmm-kanban__add-btn')
      expect(addBtn).not.toBeNull()
    })

    it('calls onAddCard callback when add button is clicked', () => {
      const onAdd = vi.fn()
      const { container } = createKanban({
        onAddCard: onAdd,
        columns: [{ id: 'backlog', title: 'Backlog', cards: [], addButton: true }],
      })

      const addBtn = container.querySelector('.cmm-kanban__add-btn') as HTMLElement
      expect(addBtn).not.toBeNull()

      addBtn.click()
      expect(onAdd).toHaveBeenCalledWith('backlog')
    })

    it('does not render add button when addButton is false or undefined', () => {
      const { container } = createKanban({
        columns: [{ id: 'col', title: 'Col', cards: [] }],
      })

      const addBtn = container.querySelector('.cmm-kanban__add-btn')
      expect(addBtn).toBeNull()
    })
  })
})

// ---------------------------------------------------------------------------
// 3. Swimlane Support
// ---------------------------------------------------------------------------

describe('Swimlane Support', () => {
  it('groups cards into swimlane sections', () => {
    const swimlaneGroups: KanbanSwimlane[] = [
      {
        id: 'high-pri',
        label: 'High Priority',
        filter: card => card.priority === 'high' || card.priority === 'critical',
      },
      {
        id: 'normal',
        label: 'Normal',
        filter: card => card.priority !== 'high' && card.priority !== 'critical',
      },
    ]

    const { container } = createKanban({
      swimlaneGroups,
      columns: [
        {
          id: 'todo',
          title: 'To Do',
          cards: [
            { id: 'c1', title: 'Urgent', priority: 'high' },
            { id: 'c2', title: 'Regular', priority: 'low' },
          ],
        },
      ],
    })

    const swimlanes = container.querySelectorAll('.cmm-kanban__swimlane')
    expect(swimlanes.length).toBe(2)
  })

  it('renders swimlane headers with title and count', () => {
    const swimlaneGroups: KanbanSwimlane[] = [
      { id: 'bugs', label: 'Bugs', filter: card => card.issueType === 'bug' },
      { id: 'other', label: 'Other', filter: card => card.issueType !== 'bug' },
    ]

    const { container } = createKanban({
      swimlaneGroups,
      columns: [
        {
          id: 'todo',
          title: 'To Do',
          cards: [
            { id: 'c1', title: 'Fix crash', issueType: 'bug' },
            { id: 'c2', title: 'Fix leak', issueType: 'bug' },
            { id: 'c3', title: 'Add feature', issueType: 'story' },
          ],
        },
      ],
    })

    const headers = container.querySelectorAll('.cmm-kanban__swimlane-header')
    expect(headers.length).toBe(2)

    // First swimlane (Bugs) should have count 2
    const bugsHeader = requirePresent(headers.item(0), 'expected bugs swimlane header')
    expect(bugsHeader.textContent).toContain('Bugs')
    expect(bugsHeader.textContent).toContain('2')
  })

  it('places cards only in matching swimlane', () => {
    const swimlaneGroups: KanbanSwimlane[] = [
      { id: 'assigned', label: 'Assigned', filter: card => card.assignee !== undefined },
      { id: 'unassigned', label: 'Unassigned', filter: card => card.assignee === undefined },
    ]

    const { container } = createKanban({
      swimlaneGroups,
      columns: [
        {
          id: 'todo',
          title: 'To Do',
          cards: [
            { id: 'c1', title: 'With owner', assignee: 'Alice' },
            { id: 'c2', title: 'No owner' },
          ],
        },
      ],
    })

    const swimlanes = container.querySelectorAll('.cmm-kanban__swimlane')
    expect(swimlanes.length).toBe(2)

    const assignedLane = requirePresent(swimlanes.item(0), 'expected assigned swimlane')
    const unassignedLane = requirePresent(swimlanes.item(1), 'expected unassigned swimlane')

    const assignedCards = assignedLane.querySelectorAll('.cmm-kanban__card')
    const unassignedCards = unassignedLane.querySelectorAll('.cmm-kanban__card')

    expect(assignedCards.length).toBe(1)
    expect(unassignedCards.length).toBe(1)
    expect(
      requirePresent(assignedCards.item(0), 'expected assigned swimlane card').getAttribute(
        'data-card-id'
      )
    ).toBe('c1')
    expect(
      requirePresent(unassignedCards.item(0), 'expected unassigned swimlane card').getAttribute(
        'data-card-id'
      )
    ).toBe('c2')
  })

  it('does not render swimlane elements when swimlaneGroups is undefined', () => {
    const { container } = createKanban()

    const swimlanes = container.querySelectorAll('.cmm-kanban__swimlane')
    expect(swimlanes.length).toBe(0)

    const swimlaneHeaders = container.querySelectorAll('.cmm-kanban__swimlane-header')
    expect(swimlaneHeaders.length).toBe(0)
  })
})

// ---------------------------------------------------------------------------
// 4. Quick Actions
// ---------------------------------------------------------------------------

describe('Quick Actions', () => {
  const cardWithActions: KanbanCard = { id: 'c1', title: 'Actionable card' }
  const testActions: KanbanAction[] = [
    { id: 'edit', label: 'Edit' },
    { id: 'delete', label: 'Delete', icon: '\uD83D\uDDD1\uFE0F' },
  ]

  it('renders action buttons on cards', () => {
    const { container } = createKanban({
      actions: testActions,
      columns: [{ id: 'col', title: 'Col', cards: [cardWithActions] }],
    })

    const actionsContainer = requirePresent(
      container.querySelector('.cmm-kanban__card-actions'),
      'expected card actions container'
    )
    const buttons = actionsContainer.querySelectorAll('button')
    expect(buttons.length).toBe(2)
  })

  it('only renders actions whose visible function returns true', () => {
    const conditionalActions: KanbanAction[] = [
      { id: 'edit', label: 'Edit', visible: () => true },
      { id: 'archive', label: 'Archive', visible: (_card, colId) => colId === 'done' },
    ]

    const { container } = createKanban({
      actions: conditionalActions,
      columns: [{ id: 'todo', title: 'To Do', cards: [cardWithActions] }],
    })

    const actionsContainer = requirePresent(
      container.querySelector('.cmm-kanban__card-actions'),
      'expected card actions container'
    )
    const buttons = actionsContainer.querySelectorAll('button')
    // Only 'edit' should be visible since card is in 'todo', not 'done'
    expect(buttons.length).toBe(1)
    expect(
      requirePresent(buttons.item(0), 'expected visible card action button').textContent
    ).toContain('Edit')
  })

  it('calls onCardAction with correct action id and card on click', () => {
    const onAction = vi.fn()
    const { container } = createKanban({
      actions: testActions,
      onCardAction: onAction,
      columns: [{ id: 'col', title: 'Col', cards: [cardWithActions] }],
    })

    const buttons = container.querySelectorAll('.cmm-kanban__card-actions button')
    expect(buttons.length).toBeGreaterThanOrEqual(1)

    // Click the first action (edit)
    ;(buttons[0] as HTMLElement).click()
    expect(onAction).toHaveBeenCalledWith('edit', expect.objectContaining({ id: 'c1' }))
  })

  it('does not render actions container when no actions are configured', () => {
    const { container } = createKanban()

    const actionsContainer = container.querySelector('.cmm-kanban__card-actions')
    expect(actionsContainer).toBeNull()
  })
})

// ---------------------------------------------------------------------------
// 5. Animations
// ---------------------------------------------------------------------------

describe('Animations', () => {
  it('applies entrance animation class with staggered delay when animations.entrance is true', () => {
    const { container } = createKanban({
      animations: { entrance: true },
      columns: [
        {
          id: 'col',
          title: 'Col',
          cards: [
            { id: 'c1', title: 'First' },
            { id: 'c2', title: 'Second' },
            { id: 'c3', title: 'Third' },
          ],
        },
      ],
    })

    const cards = container.querySelectorAll('.cmm-kanban__card')
    expect(cards.length).toBe(3)

    for (const card of cards) {
      expect(card.classList.contains('cmm-kanban__card--enter')).toBe(true)
    }

    // Check staggered animation-delay
    const delays = Array.from(cards).map(c => (c as HTMLElement).style.animationDelay)
    // Each card should have an increasing delay
    expect(delays[0]).not.toBe(delays[1])
    expect(delays[1]).not.toBe(delays[2])
  })

  it('applies entrance animation when animations is boolean true', () => {
    const { container } = createKanban({
      animations: true,
      columns: [
        {
          id: 'col',
          title: 'Col',
          cards: [{ id: 'c1', title: 'Card' }],
        },
      ],
    })

    const card = container.querySelector('.cmm-kanban__card')
    expect(card).not.toBeNull()
    expect(card?.classList.contains('cmm-kanban__card--enter')).toBe(true)
  })

  it('does not apply animation classes when animations is false', () => {
    const { container } = createKanban({
      animations: false,
      columns: [
        {
          id: 'col',
          title: 'Col',
          cards: [{ id: 'c1', title: 'Card' }],
        },
      ],
    })

    const card = container.querySelector('.cmm-kanban__card')
    expect(card).not.toBeNull()
    expect(card!.classList.contains('cmm-kanban__card--enter')).toBe(false)
  })

  it('defaults to animations enabled when not specified', () => {
    const { container } = createKanban({
      columns: [
        {
          id: 'col',
          title: 'Col',
          cards: [{ id: 'c1', title: 'Card' }],
        },
      ],
    })

    const card = container.querySelector('.cmm-kanban__card')
    expect(card).not.toBeNull()
    // By default animations should be enabled
    expect(card?.classList.contains('cmm-kanban__card--enter')).toBe(true)
  })
})

// ---------------------------------------------------------------------------
// 6. Density Mode
// ---------------------------------------------------------------------------

describe('Density Mode', () => {
  it('adds compact class when density is compact', () => {
    const { container } = createKanban({
      density: 'compact',
    })

    const wrapper = container.querySelector('.cmm-kanban')
    expect(wrapper).not.toBeNull()
    expect(wrapper?.classList.contains('cmm-kanban--compact')).toBe(true)
  })

  it('does not add compact class when density is spacious', () => {
    const { container } = createKanban({
      density: 'spacious',
    })

    const wrapper = container.querySelector('.cmm-kanban')
    expect(wrapper).not.toBeNull()
    expect(wrapper!.classList.contains('cmm-kanban--compact')).toBe(false)
  })

  it('defaults to spacious (no compact class) when density is not specified', () => {
    const { container } = createKanban()

    const wrapper = container.querySelector('.cmm-kanban')
    expect(wrapper).not.toBeNull()
    expect(wrapper!.classList.contains('cmm-kanban--compact')).toBe(false)
  })
})

// ---------------------------------------------------------------------------
// 7. Async Drag-and-Drop with Revert
// ---------------------------------------------------------------------------

describe('Async Drag-and-Drop with Revert', () => {
  it('reverts card to original column when onCardMove returns rejected promise', async () => {
    const onCardMove = vi.fn().mockRejectedValue(new Error('Move denied'))

    const container = createContainer()
    const kanban = new Kanban({
      container,
      onCardMove,
      columns: [
        { id: 'todo', title: 'To Do', cards: [{ id: 'c1', title: 'Card' }] },
        { id: 'done', title: 'Done', cards: [] },
      ],
    })

    // Programmatic move
    kanban.moveCard('c1', 'done', 0)

    // Wait for the async rejection to be processed
    await vi.waitFor(() => {
      const todoCards = container.querySelectorAll('[data-column-id="todo"] .cmm-kanban__card')
      expect(todoCards.length).toBe(1)
    })

    // Card should be back in the original column
    const todoCards = container.querySelectorAll('[data-column-id="todo"] .cmm-kanban__card')
    expect(todoCards.length).toBe(1)
    expect(
      requirePresent(todoCards.item(0), 'expected todo card after rollback').getAttribute(
        'data-card-id'
      )
    ).toBe('c1')

    const doneCards = container.querySelectorAll('[data-column-id="done"] .cmm-kanban__card')
    expect(doneCards.length).toBe(0)
  })

  it('keeps card in new column when onCardMove resolves successfully', async () => {
    const onCardMove = vi.fn().mockResolvedValue(undefined)

    const container = createContainer()
    const kanban = new Kanban({
      container,
      onCardMove,
      columns: [
        { id: 'todo', title: 'To Do', cards: [{ id: 'c1', title: 'Card' }] },
        { id: 'done', title: 'Done', cards: [] },
      ],
    })

    kanban.moveCard('c1', 'done', 0)

    await vi.waitFor(() => {
      const doneCards = container.querySelectorAll('[data-column-id="done"] .cmm-kanban__card')
      expect(doneCards.length).toBe(1)
    })

    const doneCards = container.querySelectorAll('[data-column-id="done"] .cmm-kanban__card')
    expect(doneCards.length).toBe(1)
    expect(
      requirePresent(doneCards.item(0), 'expected done card after move').getAttribute(
        'data-card-id'
      )
    ).toBe('c1')
  })

  it('applies landed animation class after successful move', async () => {
    const onCardMove = vi.fn().mockResolvedValue(undefined)

    const container = createContainer()
    const kanban = new Kanban({
      container,
      onCardMove,
      columns: [
        { id: 'todo', title: 'To Do', cards: [{ id: 'c1', title: 'Card' }] },
        { id: 'done', title: 'Done', cards: [] },
      ],
    })

    kanban.moveCard('c1', 'done', 0)

    await vi.waitFor(() => {
      const movedCard = container.querySelector('[data-card-id="c1"]')
      expect(movedCard).not.toBeNull()
      expect(movedCard!.classList.contains('cmm-kanban__card--landed')).toBe(true)
    })
  })
})

// ---------------------------------------------------------------------------
// 8. Backwards Compatibility
// ---------------------------------------------------------------------------

describe('Backwards Compatibility', () => {
  it('renders with minimal config (title only)', () => {
    const { container } = createKanban({
      columns: [
        {
          id: 'col',
          title: 'Simple',
          cards: [{ id: 'c1', title: 'Just a title' }],
        },
      ],
    })

    const card = container.querySelector('.cmm-kanban__card')
    expect(card).not.toBeNull()

    const title = container.querySelector('.cmm-kanban__card-title')
    expect(title).not.toBeNull()
    expect(title!.textContent).toBe('Just a title')
  })

  it('renders with all original fields (title, description, labels, assignee, priority)', () => {
    const { container } = createKanban({
      columns: [
        {
          id: 'col',
          title: 'Col',
          cards: [
            {
              id: 'c1',
              title: 'Full card',
              description: 'A description',
              labels: [{ text: 'Frontend', color: '#3B82F6' }],
              assignee: 'Alice',
              priority: 'high',
            },
          ],
        },
      ],
    })

    expect(container.querySelector('.cmm-kanban__card-title')?.textContent).toBe('Full card')
    expect(container.querySelector('.cmm-kanban__card-description')?.textContent).toBe(
      'A description'
    )
    expect(container.querySelector('.cmm-kanban__card-label')).not.toBeNull()
    expect(container.querySelector('.cmm-kanban__card-assignee')!.textContent).toBe('Alice')
    expect(
      container.querySelector('.cmm-kanban__card')?.classList.contains('cmm-kanban__card--high')
    ).toBe(true)
  })

  it('renders original assignee string when assignees array is not provided', () => {
    const { container } = createKanban({
      columns: [
        {
          id: 'col',
          title: 'Col',
          cards: [{ id: 'c1', title: 'Card', assignee: 'Bob' }],
        },
      ],
    })

    const assigneeEl = container.querySelector('.cmm-kanban__card-assignee')
    expect(assigneeEl).not.toBeNull()
    expect(assigneeEl!.textContent).toBe('Bob')
  })

  it('does not break when all new fields are undefined', () => {
    const { container } = createKanban({
      columns: [
        {
          id: 'col',
          title: 'Col',
          cards: [
            {
              id: 'c1',
              title: 'Plain card',
              // All new fields intentionally undefined
            },
          ],
        },
      ],
    })

    const card = container.querySelector('.cmm-kanban__card')
    expect(card).not.toBeNull()

    // No new elements should be rendered
    expect(container.querySelector('.cmm-kanban__card-issue')).toBeNull()
    expect(container.querySelector('.cmm-kanban__card-context')).toBeNull()
    expect(container.querySelector('.cmm-kanban__card-progress')).toBeNull()
    expect(container.querySelector('.cmm-kanban__card-due')).toBeNull()
    expect(container.querySelector('.cmm-kanban__card-stat')).toBeNull()
    expect(container.querySelector('.cmm-kanban__card-assignees')).toBeNull()
    expect(container.querySelector('.cmm-kanban__card-actions')).toBeNull()
  })

  it('prefers assignees array over assignee string when both provided', () => {
    const { container } = createKanban({
      columns: [
        {
          id: 'col',
          title: 'Col',
          cards: [
            {
              id: 'c1',
              title: 'Card',
              assignee: 'OldBob',
              assignees: [{ id: 'u1', name: 'NewAlice' }],
            },
          ],
        },
      ],
    })

    const assigneesEl = container.querySelector('.cmm-kanban__card-assignees')
    expect(assigneesEl).not.toBeNull()

    const avatars = container.querySelectorAll('.cmm-kanban__card-avatar')
    expect(avatars.length).toBe(1)

    // Old single assignee element should not be rendered when array is present
    const oldAssignee = container.querySelector('.cmm-kanban__card-assignee')
    expect(oldAssignee).toBeNull()
  })
})

// ---------------------------------------------------------------------------
// 9. New Callbacks
// ---------------------------------------------------------------------------

describe('New Callbacks', () => {
  it('onAddCard is called with column id when add button clicked', () => {
    const onAdd = vi.fn()
    const { container } = createKanban({
      onAddCard: onAdd,
      columns: [
        { id: 'backlog', title: 'Backlog', cards: [], addButton: true },
        { id: 'todo', title: 'To Do', cards: [], addButton: true },
      ],
    })

    const addButtons = container.querySelectorAll('.cmm-kanban__add-btn')
    expect(addButtons.length).toBe(2)
    ;(addButtons[1] as HTMLElement).click()
    expect(onAdd).toHaveBeenCalledWith('todo')
  })

  it('onColumnCollapse is called with column id and new collapsed state', () => {
    const onCollapse = vi.fn()
    const { container } = createKanban({
      onColumnCollapse: onCollapse,
      columns: [
        {
          id: 'done',
          title: 'Done',
          cards: [],
          collapsible: true,
          collapsed: false,
        },
      ],
    })

    const collapseBtn = container.querySelector('.cmm-kanban__column-collapse') as HTMLElement
    expect(collapseBtn).not.toBeNull()

    // First click: collapse
    collapseBtn.click()
    expect(onCollapse).toHaveBeenCalledWith('done', true)

    // Second click: expand
    collapseBtn.click()
    expect(onCollapse).toHaveBeenCalledWith('done', false)
  })
})

// ---------------------------------------------------------------------------
// 10. Config Options (max limits and truncation)
// ---------------------------------------------------------------------------

describe('Config Options', () => {
  describe('maxLabels', () => {
    it('limits visible labels to maxLabels with overflow indicator', () => {
      const manyLabels = [
        { text: 'A', color: '#f00' },
        { text: 'B', color: '#0f0' },
        { text: 'C', color: '#00f' },
        { text: 'D', color: '#ff0' },
        { text: 'E', color: '#0ff' },
      ]

      const { container } = createKanban({
        maxLabels: 3,
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', labels: manyLabels }],
          },
        ],
      })

      const labels = container.querySelectorAll('.cmm-kanban__card-label')
      expect(labels.length).toBe(3)

      const overflow = container.querySelector('.cmm-kanban__card-labels-overflow')
      expect(overflow).not.toBeNull()
      expect(overflow?.textContent).toBe('+2')
    })

    it('shows all labels when count does not exceed maxLabels', () => {
      const twoLabels = [
        { text: 'A', color: '#f00' },
        { text: 'B', color: '#0f0' },
      ]

      const { container } = createKanban({
        maxLabels: 3,
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', labels: twoLabels }],
          },
        ],
      })

      const labels = container.querySelectorAll('.cmm-kanban__card-label')
      expect(labels.length).toBe(2)

      const overflow = container.querySelector('.cmm-kanban__card-labels-overflow')
      expect(overflow).toBeNull()
    })

    it('defaults maxLabels to 3 when not specified', () => {
      const fiveLabels = [
        { text: 'A', color: '#f00' },
        { text: 'B', color: '#0f0' },
        { text: 'C', color: '#00f' },
        { text: 'D', color: '#ff0' },
        { text: 'E', color: '#0ff' },
      ]

      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', labels: fiveLabels }],
          },
        ],
      })

      const labels = container.querySelectorAll('.cmm-kanban__card-label')
      // Default maxLabels = 3
      expect(labels.length).toBe(3)

      const overflow = container.querySelector('.cmm-kanban__card-labels-overflow')
      expect(overflow).not.toBeNull()
      expect(overflow?.textContent).toBe('+2')
    })
  })

  describe('descriptionMaxLength', () => {
    it('truncates description to descriptionMaxLength with ellipsis', () => {
      const longDesc = 'A'.repeat(200)

      const { container } = createKanban({
        descriptionMaxLength: 50,
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', description: longDesc }],
          },
        ],
      })

      const descEl = requirePresent(
        container.querySelector('.cmm-kanban__card-description'),
        'expected card description'
      )
      expect(descEl.textContent?.length).toBeLessThanOrEqual(53) // 50 + "..."
      expect(descEl.textContent?.endsWith('...')).toBe(true)
    })

    it('does not truncate description shorter than descriptionMaxLength', () => {
      const shortDesc = 'Short text'

      const { container } = createKanban({
        descriptionMaxLength: 50,
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', description: shortDesc }],
          },
        ],
      })

      const descEl = container.querySelector('.cmm-kanban__card-description')
      expect(descEl).not.toBeNull()
      expect(descEl!.textContent).toBe('Short text')
    })

    it('defaults descriptionMaxLength to 100', () => {
      const desc = 'B'.repeat(150)

      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', description: desc }],
          },
        ],
      })

      const descEl = container.querySelector('.cmm-kanban__card-description')
      expect(descEl).not.toBeNull()
      // Default truncation at 100: the existing impl already does this
      expect(descEl!.textContent!.length).toBeLessThanOrEqual(103) // 100 + "..."
      expect(descEl?.textContent?.endsWith('...')).toBe(true)
    })
  })

  describe('maxAssigneeAvatars', () => {
    it('defaults maxAssigneeAvatars to 3', () => {
      const manyAssignees: KanbanAssignee[] = [
        { id: 'u1', name: 'A' },
        { id: 'u2', name: 'B' },
        { id: 'u3', name: 'C' },
        { id: 'u4', name: 'D' },
      ]

      const { container } = createKanban({
        columns: [
          {
            id: 'col',
            title: 'Col',
            cards: [{ id: 'c1', title: 'Card', assignees: manyAssignees }],
          },
        ],
      })

      const avatars = container.querySelectorAll('.cmm-kanban__card-avatar')
      expect(avatars.length).toBe(3)

      const overflow = container.querySelector('.cmm-kanban__card-assignees-overflow')
      expect(overflow).not.toBeNull()
      expect(overflow?.textContent).toBe('+1')
    })
  })
})
