import assert from 'node:assert/strict'
import test from 'node:test'
import { audioCapture } from '../src/renderer/audio/AudioCapture'
import {
  loadAudioPreferences,
  normalizeAudioPreferences,
  startAudioDeviceWatcher,
  useAudioStore,
  type PersistedAudioState,
} from '../src/renderer/stores/audioStore'
import { useUiStore } from '../src/renderer/stores/uiStore'
import type { CaptureBackendSupport, CaptureSourceDescriptor } from '../src/types/capture'

type GlobalWithStorage = typeof globalThis & {
  localStorage?: Storage
}

type StartDeviceOptions = {
  forceDeviceRestart?: boolean
}

type HarnessSourceProvider<T> = T[] | (() => T[] | Promise<T[]>)

const DEFAULT_SYSTEM_SOURCE_ID = '__default_system_output__'

function audioPreferences(overrides: Partial<PersistedAudioState> = {}): PersistedAudioState {
  return {
    inputGainDb: 0,
    captureMode: 'system',
    selectedSystemSourceId: DEFAULT_SYSTEM_SOURCE_ID,
    selectedDeviceId: null,
    ...overrides,
  }
}

function storedAudioPreferences(overrides: Partial<PersistedAudioState> = {}): string {
  return JSON.stringify(audioPreferences(overrides))
}

const initialAudioState = {
  ...useAudioStore.getState(),
}

const initialUiState = {
  ...useUiStore.getState(),
}

function installFakeLocalStorage(): {
  getSetCount: () => number
  getItem: (key: string) => string | null
  restore: () => void
} {
  const storage = new Map<string, string>()
  let setCount = 0
  const globalWithStorage = globalThis as GlobalWithStorage
  const previousLocalStorage = globalWithStorage.localStorage

  globalWithStorage.localStorage = {
    getItem(key: string): string | null {
      return storage.get(key) ?? null
    },
    setItem(key: string, value: string): void {
      setCount += 1
      storage.set(key, value)
    },
    removeItem(key: string): void {
      storage.delete(key)
    },
    clear(): void {
      storage.clear()
    },
    key(index: number): string | null {
      return [...storage.keys()][index] ?? null
    },
    get length(): number {
      return storage.size
    },
  } as Storage

  return {
    getSetCount: () => setCount,
    getItem(key: string): string | null {
      return storage.get(key) ?? null
    },
    restore(): void {
      if (previousLocalStorage === undefined) {
        delete globalWithStorage.localStorage
        return
      }

      globalWithStorage.localStorage = previousLocalStorage
    },
  }
}

function createBackendSupport(available: boolean, reason: string | null): CaptureBackendSupport {
  return {
    nativeBackend: {
      kind: 'native-linux',
      available,
      reason,
    },
    deviceInput: {
      kind: 'device-input',
      available: true,
      reason: null,
    },
  }
}

function defaultSystemSource(): CaptureSourceDescriptor {
  return {
    id: DEFAULT_SYSTEM_SOURCE_ID,
    label: 'Default Output',
    kind: 'system',
    isDefault: true,
  }
}

function systemSource(
  id: string,
  label: string,
  isDefault = false,
): CaptureSourceDescriptor {
  return {
    id,
    label,
    kind: 'system',
    isDefault,
    sampleRate: 48000,
    channelCount: 2,
  }
}

function mediaDevice(deviceId: string, label: string, groupId = ''): MediaDeviceInfo {
  return {
    deviceId,
    groupId,
    kind: 'audioinput',
    label,
    toJSON() {
      return {}
    },
  } as MediaDeviceInfo
}

function getDefaultSystemSourceId(sources: CaptureSourceDescriptor[]): string | null {
  return sources.find((source) => source.id !== DEFAULT_SYSTEM_SOURCE_ID && source.isDefault)?.id ?? null
}

function getDefaultInputDeviceId(devices: MediaDeviceInfo[]): string | null {
  return devices.find((device) => device.deviceId !== 'default')?.deviceId ?? devices[0]?.deviceId ?? null
}

async function resolveHarnessSources<T>(provider: HarnessSourceProvider<T> | undefined, fallback: T[]): Promise<T[]> {
  if (!provider) {
    return fallback
  }

  return typeof provider === 'function'
    ? provider()
    : provider
}

function deferred<T>(): {
  promise: Promise<T>
  resolve: (value: T) => void
} {
  let resolve!: (value: T) => void
  const promise = new Promise<T>((nextResolve) => {
    resolve = nextResolve
  })
  return { promise, resolve }
}

