import { describe, expect, it } from 'vitest'
import { countWorkingTree, parsePorcelainStatus } from '../git.js'

describe('countWorkingTree', () => {
  it('counts purely-unstaged entries (the leading-space regression)', () => {
    // 4 modified-in-worktree files. The historical bug: execGit ran .trim(),
    // eating the FIRST line's leading space (" M" → "M"), so it was miscounted
    // as staged → 3 unstaged / 1 staged. With column parsing it must be 4 / 0.
    const porcelain = ' M src/commands/dev.ts\n M src/lib/dev-configure.ts\n M src/lib/x.ts\n M templates/skills/dev-start/SKILL.md\n'
    expect(countWorkingTree(porcelain)).toEqual({ staged: 0, unstaged: 4 })
  })

  it('counts a staged-only modification (X column)', () => {
    expect(countWorkingTree('M  foo.ts')).toEqual({ staged: 1, unstaged: 0 })
  })

  it('counts a combined staged + unstaged entry (MM) on both axes', () => {
    expect(countWorkingTree('MM foo.ts')).toEqual({ staged: 1, unstaged: 1 })
  })

  it('counts a staged addition (A) and an unstaged deletion (D)', () => {
    expect(countWorkingTree('A  added.ts\n D removed.ts')).toEqual({ staged: 1, unstaged: 1 })
  })

  it('ignores untracked entries (??)', () => {
    expect(countWorkingTree('?? new.ts\n M tracked.ts')).toEqual({ staged: 0, unstaged: 1 })
  })

  it('handles a trailing newline / empty input without over-counting', () => {
    expect(countWorkingTree('')).toEqual({ staged: 0, unstaged: 0 })
    expect(countWorkingTree('\n')).toEqual({ staged: 0, unstaged: 0 })
  })

  it('counts a realistic mixed changeset by column', () => {
    const porcelain = [
      'M  staged-only.ts', //   staged
      ' M unstaged-only.ts', // unstaged
      'MM both.ts', //          staged + unstaged
      'A  staged-add.ts', //    staged
      '?? untracked.ts', //     neither
      '',
    ].join('\n')
    expect(countWorkingTree(porcelain)).toEqual({ staged: 3, unstaged: 2 })
  })
})

describe('parsePorcelainStatus', () => {
  it('splits staged / modified / untracked by column', () => {
    const out = ['M  staged.ts', ' M modified.ts', '?? new.ts'].join('\n')
    expect(parsePorcelainStatus(out)).toEqual({
      staged: ['staged.ts'],
      modified: ['modified.ts'],
      untracked: ['new.ts'],
    })
  })

  it('a combined MM entry lands in BOTH staged and modified', () => {
    expect(parsePorcelainStatus('MM both.ts')).toEqual({
      staged: ['both.ts'],
      modified: ['both.ts'],
      untracked: [],
    })
  })

  it('a STAGED RENAME yields the NEW path, not "old -> new" (the EF-guard false-negative fix)', () => {
    // Before the fix, slice(3) kept the whole "old -> new" string → existsSync
    // failed → the renamed migration was silently skipped by the destructive scan.
    const out =
      'R  src/Migrations/20260601_core_v3_54_0_001_InitialCreate.cs -> src/Migrations/20260602_core_v3_55_0_001_InitialCreate.cs'
    expect(parsePorcelainStatus(out)).toEqual({
      staged: ['src/Migrations/20260602_core_v3_55_0_001_InitialCreate.cs'],
      modified: [],
      untracked: [],
    })
  })

  it('preserves the leading-space column on the first line (no .trim regression)', () => {
    expect(parsePorcelainStatus(' M src/a.ts')).toEqual({
      staged: [],
      modified: ['src/a.ts'],
      untracked: [],
    })
  })

  it('ignores blank / short lines without crashing', () => {
    expect(parsePorcelainStatus('\n\nM  x.ts\n')).toEqual({
      staged: ['x.ts'],
      modified: [],
      untracked: [],
    })
  })
})
