import { act, renderHook } from "@testing-library/react"
import { describe, expect, it, vi } from "vitest"

import { useDebounce } from "@/hooks/use-debounce"

describe("useDebounce", () => {
  it("returns initial value immediately", () => {
    const { result } = renderHook(() => useDebounce("hello", 300))
    expect(result.current).toBe("hello")
  })

  it("does not update value before delay", () => {
    vi.useFakeTimers()

    const { result, rerender } = renderHook(
      ({ value, delay }) => useDebounce(value, delay),
      { initialProps: { value: "initial", delay: 300 } }
    )

    rerender({ value: "updated", delay: 300 })

    // Value should still be initial (timer hasn't fired)
    expect(result.current).toBe("initial")

    vi.useRealTimers()
  })

  it("updates value after delay", () => {
    vi.useFakeTimers()

    const { result, rerender } = renderHook(
      ({ value, delay }) => useDebounce(value, delay),
      { initialProps: { value: "initial", delay: 300 } }
    )

    rerender({ value: "updated", delay: 300 })

    act(() => {
      vi.advanceTimersByTime(300)
    })

    expect(result.current).toBe("updated")

    vi.useRealTimers()
  })

  it("resets timer on new value before delay expires", () => {
    vi.useFakeTimers()

    const { result, rerender } = renderHook(
      ({ value, delay }) => useDebounce(value, delay),
      { initialProps: { value: "first", delay: 300 } }
    )

    rerender({ value: "second", delay: 300 })

    act(() => {
      vi.advanceTimersByTime(200)
    })

    // Change again before first timer fires
    rerender({ value: "third", delay: 300 })

    act(() => {
      vi.advanceTimersByTime(200)
    })

    // Still not updated because timer was reset
    expect(result.current).toBe("first")

    act(() => {
      vi.advanceTimersByTime(100)
    })

    // Now it should be the latest value
    expect(result.current).toBe("third")

    vi.useRealTimers()
  })

  it("uses custom delay when provided", () => {
    vi.useFakeTimers()

    const { result, rerender } = renderHook(
      ({ value, delay }) => useDebounce(value, delay),
      { initialProps: { value: "initial", delay: 500 } }
    )

    rerender({ value: "updated", delay: 500 })

    act(() => {
      vi.advanceTimersByTime(300)
    })

    // 300ms < 500ms delay — should still be initial
    expect(result.current).toBe("initial")

    act(() => {
      vi.advanceTimersByTime(200)
    })

    // Now past 500ms — should update
    expect(result.current).toBe("updated")

    vi.useRealTimers()
  })
})
