// @vitest-environment jsdom
/**
 * test/cell-monitor.test.ts — read-only monitor preview for a grid quadrant.
 *
 * Stubs xterm + preview-grid.fetchPreview so we can assert the poll→write loop and
 * the dispose teardown without a real terminal or server.
 */

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

const terms: FakeTerm[] = []
class FakeTerm {
  cols = 80
  rows = 24
  open = vi.fn()
  write = vi.fn((_data: string, cb?: () => void) => cb?.())
  reset = vi.fn()
  resize = vi.fn()
  dispose = vi.fn()
  constructor() {
    terms.push(this)
  }
}
vi.mock('@xterm/xterm', () => ({ Terminal: FakeTerm }))

const fetchPreview = vi.fn()
vi.mock('../public/preview-grid.js', () => ({
  fetchPreview,
  PREVIEW_CLEAR: '<CLEAR>',
  PREVIEW_THEME: {},
}))

const { mountCellMonitor } = await import('../public/cell-monitor.js')

beforeEach(() => {
  terms.length = 0
  fetchPreview.mockReset()
})
afterEach(() => vi.restoreAllMocks())

describe('cell-monitor', () => {
  it('opens a read-only term and writes the fetched preview into it', async () => {
    fetchPreview.mockResolvedValue({ id: 's1', cols: 100, rows: 30, data: 'HELLO' })
    const host = document.createElement('div')
    const m = mountCellMonitor(host, 's1')

    expect(terms).toHaveLength(1)
    expect(terms[0]!.open).toHaveBeenCalledWith(host)
    expect(fetchPreview).toHaveBeenCalledWith('s1')

    await vi.waitFor(() => expect(terms[0]!.write).toHaveBeenCalled())
    expect(terms[0]!.resize).toHaveBeenCalledWith(100, 30)
    expect(terms[0]!.write.mock.calls[0]![0]).toBe('<CLEAR>HELLO')
    m.dispose()
  })

  it('dispose() tears down the term', () => {
    fetchPreview.mockResolvedValue({ id: 's1', cols: 80, rows: 24, data: 'X' })
    const m = mountCellMonitor(document.createElement('div'), 's1')
    m.dispose()
    expect(terms[0]!.dispose).toHaveBeenCalled()
  })

  it('a failed preview fetch skips the write (best-effort)', async () => {
    fetchPreview.mockResolvedValue(null)
    const m = mountCellMonitor(document.createElement('div'), 's1')
    await Promise.resolve()
    await Promise.resolve()
    expect(terms[0]!.write).not.toHaveBeenCalled()
    m.dispose()
  })

  it('does not write after dispose (guards a late resolve)', async () => {
    let resolve!: (v: unknown) => void
    fetchPreview.mockReturnValue(new Promise((r) => (resolve = r)))
    const m = mountCellMonitor(document.createElement('div'), 's1')
    m.dispose() // dispose BEFORE the fetch resolves
    resolve({ id: 's1', cols: 80, rows: 24, data: 'LATE' })
    await Promise.resolve()
    await Promise.resolve()
    expect(terms[0]!.write).not.toHaveBeenCalled()
  })
})
