import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
import { promises as fs } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { copySourceApps, SourceAppCollisionError } from './copy-source-apps.js';
let tmp;
let projectRoot;
let outputDir;
beforeEach(async () => {
    tmp = await fs.mkdtemp(join(tmpdir(), 'source-apps-'));
    projectRoot = join(tmp, 'project');
    outputDir = projectRoot;
    await fs.mkdir(projectRoot, { recursive: true });
});
afterEach(async () => {
    await fs.rm(tmp, { recursive: true, force: true }).catch(() => { });
});
async function writeApp(rel, content) {
    const full = join(projectRoot, 'quickback', 'apps', rel);
    await fs.mkdir(join(full, '..'), { recursive: true });
    await fs.writeFile(full, content);
}
async function readDest(rel) {
    return fs.readFile(join(outputDir, 'src', 'apps', rel));
}
async function pathExists(p) {
    try {
        await fs.access(p);
        return true;
    }
    catch {
        return false;
    }
}
describe('copySourceApps — quickback/apps passthrough', () => {
    test('no-op when apps folder is missing', async () => {
        const result = await copySourceApps({
            projectRoot,
            outputDir,
            manifestPaths: ['src/index.ts'],
        });
        expect(result.copied).toBe(0);
        expect(result.destPaths).toEqual([]);
        expect(await pathExists(join(outputDir, 'src', 'apps'))).toBe(false);
    });
    test('copies hand-authored TSX files into src/apps/<name>/', async () => {
        await writeApp('m/index.tsx', 'export default function App() { return null; }');
        await writeApp('m/styles.css', 'body { margin: 0; }');
        const result = await copySourceApps({
            projectRoot,
            outputDir,
            manifestPaths: ['src/index.ts'],
        });
        expect(result.copied).toBe(2);
        expect(result.destPaths).toContain(join('src', 'apps', 'm', 'index.tsx'));
        expect(result.destPaths).toContain(join('src', 'apps', 'm', 'styles.css'));
        expect((await readDest('m/index.tsx')).toString('utf-8')).toContain('export default');
    });
    test('preserves nested directory structure across multiple apps', async () => {
        await writeApp('m/components/Button.tsx', 'export const Button = () => null;');
        await writeApp('m/lib/utils.ts', 'export const x = 1;');
        await writeApp('admin/index.tsx', 'export default () => null;');
        const result = await copySourceApps({
            projectRoot,
            outputDir,
            manifestPaths: [],
        });
        expect(result.copied).toBe(3);
        expect(result.destPaths).toContain(join('src', 'apps', 'm', 'components', 'Button.tsx'));
        expect(result.destPaths).toContain(join('src', 'apps', 'm', 'lib', 'utils.ts'));
        expect(result.destPaths).toContain(join('src', 'apps', 'admin', 'index.tsx'));
    });
    test('preserves binary fixtures (e.g., sprite sheets) byte-for-byte', async () => {
        const bytes = Buffer.from([0xff, 0xfe, 0x00, 0x80, 0xc0, 0xff]);
        await writeApp('m/assets/sprite.bin', bytes);
        await copySourceApps({ projectRoot, outputDir, manifestPaths: [] });
        const out = await readDest('m/assets/sprite.bin');
        expect(Buffer.compare(out, bytes)).toBe(0);
    });
    test('skips hidden files and directories (.DS_Store, .git/, dotfiles)', async () => {
        await writeApp('m/.DS_Store', 'junk');
        await writeApp('m/.git/HEAD', 'ref: refs/heads/main');
        await writeApp('m/index.tsx', 'export default () => null;');
        const result = await copySourceApps({
            projectRoot,
            outputDir,
            manifestPaths: [],
        });
        expect(result.copied).toBe(1);
        expect(result.destPaths).toContain(join('src', 'apps', 'm', 'index.tsx'));
        expect(await pathExists(join(outputDir, 'src', 'apps', 'm', '.DS_Store'))).toBe(false);
        expect(await pathExists(join(outputDir, 'src', 'apps', 'm', '.git'))).toBe(false);
    });
    test('skips node_modules / dist / build / .next / .turbo / .cache', async () => {
        await writeApp('m/index.tsx', 'export default () => null;');
        await writeApp('m/node_modules/react/index.js', 'module.exports = {};');
        await writeApp('m/dist/bundle.js', 'compiled');
        await writeApp('m/build/main.js', 'compiled');
        await writeApp('m/.next/cache/x', 'x');
        await writeApp('m/.turbo/run.log', 'x');
        await writeApp('m/.cache/blob', 'x');
        const result = await copySourceApps({
            projectRoot,
            outputDir,
            manifestPaths: [],
        });
        expect(result.copied).toBe(1);
        expect(result.destPaths).toEqual([join('src', 'apps', 'm', 'index.tsx')]);
        expect(await pathExists(join(outputDir, 'src', 'apps', 'm', 'node_modules'))).toBe(false);
        expect(await pathExists(join(outputDir, 'src', 'apps', 'm', 'dist'))).toBe(false);
        expect(await pathExists(join(outputDir, 'src', 'apps', 'm', 'build'))).toBe(false);
    });
    test('refuses to overwrite a compiler-emitted manifest path', async () => {
        await writeApp('cms/index.tsx', 'export default () => null;');
        await expect(copySourceApps({
            projectRoot,
            outputDir,
            manifestPaths: ['src/apps/cms/index.tsx', 'src/index.ts'],
        })).rejects.toBeInstanceOf(SourceAppCollisionError);
        // Nothing should have been written when we threw.
        expect(await pathExists(join(outputDir, 'src', 'apps', 'cms'))).toBe(false);
    });
    test('reports every collision in one error, not just the first', async () => {
        await writeApp('cms/index.tsx', 'x');
        await writeApp('account/index.tsx', 'y');
        await writeApp('m/index.tsx', 'z');
        let err;
        try {
            await copySourceApps({
                projectRoot,
                outputDir,
                manifestPaths: ['src/apps/cms/index.tsx', 'src/apps/account/index.tsx'],
            });
        }
        catch (e) {
            err = e;
        }
        expect(err).toBeInstanceOf(SourceAppCollisionError);
        expect(err.collisions).toHaveLength(2);
        const paths = err.collisions.map((c) => c.sourcePath).sort();
        expect(paths).toEqual(['account/index.tsx', 'cms/index.tsx']);
    });
    test('empty apps folder reports zero copied without erroring', async () => {
        await fs.mkdir(join(projectRoot, 'quickback', 'apps'), { recursive: true });
        const result = await copySourceApps({
            projectRoot,
            outputDir,
            manifestPaths: ['src/index.ts'],
        });
        expect(result.copied).toBe(0);
        expect(result.sourceDir).toBeDefined();
    });
    test('outputDir distinct from projectRoot still resolves correctly', async () => {
        outputDir = join(tmp, 'dist');
        await fs.mkdir(outputDir, { recursive: true });
        await writeApp('m/main.tsx', 'export default () => null;');
        const result = await copySourceApps({
            projectRoot,
            outputDir,
            manifestPaths: [],
        });
        expect(result.copied).toBe(1);
        expect((await fs.readFile(join(outputDir, 'src', 'apps', 'm', 'main.tsx'))).toString())
            .toContain('export default');
        // Source still in projectRoot, untouched.
        expect(await pathExists(join(projectRoot, 'quickback', 'apps', 'm', 'main.tsx')))
            .toBe(true);
    });
});
//# sourceMappingURL=copy-source-apps.test.js.map