/**
 * Static-asset passthrough for `quickback/public/`.
 *
 * Anything under `<projectRoot>/quickback/public/` is copied verbatim into
 * `<outputDir>/src/apps/`, preserving directory structure. The compiler
 * never sees these files — they ride past it as bytes and land in the
 * Cloudflare ASSETS directory the worker already binds for the SPAs.
 *
 * Why bytes, not strings: `writeFilesWithMerge` writes `content` as utf-8,
 * which corrupts PNGs / fonts / PDFs. Routing the copy through a separate
 * `fs.copyFile` step keeps binaries intact and avoids a base64 round-trip
 * through the compiler API.
 *
 * Why after the wipe-and-write: `prepareGeneratedDir` wipes `src/` based on
 * the compiler's manifest, so on every compile we start clean and copy
 * fresh. Files removed from `quickback/public/` disappear automatically on
 * the next compile — no orphan tracking needed.
 *
 * Collisions with compiler-emitted paths (e.g. a user dropping
 * `cms/index.html` into public/ and shadowing the SPA shell) are rejected
 * with a clear error rather than silently overwritten — silent overwrite
 * would make a confused SPA build look like a broken compiler.
 */
import { promises as fs } from 'fs';
import { join, relative, sep } from 'path';
const PUBLIC_DIR_NAME = 'public';
const TARGET_PREFIX = join('src', 'apps');
export class PublicAssetCollisionError extends Error {
    collisions;
    constructor(collisions) {
        const lines = collisions.map((c) => `  • quickback/${PUBLIC_DIR_NAME}/${c.publicPath} → ${c.manifestPath} (already emitted by compiler)`);
        super([
            `quickback/${PUBLIC_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 = 'PublicAssetCollisionError';
        this.collisions = collisions;
    }
}
/**
 * Copy `<projectRoot>/quickback/public/**` into `<outputDir>/src/apps/**`.
 *
 * Returns `{ copied: 0 }` when the source folder is absent — public/ is
 * an opt-in convention, not a required directory.
 */
export async function copyPublicAssets(options) {
    const sourceDir = join(options.projectRoot, 'quickback', PUBLIC_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({ publicPath: toPosix(rel), manifestPath: destPosix });
        }
        destPaths.push(dest);
    }
    if (collisions.length > 0) {
        throw new PublicAssetCollisionError(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/`, etc.) are skipped —
 * the user did not "drop these in public/" intentionally and they break
 * worker asset bindings.
 */
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;
            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-public-assets.js.map