import {describe, expect, test} from 'bun:test'

import {
  detectMentions,
  getMentionAt,
  insertMentionAt,
  resolveMentionFacets,
  segmentByFacets,
} from './mentions'

describe('detectMentions', () => {
  test('finds a mention at the start of the text', () => {
    expect(detectMentions('@sunny hi')).toEqual([
      {start: 0, end: 6, handle: 'sunny'},
    ])
  })

  test('finds mentions after whitespace and newlines', () => {
    expect(detectMentions('hi @sunny\n@bee-keeper')).toEqual([
      {start: 3, end: 9, handle: 'sunny'},
      {start: 10, end: 21, handle: 'bee-keeper'},
    ])
  })

  test('finds a mention after an opening paren', () => {
    expect(detectMentions('hello (@sunny)')).toEqual([
      {start: 7, end: 13, handle: 'sunny'},
    ])
  })

  test('ignores a mid-word @', () => {
    expect(detectMentions('mail me at hi@example')).toEqual([])
  })

  test('stops at punctuation', () => {
    expect(detectMentions('thanks @sunny!')).toEqual([
      {start: 7, end: 13, handle: 'sunny'},
    ])
  })

  test('uses utf-16 indices after astral chars', () => {
    const text = '🥐 @sunny'
    const [mention] = detectMentions(text)
    expect(mention).toEqual({start: 3, end: 9, handle: 'sunny'})
    expect(text.slice(mention!.start, mention!.end)).toBe('@sunny')
  })

  test('returns empty for text without mentions', () => {
    expect(detectMentions('no one here')).toEqual([])
  })
})

describe('getMentionAt', () => {
  test('returns the partial handle the cursor sits inside', () => {
    expect(getMentionAt('hi @sun', 7)).toEqual({
      value: 'sun',
      index: 3,
    })
  })

  test('matches with the cursor mid-token', () => {
    expect(getMentionAt('hi @sunny x', 6)).toEqual({
      value: 'sunny',
      index: 3,
    })
  })

  test('matches a bare @ with empty value', () => {
    expect(getMentionAt('hi @', 4)).toEqual({value: '', index: 3})
  })

  test('returns undefined outside a mention token', () => {
    expect(getMentionAt('hi @sunny x', 11)).toBeUndefined()
    expect(getMentionAt('plain text', 5)).toBeUndefined()
  })

  test('returns undefined for a mid-word @', () => {
    expect(getMentionAt('hi@sunny', 8)).toBeUndefined()
  })
})

describe('insertMentionAt', () => {
  test('replaces the partial token with the handle and a trailing space', () => {
    expect(insertMentionAt('hi @sun', 7, 'sunny')).toBe('hi @sunny ')
  })

  test('preserves text after the token', () => {
    expect(insertMentionAt('hi @sun bye', 7, 'sunny')).toBe(
      'hi @sunny bye',
    )
  })

  test('returns the text unchanged when no token is at the cursor', () => {
    expect(insertMentionAt('plain', 5, 'sunny')).toBe('plain')
  })
})

describe('resolveMentionFacets', () => {
  const members = [
    {id: 'id-sunny', handle: 'sunny'},
    {id: 'id-bee', handle: 'bee-keeper'},
  ]

  test('resolves member handles to facets', () => {
    expect(resolveMentionFacets('hi @sunny', members)).toEqual([
      {start: 3, end: 9, userId: 'id-sunny'},
    ])
  })

  test('matches case-insensitively', () => {
    expect(resolveMentionFacets('hi @SunNy', members)).toEqual([
      {start: 3, end: 9, userId: 'id-sunny'},
    ])
  })

  test('drops handles that are not members', () => {
    expect(resolveMentionFacets('hi @stranger', members)).toEqual([])
  })

  test('keeps duplicate mentions as separate facets', () => {
    expect(
      resolveMentionFacets('@sunny meet @sunny', members),
    ).toEqual([
      {start: 0, end: 6, userId: 'id-sunny'},
      {start: 12, end: 18, userId: 'id-sunny'},
    ])
  })
})

describe('segmentByFacets', () => {
  test('splits text into plain and faceted segments', () => {
    const facets = [{start: 3, end: 9, userId: 'id-sunny'}]
    expect(segmentByFacets('hi @sunny bye', facets)).toEqual([
      {text: 'hi '},
      {text: '@sunny', facet: facets[0]},
      {text: ' bye'},
    ])
  })

  test('handles facets at the start and end', () => {
    const facets = [
      {start: 0, end: 6, userId: 'a'},
      {start: 12, end: 16, userId: 'b'},
    ]
    expect(segmentByFacets('@sunny meet @bee', facets)).toEqual([
      {text: '@sunny', facet: facets[0]},
      {text: ' meet '},
      {text: '@bee', facet: facets[1]},
    ])
  })

  test('returns one plain segment when there are no facets', () => {
    expect(segmentByFacets('plain', [])).toEqual([{text: 'plain'}])
  })

  test('ignores out-of-range facets', () => {
    expect(
      segmentByFacets('hi', [{start: 5, end: 9, userId: 'a'}]),
    ).toEqual([{text: 'hi'}])
  })
})
