import { describe, expect, it } from 'vitest'
import {
  addSong,
  deleteSong,
  moveSong,
  nextId,
  youtubeSearchUrl,
  type Song,
} from '../logic/playlist'

const sample = (): Song[] => [
  { id: 1, singer: '周杰伦', title: '七里香' },
  { id: 2, singer: '林俊杰', title: '江南' },
  { id: 3, singer: '陶喆', title: '小镇姑娘' },
]

describe('nextId', () => {
  it('returns 1 for empty playlist', () => {
    expect(nextId([])).toBe(1)
  })
  it('returns max + 1', () => {
    expect(nextId(sample())).toBe(4)
  })
  it('handles gaps correctly', () => {
    expect(nextId([{ id: 7, singer: 'a', title: 'b' }])).toBe(8)
  })
})

describe('addSong', () => {
  it('appends to the end with next id', () => {
    const out = addSong(sample(), '邓紫棋', '泡沫')
    expect(out).toHaveLength(4)
    expect(out[3]).toEqual({ id: 4, singer: '邓紫棋', title: '泡沫' })
  })

  it('trims whitespace', () => {
    const out = addSong([], '  Adele  ', '  Hello  ')
    expect(out[0]).toEqual({ id: 1, singer: 'Adele', title: 'Hello' })
  })

  it('rejects empty singer or title (returns unchanged)', () => {
    const list = sample()
    expect(addSong(list, '', 'Hello')).toBe(list)
    expect(addSong(list, 'Adele', '')).toBe(list)
    expect(addSong(list, '   ', '   ')).toBe(list)
  })

  it('does not mutate input', () => {
    const list = sample()
    addSong(list, 'a', 'b')
    expect(list).toHaveLength(3)
  })
})

describe('deleteSong', () => {
  it('removes by id', () => {
    const out = deleteSong(sample(), 2)
    expect(out).toHaveLength(2)
    expect(out.map((s) => s.id)).toEqual([1, 3])
  })

  it('is noop for missing id', () => {
    const out = deleteSong(sample(), 999)
    expect(out).toHaveLength(3)
  })

  it('does not mutate input', () => {
    const list = sample()
    deleteSong(list, 1)
    expect(list).toHaveLength(3)
  })
})

describe('moveSong', () => {
  it("moves 'up'", () => {
    const out = moveSong(sample(), 2, 'up')
    expect(out.map((s) => s.id)).toEqual([2, 1, 3])
  })

  it("moves 'down'", () => {
    const out = moveSong(sample(), 2, 'down')
    expect(out.map((s) => s.id)).toEqual([1, 3, 2])
  })

  it("moves 'first'", () => {
    const out = moveSong(sample(), 3, 'first')
    expect(out.map((s) => s.id)).toEqual([3, 1, 2])
  })

  it("'up' on first item is noop", () => {
    const out = moveSong(sample(), 1, 'up')
    expect(out.map((s) => s.id)).toEqual([1, 2, 3])
  })

  it("'down' on last item is noop", () => {
    const out = moveSong(sample(), 3, 'down')
    expect(out.map((s) => s.id)).toEqual([1, 2, 3])
  })

  it("'first' on first item is noop", () => {
    const out = moveSong(sample(), 1, 'first')
    expect(out.map((s) => s.id)).toEqual([1, 2, 3])
  })

  it('handles missing id', () => {
    const out = moveSong(sample(), 999, 'up')
    expect(out.map((s) => s.id)).toEqual([1, 2, 3])
  })

  it('preserves the song count', () => {
    for (const dir of ['up', 'down', 'first'] as const) {
      for (const id of [1, 2, 3]) {
        const out = moveSong(sample(), id, dir)
        expect(out).toHaveLength(3)
        expect(out.map((s) => s.id).sort()).toEqual([1, 2, 3])
      }
    }
  })

  it('does not mutate input', () => {
    const list = sample()
    moveSong(list, 2, 'up')
    expect(list.map((s) => s.id)).toEqual([1, 2, 3])
  })
})

describe('youtubeSearchUrl', () => {
  it('builds an encoded search URL', () => {
    const url = youtubeSearchUrl({ id: 1, singer: '周杰伦', title: '七里香' })
    expect(url.startsWith('https://www.youtube.com/results?search_query=')).toBe(true)
    expect(url).toContain(encodeURIComponent('周杰伦 七里香'))
  })

  it('encodes special characters', () => {
    const url = youtubeSearchUrl({ id: 1, singer: 'A&B', title: 'C/D' })
    expect(url).toContain('A%26B')
    expect(url).toContain('C%2FD')
  })
})