async function flushPromises(): Promise<void> {
  await Promise.resolve()
  await Promise.resolve()
}

async function flushAsyncWork(): Promise<void> {
  await new Promise<void>((resolve) => {
    setTimeout(resolve, 0)
  })
  await flushPromises()
}

function installFakeDeviceWatcherEnvironment(): {
  dispatchDeviceChange: () => void
  getIntervalCount: () => number
  getListenerCount: () => number
  runIntervals: () => void
  restore: () => void
} {
  const globalWithWindow = globalThis as typeof globalThis & {
    window?: Window
    navigator?: Navigator
  }
  const previousWindowDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'window')
  const previousNavigatorDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'navigator')
  const intervals = new Map<number, () => void>()
  const deviceChangeListeners = new Set<() => void>()
  let nextIntervalId = 1

  const fakeWindow = {
    setInterval(callback: () => void): number {
      const id = nextIntervalId
      nextIntervalId += 1
      intervals.set(id, callback)
      return id
    },
    clearInterval(id: number): void {
      intervals.delete(id)
    },
  } as unknown as Window

  const fakeMediaDevices = {
    addEventListener(type: string, listener: EventListener): void {
      if (type === 'devicechange') {
        deviceChangeListeners.add(listener as () => void)
      }
    },
    removeEventListener(type: string, listener: EventListener): void {
      if (type === 'devicechange') {
        deviceChangeListeners.delete(listener as () => void)
      }
    },
    enumerateDevices: async () => [],
  } as unknown as MediaDevices

  const fakeNavigator = {
    mediaDevices: fakeMediaDevices,
  } as Navigator

  Object.defineProperty(globalThis, 'window', {
    configurable: true,
    value: fakeWindow,
  })
  Object.defineProperty(globalThis, 'navigator', {
    configurable: true,
    value: fakeNavigator,
  })

  return {
    dispatchDeviceChange(): void {
      for (const listener of deviceChangeListeners) {
        listener()
      }
    },
    getIntervalCount(): number {
      return intervals.size
    },
    getListenerCount(): number {
      return deviceChangeListeners.size
    },
    runIntervals(): void {
      for (const callback of [...intervals.values()]) {
        callback()
      }
    },
    restore(): void {
      if (previousWindowDescriptor) {
        Object.defineProperty(globalThis, 'window', previousWindowDescriptor)
      } else {
        delete globalWithWindow.window
      }

      if (previousNavigatorDescriptor) {
        Object.defineProperty(globalThis, 'navigator', previousNavigatorDescriptor)
      } else {
        delete globalWithWindow.navigator
      }
    },
  }
}

function resetStores(): void {
  audioCapture.setSelectedSystemSourceId(DEFAULT_SYSTEM_SOURCE_ID)
  audioCapture.setSelectedDeviceId(null)
  audioCapture.setCaptureMode('system')
  audioCapture.setInputGain(0)
  useAudioStore.setState({
    ...initialAudioState,
    systemSources: [],
    devices: [],
    selectedSystemSourceId: '__default_system_output__',
    selectedDeviceId: null,
    captureMode: 'system',
    activeBackendKind: null,
    backendSupport: null,
    isCapturing: false,
    captureStatus: 'idle',
    captureError: null,
    captureNotice: null,
    sampleRate: 48000,
    channelCount: 2,
    activeSourceId: null,
    activeSourceLabel: null,
    inputGainDb: 0,
  })

  useUiStore.setState({
    ...initialUiState,
    banner: null,
    settingsOpen: false,
  })
}

