/** Measurable virtual-row grouping and durable identity contracts. */

import { describe, expect, it } from 'vitest'
import type { TrajectoryCellProps } from '../src/client/trajectory-record.ts'
import {
  groupTrajectoryVirtualRows, trajectoryVirtualRecordKey,
  type VirtualizableTrajectoryRecord,
} from '../src/client/trajectory-virtual-rows.ts'

function record(
  index: number,
  cell: Partial<TrajectoryCellProps> = {},
  collapsedSummaryKind?: 'turn' | 'assistant',
): VirtualizableTrajectoryRecord {
  return {
    cell: {
      index,
      kind: 'message',
      text: `record ${index}`,
      timeSeconds: 0,
      ...cell,
    },
    ...(collapsedSummaryKind === undefined ? {} : { collapsedSummaryKind }),
  }
}

describe('trajectory virtual rows', () => {
  it('groups zero-height request boundaries with the following content row', () => {
    const first = record(1, { requestOnly: true, sourceSeq: 10 })
    const second = record(2, { requestOnly: true, sourceSeq: 11 })
    const content = record(3, { sourceSeq: 12 })

    expect(groupTrajectoryVirtualRows([first, second, content])).toEqual([{
      entries: [
        { logicalIndex: 0, record: first },
        { logicalIndex: 1, record: second },
        { logicalIndex: 2, record: content },
      ],
      height: 30,
      key: trajectoryVirtualRecordKey(content),
    }])
  })

  it('retains terminal request-boundary clearance as a measurable row', () => {
    const content = record(1, { sourceSeq: 10 })
    const boundary = record(2, { requestOnly: true, sourceSeq: 11 })
    const rows = groupTrajectoryVirtualRows([content, boundary])

    expect(rows).toHaveLength(2)
    expect(rows[1]).toEqual({
      entries: [{ logicalIndex: 1, record: boundary }],
      height: 9,
      key: trajectoryVirtualRecordKey(boundary),
    })
  })

  it('uses the rendered collapsed-summary height', () => {
    const summary = record(1, { sourceSeq: 10 }, 'turn')

    expect(groupTrajectoryVirtualRows([summary])[0]?.height).toBe(20)
  })

  it('keeps an existing row key stable when older history is prepended', () => {
    const existing = record(2, { sourceSeq: 100 })
    const prepended = record(1, { sourceSeq: 10 })

    const before = groupTrajectoryVirtualRows([existing])[0]?.key
    const after = groupTrajectoryVirtualRows([prepended, existing])[1]?.key

    expect(after).toBe(before)
  })

  it('keeps the content key when a request boundary joins its row', () => {
    const content = record(2, { sourceSeq: 100 })
    const boundary = record(1, { requestOnly: true, sourceSeq: 99 })

    expect(groupTrajectoryVirtualRows([boundary, content])[0]?.key)
      .toBe(groupTrajectoryVirtualRows([content])[0]?.key)
  })

  it('distinguishes a folded summary from its source record', () => {
    const source = record(1, { sourceSeq: 10 })
    const summary = record(1, { sourceSeq: 10 }, 'assistant')

    expect(trajectoryVirtualRecordKey(summary)).not.toBe(trajectoryVirtualRecordKey(source))
  })

  it('exposes a DOM-safe semantic key', () => {
    const source = record(1, { callId: 'call with spaces/and?punctuation' })

    expect(trajectoryVirtualRecordKey(source)).toBe(
      'message%00call%00call%20with%20spaces%2Fand%3Fpunctuation',
    )
  })
})
