/**
 * test/desktop/notifications.test.ts — B2: computeNotifications batch diff.
 */

import { describe, test, expect } from 'vitest'
import { computeNotifications } from '../../desktop/src/notifications.js'
import type { NotifyOptions } from '../../desktop/src/notify-policy.js'
import type { SessionStatusSnapshot } from '../../desktop/src/types.js'

function snapshot(overrides: Partial<SessionStatusSnapshot> = {}): SessionStatusSnapshot {
  return {
    sessionId: 'sess-a',
    claudeStatus: 'idle',
    pendingApproval: false,
    gate: null,
    title: undefined,
    ...overrides,
  }
}

const OPTS: NotifyOptions = {
  windowFocused: false,
  notifyOnApproval: true,
  notifyOnStatusChange: true,
}

describe('computeNotifications', () => {
  test('collects only the notifying decisions', () => {
    // Arrange: sess-a raises an approval (notify), sess-b is unchanged (silent).
    const prev = new Map<string, SessionStatusSnapshot>([
      ['sess-a', snapshot({ sessionId: 'sess-a', pendingApproval: false })],
      ['sess-b', snapshot({ sessionId: 'sess-b', claudeStatus: 'working' })],
    ])
    const snapshots = [
      snapshot({ sessionId: 'sess-a', pendingApproval: true, gate: 'tool' }),
      snapshot({ sessionId: 'sess-b', claudeStatus: 'working' }),
    ]

    // Act
    const { decisions } = computeNotifications(prev, snapshots, OPTS)

    // Assert
    expect(decisions).toHaveLength(1)
    expect(decisions[0].title).toBe('Approval needed')
  })

  test('returns a new map keyed by sessionId', () => {
    const snapshots = [
      snapshot({ sessionId: 'sess-a' }),
      snapshot({ sessionId: 'sess-b' }),
    ]

    const { next } = computeNotifications(new Map(), snapshots, OPTS)

    expect(next).toBeInstanceOf(Map)
    expect([...next.keys()].sort()).toEqual(['sess-a', 'sess-b'])
    expect(next.get('sess-a')).toEqual(snapshots[0])
  })

  test('does not mutate the previous map', () => {
    // Arrange
    const prev = new Map<string, SessionStatusSnapshot>([
      ['sess-a', snapshot({ sessionId: 'sess-a', claudeStatus: 'working' })],
    ])
    const snapshots = [snapshot({ sessionId: 'sess-a', claudeStatus: 'waiting' })]

    // Act
    const { next } = computeNotifications(prev, snapshots, OPTS)

    // Assert: prev is untouched; next is a distinct object with the new value.
    expect(prev.size).toBe(1)
    expect(prev.get('sess-a')?.claudeStatus).toBe('working')
    expect(next).not.toBe(prev)
    expect(next.get('sess-a')?.claudeStatus).toBe('waiting')
  })

  test('drops sessions that ended (absent from snapshots) from next', () => {
    // Arrange: sess-b existed before but is gone this round.
    const prev = new Map<string, SessionStatusSnapshot>([
      ['sess-a', snapshot({ sessionId: 'sess-a' })],
      ['sess-b', snapshot({ sessionId: 'sess-b' })],
    ])
    const snapshots = [snapshot({ sessionId: 'sess-a' })]

    // Act
    const { next } = computeNotifications(prev, snapshots, OPTS)

    // Assert
    expect(next.has('sess-b')).toBe(false)
    expect(next.has('sess-a')).toBe(true)
  })

  test('handles multiple notifying sessions', () => {
    const snapshots = [
      snapshot({ sessionId: 'sess-a', pendingApproval: true, gate: 'plan' }),
      snapshot({ sessionId: 'sess-b', claudeStatus: 'stuck' }),
    ]

    const { decisions, next } = computeNotifications(new Map(), snapshots, OPTS)

    expect(decisions).toHaveLength(2)
    expect(next.size).toBe(2)
  })

  test('returns no decisions for an empty snapshot list', () => {
    const prev = new Map<string, SessionStatusSnapshot>([['sess-a', snapshot()]])

    const { decisions, next } = computeNotifications(prev, [], OPTS)

    expect(decisions).toEqual([])
    expect(next.size).toBe(0)
  })
})
