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

// Module-level mutable state — reset to a fresh instance per test (L139 interlock).
type Lock = typeof import('../src/main/ytdlpLock')
let lock: Lock
beforeEach(async () => {
  vi.resetModules()
  lock = await import('../src/main/ytdlpLock')
})

describe('ytdlpLock (L139 spawn↔update interlock)', () => {
  it('starts idle: not updating, zero spawns, settle already resolved', async () => {
    expect(lock.isYtdlpUpdating()).toBe(false)
    expect(lock.liveSpawnCount()).toBe(0)
    await expect(lock.whenYtdlpUpdateSettled()).resolves.toBeUndefined()
  })

  it('acquire/release tracks the live count and clamps at zero', () => {
    lock.acquireSpawn()
    lock.acquireSpawn()
    expect(lock.liveSpawnCount()).toBe(2)
    lock.releaseSpawn()
    expect(lock.liveSpawnCount()).toBe(1)
    lock.releaseSpawn()
    lock.releaseSpawn() // extra release must not underflow
    expect(lock.liveSpawnCount()).toBe(0)
  })

  it('beginYtdlpUpdate refuses (and does NOT take the lock) while a spawn is live', () => {
    lock.acquireSpawn()
    expect(lock.beginYtdlpUpdate()).toBe(false)
    expect(lock.isYtdlpUpdating()).toBe(false)
    lock.releaseSpawn()
    expect(lock.beginYtdlpUpdate()).toBe(true)
    expect(lock.isYtdlpUpdating()).toBe(true)
    lock.endYtdlpUpdate()
    expect(lock.isYtdlpUpdating()).toBe(false)
  })

  it('holds a spawn until endYtdlpUpdate resolves whenYtdlpUpdateSettled', async () => {
    expect(lock.beginYtdlpUpdate()).toBe(true)
    let resolved = false
    const held = lock.whenYtdlpUpdateSettled().then(() => {
      resolved = true
    })
    await Promise.resolve() // flush a microtask tick — still pending mid-update
    expect(resolved).toBe(false)
    lock.endYtdlpUpdate()
    await held
    expect(resolved).toBe(true)
  })

  it('resolves immediately (no hold) when no update is in progress', async () => {
    let resolved = false
    await lock.whenYtdlpUpdateSettled().then(() => {
      resolved = true
    })
    expect(resolved).toBe(true)
  })

  it('refuses a CONCURRENT second update — the first end() must release only its own lock', async () => {
    expect(lock.beginYtdlpUpdate()).toBe(true)
    // A racing second begin (startup auto-update vs manual "Update now") must be
    // refused — accepting it would swap the settle promise, letting the first
    // update's end() release spawns held by the still-running second update.
    expect(lock.beginYtdlpUpdate()).toBe(false)
    expect(lock.isYtdlpUpdating()).toBe(true) // the refusal didn't clobber the lock
    let resolved = false
    const held = lock.whenYtdlpUpdateSettled().then(() => {
      resolved = true
    })
    await Promise.resolve()
    expect(resolved).toBe(false) // still held by the rightful owner
    lock.endYtdlpUpdate()
    await held
    expect(resolved).toBe(true)
    expect(lock.isYtdlpUpdating()).toBe(false)
  })

  it('a second update after one settles holds again on a fresh promise', async () => {
    lock.beginYtdlpUpdate()
    lock.endYtdlpUpdate()
    expect(lock.beginYtdlpUpdate()).toBe(true)
    let resolved = false
    const held = lock.whenYtdlpUpdateSettled().then(() => {
      resolved = true
    })
    await Promise.resolve()
    expect(resolved).toBe(false)
    lock.endYtdlpUpdate()
    await held
    expect(resolved).toBe(true)
  })
})
