/**
 * Source-app passthrough for `quickback/apps/`.
 *
 * Anything under `<projectRoot>/quickback/apps/<name>/` is copied verbatim
 * into `<outputDir>/src/apps/<name>/`, preserving directory structure. The
 * compiler never sees these files — they ride past it as bytes and land
 * alongside the SPA shells the compiler emits.
 *
 * Why a separate folder from `quickback/public/`: `public/` is for static
 * binary assets (logos, fonts, OG images) that ride past the worker as
 * bytes. `apps/` is for hand-authored TypeScript / JavaScript / TSX apps
 * — companion mini-SPAs (e.g. `m/`, `admin/`) that live alongside the
 * compiler-emitted `cms/` and `account/` shells. Different conventions,
 * different ergonomics: this folder additionally skips `node_modules/`,
 * `dist/`, and `build/` so a casual `bun install` inside a hand-authored
 * app folder doesn't drag the world into the worker bundle.
 *
 * Why bytes, not strings: the compiler manifest is a utf-8 string channel
 * which is fine for source code in practice, but corrupts any binary fixture
 * a source-authored app might bundle (sprite sheets, sample WAVs, etc.).
 * Routing the copy through `fs.copyFile` keeps everything intact.
 *
 * Why after the wipe-and-write: `prepareGeneratedDir` wipes `src/` based on
 * the compiler manifest. We restore the user's hand-authored tree on top of
 * the fresh write. Files removed from `quickback/apps/` disappear on the
 * next compile — no orphan tracking needed.
 *
 * Collisions with compiler-emitted paths (e.g. dropping a hand-authored
 * `cms/index.tsx` that would shadow the SPA shell) are rejected with a
 * clear error rather than silently overwritten.
 */
import { promises as fs } from 'fs';
import { join, relative, sep } from 'path';
const APPS_DIR_NAME = 'apps';
const TARGET_PREFIX = join('src', 'apps');
/**
 * Subdirectories we never copy. Symmetric with what a developer would
 * `.gitignore` inside a hand-authored app folder. Not configurable for now —
 * if a user has a compelling case to ship `dist/` through the passthrough,
 * they can stage it under `public/` instead.
 */
const SKIP_DIRS = new Set(['node_modules', 'dist', 'build', '.next', '.turbo', '.cache']);
export class SourceAppCollisionError extends Error {
    collisions;
    constructor(collisions) {
        const lines = collisions.map((c) => `  • quickback/${APPS_DIR_NAME}/${c.sourcePath} → ${c.manifestPath} (already emitted by compiler)`);
        super([
            `quickback/${APPS_DIR_NAME}/ contains ${collisions.length} file(s) that would overwrite compiler output:`,
            ...lines,
            `Rename or remove these files. Reserved subpaths under src/apps/ include cms/ and account/ when those SPAs are enabled.`,
        ].join('\n'));
        this.name = 'SourceAppCollisionError';
        this.collisions = collisions;
    }
}
/**
 * Copy `<projectRoot>/quickback/apps/**` into `<outputDir>/src/apps/**`.
 *
 * Returns `{ copied: 0 }` when the source folder is absent — apps/ is an
 * opt-in convention, not a required directory.
 */
export async function copySourceApps(options) {
    const sourceDir = join(options.projectRoot, 'quickback', APPS_DIR_NAME);
    if (!(await isDirectory(sourceDir))) {
        return { copied: 0, destPaths: [] };
    }
    const sources = await collectFiles(sourceDir);
    if (sources.length === 0) {
        return { copied: 0, sourceDir, destPaths: [] };
    }
    const manifestSet = new Set();
    for (const p of options.manifestPaths)
        manifestSet.add(toPosix(p));
    const collisions = [];
    const destPaths = [];
    for (const rel of sources) {
        const dest = join(TARGET_PREFIX, rel);
        const destPosix = toPosix(dest);
        if (manifestSet.has(destPosix)) {
            collisions.push({ sourcePath: toPosix(rel), manifestPath: destPosix });
        }
        destPaths.push(dest);
    }
    if (collisions.length > 0) {
        throw new SourceAppCollisionError(collisions);
    }
    for (const rel of sources) {
        const src = join(sourceDir, rel);
        const dest = join(options.outputDir, TARGET_PREFIX, rel);
        await fs.mkdir(join(dest, '..'), { recursive: true });
        await fs.copyFile(src, dest);
    }
    return { copied: sources.length, sourceDir, destPaths };
}
async function isDirectory(p) {
    try {
        const s = await fs.stat(p);
        return s.isDirectory();
    }
    catch {
        return false;
    }
}
/**
 * Walk a directory tree and return every regular file as a path relative
 * to the root. Hidden entries (`.DS_Store`, `.git/`, dotfiles) and the
 * `SKIP_DIRS` set (`node_modules/`, `dist/`, `build/`, etc.) are pruned —
 * accidentally bundling those would inflate the worker output and was a
 * top reason `quickback/public/` couldn't simply absorb this use case.
 */
async function collectFiles(root) {
    const out = [];
    async function walk(dir) {
        const entries = await fs.readdir(dir, { withFileTypes: true });
        for (const entry of entries) {
            if (entry.name.startsWith('.'))
                continue;
            if (entry.isDirectory() && SKIP_DIRS.has(entry.name))
                continue;
            const full = join(dir, entry.name);
            if (entry.isDirectory()) {
                await walk(full);
            }
            else if (entry.isFile()) {
                out.push(relative(root, full));
            }
        }
    }
    await walk(root);
    out.sort();
    return out;
}
function toPosix(p) {
    return sep === '/' ? p : p.split(sep).join('/');
}
//# sourceMappingURL=copy-source-apps.js.map