function installAudioCaptureHarness(options: {
  support: CaptureBackendSupport
  systemSources?: HarnessSourceProvider<CaptureSourceDescriptor>
  devices?: HarnessSourceProvider<MediaDeviceInfo>
  startSystemAudio?: (deviceId?: string) => Promise<void>
  startDevice?: (deviceId?: string, options?: StartDeviceOptions) => Promise<void>
}): {
  restore: () => void
  calls: {
    listDevices: number
    listSources: number
    startDevice: number
    startDeviceRequests: Array<{ deviceId: string | null; forceDeviceRestart: boolean }>
    startSystemAudio: number
    startSystemAudioDeviceIds: Array<string | undefined>
  }
} {
  const originalMethods = {
    refreshBackendSupport: audioCapture.refreshBackendSupport,
    listSources: audioCapture.listSources,
    listDevices: audioCapture.listDevices,
    startSystemAudio: audioCapture.startSystemAudio,
    startDevice: audioCapture.startDevice,
    getStatus: audioCapture.getStatus,
    setCaptureMode: audioCapture.setCaptureMode,
    setSelectedDeviceId: audioCapture.setSelectedDeviceId,
    setSelectedSystemSourceId: audioCapture.setSelectedSystemSourceId,
    setInputGain: audioCapture.setInputGain,
  }

  const calls = {
    listDevices: 0,
    listSources: 0,
    startSystemAudio: 0,
    startDevice: 0,
    startSystemAudioDeviceIds: [] as Array<string | undefined>,
    startDeviceRequests: [] as Array<{ deviceId: string | null; forceDeviceRestart: boolean }>,
  }

  let captureMode: 'system' | 'device' = 'system'
  let selectedDeviceId: string | null = null
  let selectedSystemSourceId = DEFAULT_SYSTEM_SOURCE_ID
  let activeBackendKind: 'device-input' | 'native-linux' | null = null
  let isCapturing = false
  let activeSourceId: string | null = null
  let activeSourceLabel: string | null = null

  audioCapture.refreshBackendSupport = async () => options.support
  audioCapture.listSources = async () => {
    calls.listSources += 1
    return resolveHarnessSources(options.systemSources, [defaultSystemSource()])
  }
  audioCapture.listDevices = async () => {
    calls.listDevices += 1
    return resolveHarnessSources(options.devices, [])
  }
  audioCapture.startSystemAudio = async (deviceId?: string) => {
    calls.startSystemAudio += 1
    calls.startSystemAudioDeviceIds.push(deviceId)
    if (options.startSystemAudio) {
      await options.startSystemAudio(deviceId)
    } else {
      const systemSources = await resolveHarnessSources(options.systemSources, [defaultSystemSource()])
      const requestedSourceId = deviceId && deviceId !== DEFAULT_SYSTEM_SOURCE_ID
        ? deviceId
        : getDefaultSystemSourceId(systemSources)
      const requestedSource = systemSources.find((source) => source.id === requestedSourceId) ?? null
      captureMode = 'system'
      activeBackendKind = 'native-linux'
      activeSourceId = requestedSource?.id ?? null
      activeSourceLabel = requestedSource?.label ?? null
      isCapturing = true
    }
  }
  audioCapture.startDevice = async (deviceId?: string, startOptions?: StartDeviceOptions) => {
    calls.startDevice += 1
    calls.startDeviceRequests.push({
      deviceId: deviceId ?? null,
      forceDeviceRestart: startOptions?.forceDeviceRestart === true,
    })
    if (options.startDevice) {
      await options.startDevice(deviceId, startOptions)
    } else {
      const devices = await resolveHarnessSources(options.devices, [])
      const resolvedDeviceId = deviceId ?? getDefaultInputDeviceId(devices)
      const resolvedDevice = devices.find((device) => device.deviceId === resolvedDeviceId) ?? null
      captureMode = 'device'
      selectedDeviceId = deviceId ?? null
      activeBackendKind = 'device-input'
      activeSourceId = resolvedDevice?.deviceId ?? resolvedDeviceId
      activeSourceLabel = resolvedDevice?.label ?? null
      isCapturing = true
    }
  }
  audioCapture.getStatus = () => ({
    captureMode,
    activeBackendKind,
    backendSupport: options.support,
    sampleRate: 48000,
    channelCount: 2,
    isCapturing,
    activeSourceId: isCapturing ? activeSourceId : null,
    activeSourceLabel: isCapturing ? activeSourceLabel : null,
  })
  audioCapture.setCaptureMode = (mode) => {
    captureMode = mode
  }
  audioCapture.setSelectedDeviceId = (id) => {
    selectedDeviceId = id
  }
  audioCapture.setSelectedSystemSourceId = (id) => {
    selectedSystemSourceId = id ?? DEFAULT_SYSTEM_SOURCE_ID
  }

  return {
    restore: () => {
      audioCapture.refreshBackendSupport = originalMethods.refreshBackendSupport
      audioCapture.listSources = originalMethods.listSources
      audioCapture.listDevices = originalMethods.listDevices
      audioCapture.startSystemAudio = originalMethods.startSystemAudio
      audioCapture.startDevice = originalMethods.startDevice
      audioCapture.getStatus = originalMethods.getStatus
      audioCapture.setCaptureMode = originalMethods.setCaptureMode
      audioCapture.setSelectedDeviceId = originalMethods.setSelectedDeviceId
      audioCapture.setSelectedSystemSourceId = originalMethods.setSelectedSystemSourceId
      audioCapture.setInputGain = originalMethods.setInputGain

      void selectedDeviceId
      void selectedSystemSourceId
    },
    calls,
  }
}

