import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'

import { createUndoableDeleteEngine } from '../undoable-delete-engine'

interface Item {
  id: string
  title: string
}

interface AgendaSnapshot {
  dayId: string
  index: number
  item: Item
}

describe('createUndoableDeleteEngine', () => {
  beforeEach(() => {
    vi.useFakeTimers()
  })
  afterEach(() => {
    vi.useRealTimers()
  })

  it('starts with state = null', () => {
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({ onUndo: () => {} })
    expect(engine.getState()).toBeNull()
  })

  it('captureDelete sets state to the captured snapshot', () => {
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({ onUndo: () => {} })
    const snapshot: AgendaSnapshot = { dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } }
    engine.capture(snapshot)
    expect(engine.getState()).toEqual(snapshot)
  })

  it('undo calls onUndo with the captured snapshot and clears state', () => {
    const onUndo = vi.fn()
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({ onUndo })
    const snapshot: AgendaSnapshot = { dayId: 'd1', index: 2, item: { id: 'a', title: 'A' } }
    engine.capture(snapshot)
    engine.undo()
    expect(onUndo).toHaveBeenCalledWith(snapshot)
    expect(engine.getState()).toBeNull()
  })

  it('undo is a no-op when nothing has been deleted', () => {
    const onUndo = vi.fn()
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({ onUndo })
    engine.undo()
    expect(onUndo).not.toHaveBeenCalled()
    expect(engine.getState()).toBeNull()
  })

  it('dismiss clears state without calling onUndo', () => {
    const onUndo = vi.fn()
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({ onUndo })
    engine.capture({ dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } })
    engine.dismiss()
    expect(engine.getState()).toBeNull()
    expect(onUndo).not.toHaveBeenCalled()
  })

  it('capture replaces any previous capture (single-slot semantics)', () => {
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({ onUndo: () => {} })
    const s1: AgendaSnapshot = { dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } }
    const s2: AgendaSnapshot = { dayId: 'd2', index: 5, item: { id: 'b', title: 'B' } }
    engine.capture(s1)
    engine.capture(s2)
    expect(engine.getState()).toEqual(s2)
  })

  it('notifies subscribers on every state change', () => {
    const onChange = vi.fn()
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({ onUndo: () => {}, onChange })
    engine.capture({ dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } })
    expect(onChange).toHaveBeenCalledTimes(1)
    engine.dismiss()
    expect(onChange).toHaveBeenCalledTimes(2)
  })

  it('autoDismissMs auto-clears state after the given delay', () => {
    const onChange = vi.fn()
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({
      onUndo: () => {},
      onChange,
      autoDismissMs: 5000,
    })
    engine.capture({ dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } })
    expect(engine.getState()).not.toBeNull()
    vi.advanceTimersByTime(4999)
    expect(engine.getState()).not.toBeNull()
    vi.advanceTimersByTime(1)
    expect(engine.getState()).toBeNull()
    // 1× capture-change + 1× auto-dismiss-change.
    expect(onChange).toHaveBeenCalledTimes(2)
  })

  it('capture restarts the auto-dismiss timer', () => {
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({
      onUndo: () => {},
      autoDismissMs: 5000,
    })
    engine.capture({ dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } })
    vi.advanceTimersByTime(4000)
    // Second capture starts a fresh 5000 ms window.
    engine.capture({ dayId: 'd2', index: 1, item: { id: 'b', title: 'B' } })
    vi.advanceTimersByTime(4999)
    expect(engine.getState()).not.toBeNull()
    vi.advanceTimersByTime(1)
    expect(engine.getState()).toBeNull()
  })

  it('undo cancels the pending auto-dismiss timer', () => {
    const onUndo = vi.fn()
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({
      onUndo,
      autoDismissMs: 5000,
    })
    engine.capture({ dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } })
    engine.undo()
    vi.advanceTimersByTime(10_000)
    expect(onUndo).toHaveBeenCalledTimes(1)
  })

  it('dismiss cancels the pending auto-dismiss timer', () => {
    const onChange = vi.fn()
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({
      onUndo: () => {},
      onChange,
      autoDismissMs: 5000,
    })
    engine.capture({ dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } })
    engine.dismiss()
    onChange.mockClear()
    vi.advanceTimersByTime(10_000)
    expect(onChange).not.toHaveBeenCalled()
  })

  it('autoDismissMs = 0 disables the auto-dismiss timer', () => {
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({
      onUndo: () => {},
      autoDismissMs: 0,
    })
    engine.capture({ dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } })
    vi.advanceTimersByTime(60_000)
    expect(engine.getState()).not.toBeNull()
  })

  it('teardown cancels any pending timer (used by React unmount)', () => {
    const onChange = vi.fn()
    const engine = createUndoableDeleteEngine<AgendaSnapshot>({
      onUndo: () => {},
      onChange,
      autoDismissMs: 5000,
    })
    engine.capture({ dayId: 'd1', index: 0, item: { id: 'a', title: 'A' } })
    engine.teardown()
    onChange.mockClear()
    vi.advanceTimersByTime(10_000)
    expect(onChange).not.toHaveBeenCalled()
  })
})
