import { describe, it, expect } from 'vitest'
import { relativeTime, truncateMiddle, debounce } from './utils'

describe('relativeTime', () => {
  it('returns "never" for null/undefined/empty', () => {
    expect(relativeTime(null)).toBe('never')
    expect(relativeTime(undefined)).toBe('never')
    expect(relativeTime('')).toBe('never')
  })

  it('returns "just now" for future timestamps', () => {
    const future = new Date(Date.now() + 10_000).toISOString()
    expect(relativeTime(future)).toBe('just now')
  })

  it('formats seconds/minutes/hours/days', () => {
    const now = Date.now()
    expect(relativeTime(new Date(now - 5_000).toISOString())).toBe('5s ago')
    expect(relativeTime(new Date(now - 120_000).toISOString())).toBe('2m ago')
    expect(relativeTime(new Date(now - 3_600_000).toISOString())).toBe('1h ago')
    expect(relativeTime(new Date(now - 86_400_000 * 2).toISOString())).toBe('2d ago')
  })
})

describe('truncateMiddle', () => {
  it('returns the string unchanged when at or under maxLen', () => {
    expect(truncateMiddle('short', 36)).toBe('short')
    expect(truncateMiddle('exactly36chars_exactly36chars_xxxxx', 36)).toBe(
      'exactly36chars_exactly36chars_xxxxx'
    )
  })

  it('truncates in the middle, preserving both ends', () => {
    const out = truncateMiddle('investigation:nomos/dragonfly-memlock-overrun', 20)
    expect(out).toContain('…')
    // keep = floor((20 - 1) / 2) = 9 chars from each end
    expect(out.startsWith('investiga')).toBe(true)
    expect(out.endsWith('-overrun')).toBe(true)
    expect(out.length).toBeLessThanOrEqual(20)
  })
})

describe('debounce', () => {
  it('collapses rapid calls into one trailing invocation', async () => {
    let calls = 0
    const fn = debounce(() => calls++, 20)
    fn()
    fn()
    fn()
    expect(calls).toBe(0)
    await new Promise((r) => setTimeout(r, 40))
    expect(calls).toBe(1)
  })
})
