import { describe, expect, it } from 'vitest'
import { parseInline, parseMarkdown, toggleCheckbox } from './markdown-parser.js'

describe('parseInline', () => {
  it('should parse plain text', () => {
    const result = parseInline('hello world')
    expect(result).toEqual([{ type: 'text', content: 'hello world' }])
  })

  it('should parse inline code', () => {
    const result = parseInline('use `const x = 1` here')
    expect(result).toEqual([
      { type: 'text', content: 'use ' },
      { type: 'code', content: 'const x = 1' },
      { type: 'text', content: ' here' },
    ])
  })

  it('should parse bold with **', () => {
    const result = parseInline('this is **bold** text')
    expect(result).toEqual([
      { type: 'text', content: 'this is ' },
      { type: 'bold', children: [{ type: 'text', content: 'bold' }] },
      { type: 'text', content: ' text' },
    ])
  })

  it('should parse italic with *', () => {
    const result = parseInline('this is *italic* text')
    expect(result).toEqual([
      { type: 'text', content: 'this is ' },
      { type: 'italic', children: [{ type: 'text', content: 'italic' }] },
      { type: 'text', content: ' text' },
    ])
  })

  it('should parse bold+italic with ***', () => {
    const result = parseInline('this is ***bold italic*** text')
    expect(result).toEqual([
      { type: 'text', content: 'this is ' },
      { type: 'bold', children: [{ type: 'italic', children: [{ type: 'text', content: 'bold italic' }] }] },
      { type: 'text', content: ' text' },
    ])
  })

  it('should parse links', () => {
    const result = parseInline('click [here](https://example.com) now')
    expect(result).toEqual([
      { type: 'text', content: 'click ' },
      { type: 'link', href: 'https://example.com', children: [{ type: 'text', content: 'here' }] },
      { type: 'text', content: ' now' },
    ])
  })

  it('should parse images', () => {
    const result = parseInline('see ![alt text](image.png) here')
    expect(result).toEqual([
      { type: 'text', content: 'see ' },
      { type: 'image', src: 'image.png', alt: 'alt text' },
      { type: 'text', content: ' here' },
    ])
  })

  it('should parse nested bold in link', () => {
    const result = parseInline('[**bold link**](url)')
    expect(result).toEqual([
      {
        type: 'link',
        href: 'url',
        children: [{ type: 'bold', children: [{ type: 'text', content: 'bold link' }] }],
      },
    ])
  })

  it('should handle empty string', () => {
    expect(parseInline('')).toEqual([])
  })
})

