import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { ref, nextTick } from 'vue'
import { useDebouncedRef, useDebounce } from '@/composables/useDebounce'

describe('useDebounce', () => {
  beforeEach(() => {
    vi.useFakeTimers()
  })

  afterEach(() => {
    vi.useRealTimers()
  })

  describe('useDebouncedRef', () => {
    it('should debounce ref value changes', async () => {
      const sourceRef = ref('initial')
      const { debouncedValue } = useDebouncedRef(sourceRef, 300)

      expect(debouncedValue.value).toBe('initial')

      sourceRef.value = 'updated'
      await nextTick() // Allow Vue watcher to trigger
      expect(debouncedValue.value).toBe('initial') // Not yet updated

      vi.advanceTimersByTime(200)
      expect(debouncedValue.value).toBe('initial') // Still not updated

      vi.advanceTimersByTime(100)
      expect(debouncedValue.value).toBe('updated') // Now updated after 300ms
    })

    it('should cancel pending update when new value is set', async () => {
      const sourceRef = ref('initial')
      const { debouncedValue } = useDebouncedRef(sourceRef, 300)

      sourceRef.value = 'first'
      await nextTick()
      vi.advanceTimersByTime(200)

      sourceRef.value = 'second'
      await nextTick()
      vi.advanceTimersByTime(200)

      expect(debouncedValue.value).toBe('initial') // Neither update applied yet

      vi.advanceTimersByTime(100)
      expect(debouncedValue.value).toBe('second') // Only second value applied
    })

    it('should allow manual cancellation', async () => {
      const sourceRef = ref('initial')
      const { debouncedValue, cancel } = useDebouncedRef(sourceRef, 300)

      sourceRef.value = 'updated'
      await nextTick()
      vi.advanceTimersByTime(200)

      cancel()

      vi.advanceTimersByTime(200)
      expect(debouncedValue.value).toBe('initial') // Cancelled, no update
    })
  })

  describe('useDebounce', () => {
    it('should debounce function calls', () => {
      const callback = vi.fn()
      const { debouncedFn } = useDebounce(callback, 300)

      debouncedFn()
      expect(callback).not.toHaveBeenCalled()

      vi.advanceTimersByTime(300)
      expect(callback).toHaveBeenCalledTimes(1)
    })

    it('should pass arguments to callback', () => {
      const callback = vi.fn()
      const { debouncedFn } = useDebounce(callback, 300)

      debouncedFn('arg1', 'arg2')
      vi.advanceTimersByTime(300)

      expect(callback).toHaveBeenCalledWith('arg1', 'arg2')
    })

    it('should only call callback once for rapid calls', () => {
      const callback = vi.fn()
      const { debouncedFn } = useDebounce(callback, 300)

      debouncedFn()
      debouncedFn()
      debouncedFn()

      vi.advanceTimersByTime(300)
      expect(callback).toHaveBeenCalledTimes(1)
    })

    it('should allow manual cancellation', () => {
      const callback = vi.fn()
      const { debouncedFn, cancel } = useDebounce(callback, 300)

      debouncedFn()
      vi.advanceTimersByTime(200)
      cancel()
      vi.advanceTimersByTime(200)

      expect(callback).not.toHaveBeenCalled()
    })
  })
})
