import { execFile } from 'node:child_process';
import { promises as fs } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { promisify } from 'node:util';
import { describe, expect, it } from 'vitest';
import { createAgentsWorkStashSnapshotRef, createScopedFilePathspecArgs, createScopedGitPathspecArgs, mergeGitDiffSummaryFiles, normalizeGitCommitMessage, parseGitDiffSummary, parseGitStashListLine, parseGitStatusStagedPaths, parseLatestGitStash, persistAgentsWorkStashSnapshotRef, } from './git.js';
const execFileAsync = promisify(execFile);
async function runGit(cwd, args) {
    const result = await execFileAsync('git', [...args], { cwd });
    return result.stdout;
}
describe('git commit helpers', () => {
    it('normalizes commit messages before executing git commit', () => {
        expect(normalizeGitCommitMessage('  chore: update dashboard  ')).toBe('chore: update dashboard');
        expect(normalizeGitCommitMessage('   ')).toBe('');
        expect(normalizeGitCommitMessage(null)).toBe('');
    });
    it('creates pathspec args scoped to the selected workspace path', () => {
        expect(createScopedGitPathspecArgs({ relativeWorkspacePath: 'packages/app' })).toEqual(['--', 'packages/app']);
        expect(createScopedGitPathspecArgs({ relativeWorkspacePath: '' })).toEqual(['--', '.']);
    });
    it('creates file pathspec args relative to parent git repositories', () => {
        expect(createScopedFilePathspecArgs('src/App.vue', { relativeWorkspacePath: 'packages/app' })).toEqual([
            '--',
            'packages/app/src/App.vue',
        ]);
    });
    it('parses staged paths from porcelain status output', () => {
        const stagedPaths = parseGitStatusStagedPaths('M  staged.ts\n M unstaged.ts\nA  added.ts\n?? untracked.ts\nR  old.ts -> renamed.ts\n');
        expect([...stagedPaths].sort()).toEqual(['added.ts', 'renamed.ts', 'staged.ts']);
    });
    it('marks cached diff files as staged when parsing staged summaries', () => {
        const files = parseGitDiffSummary('M\tsrc/staged.ts\n', '2\t1\tsrc/staged.ts\n', new Set(), true);
        expect(files).toEqual([
            {
                path: 'src/staged.ts',
                status: 'modified',
                additions: 2,
                deletions: 1,
                staged: true,
            },
        ]);
    });
    it('merges unstaged and staged summaries while preserving staged-only files', () => {
        const unstagedFiles = parseGitDiffSummary('M\tsrc/mixed.ts\nM\tsrc/unstaged.ts\n', '1\t0\tsrc/mixed.ts\n3\t1\tsrc/unstaged.ts\n', new Set(['src/mixed.ts']));
        const stagedFiles = parseGitDiffSummary('M\tsrc/mixed.ts\nA\tsrc/staged-only.ts\n', '2\t1\tsrc/mixed.ts\n5\t0\tsrc/staged-only.ts\n', new Set(['src/mixed.ts', 'src/staged-only.ts']), true);
        expect(mergeGitDiffSummaryFiles(unstagedFiles, stagedFiles)).toEqual([
            {
                path: 'src/mixed.ts',
                status: 'modified',
                additions: 3,
                deletions: 1,
                staged: true,
            },
            {
                path: 'src/unstaged.ts',
                status: 'modified',
                additions: 3,
                deletions: 1,
                staged: false,
            },
            {
                path: 'src/staged-only.ts',
                status: 'added',
                additions: 5,
                deletions: 0,
                staged: true,
            },
        ]);
    });
});
describe('git stash reference helpers', () => {
    it('parses stable stash object hashes while preserving the display selector', () => {
        const stash = parseGitStashListLine('0123456789abcdef0123456789abcdef01234567\u0000stash@{3}\u0000On main: 快照: 任务线-2026-06-02 10:00:00\u00002 hours ago');
        expect(stash).toEqual({
            ref: '0123456789abcdef0123456789abcdef01234567',
            stashName: 'stash@{3}',
            message: '快照: 任务线-2026-06-02 10:00:00',
            branch: 'main',
            createdAt: '2 hours ago',
        });
    });
    it('uses the stable stash object hash for the latest no-change snapshot reuse', () => {
        const latest = parseLatestGitStash('fedcba9876543210fedcba9876543210fedcba98\u0000stash@{0}\u0000On master: 快照: latest\u0000just now\n');
        expect(latest).toEqual({
            ref: 'fedcba9876543210fedcba9876543210fedcba98',
            stashName: 'stash@{0}',
            message: '快照: latest',
        });
    });
    it('keeps flow start snapshots restorable after the original stash selector is removed', async () => {
        let workspacePath = null;
        try {
            workspacePath = await fs.mkdtemp(path.join(tmpdir(), 'aws-stash-ref-'));
            await runGit(workspacePath, ['init']);
            await runGit(workspacePath, ['config', 'core.autocrlf', 'false']);
            await runGit(workspacePath, ['config', 'user.email', 'test@example.com']);
            await runGit(workspacePath, ['config', 'user.name', 'Tester']);
            await fs.writeFile(path.join(workspacePath, 'task.txt'), 'base\n', 'utf8');
            await runGit(workspacePath, ['add', 'task.txt']);
            await runGit(workspacePath, ['commit', '-m', 'init']);
            await fs.writeFile(path.join(workspacePath, 'task.txt'), 'snapshot-content\n', 'utf8');
            await runGit(workspacePath, ['stash', 'push', '-m', 'flow start snapshot']);
            const latest = parseLatestGitStash(await runGit(workspacePath, ['stash', 'list', '-n', '1', '--format=%H%x00%gd%x00%gs%x00%cr']));
            if (!latest) {
                throw new Error('expected a latest stash entry');
            }
            const persistentRef = createAgentsWorkStashSnapshotRef(latest.ref);
            if (!persistentRef) {
                throw new Error('expected a persistent AgentsWork stash ref');
            }
            const persistedRef = await persistAgentsWorkStashSnapshotRef(workspacePath, latest.ref);
            if (!persistedRef) {
                throw new Error('expected the persistent ref to be written');
            }
            expect(persistedRef).toBe(persistentRef);
            await runGit(workspacePath, ['stash', 'drop', latest.stashName]);
            await runGit(workspacePath, ['stash', 'apply', persistedRef]);
            await expect(fs.readFile(path.join(workspacePath, 'task.txt'), 'utf8')).resolves.toBe('snapshot-content\n');
        }
        finally {
            if (workspacePath) {
                await fs.rm(workspacePath, { recursive: true, force: true });
            }
        }
    }, 15_000);
});
