/**
 * test/desktop/notify-policy.test.ts — B2: shouldNotify decision rules.
 */

import { describe, test, expect } from 'vitest'
import { shouldNotify, 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-1234abcd-ef',
    claudeStatus: 'idle',
    pendingApproval: false,
    gate: null,
    title: undefined,
    ...overrides,
  }
}

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

describe('shouldNotify — focus suppression', () => {
  test('suppresses everything when the window is focused', () => {
    // Arrange
    const prev = snapshot()
    const next = snapshot({ pendingApproval: true, gate: 'plan', claudeStatus: 'waiting' })

    // Act
    const decision = shouldNotify(prev, next, { ...OPTS, windowFocused: true })

    // Assert
    expect(decision).toEqual({ notify: false, title: '', body: '' })
  })
})

describe('shouldNotify — approval rising edge', () => {
  test('notifies on the false → true approval edge', () => {
    const prev = snapshot({ pendingApproval: false })
    const next = snapshot({ pendingApproval: true, gate: 'tool' })

    const decision = shouldNotify(prev, next, OPTS)

    expect(decision.notify).toBe(true)
    expect(decision.title).toBe('Approval needed')
    expect(decision.body).toContain('tool approval')
    expect(decision.body).not.toContain('tool tool')
  })

  test('reads "tool approval" (never "tool tool") for a tool or null gate', () => {
    for (const gate of ['tool', null] as const) {
      const next = snapshot({ pendingApproval: true, gate })

      const decision = shouldNotify(undefined, next, OPTS)

      expect(decision.body).toContain('tool approval')
      expect(decision.body).not.toContain('tool tool')
    }
  })

  test('notifies when there is no previous snapshot and approval is pending', () => {
    const next = snapshot({ pendingApproval: true, gate: 'plan' })

    const decision = shouldNotify(undefined, next, OPTS)

    expect(decision.notify).toBe(true)
    expect(decision.body).toContain('plan approval')
  })

  test('does NOT notify when approval was already pending', () => {
    const prev = snapshot({ pendingApproval: true, gate: 'tool' })
    const next = snapshot({ pendingApproval: true, gate: 'tool' })

    const decision = shouldNotify(prev, next, OPTS)

    expect(decision.notify).toBe(false)
  })

  test('is gated off by notifyOnApproval=false', () => {
    const prev = snapshot({ pendingApproval: false })
    const next = snapshot({ pendingApproval: true, gate: 'tool' })

    const decision = shouldNotify(prev, next, { ...OPTS, notifyOnApproval: false })

    expect(decision.notify).toBe(false)
  })

  test('uses the session title in the body when known', () => {
    const next = snapshot({ pendingApproval: true, gate: 'plan', title: 'web-terminal' })

    const decision = shouldNotify(undefined, next, OPTS)

    expect(decision.body).toContain('web-terminal')
  })

  test('falls back to a short id label when title is undefined', () => {
    const next = snapshot({ pendingApproval: true, gate: 'tool', title: undefined })

    const decision = shouldNotify(undefined, next, OPTS)

    expect(decision.body).toContain('session sess-123')
  })
})

describe('shouldNotify — status change', () => {
  test('notifies on a transition to waiting', () => {
    const prev = snapshot({ claudeStatus: 'working' })
    const next = snapshot({ claudeStatus: 'waiting' })

    const decision = shouldNotify(prev, next, OPTS)

    expect(decision.notify).toBe(true)
    expect(decision.body).toContain('waiting')
  })

  test('notifies on a transition to stuck', () => {
    const prev = snapshot({ claudeStatus: 'working' })
    const next = snapshot({ claudeStatus: 'stuck' })

    const decision = shouldNotify(prev, next, OPTS)

    expect(decision.notify).toBe(true)
    expect(decision.title).toBe('Session may be stuck')
    expect(decision.body).toContain('stuck')
  })

  test('does NOT notify on a transition to working', () => {
    const prev = snapshot({ claudeStatus: 'idle' })
    const next = snapshot({ claudeStatus: 'working' })

    const decision = shouldNotify(prev, next, OPTS)

    expect(decision.notify).toBe(false)
  })

  test('does NOT notify on a transition to idle', () => {
    const prev = snapshot({ claudeStatus: 'working' })
    const next = snapshot({ claudeStatus: 'idle' })

    const decision = shouldNotify(prev, next, OPTS)

    expect(decision.notify).toBe(false)
  })

  test('does NOT notify when the status is unchanged', () => {
    const prev = snapshot({ claudeStatus: 'waiting' })
    const next = snapshot({ claudeStatus: 'waiting' })

    const decision = shouldNotify(prev, next, OPTS)

    expect(decision.notify).toBe(false)
  })

  test('is gated off by notifyOnStatusChange=false', () => {
    const prev = snapshot({ claudeStatus: 'working' })
    const next = snapshot({ claudeStatus: 'waiting' })

    const decision = shouldNotify(prev, next, { ...OPTS, notifyOnStatusChange: false })

    expect(decision.notify).toBe(false)
  })
})

describe('shouldNotify — priority', () => {
  test('approval edge takes priority over a concurrent status change', () => {
    // Arrange: both an approval rising edge AND a status change to waiting.
    const prev = snapshot({ pendingApproval: false, claudeStatus: 'working' })
    const next = snapshot({ pendingApproval: true, gate: 'plan', claudeStatus: 'waiting' })

    // Act
    const decision = shouldNotify(prev, next, OPTS)

    // Assert: it is the approval notification, not the status one.
    expect(decision.title).toBe('Approval needed')
  })

  test('returns a silent decision when nothing is notable', () => {
    const prev = snapshot({ claudeStatus: 'working' })
    const next = snapshot({ claudeStatus: 'unknown' })

    const decision = shouldNotify(prev, next, OPTS)

    expect(decision).toEqual({ notify: false, title: '', body: '' })
  })
})