test('loadAudioPreferences falls back to 0 dB when storage is unavailable', () => {
  assert.deepEqual(loadAudioPreferences(null), audioPreferences())
})

test('loadAudioPreferences falls back to 0 dB when stored JSON is invalid', () => {
  const storage = {
    getItem: () => '{not valid json',
    setItem: () => {},
  }

  assert.deepEqual(loadAudioPreferences(storage), audioPreferences())
})

test('loadAudioPreferences keeps legacy trim-only preferences compatible', () => {
  const storage = {
    getItem: () => JSON.stringify({ inputGainDb: 6.26 }),
    setItem: () => {},
  }

  assert.deepEqual(loadAudioPreferences(storage), audioPreferences({ inputGainDb: 6.5 }))
})

test('normalizeAudioPreferences defaults invalid persisted selector values', () => {
  assert.deepEqual(normalizeAudioPreferences({
    inputGainDb: 3,
    captureMode: 'surround',
    selectedSystemSourceId: '',
    selectedDeviceId: '',
  }), audioPreferences({ inputGainDb: 3 }))
})

test('normalizeAudioPreferences preserves valid persisted selector values', () => {
  assert.deepEqual(normalizeAudioPreferences({
    inputGainDb: -3,
    captureMode: 'device',
    selectedSystemSourceId: 'speaker',
    selectedDeviceId: 'mic-1',
  }), audioPreferences({
    inputGainDb: -3,
    captureMode: 'device',
    selectedSystemSourceId: 'speaker',
    selectedDeviceId: 'mic-1',
  }))
})

test('normalizeAudioPreferences clamps out-of-range trim values', () => {
  assert.deepEqual(normalizeAudioPreferences({ inputGainDb: -18 }), audioPreferences({ inputGainDb: -12 }))
  assert.deepEqual(normalizeAudioPreferences({ inputGainDb: 18 }), audioPreferences({ inputGainDb: 12 }))
})

test('normalizeAudioPreferences rounds trim values to 0.5 dB steps', () => {
  assert.deepEqual(normalizeAudioPreferences({ inputGainDb: 6.24 }), audioPreferences({ inputGainDb: 6 }))
  assert.deepEqual(normalizeAudioPreferences({ inputGainDb: 6.26 }), audioPreferences({ inputGainDb: 6.5 }))
})

test('audio store persists normalized trim values and forwards them to audioCapture', () => {
  resetStores()
  const fakeStorage = installFakeLocalStorage()
  const originalSetInputGain = audioCapture.setInputGain
  const forwardedValues: number[] = []

  audioCapture.setInputGain = (db: number) => {
    forwardedValues.push(db)
  }

  try {
    useAudioStore.getState().setInputGain(6.26)

    assert.equal(useAudioStore.getState().inputGainDb, 6.5)
    assert.deepEqual(forwardedValues, [6.5])
    assert.equal(fakeStorage.getItem('prism:audio'), storedAudioPreferences({ inputGainDb: 6.5 }))
    assert.equal(fakeStorage.getSetCount(), 1)

    useAudioStore.getState().setInputGain(6.49)

    assert.deepEqual(forwardedValues, [6.5])
    assert.equal(fakeStorage.getSetCount(), 1)

    useAudioStore.getState().setInputGain(0)

    assert.equal(useAudioStore.getState().inputGainDb, 0)
    assert.deepEqual(forwardedValues, [6.5, 0])
    assert.equal(fakeStorage.getItem('prism:audio'), storedAudioPreferences({ inputGainDb: 0 }))
    assert.equal(fakeStorage.getSetCount(), 2)
  } finally {
    audioCapture.setInputGain = originalSetInputGain
    fakeStorage.restore()
    resetStores()
  }
})

