import { describe, expect, it, vi } from 'vitest'
import {
  MATH_BLOCK_TYPE,
  MATH_INLINE_TYPE,
  injectMathInBlocks,
  preProcessMathMarkdown,
  readCompletedInlineMathAtEnd,
  serializeMathAwareBlocks,
} from './mathMarkdown'

describe('math markdown round-trip', () => {
  it('does not treat financial prose between dollar amounts as inline math', () => {
    const markdown = 'FY2025 revenue grew 178.5% YoY to $24.59M, and gross margins improved dramatically from 63.0% to 82.6%. The company has a solid cash position ($884M) and a strategic focus.'

    expect(preProcessMathMarkdown({ markdown })).toBe(markdown)
  })

  it('injects inline math placeholders into BlockNote inline content', () => {
    const preprocessed = preProcessMathMarkdown({ markdown: 'Energy is $E=mc^2$ in prose.' })
    const blocks = [{
      type: 'paragraph',
      content: [{ type: 'text', text: preprocessed, styles: {} }],
      children: [],
    }]

    const [block] = injectMathInBlocks(blocks) as Array<{ content: unknown[] }>

    expect(block.content).toEqual([
      { type: 'text', text: 'Energy is ', styles: {} },
      { type: MATH_INLINE_TYPE, props: { latex: 'E=mc^2' }, content: undefined },
      { type: 'text', text: ' in prose.', styles: {} },
    ])
  })

  it('injects display math placeholders into dedicated math blocks', () => {
    const preprocessed = preProcessMathMarkdown({ markdown: '$$\n\\int_0^1 x\\,dx\n$$' })
    const blocks = [{
      type: 'paragraph',
      content: [{ type: 'text', text: preprocessed, styles: {} }],
      children: [],
    }]

    const [block] = injectMathInBlocks(blocks) as Array<{ type: string; props: { latex: string } }>

    expect(block.type).toBe(MATH_BLOCK_TYPE)
    expect(block.props.latex).toBe('\\int_0^1 x\\,dx')
  })

  it('preprocesses single-line display math without rewinding later lines', () => {
    const preprocessed = preProcessMathMarkdown({
      markdown: 'Intro\n\n$$x^2$$\n\nDone',
    })
    const blocks = preprocessed.split('\n\n').map((text) => ({
      type: 'paragraph',
      content: [{ type: 'text', text, styles: {} }],
      children: [],
    }))

    const [, block, afterBlock] = injectMathInBlocks(blocks) as Array<{
      type: string
      props?: { latex: string }
      content?: Array<{ text: string }>
    }>

    expect(block.type).toBe(MATH_BLOCK_TYPE)
    expect(block.props?.latex).toBe('x^2')
    expect(afterBlock.content?.[0]?.text).toBe('Done')
  })

  it('serializes math nodes back to Markdown-compatible source', () => {
    const editor = {
      blocksToMarkdownLossy: vi.fn((blocks: unknown[]) => {
        return (blocks as Array<{ content?: Array<{ text?: string }> }>)
          .map((block) => block.content?.map((item) => item.text ?? '').join('') ?? '')
          .join('\n\n')
      }),
    }
    const blocks = [
      {
        type: 'paragraph',
        content: [
          { type: 'text', text: 'Inline ', styles: {} },
          { type: MATH_INLINE_TYPE, props: { latex: 'a^2+b^2=c^2' } },
        ],
        children: [],
      },
      {
        type: MATH_BLOCK_TYPE,
        props: { latex: '\\frac{1}{2}' },
        children: [],
      },
      {
        type: 'paragraph',
        content: [{ type: 'text', text: 'Done', styles: {} }],
        children: [],
      },
    ]

    expect(serializeMathAwareBlocks(editor, blocks)).toBe(
      'Inline $a^2+b^2=c^2$\n\n$$\n\\frac{1}{2}\n$$\n\nDone',
    )
  })

  it('round-trips inline math inside table cells', () => {
    const tableCellMath = preProcessMathMarkdown({ markdown: '$a+b$' })
    const blocks = [{
      type: 'table',
      content: {
        type: 'tableContent',
        rows: [{
          cells: [{
            type: 'tableCell',
            content: [{ type: 'text', text: tableCellMath, styles: {} }],
          }],
        }],
      },
      children: [],
    }]

    const [tableBlock] = injectMathInBlocks(blocks) as Array<{
      content: { rows: Array<{ cells: Array<{ content: unknown[] }> }> }
    }>

    expect(tableBlock.content.rows[0].cells[0].content).toEqual([{
      type: MATH_INLINE_TYPE,
      props: { latex: 'a+b' },
      content: undefined,
    }])

    const editor = {
      blocksToMarkdownLossy: vi.fn(() => '| Formula |\n| --- |\n| $a+b$ |'),
    }

    expect(serializeMathAwareBlocks(editor, [tableBlock])).toBe('| Formula |\n| --- |\n| $a+b$ |')
    expect(editor.blocksToMarkdownLossy).toHaveBeenCalledWith([{
      type: 'table',
      content: {
        type: 'tableContent',
        rows: [{
          cells: [{
            type: 'tableCell',
            content: [{ type: 'text', text: '$a+b$' }],
          }],
        }],
      },
      children: [],
    }])
  })

  it('leaves display-style math inside table cells as Markdown source', () => {
    expect(preProcessMathMarkdown({ markdown: '| Formula |\n| --- |\n| $$c$$ |' })).toBe(
      '| Formula |\n| --- |\n| $$c$$ |',
    )
  })

  it('leaves inline code and fenced code math-looking text untouched', () => {
    const markdown = [
      'Keep `$not_math$` literal.',
      '',
      '```',
      '$$',
      'x^2',
      '$$',
      '```',
    ].join('\n')

    expect(preProcessMathMarkdown({ markdown })).toBe(markdown)
  })

  it('recognizes completed inline math at the end of text', () => {
    expect(readCompletedInlineMathAtEnd({ text: 'Energy is $E=mc^2$' })).toEqual({
      latex: 'E=mc^2',
      start: 10,
      end: 17,
    })
  })

  it('ignores incomplete, escaped, and display-style dollar sequences at text end', () => {
    expect(readCompletedInlineMathAtEnd({ text: 'Energy is $E=mc^2' })).toBeNull()
    expect(readCompletedInlineMathAtEnd({ text: String.raw`Energy is $E=mc^2\$` })).toBeNull()
    expect(readCompletedInlineMathAtEnd({ text: '$$x^2$$' })).toBeNull()
  })
})
