import { describe, it, expect, beforeEach, vi } from 'vitest'
import { createBarDownloadConfig } from './download'
import type { BarWidgetData } from './types'

const sample: BarWidgetData = [[{ name: 'a', value: 1 }]]
const multi: BarWidgetData = [
  [
    { name: 'a', value: 1 },
    { name: 'b', value: 2 },
  ],
  [{ name: 'c', value: 3 }],
]

let revokeSpy: ReturnType<typeof vi.spyOn>

beforeEach(() => {
  // jsdom/happy-dom often miss URL.* — stub deterministically.
  vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock')
  revokeSpy = vi
    .spyOn(URL, 'revokeObjectURL')
    .mockImplementation(() => undefined)
})

describe('createBarDownloadConfig', () => {
  it('returns CSV-only items when getCaptureEl is not provided', () => {
    const items = createBarDownloadConfig({
      filename: 'demo',
      getData: () => sample,
    })
    expect(items.map((i) => i.id)).toEqual(['csv'])
  })

  it('prepends a PNG item when getCaptureEl is provided', () => {
    const items = createBarDownloadConfig({
      filename: 'demo',
      getData: () => sample,
      getCaptureEl: () => document.createElement('div'),
    })
    expect(items.map((i) => i.id)).toEqual(['png', 'csv'])
    const pngItem = items[0]
    expect(pngItem?.label).toBe('PNG')
    expect(pngItem?.icon).toBeTruthy()
  })

  it('the PNG item rejects when the captureEl getter returns null', async () => {
    const items = createBarDownloadConfig({
      filename: 'demo',
      getData: () => sample,
      getCaptureEl: () => null,
    })
    const png = items.find((i) => i.id === 'png')
    expect(png).toBeTruthy()
    await expect(png!.resolve()).rejects.toThrow(/No PNG capture element/)
  })

  it('CSV resolve returns a download handle with the filename + revoke fn (single series)', async () => {
    const items = createBarDownloadConfig({
      filename: 'sales',
      getData: () => sample,
    })
    const csv = items.find((i) => i.id === 'csv')!
    const handle = await csv.resolve()
    expect(handle.url).toBe('blob:mock')
    expect(handle.filename).toBe('sales.csv')
    expect(typeof handle.revoke).toBe('function')
    handle.revoke?.()
    expect(revokeSpy).toHaveBeenCalledWith('blob:mock')
  })

  it('CSV resolve emits empty-row separators between multiple series', async () => {
    let csvText = ''
    // Intercept the Blob payload so we can assert on the serialised CSV.
    const RealBlob = global.Blob
    vi.stubGlobal(
      'Blob',
      class extends RealBlob {
        constructor(parts: BlobPart[], opts?: BlobPropertyBag) {
          csvText = typeof parts[0] === 'string' ? parts[0] : ''
          super(parts, opts)
        }
      },
    )
    const items = createBarDownloadConfig({
      filename: 'multi',
      getData: () => multi,
    })
    await items.find((i) => i.id === 'csv')!.resolve()
    expect(csvText).toBe('name,value\na,1\nb,2\n\nname,value\nc,3')
    vi.unstubAllGlobals()
  })
})