test('audio store persists custom output source selections', async () => {
  resetStores()
  const fakeStorage = installFakeLocalStorage()

  try {
    await useAudioStore.getState().selectSystemSource('headphones')

    const state = useAudioStore.getState()
    assert.equal(state.captureMode, 'system')
    assert.equal(state.selectedSystemSourceId, 'headphones')
    assert.equal(fakeStorage.getItem('prism:audio'), storedAudioPreferences({
      captureMode: 'system',
      selectedSystemSourceId: 'headphones',
    }))
  } finally {
    fakeStorage.restore()
    resetStores()
  }
})

test('audio store persists input source selections', async () => {
  resetStores()
  const fakeStorage = installFakeLocalStorage()

  try {
    await useAudioStore.getState().selectDevice('mic-1')

    const state = useAudioStore.getState()
    assert.equal(state.captureMode, 'device')
    assert.equal(state.selectedDeviceId, 'mic-1')
    assert.equal(fakeStorage.getItem('prism:audio'), storedAudioPreferences({
      captureMode: 'device',
      selectedDeviceId: 'mic-1',
    }))
  } finally {
    fakeStorage.restore()
    resetStores()
  }
})

test('audio store auto-switches to device input when native system capture is unavailable on startup', async () => {
  resetStores()
  const fakeStorage = installFakeLocalStorage()
  const harness = installAudioCaptureHarness({
    support: createBackendSupport(false, 'PulseAudio is unavailable.'),
  })

  try {
    await useAudioStore.getState().startCapture()

    const state = useAudioStore.getState()
    assert.equal(harness.calls.startSystemAudio, 0)
    assert.equal(harness.calls.startDevice, 1)
    assert.equal(state.captureMode, 'device')
    assert.equal(state.captureStatus, 'capturing')
    assert.equal(state.captureError, null)
    assert.equal(state.activeBackendKind, 'device-input')
    assert.match(state.captureNotice ?? '', /switched to Default Input/i)
    assert.equal(fakeStorage.getItem('prism:audio'), storedAudioPreferences({
      captureMode: 'device',
      selectedDeviceId: null,
    }))

    const banner = useUiStore.getState().banner
    assert.ok(banner)
    assert.equal(banner?.tone, 'info')
    assert.match(banner?.message ?? '', /PulseAudio is unavailable/i)
  } finally {
    fakeStorage.restore()
    harness.restore()
    resetStores()
  }
})

test('audio store falls back to device input when native system capture fails at start time', async () => {
  resetStores()
  const fakeStorage = installFakeLocalStorage()
  const harness = installAudioCaptureHarness({
    support: createBackendSupport(true, null),
    startSystemAudio: async () => {
      throw new Error('PulseAudio monitor stream failed.')
    },
  })

  try {
    await useAudioStore.getState().startCapture()

    const state = useAudioStore.getState()
    assert.equal(harness.calls.startSystemAudio, 1)
    assert.equal(harness.calls.startDevice, 1)
    assert.equal(state.captureMode, 'device')
    assert.equal(state.captureStatus, 'capturing')
    assert.equal(state.captureError, null)
    assert.match(state.captureNotice ?? '', /PulseAudio monitor stream failed/i)
    assert.equal(fakeStorage.getItem('prism:audio'), storedAudioPreferences({
      captureMode: 'device',
      selectedDeviceId: null,
    }))

    const banner = useUiStore.getState().banner
    assert.ok(banner)
    assert.match(banner?.message ?? '', /PulseAudio monitor stream failed/i)
  } finally {
    fakeStorage.restore()
    harness.restore()
    resetStores()
  }
})

test('audio store does not restart capture when refreshed output devices are unchanged', async () => {
  resetStores()
  const support = createBackendSupport(true, null)
  const sources = [
    defaultSystemSource(),
    systemSource('speaker', 'Speakers', true),
  ]
  const harness = installAudioCaptureHarness({
    support,
    systemSources: sources,
  })

  try {
    useAudioStore.setState({
      backendSupport: support,
      systemSources: sources,
      selectedSystemSourceId: DEFAULT_SYSTEM_SOURCE_ID,
      captureMode: 'system',
      captureStatus: 'capturing',
      isCapturing: true,
      activeBackendKind: 'native-linux',
      activeSourceId: 'speaker',
      activeSourceLabel: 'Speakers',
    })

    await useAudioStore.getState().refreshSystemSources({ rebindActiveCapture: true })

    assert.equal(harness.calls.startSystemAudio, 0)
    assert.equal(useAudioStore.getState().activeSourceId, 'speaker')
  } finally {
    harness.restore()
    resetStores()
  }
})

