/**
 * test/http/session-groups.test.ts (W5) — pure grouping helpers, no I/O.
 *
 * deriveRepoRoot strips a `<name>-worktrees/<lane>` parent to `<...>/<name>`;
 * groupSessionsByRepo clusters a repo + its worktree lanes into one SessionGroup,
 * skips cwd:null, and preserves manager.list() (newest-first) order.
 */

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

import { deriveRepoRoot, groupSessionsByRepo } from '../../src/http/session-groups.js'
import type { LiveSessionInfo } from '../../src/types.js'

function live(id: string, cwd: string | null): LiveSessionInfo {
  return {
    id,
    createdAt: 0,
    clientCount: 0,
    status: 'unknown',
    exited: false,
    cwd,
    cols: 80,
    rows: 24,
  }
}

describe('deriveRepoRoot', () => {
  it('strips a *-worktrees parent to the repo root', () => {
    expect(deriveRepoRoot('/a/proj-worktrees/lane-1')).toBe('/a/proj')
  })

  it('returns the cwd unchanged when the parent is not a *-worktrees dir', () => {
    expect(deriveRepoRoot('/a/proj')).toBe('/a/proj')
  })

  it('returns null for a null cwd', () => {
    expect(deriveRepoRoot(null)).toBe(null)
  })

  it('returns null for an empty cwd', () => {
    expect(deriveRepoRoot('')).toBe(null)
  })

  it('handles a nested repo path with a *-worktrees parent', () => {
    expect(deriveRepoRoot('/home/me/code/web-terminal-worktrees/feat-x-lane-2')).toBe(
      '/home/me/code/web-terminal',
    )
  })

  it('does not treat a bare "-worktrees" segment (no repo name) specially', () => {
    // parentName === '-worktrees' has no repo name to keep → falls through to cwd.
    expect(deriveRepoRoot('/a/-worktrees/x')).toBe('/a/-worktrees/x')
  })
})

describe('groupSessionsByRepo', () => {
  it('returns [] for no sessions', () => {
    expect(groupSessionsByRepo([])).toEqual([])
  })

  it('clusters a repo and its worktree lanes into ONE group', () => {
    const sessions = [
      live('s1', '/a/proj-worktrees/lane-1'),
      live('s2', '/a/proj-worktrees/lane-2'),
    ]
    const groups = groupSessionsByRepo(sessions)
    expect(groups).toHaveLength(1)
    expect(groups[0]!.repoRoot).toBe('/a/proj')
    expect(groups[0]!.label).toBe('proj')
    expect(groups[0]!.sessions.map((s) => s.id)).toEqual(['s1', 's2'])
  })

  it('groups the main repo cwd together with its worktree lanes', () => {
    const sessions = [
      live('main', '/a/proj'),
      live('lane', '/a/proj-worktrees/feat-lane-1'),
    ]
    const groups = groupSessionsByRepo(sessions)
    expect(groups).toHaveLength(1)
    expect(groups[0]!.repoRoot).toBe('/a/proj')
    expect(groups[0]!.sessions.map((s) => s.id)).toEqual(['main', 'lane'])
  })

  it('puts an unrelated cwd in its own group', () => {
    const groups = groupSessionsByRepo([
      live('a', '/a/proj-worktrees/lane-1'),
      live('b', '/b/other'),
    ])
    expect(groups).toHaveLength(2)
    expect(groups[0]!.repoRoot).toBe('/a/proj')
    expect(groups[1]!.repoRoot).toBe('/b/other')
    expect(groups[1]!.label).toBe('other')
  })

  it('skips sessions with a null cwd (not attributable to a repo)', () => {
    const groups = groupSessionsByRepo([
      live('nocwd', null),
      live('real', '/a/proj'),
    ])
    expect(groups).toHaveLength(1)
    expect(groups[0]!.repoRoot).toBe('/a/proj')
    expect(groups[0]!.sessions.map((s) => s.id)).toEqual(['real'])
  })

  it('preserves the input (newest-first) order across and within groups', () => {
    const groups = groupSessionsByRepo([
      live('newest', '/a/proj-worktrees/lane-2'),
      live('older', '/a/proj-worktrees/lane-1'),
    ])
    expect(groups[0]!.sessions.map((s) => s.id)).toEqual(['newest', 'older'])
  })

  it('does not mutate the input array', () => {
    const input = [live('a', '/a/proj')]
    const copy = [...input]
    groupSessionsByRepo(input)
    expect(input).toEqual(copy)
  })
})
