/**
 * Unit tests for the pure window-state geometry helpers. These cover the logic
 * that protects the user: garbage rejection, off-screen fallback, oversized
 * clamping, and the debounce that collapses mid-drag write storms.
 */

const test = require('node:test')
const assert = require('node:assert/strict')

const {
  DEFAULT_WIDTH,
  DEFAULT_HEIGHT,
  MIN_WIDTH,
  MIN_HEIGHT,
  sanitizeWindowState,
  onScreen,
  computeWindowOptions,
  debounce
} = require('./window-state.cjs')

// A single 1920×1080 monitor (work area trimmed for the taskbar).
const PRIMARY = [{ workArea: { x: 0, y: 0, width: 1920, height: 1040 } }]
// A laptop panel left behind after a bigger external monitor is unplugged.
const LAPTOP = [{ workArea: { x: 0, y: 0, width: 1366, height: 728 } }]

// ─── sanitizeWindowState ───────────────────────────────────────────────────

test('sanitizeWindowState rejects missing/garbage input', () => {
  for (const bad of [null, undefined, 'nope', 42, {}, { width: 'x', height: 800 }, { width: NaN, height: 800 }, { width: 1000 }]) {
    assert.equal(sanitizeWindowState(bad), null)
  }
})

test('sanitizeWindowState keeps a valid full state and rounds HiDPI fractions', () => {
  assert.deepEqual(sanitizeWindowState({ x: 100.6, y: 50.2, width: 1400.4, height: 900.7, isMaximized: true }), {
    x: 101,
    y: 50,
    width: 1400,
    height: 901,
    isMaximized: true
  })
})

test('sanitizeWindowState floors size to the minimums', () => {
  const state = sanitizeWindowState({ width: 10, height: 10 })
  assert.equal(state.width, MIN_WIDTH)
  assert.equal(state.height, MIN_HEIGHT)
})

test('sanitizeWindowState drops a partial position but keeps the size', () => {
  assert.deepEqual(sanitizeWindowState({ x: 100, width: 1400, height: 900 }), {
    width: 1400,
    height: 900,
    isMaximized: false
  })
})

test('sanitizeWindowState treats isMaximized strictly', () => {
  assert.equal(sanitizeWindowState({ width: 1400, height: 900, isMaximized: 'yes' }).isMaximized, false)
})

// ─── onScreen ──────────────────────────────────────────────────────────────

test('onScreen accepts a window on the primary or a secondary display', () => {
  const dual = [...PRIMARY, { workArea: { x: 1920, y: 0, width: 2560, height: 1400 } }]
  assert.equal(onScreen({ x: 100, y: 100, width: 1220, height: 800 }, PRIMARY), true)
  assert.equal(onScreen({ x: 2200, y: 200, width: 1220, height: 800 }, dual), true)
})

test('onScreen rejects off-screen, slivers, and bad input', () => {
  assert.equal(onScreen({ x: 3000, y: 100, width: 1220, height: 800 }, PRIMARY), false) // past right edge
  assert.equal(onScreen({ x: 100, y: -900, width: 1220, height: 800 }, PRIMARY), false) // above top
  assert.equal(onScreen({ x: 1910, y: 100, width: 1220, height: 800 }, PRIMARY), false) // ~10px sliver
  assert.equal(onScreen({ x: 0, y: 0, width: 1220, height: 800 }, []), false)
  assert.equal(onScreen({ x: 0, y: 0, width: 1220, height: 800 }, null), false)
})

// ─── computeWindowOptions ──────────────────────────────────────────────────

test('computeWindowOptions falls back to defaults with no saved state', () => {
  assert.deepEqual(computeWindowOptions(null, PRIMARY), { width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT })
})

test('computeWindowOptions restores an on-screen position', () => {
  const saved = sanitizeWindowState({ x: 200, y: 150, width: 1400, height: 900 })
  assert.deepEqual(computeWindowOptions(saved, PRIMARY), { width: 1400, height: 900, x: 200, y: 150 })
})

test('computeWindowOptions keeps the size but drops an off-screen position', () => {
  const saved = sanitizeWindowState({ x: 5000, y: 150, width: 1400, height: 900 })
  assert.deepEqual(computeWindowOptions(saved, PRIMARY), { width: 1400, height: 900 })
})

test('computeWindowOptions clamps a size larger than the only display', () => {
  const saved = sanitizeWindowState({ width: 2560, height: 1440 })
  assert.deepEqual(computeWindowOptions(saved, LAPTOP), { width: 1366, height: 728 })
})

test('computeWindowOptions keeps the MIN floor on a sub-minimum display', () => {
  const tiny = [{ workArea: { x: 0, y: 0, width: 360, height: 480 } }]
  const saved = sanitizeWindowState({ width: 2000, height: 1500 })
  assert.deepEqual(computeWindowOptions(saved, tiny), { width: MIN_WIDTH, height: MIN_HEIGHT })
})

test('computeWindowOptions does not clamp when displays are unknown', () => {
  const saved = sanitizeWindowState({ width: 2560, height: 1440 })
  assert.deepEqual(computeWindowOptions(saved, []), { width: 2560, height: 1440 })
})

// ─── debounce ──────────────────────────────────────────────────────────────

test('debounce coalesces a burst into one trailing run', t => {
  t.mock.timers.enable({ apis: ['setTimeout'] })
  let calls = 0
  const d = debounce(() => { calls += 1 }, 250)

  d(); d(); d()
  assert.equal(calls, 0)
  t.mock.timers.tick(249)
  assert.equal(calls, 0)
  t.mock.timers.tick(1)
  assert.equal(calls, 1)
})

test('debounce.flush runs now and cancels the pending timer', t => {
  t.mock.timers.enable({ apis: ['setTimeout'] })
  let calls = 0
  const d = debounce(() => { calls += 1 }, 250)

  d()
  d.flush()
  assert.equal(calls, 1)
  t.mock.timers.tick(1000)
  assert.equal(calls, 1)
})