describe('parseMarkdown', () => {
  it('should return empty array for empty input', () => {
    expect(parseMarkdown('')).toEqual([])
  })

  it('should parse headings level 1–6', () => {
    const md = '# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6'
    const result = parseMarkdown(md)
    expect(result).toHaveLength(6)
    for (let i = 0; i < 6; i++) {
      expect(result[i]).toMatchObject({ type: 'heading', level: i + 1 })
    }
  })

  it('should parse a paragraph', () => {
    const result = parseMarkdown('Hello world\nthis is a paragraph.')
    expect(result).toEqual([
      {
        type: 'paragraph',
        children: [{ type: 'text', content: 'Hello world this is a paragraph.' }],
      },
    ])
  })

  it('should split paragraphs on blank lines', () => {
    const result = parseMarkdown('First paragraph.\n\nSecond paragraph.')
    expect(result).toHaveLength(2)
    expect(result[0]).toMatchObject({ type: 'paragraph' })
    expect(result[1]).toMatchObject({ type: 'paragraph' })
  })

  it('should parse fenced code blocks', () => {
    const md = '```typescript\nconst x = 1\nconsole.log(x)\n```'
    const result = parseMarkdown(md)
    expect(result).toEqual([
      {
        type: 'codeBlock',
        language: 'typescript',
        content: 'const x = 1\nconsole.log(x)',
      },
    ])
  })

  it('should parse fenced code blocks without language', () => {
    const md = '```\nhello\n```'
    const result = parseMarkdown(md)
    expect(result).toEqual([
      {
        type: 'codeBlock',
        language: undefined,
        content: 'hello',
      },
    ])
  })

  it('should parse horizontal rules', () => {
    for (const rule of ['---', '***', '___', '----', '****']) {
      const result = parseMarkdown(rule)
      expect(result).toEqual([{ type: 'horizontalRule' }])
    }
  })

  it('should parse unordered lists', () => {
    const md = '- Item 1\n- Item 2\n- Item 3'
    const result = parseMarkdown(md)
    expect(result).toHaveLength(1)
    expect(result[0]).toMatchObject({ type: 'list', ordered: false })
    const list = result[0] as { type: 'list'; items: unknown[] }
    expect(list.items).toHaveLength(3)
  })

  it('should parse ordered lists', () => {
    const md = '1. First\n2. Second\n3. Third'
    const result = parseMarkdown(md)
    expect(result).toHaveLength(1)
    expect(result[0]).toMatchObject({ type: 'list', ordered: true })
    const list = result[0] as { type: 'list'; items: unknown[] }
    expect(list.items).toHaveLength(3)
  })

  it('should parse checkboxes in unordered lists', () => {
    const md = '- [x] Done task\n- [ ] Todo task\n- Regular item'
    const result = parseMarkdown(md)
    expect(result).toHaveLength(1)
    const list = result[0] as { type: 'list'; items: Array<{ checkbox?: string }> }
    expect(list.items[0].checkbox).toBe('checked')
    expect(list.items[1].checkbox).toBe('unchecked')
    expect(list.items[2].checkbox).toBeUndefined()
  })

  it('should track sourceLineIndex on list items', () => {
    const md = '- [x] Done\n- [ ] Todo'
    const result = parseMarkdown(md)
    const list = result[0] as { type: 'list'; items: Array<{ sourceLineIndex: number }> }
    expect(list.items[0].sourceLineIndex).toBe(0)
    expect(list.items[1].sourceLineIndex).toBe(1)
  })

  it('should parse blockquotes', () => {
    const md = '> This is a quote\n> Second line'
    const result = parseMarkdown(md)
    expect(result).toHaveLength(1)
    expect(result[0]).toMatchObject({ type: 'blockquote' })
    const bq = result[0] as { type: 'blockquote'; children: unknown[] }
    expect(bq.children).toHaveLength(1)
    expect(bq.children[0]).toMatchObject({ type: 'paragraph' })
  })

  it('should parse a complex document', () => {
    const md = [
      '# Title',
      '',
      'A paragraph with **bold** and *italic*.',
      '',
      '## List Section',
      '',
      '- [x] Task done',
      '- [ ] Task pending',
      '',
      '> A blockquote',
      '',
      '---',
      '',
      '```js',
      'console.log("hi")',
      '```',
    ].join('\n')

    const result = parseMarkdown(md)
    expect(result[0]).toMatchObject({ type: 'heading', level: 1 })
    expect(result[1]).toMatchObject({ type: 'paragraph' })
    expect(result[2]).toMatchObject({ type: 'heading', level: 2 })
    expect(result[3]).toMatchObject({ type: 'list', ordered: false })
    expect(result[4]).toMatchObject({ type: 'blockquote' })
    expect(result[5]).toMatchObject({ type: 'horizontalRule' })
    expect(result[6]).toMatchObject({ type: 'codeBlock', language: 'js' })
  })
})

describe('toggleCheckbox', () => {
  it('should toggle an unchecked checkbox to checked', () => {
    const source = '- [ ] Todo item'
    const result = toggleCheckbox(source, 0)
    expect(result).toBe('- [x] Todo item')
  })

  it('should toggle a checked checkbox to unchecked', () => {
    const source = '- [x] Done item'
    const result = toggleCheckbox(source, 0)
    expect(result).toBe('- [ ] Done item')
  })

  it('should toggle the correct line in a multi-line document', () => {
    const source = '- [x] Done\n- [ ] Todo\n- [ ] Another'
    const result = toggleCheckbox(source, 1)
    expect(result).toBe('- [x] Done\n- [x] Todo\n- [ ] Another')
  })

  it('should return original string for out-of-bounds index', () => {
    const source = '- [ ] Todo'
    expect(toggleCheckbox(source, 5)).toBe(source)
    expect(toggleCheckbox(source, -1)).toBe(source)
  })

  it('should return original string for non-checkbox line', () => {
    const source = 'Regular text'
    expect(toggleCheckbox(source, 0)).toBe(source)
  })

  it('should not toggle [ ] that appears outside a list item', () => {
    const source = 'This line has [ ] in it but is not a list item'
    expect(toggleCheckbox(source, 0)).toBe(source)
  })

  it('should toggle checkboxes with * list marker', () => {
    const source = '* [ ] Star item'
    expect(toggleCheckbox(source, 0)).toBe('* [x] Star item')
  })
})
