import { describe, it, expect } from 'vitest'
import { EDITOR_ONLY_TAGS } from '../stores/recipes'

// ---------------------------------------------------------------------------
// Tag sorting
// ---------------------------------------------------------------------------
describe('Tag sorting', () => {
  it('sorts tags alphabetically with localeCompare zh', () => {
    const tags = ['香水', '呼吸', '消化', '美容']
    const sorted = [...tags].sort((a, b) => a.localeCompare(b, 'zh'))
    expect(sorted[0]).toBe('呼吸')
    // All sorted
    for (let i = 1; i < sorted.length; i++) {
      expect(sorted[i - 1].localeCompare(sorted[i], 'zh')).toBeLessThanOrEqual(0)
    }
  })

  it('EDITOR_ONLY_TAGS filters correctly', () => {
    const allTags = ['呼吸', '已审核', '消化', '香水']
    const visible = allTags.filter(t => !EDITOR_ONLY_TAGS.includes(t))
    expect(visible).not.toContain('已审核')
    expect(visible).toContain('呼吸')
    expect(visible).toHaveLength(3)
  })
})

// ---------------------------------------------------------------------------
// Recipe save data format
// ---------------------------------------------------------------------------
describe('Recipe data format', () => {
  it('oil_name format overwrites oil format when spread', () => {
    // This test documents the bug that was fixed
    const localRecipe = {
      ingredients: [{ oil: '薰衣草', drops: 3 }],
    }
    const payload = {
      ingredients: [{ oil_name: '薰衣草', drops: 3 }],
    }
    const merged = { ...localRecipe, ...payload }
    // After merge, ingredients have oil_name not oil — this was the bug
    expect(merged.ingredients[0]).toHaveProperty('oil_name')
    expect(merged.ingredients[0]).not.toHaveProperty('oil')
  })

  it('loadRecipes mapping converts oil_name to oil', () => {
    // Simulate what loadRecipes does
    const apiData = [
      { id: 1, name: 'test', ingredients: [{ oil_name: '薰衣草', drops: 3 }], tags: [] }
    ]
    const mapped = apiData.map(r => ({
      ...r,
      ingredients: r.ingredients.map(ing => ({
        oil: ing.oil_name ?? ing.oil,
        drops: ing.drops,
      }))
    }))
    expect(mapped[0].ingredients[0].oil).toBe('薰衣草')
    expect(mapped[0].ingredients[0]).not.toHaveProperty('oil_name')
  })
})

// ---------------------------------------------------------------------------
// Volume detection from ingredients
// ---------------------------------------------------------------------------
describe('Volume detection', () => {
  const DROPS_PER_ML = 18.6

  function guessVolume(eoDrops, cocoDrops) {
    const totalDrops = eoDrops + cocoDrops
    const ml = totalDrops / DROPS_PER_ML
    if (ml <= 2) return 'single'
    if (Math.abs(ml - 5) < 1.5) return '5'
    if (Math.abs(ml - 10) < 2.5) return '10'
    if (Math.abs(ml - 15) < 2.5) return '15'
    if (Math.abs(ml - 20) < 3) return '20'
    if (Math.abs(ml - 30) < 6) return '30'
    return 'custom'
  }

  it('detects single use (small amounts)', () => {
    expect(guessVolume(5, 10)).toBe('single')
  })

  it('detects 5ml', () => {
    expect(guessVolume(15, Math.round(5 * DROPS_PER_ML) - 15)).toBe('5')
  })

  it('detects 10ml', () => {
    expect(guessVolume(20, Math.round(10 * DROPS_PER_ML) - 20)).toBe('10')
  })

  it('detects 30ml', () => {
    expect(guessVolume(50, Math.round(30 * DROPS_PER_ML) - 50)).toBe('30')
  })

  it('no coconut returns no volume', () => {
    // When cocoDrops is 0, function still returns based on total
    // But in real code, no coconut → formVolume = ''
    expect(guessVolume(10, 0)).toBe('single')
  })

  it('detects custom for large volumes', () => {
    expect(guessVolume(100, 1000)).toBe('custom')
  })
})

// ---------------------------------------------------------------------------
// Dilution ratio calculation
// ---------------------------------------------------------------------------
describe('Dilution ratio', () => {
  it('calculates ratio correctly', () => {
    expect(Math.round(60 / 10)).toBe(6) // 1:6
    expect(Math.round(30 / 10)).toBe(3) // 1:3
    expect(Math.round(100 / 10)).toBe(10) // 1:10
  })

  it('snaps to nearest option', () => {
    const options = [3, 4, 5, 6, 7, 8, 9, 10, 12, 15, 20]
    const snap = (ratio) => options.reduce((a, b) => Math.abs(b - ratio) < Math.abs(a - ratio) ? b : a)
    expect(snap(6)).toBe(6)
    expect([10, 12]).toContain(snap(11)) // equidistant
    expect(snap(13)).toBe(12)
    expect([12, 15]).toContain(snap(14))
    expect(snap(18)).toBe(20)
  })
})