test('audio store rebinds system capture when Default Output resolves to a new device', async () => {
  resetStores()
  const support = createBackendSupport(true, null)
  let sources = [
    defaultSystemSource(),
    systemSource('speaker', 'Speakers', true),
    systemSource('headphones', 'Headphones', false),
  ]
  const harness = installAudioCaptureHarness({
    support,
    systemSources: () => sources,
  })

  try {
    useAudioStore.setState({
      backendSupport: support,
      systemSources: sources,
      selectedSystemSourceId: DEFAULT_SYSTEM_SOURCE_ID,
      captureMode: 'system',
      captureStatus: 'capturing',
      isCapturing: true,
      activeBackendKind: 'native-linux',
      activeSourceId: 'speaker',
      activeSourceLabel: 'Speakers',
    })

    sources = [
      defaultSystemSource(),
      systemSource('speaker', 'Speakers', false),
      systemSource('headphones', 'Headphones', true),
    ]

    await useAudioStore.getState().refreshSystemSources({ rebindActiveCapture: true })

    assert.equal(harness.calls.startSystemAudio, 1)
    assert.deepEqual(harness.calls.startSystemAudioDeviceIds, [DEFAULT_SYSTEM_SOURCE_ID])
    assert.equal(useAudioStore.getState().activeSourceId, 'headphones')
    assert.equal(useAudioStore.getState().captureStatus, 'capturing')
  } finally {
    harness.restore()
    resetStores()
  }
})

test('audio store keeps explicit output selections pinned when the OS default changes', async () => {
  resetStores()
  const support = createBackendSupport(true, null)
  let sources = [
    defaultSystemSource(),
    systemSource('speaker', 'Speakers', true),
    systemSource('headphones', 'Headphones', false),
  ]
  const harness = installAudioCaptureHarness({
    support,
    systemSources: () => sources,
  })

  try {
    useAudioStore.setState({
      backendSupport: support,
      systemSources: sources,
      selectedSystemSourceId: 'headphones',
      captureMode: 'system',
      captureStatus: 'capturing',
      isCapturing: true,
      activeBackendKind: 'native-linux',
      activeSourceId: 'headphones',
      activeSourceLabel: 'Headphones',
    })

    sources = [
      defaultSystemSource(),
      systemSource('speaker', 'Speakers', false),
      systemSource('headphones', 'Headphones', false),
      systemSource('monitor', 'Monitor', true),
    ]

    await useAudioStore.getState().refreshSystemSources({ rebindActiveCapture: true })

    assert.equal(harness.calls.startSystemAudio, 0)
    assert.equal(useAudioStore.getState().selectedSystemSourceId, 'headphones')
    assert.equal(useAudioStore.getState().activeSourceId, 'headphones')
  } finally {
    harness.restore()
    resetStores()
  }
})

test('audio store falls back to Default Output when an explicit output disappears', async () => {
  resetStores()
  const fakeStorage = installFakeLocalStorage()
  const support = createBackendSupport(true, null)
  let sources = [
    defaultSystemSource(),
    systemSource('speaker', 'Speakers', false),
    systemSource('headphones', 'Headphones', true),
  ]
  const harness = installAudioCaptureHarness({
    support,
    systemSources: () => sources,
  })

  try {
    useAudioStore.setState({
      backendSupport: support,
      systemSources: sources,
      selectedSystemSourceId: 'speaker',
      captureMode: 'system',
      captureStatus: 'capturing',
      isCapturing: true,
      activeBackendKind: 'native-linux',
      activeSourceId: 'speaker',
      activeSourceLabel: 'Speakers',
    })

    sources = [
      defaultSystemSource(),
      systemSource('headphones', 'Headphones', true),
    ]

    await useAudioStore.getState().refreshSystemSources({ rebindActiveCapture: true })

    const state = useAudioStore.getState()
    assert.equal(harness.calls.startSystemAudio, 1)
    assert.equal(state.selectedSystemSourceId, DEFAULT_SYSTEM_SOURCE_ID)
    assert.equal(state.activeSourceId, 'headphones')
    assert.match(state.captureNotice ?? '', /Speakers is unavailable/i)
    assert.equal(fakeStorage.getItem('prism:audio'), storedAudioPreferences({
      captureMode: 'system',
      selectedSystemSourceId: DEFAULT_SYSTEM_SOURCE_ID,
    }))
  } finally {
    fakeStorage.restore()
    harness.restore()
    resetStores()
  }
})

