import { describe, expect, it } from 'vitest'

import { describeChange, type IssueChange, planIssueChange, proposalsFor } from './apply-changes-v0.js'

describe('planIssueChange', () => {
  it('swaps the estimate label, keeping non-axis labels', () => {
    const plan = planIssueChange(['est/2d', 'p/1', 'backend'], { kind: 'reestimate', issue: 1, estimate: 'est/5d' })
    expect(plan.removed).toEqual(['est/2d'])
    expect(plan.added).toEqual(['est/5d'])
    expect(plan.labels).toEqual(['p/1', 'backend', 'est/5d'])
    expect(plan.noop).toBe(false)
  })

  it('adds an estimate when none was set', () => {
    const plan = planIssueChange(['p/2'], { kind: 'reestimate', issue: 1, estimate: 'est/1d' })
    expect(plan.removed).toEqual([])
    expect(plan.added).toEqual(['est/1d'])
    expect(plan.labels).toEqual(['p/2', 'est/1d'])
  })

  it('clears the axis when the target is null', () => {
    const plan = planIssueChange(['est/3d', 'p/1'], { kind: 'reprioritize', issue: 1, priority: null })
    expect(plan.removed).toEqual(['p/1'])
    expect(plan.added).toEqual([])
    expect(plan.labels).toEqual(['est/3d'])
  })

  it('is a noop when the target already holds the axis alone', () => {
    const plan = planIssueChange(['est/2d', 'p/1'], { kind: 'reestimate', issue: 1, estimate: 'est/2d' })
    expect(plan.noop).toBe(true)
    expect(plan.labels).toEqual(['p/1', 'est/2d'])
  })

  it('cleans up a duplicated axis down to the target', () => {
    // two est/* labels — the change collapses to one
    const plan = planIssueChange(['est/2d', 'est/5d', 'p/1'], { kind: 'reestimate', issue: 1, estimate: 'est/5d' })
    expect(plan.removed).toEqual(['est/2d'])
    expect(plan.added).toEqual([]) // est/5d already present
    expect(plan.labels).toEqual(['p/1', 'est/5d'])
    expect(plan.noop).toBe(false)
  })

  it('reprioritize only touches the priority axis', () => {
    const plan = planIssueChange(['est/2d', 'p/3'], { kind: 'reprioritize', issue: 1, priority: 'p/1' })
    expect(plan.labels).toEqual(['est/2d', 'p/1'])
  })

  it('describeChange renders the diff', () => {
    const change: IssueChange = { kind: 'reestimate', issue: 1, estimate: 'est/5d' }
    expect(describeChange(planIssueChange(['est/2d'], change))).toBe('est/2d → est/5d')
    expect(describeChange(planIssueChange(['p/1'], { kind: 'reprioritize', issue: 1, priority: null }))).toBe('p/1 → ∅')
    expect(describeChange(planIssueChange(['est/5d'], change))).toBe('no change')
  })
})

describe('proposalsFor', () => {
  it('builds one proposal per changed axis, carrying the concrete change + diff', () => {
    const props = proposalsFor({ issue: 2, estimate: 'est/5d', priority: 'p/1' }, ['est/2d', 'p/3'], 'ChangeSource')
    expect(props).toHaveLength(2)
    expect(props[0].change).toEqual({ kind: 'reestimate', issue: 2, estimate: 'est/5d' })
    expect(describeChange(props[0].plan)).toBe('est/2d → est/5d')
    expect(props[1].change).toEqual({ kind: 'reprioritize', issue: 2, priority: 'p/1' })
    expect(props[0].issueTitle).toBe('ChangeSource')
  })

  it('drops a noop axis (already at the requested value)', () => {
    const props = proposalsFor({ issue: 2, estimate: 'est/2d', priority: 'p/1' }, ['est/2d', 'p/3'])
    expect(props.map((p) => p.change.kind)).toEqual(['reprioritize']) // estimate unchanged
  })

  it('ignores invalid label values from the model', () => {
    const props = proposalsFor({ issue: 2, estimate: 'est/4d' as never, priority: 'high' as never }, [])
    expect(props).toEqual([])
  })

  it('returns nothing when no axis is provided', () => {
    expect(proposalsFor({ issue: 2 }, ['est/2d'])).toEqual([])
  })
})
