import { createRef } from 'react'
import { act, fireEvent, render, screen } from '@testing-library/react'
import { LayerProvider } from '@astryxdesign/core'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { UndoToast, type UndoToastHandle } from './UndoToast'
import { flushToastExitAnimations } from './test-utils'

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

  function setup() {
    const handleRef = createRef<UndoToastHandle>()
    render(
      <LayerProvider>
        <UndoToast handleRef={handleRef} />
      </LayerProvider>,
    )
    return handleRef
  }

  it('dismisses a plain toast after 3.4 seconds', () => {
    const handleRef = setup()

    act(() => handleRef.current?.toast('Pause requested'))
    expect(screen.getByText('Pause requested')).toBeInTheDocument()

    act(() => vi.advanceTimersByTime(3_399))
    expect(screen.getByText('Pause requested')).toBeInTheDocument()
    act(() => vi.advanceTimersByTime(1))
    act(() => flushToastExitAnimations())
    expect(screen.queryByText('Pause requested')).not.toBeInTheDocument()
  })

  it('ticks the countdown and commits only on expiry', () => {
    const handleRef = setup()
    const onCommit = vi.fn()

    act(() =>
      handleRef.current?.toastUndo('Aborting t4 — nothing sent yet.', {
        seconds: 5,
        onUndo: vi.fn(),
        onCommit,
      }),
    )

    expect(screen.getByRole('button', { name: 'Undo (5)' })).toHaveFocus()
    act(() => vi.advanceTimersByTime(1_000))
    expect(screen.getByRole('button', { name: 'Undo (4)' })).toBeInTheDocument()
    expect(onCommit).not.toHaveBeenCalled()

    act(() => vi.advanceTimersByTime(4_000))
    act(() => flushToastExitAnimations())
    expect(onCommit).toHaveBeenCalledTimes(1)
    expect(document.querySelector('[data-toast-id]')).toBeNull()
  })

  it('dismisses and restores through undo without committing', () => {
    const handleRef = setup()
    const onUndo = vi.fn()
    const onCommit = vi.fn()

    act(() =>
      handleRef.current?.toastUndo('Aborting t4 — nothing sent yet.', {
        onUndo,
        onCommit,
      }),
    )
    fireEvent.click(screen.getByRole('button', { name: 'Undo (5)' }))
    act(() => flushToastExitAnimations())
    act(() => vi.advanceTimersByTime(5_000))

    expect(onUndo).toHaveBeenCalledTimes(1)
    expect(onCommit).not.toHaveBeenCalled()
    expect(screen.queryByText('Aborting t4 — nothing sent yet.')).not.toBeInTheDocument()
  })

  it('commits a pending action before replacing it', () => {
    const handleRef = setup()
    const firstCommit = vi.fn()

    act(() =>
      handleRef.current?.toastUndo('First action', {
        onUndo: vi.fn(),
        onCommit: firstCommit,
      }),
    )
    act(() =>
      handleRef.current?.toastUndo('Second action', {
        onUndo: vi.fn(),
        onCommit: vi.fn(),
      }),
    )

    expect(firstCommit).toHaveBeenCalledTimes(1)
    expect(screen.queryByText('First action')).not.toBeInTheDocument()
    expect(screen.getByText('Second action')).toBeInTheDocument()
  })

  it('enforces the five-second minimum', () => {
    const handleRef = setup()
    const onCommit = vi.fn()

    act(() =>
      handleRef.current?.toastUndo('Protected action', {
        seconds: 1,
        onUndo: vi.fn(),
        onCommit,
      }),
    )

    expect(screen.getByRole('button', { name: 'Undo (5)' })).toBeInTheDocument()
    act(() => vi.advanceTimersByTime(4_999))
    expect(onCommit).not.toHaveBeenCalled()
    act(() => vi.advanceTimersByTime(1))
    act(() => flushToastExitAnimations())
    expect(onCommit).toHaveBeenCalledTimes(1)
  })
})