test('audio store persists Default Input when an explicit input disappears', async () => {
  resetStores()
  const fakeStorage = installFakeLocalStorage()
  const support = createBackendSupport(true, null)
  let devices = [
    mediaDevice('mic-1', 'Mic 1', 'group-1'),
    mediaDevice('mic-2', 'Mic 2', 'group-2'),
  ]
  const harness = installAudioCaptureHarness({
    support,
    devices: () => devices,
  })

  try {
    useAudioStore.setState({
      backendSupport: support,
      devices,
      selectedDeviceId: 'mic-1',
      captureMode: 'device',
      captureStatus: 'capturing',
      isCapturing: true,
      activeBackendKind: 'device-input',
      activeSourceId: 'mic-1',
      activeSourceLabel: 'Mic 1',
    })

    devices = [
      mediaDevice('mic-2', 'Mic 2', 'group-2'),
    ]

    await useAudioStore.getState().refreshDevices({ rebindActiveCapture: true })

    const state = useAudioStore.getState()
    assert.equal(harness.calls.startDevice, 1)
    assert.deepEqual(harness.calls.startDeviceRequests, [{
      deviceId: null,
      forceDeviceRestart: true,
    }])
    assert.equal(state.selectedDeviceId, null)
    assert.equal(state.activeSourceId, 'mic-2')
    assert.match(state.captureNotice ?? '', /Mic 1 is unavailable/i)
    assert.equal(fakeStorage.getItem('prism:audio'), storedAudioPreferences({
      captureMode: 'device',
      selectedDeviceId: null,
    }))
  } finally {
    fakeStorage.restore()
    harness.restore()
    resetStores()
  }
})

test('audio store forces default input reacquisition when the default input signature changes', async () => {
  resetStores()
  const support = createBackendSupport(true, null)
  let devices = [
    mediaDevice('default', 'Default - Mic 1', 'default-group'),
    mediaDevice('mic-1', 'Mic 1', 'group-1'),
  ]
  const harness = installAudioCaptureHarness({
    support,
    devices: () => devices,
  })

  try {
    useAudioStore.setState({
      backendSupport: support,
      devices,
      selectedDeviceId: null,
      captureMode: 'device',
      captureStatus: 'capturing',
      isCapturing: true,
      activeBackendKind: 'device-input',
      activeSourceId: 'mic-1',
      activeSourceLabel: 'Mic 1',
    })

    devices = [
      mediaDevice('default', 'Default - Mic 2', 'default-group'),
      mediaDevice('mic-2', 'Mic 2', 'group-2'),
    ]

    await useAudioStore.getState().refreshDevices({ rebindActiveCapture: true })

    assert.equal(harness.calls.startDevice, 1)
    assert.deepEqual(harness.calls.startDeviceRequests, [{
      deviceId: null,
      forceDeviceRestart: true,
    }])
    assert.equal(useAudioStore.getState().activeSourceId, 'mic-2')
  } finally {
    harness.restore()
    resetStores()
  }
})

test('audio device watcher coalesces refreshes and cleans up timers and listeners', async () => {
  resetStores()
  const support = createBackendSupport(true, null)
  const pendingSources = deferred<CaptureSourceDescriptor[]>()
  const harness = installAudioCaptureHarness({
    support,
    systemSources: () => pendingSources.promise,
    devices: [],
  })
  const fakeEnvironment = installFakeDeviceWatcherEnvironment()

  try {
    const stopWatcher = startAudioDeviceWatcher()
    await flushPromises()

    assert.ok(fakeEnvironment.getIntervalCount() >= 1)
    assert.equal(fakeEnvironment.getListenerCount(), 1)
    assert.equal(harness.calls.listSources, 1)

    fakeEnvironment.runIntervals()
    fakeEnvironment.runIntervals()
    assert.equal(harness.calls.listSources, 1)

    pendingSources.resolve([defaultSystemSource()])
    await flushAsyncWork()
    fakeEnvironment.runIntervals()
    await flushAsyncWork()

    assert.equal(harness.calls.listSources, 2)

    stopWatcher()

    assert.equal(fakeEnvironment.getIntervalCount(), 0)
    assert.equal(fakeEnvironment.getListenerCount(), 0)
  } finally {
    fakeEnvironment.restore()
    harness.restore()
    resetStores()
  }
})
