import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
import { promises as fs } from 'fs';
import { join, sep } from 'path';
import { tmpdir } from 'os';
import { sweepStaleGeneratedFiles } from './sweep-stale.js';
const HEADER = '/**\n * Generated by quickback.dev\n */\n';
const PLAIN = '// hand-authored content\n';
let tmp;
beforeEach(async () => {
    tmp = await fs.mkdtemp(join(tmpdir(), 'sweep-stale-'));
});
afterEach(async () => {
    await fs.rm(tmp, { recursive: true, force: true }).catch(() => { });
});
async function writeFile(rel, content) {
    const full = join(tmp, ...rel.split('/'));
    await fs.mkdir(join(full, '..'), { recursive: true });
    await fs.writeFile(full, content);
}
async function fileExists(rel) {
    try {
        await fs.access(join(tmp, ...rel.split('/')));
        return true;
    }
    catch {
        return false;
    }
}
describe('sweepStaleGeneratedFiles', () => {
    // User story: As a developer who renames a feature, I want stale generator
    // output from the old name to disappear so my project tree reflects only
    // what the current config produces — no ghost routes/types lingering.
    test('deletes generated files not in the manifest', async () => {
        await writeFile('src/features/old/schema.ts', HEADER);
        await writeFile('src/features/new/schema.ts', HEADER);
        const result = await sweepStaleGeneratedFiles(tmp, 'src', ['src/features/new/schema.ts']);
        expect(result.deleted.sort()).toEqual(['src/features/old/schema.ts']);
        expect(await fileExists('src/features/old/schema.ts')).toBe(false);
        expect(await fileExists('src/features/new/schema.ts')).toBe(true);
    });
    // User story: As a developer who pasted a personal note into src/, I want
    // the sweep to leave my hand-authored file alone — the header check is the
    // line between "compiler owns this" and "the user owns this".
    test('preserves files without the generated-header marker', async () => {
        await writeFile('src/features/new/notes.md', PLAIN);
        await writeFile('src/features/new/schema.ts', HEADER);
        const result = await sweepStaleGeneratedFiles(tmp, 'src', ['src/features/new/schema.ts']);
        expect(result.deleted).toEqual([]);
        expect(result.preserved).toEqual(['src/features/new/notes.md']);
        expect(await fileExists('src/features/new/notes.md')).toBe(true);
    });
    // User story: As a developer running quickback compile back-to-back with no
    // changes, I want the sweep to be a fast no-op — no spurious archive dirs
    // littering the repo every time I recompile.
    test('does not archive when nothing is stale', async () => {
        await writeFile('src/features/new/schema.ts', HEADER);
        const archiveDir = join(tmp, 'quickback', '.archives');
        const result = await sweepStaleGeneratedFiles(tmp, 'src', ['src/features/new/schema.ts'], {
            archiveDir,
        });
        expect(result.deleted).toEqual([]);
        expect(result.archivePath).toBeUndefined();
        expect(await fileExists('quickback/.archives')).toBe(false);
    });
    // User story: As a developer about to lose 30 generator-emitted files in a
    // sweep, I want a snapshot of src/ saved automatically so I can recover if
    // the compiler stopped emitting something I was depending on.
    test('archives src/ before deleting when orphans are present', async () => {
        await writeFile('src/features/old/schema.ts', HEADER);
        await writeFile('src/features/new/schema.ts', HEADER);
        const archiveDir = join(tmp, 'quickback', '.archives');
        const result = await sweepStaleGeneratedFiles(tmp, 'src', ['src/features/new/schema.ts'], {
            archiveDir,
        });
        expect(result.deleted).toEqual(['src/features/old/schema.ts']);
        expect(result.archivePath).toBeDefined();
        // Archive should contain a copy of the pre-sweep tree.
        const archived = await fs.readdir(result.archivePath, { recursive: true });
        const flat = archived.map((p) => (sep === '/' ? p : p.split(sep).join('/')));
        expect(flat).toContain('features/old/schema.ts');
        expect(flat).toContain('features/new/schema.ts');
    });
    // User story: As a developer who refactors aggressively over a week, I want
    // older snapshots pruned automatically so the .archives folder doesn't
    // balloon — last 3 generations is enough to walk back from a regret.
    test('prunes old archives, keeping most recent N', async () => {
        const archiveDir = join(tmp, 'archives');
        await fs.mkdir(archiveDir, { recursive: true });
        // Pre-seed 5 fake archives with sortable names.
        for (const stamp of ['src-0001', 'src-0002', 'src-0003', 'src-0004']) {
            await fs.mkdir(join(archiveDir, stamp));
        }
        await writeFile('src/features/old/schema.ts', HEADER);
        await writeFile('src/features/new/schema.ts', HEADER);
        await sweepStaleGeneratedFiles(tmp, 'src', ['src/features/new/schema.ts'], {
            archiveDir,
            archiveKeep: 3,
        });
        const remaining = (await fs.readdir(archiveDir)).filter((d) => d.startsWith('src-'));
        expect(remaining.length).toBe(3);
        // Newest survives — pruning is alphabetical-descending.
        expect(remaining).toContain('src-0004');
        expect(remaining).not.toContain('src-0001');
    });
    // User story: As a developer with SPA bundles in src/apps/cms/assets/, I want
    // the sweep to leave that subtree alone — the upstream code wipes it
    // wholesale before each write, so the per-file header probe would be
    // wasted work.
    test('respects skipPrefixes (SPA asset dirs)', async () => {
        await writeFile('src/apps/cms/assets/main-abc123.js', HEADER);
        await writeFile('src/apps/cms/assets/main-def456.js', HEADER);
        const result = await sweepStaleGeneratedFiles(tmp, 'src', ['src/apps/cms/assets/main-def456.js'], {
            skipPrefixes: ['src/apps/cms/assets'],
        });
        expect(result.deleted).toEqual([]);
        expect(await fileExists('src/apps/cms/assets/main-abc123.js')).toBe(true);
    });
    // User story: As a developer with a now-empty src/features/old/ after a
    // sweep, I don't want the bare directory hanging around — the file tree
    // should match the manifest, including its shape.
    test('removes empty directories left after deletion', async () => {
        await writeFile('src/features/old/schema.ts', HEADER);
        await writeFile('src/features/old/routes.ts', HEADER);
        await writeFile('src/features/new/schema.ts', HEADER);
        await sweepStaleGeneratedFiles(tmp, 'src', ['src/features/new/schema.ts']);
        expect(await fileExists('src/features/old')).toBe(false);
        expect(await fileExists('src/features/new')).toBe(true);
    });
});
//# sourceMappingURL=sweep-stale.test.js.map