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

import {
  $perSessionBrowse,
  browseBackward,
  browseForward,
  deriveUserHistory,
  isBrowsingHistory,
  resetBrowseState
} from './composer-input-history'

const SESSION_A = 'session-a'
const SESSION_B = 'session-b'

// Newest-first user text ring, what the caller passes to browse*.
const HISTORY = ['third', 'second', 'first']

const MSG = (role: string, text: string) => ({ id: '', role, text })

beforeEach(() => {
  $perSessionBrowse.set({})
})

describe('deriveUserHistory', () => {
  it('returns user messages newest-first with empty/whitespace skipped', () => {
    const messages = [
      MSG('user', '   '),
      MSG('assistant', 'hi'),
      MSG('user', 'first'),
      MSG('user', 'second')
    ]

    expect(deriveUserHistory(messages, m => m.text)).toEqual(['second', 'first'])
  })
})

describe('browseBackward', () => {
  it('returns null when history is empty', () => {
    expect(browseBackward(SESSION_A, '', [])).toBeNull()
  })

  it('returns the most recent entry on first press and saves the draft', () => {
    const result = browseBackward(SESSION_A, 'unsent draft', HISTORY)

    expect(result).toBe('third')
    expect($perSessionBrowse.get()[SESSION_A]!.draftSnapshot).toBe('unsent draft')
  })

  it('moves to older entries on subsequent presses and stops at the oldest', () => {
    expect(browseBackward(SESSION_A, '', HISTORY)).toBe('third')
    expect(browseBackward(SESSION_A, '', HISTORY)).toBe('second')
    expect(browseBackward(SESSION_A, '', HISTORY)).toBe('first')
    expect(browseBackward(SESSION_A, '', HISTORY)).toBeNull()
  })

  it('uses caller-provided history, not a mirrored ring', () => {
    // The store never owns the ring — the caller passes it every press.
    // If the ring changes between presses (e.g. a new message was sent),
    // the next press sees the updated ring and the cursor continues
    // from where it was within it.
    expect(browseBackward(SESSION_A, '', ['youngest', 'older'])).toBe('youngest')

    // Caller added a new message; ring is now [brand-new, youngest, older].
    // Cursor was at 0, next press advances to 1 -> "youngest".
    expect(
      browseBackward(SESSION_A, '', ['brand-new', 'youngest', 'older'])
    ).toBe('youngest')

    // One more press -> "older".
    expect(
      browseBackward(SESSION_A, '', ['brand-new', 'youngest', 'older'])
    ).toBe('older')
  })
})

describe('browseForward', () => {
  it('returns null when not browsing', () => {
    expect(browseForward(SESSION_A, HISTORY)).toBeNull()
  })

  it('moves toward the present', () => {
    browseBackward(SESSION_A, 'draft', HISTORY) // cursor 0 -> 'third'
    browseBackward(SESSION_A, '', HISTORY) // cursor 1 -> 'second'

    expect(browseForward(SESSION_A, HISTORY)).toEqual({
      text: 'third',
      returnedToPresent: false
    })
  })

  it('restores the saved draft and resets when reaching the present', () => {
    browseBackward(SESSION_A, 'my original draft', HISTORY)

    const result = browseForward(SESSION_A, HISTORY)

    expect(result).toEqual({ text: 'my original draft', returnedToPresent: true })
    expect(isBrowsingHistory(SESSION_A)).toBe(false)
  })
})

describe('per-session isolation', () => {
  it('tracks cursor and draft independently per session', () => {
    browseBackward(SESSION_A, 'draft-a', HISTORY)
    browseBackward(SESSION_A, '', HISTORY) // older

    browseBackward(SESSION_B, 'draft-b', HISTORY)

    const a = $perSessionBrowse.get()[SESSION_A]!
    const b = $perSessionBrowse.get()[SESSION_B]!

    expect(a.cursor).toBe(1)
    expect(a.draftSnapshot).toBe('draft-a')
    expect(b.cursor).toBe(0)
    expect(b.draftSnapshot).toBe('draft-b')
  })
})

describe('resetBrowseState', () => {
  it('clears cursor and draft snapshot', () => {
    browseBackward(SESSION_A, 'draft', HISTORY)
    resetBrowseState(SESSION_A)

    const s = $perSessionBrowse.get()[SESSION_A]!

    expect(s.cursor).toBe(-1)
    expect(s.draftSnapshot).toBe('')
  })
})

describe('session switch behavior', () => {
  it('resets the previous session cursor and lets the new session derive its own ring', () => {
    // Session A: user browsed into the past
    browseBackward(SESSION_A, '', HISTORY)
    expect(isBrowsingHistory(SESSION_A)).toBe(true)

    // Caller switches to session B; resets A's browse state
    resetBrowseState(SESSION_A)

    // Session B's ring is derived from B's messages, not A's
    const sessionBMessages = [MSG('user', 'hello-b'), MSG('user', 'world-b')]
    const sessionBHistory = deriveUserHistory(sessionBMessages, m => m.text)

    expect(browseBackward(SESSION_B, '', sessionBHistory)).toBe('world-b')
    expect(browseBackward(SESSION_B, '', sessionBHistory)).toBe('hello-b')
    expect(isBrowsingHistory(SESSION_A)).toBe(false)
  })
})
