import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
import { createHash } from "node:crypto";
import { dirname, extname, join, relative, resolve } from "node:path";
export const DEFAULT_AURA_ASSET_MANIFEST = "aura.assets.json";
export const DEFAULT_AURA_ASSET_OUTPUT_DIR = "public/aura-assets";
export const DEFAULT_AURA_ASSET_PUBLIC_PATH = "/aura-assets/";
export const DEFAULT_AURA_ASSET_TYPEGEN = "src/aura-assets.ts";
export function addAsset(options) {
    const projectDir = resolve(options.projectDir ?? process.cwd());
    const sourcePath = resolve(projectDir, options.file);
    if (!existsSync(sourcePath)) {
        throw new Error(`Aura3D assets add failed: "${options.file}" does not exist. Suggested fix: pass a real local GLB/glTF/texture path.`);
    }
    const manifestPath = resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST);
    const current = readAssetManifest(projectDir);
    const outputDir = normalizeRelativePath(options.outputDir ?? current.outputDir ?? DEFAULT_AURA_ASSET_OUTPUT_DIR);
    const publicPath = normalizePublicPath(options.publicPath ?? current.assetBasePath ?? DEFAULT_AURA_ASSET_PUBLIC_PATH);
    const typegen = normalizeRelativePath(options.typegen ?? current.typegen ?? DEFAULT_AURA_ASSET_TYPEGEN);
    const hash = hashFile(sourcePath);
    const format = extname(sourcePath).slice(1).toLowerCase();
    const type = options.type ?? inferAssetType(format);
    const inspection = inspectAssetFile(sourcePath, format);
    const outputFileName = `${options.name}.${hash.slice(0, 8)}.${format}`;
    const outputPath = join(outputDir, outputFileName);
    if (options.copy !== false) {
        mkdirSync(resolve(projectDir, outputDir), { recursive: true });
        copyFileSync(sourcePath, resolve(projectDir, outputPath));
        copyAssetDependencies(projectDir, sourcePath, outputDir, inspection.dependencies);
    }
    const thumbnailUrl = writeThumbnail(projectDir, outputDir, publicPath, options.name, inspection.bounds);
    const entry = {
        id: options.name,
        type,
        format,
        source: normalizeRelativePath(relative(projectDir, sourcePath)),
        outputPath: normalizeRelativePath(outputPath),
        url: `${publicPath}${outputFileName}`,
        hash: `sha256-${hash}`,
        sizeBytes: statSync(sourcePath).size,
        bounds: inspection.bounds,
        materials: inspection.materials,
        animations: inspection.animations,
        textures: inspection.textures,
        dependencies: inspection.dependencies,
        thumbnailUrl,
        warnings: createAssetWarnings(sourcePath, inspection)
    };
    const manifest = sortManifest({
        schema: "aura3d.assets/1.0",
        assetBasePath: publicPath,
        outputDir,
        typegen,
        assets: [
            ...current.assets.filter((asset) => asset.id !== entry.id),
            entry
        ]
    });
    writeAssetManifest(projectDir, manifest);
    writeTypedAssets(projectDir, manifest);
    return {
        ok: true,
        manifestPath,
        manifest,
        messages: [
            `Added ${entry.id} -> ${entry.url}`,
            `Wrote ${DEFAULT_AURA_ASSET_MANIFEST}`,
            `Wrote ${manifest.typegen}`
        ]
    };
}
export function scanAssets(options) {
    const projectDir = resolve(options.projectDir ?? process.cwd());
    const directory = resolve(projectDir, options.directory);
    if (!existsSync(directory))
        throw new Error(`Aura3D assets scan failed: "${options.directory}" does not exist.`);
    let result;
    for (const file of readdirSync(directory)) {
        const path = join(directory, file);
        if (!statSync(path).isFile())
            continue;
        const format = extname(path).slice(1).toLowerCase();
        if (!["glb", "gltf", "png", "jpg", "jpeg", "webp", "ktx2", "hdr", "exr", "mp3", "wav", "ogg"].includes(format))
            continue;
        const name = sanitizeAssetId(file.replace(/\.[^.]+$/, ""));
        result = addAsset({ projectDir, file: relative(projectDir, path), name });
    }
    return result ?? {
        ok: true,
        manifestPath: resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST),
        manifest: readAssetManifest(projectDir),
        messages: ["No supported assets found."]
    };
}
export function validateAssets(options = {}) {
    const projectDir = resolve(options.projectDir ?? process.cwd());
    const manifestPath = resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST);
    const manifestMissing = !existsSync(manifestPath);
    const manifest = readAssetManifest(projectDir);
    const failures = manifestMissing
        ? [`Missing ${DEFAULT_AURA_ASSET_MANIFEST}. Suggested fix: run aura3d assets add ./asset.glb --name product or aura3d assets scan ./assets.`]
        : [];
    const warnings = [];
    for (const asset of manifest.assets) {
        const outputPath = resolve(projectDir, asset.outputPath);
        if (!existsSync(outputPath)) {
            failures.push(`Missing asset output for "${asset.id}": ${asset.outputPath}`);
            continue;
        }
        const actualHash = `sha256-${hashFile(outputPath)}`;
        if (actualHash !== asset.hash)
            failures.push(`Hash mismatch for "${asset.id}": expected ${asset.hash}, found ${actualHash}`);
        warnings.push(...asset.warnings.map((warning) => `${asset.id}: ${warning}`));
        if (asset.format === "gltf") {
            for (const dependency of asset.dependencies ?? asset.textures) {
                if (dependency.startsWith("data:"))
                    continue;
                const sourceDependencyPath = resolve(dirname(resolve(projectDir, asset.source)), dependency);
                const outputDependencyPath = resolve(dirname(resolve(projectDir, asset.outputPath)), dependency);
                if (!existsSync(sourceDependencyPath) && !existsSync(outputDependencyPath)) {
                    failures.push(`Missing referenced asset dependency for "${asset.id}": ${dependency}`);
                }
            }
        }
    }
    const typegenPath = resolve(projectDir, manifest.typegen);
    if (!existsSync(typegenPath))
        failures.push(`Missing typed asset module: ${manifest.typegen}. Run assets typegen.`);
    return {
        ok: failures.length === 0,
        manifestPath,
        manifest,
        failures,
        warnings,
        messages: failures.length === 0 ? ["Asset manifest is valid."] : failures
    };
}
export function writeTypedAssets(projectDir, manifest = readAssetManifest(projectDir)) {
    const path = resolve(projectDir, manifest.typegen);
    mkdirSync(dirname(path), { recursive: true });
    const lines = [
        `import { defineAuraAssets } from "../engine/index.js";`,
        "",
        "export const assets = defineAuraAssets({",
        ...manifest.assets.map((asset) => {
            const metadata = {
                materials: asset.materials,
                animations: asset.animations,
                textures: asset.textures,
                dependencies: asset.dependencies ?? [],
                thumbnailUrl: asset.thumbnailUrl
            };
            const fields = [
                `type: ${JSON.stringify(asset.type)}`,
                `format: ${JSON.stringify(asset.format)}`,
                `url: ${JSON.stringify(asset.url)}`,
                `hash: ${JSON.stringify(asset.hash)}`,
                `bounds: ${JSON.stringify(asset.bounds ?? [0, 0, 0])}`,
                `sizeBytes: ${asset.sizeBytes}`,
                `metadata: ${JSON.stringify(metadata)}`
            ];
            return `  ${JSON.stringify(asset.id)}: { ${fields.join(", ")} },`;
        }),
        "} as const);",
        "",
        "export type AuraGeneratedAssets = typeof assets;",
        ""
    ];
    writeFileSync(path, lines.join("\n"));
    return path;
}
export function listAssets(options = {}) {
    return readAssetManifest(resolve(options.projectDir ?? process.cwd())).assets;
}
export function createAssetThumbnails(options = {}) {
    const projectDir = resolve(options.projectDir ?? process.cwd());
    const manifest = readAssetManifest(projectDir);
    const assets = manifest.assets.map((asset) => ({
        ...asset,
        thumbnailUrl: writeThumbnail(projectDir, manifest.outputDir, manifest.assetBasePath, asset.id, asset.bounds)
    }));
    const next = sortManifest({ ...manifest, assets });
    writeAssetManifest(projectDir, next);
    writeTypedAssets(projectDir, next);
    return {
        ok: true,
        manifestPath: resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST),
        manifest: next,
        messages: [`Generated ${next.assets.length} thumbnails.`]
    };
}
export function doctor(options = {}) {
    const validation = validateAssets(options);
    const packagePath = resolve(options.projectDir ?? process.cwd(), "package.json");
    const failures = [...validation.failures];
    if (!existsSync(packagePath))
        failures.push("Missing package.json.");
    return {
        ...validation,
        ok: failures.length === 0,
        failures,
        messages: failures.length === 0 ? ["Aura3D project doctor passed."] : failures
    };
}
export function checkDeploy(options = {}) {
    const projectDir = resolve(options.projectDir ?? process.cwd());
    const manifest = readAssetManifest(projectDir);
    const distDir = normalizeRelativePath(options.distDir ?? "dist");
    const failures = [];
    const warnings = [];
    for (const asset of manifest.assets) {
        const distPath = resolve(projectDir, distDir, asset.url.replace(/^\//, ""));
        const publicPath = resolve(projectDir, asset.outputPath);
        if (!existsSync(distPath) && !existsSync(publicPath)) {
            failures.push(`Deploy check missing hashed asset for "${asset.id}": expected ${asset.url} in ${distDir} or ${asset.outputPath}`);
        }
        if (!/[a-f0-9]{8}\.[^.]+$/i.test(asset.url))
            warnings.push(`${asset.id}: URL is not fingerprinted: ${asset.url}`);
    }
    const validation = validateAssets({ projectDir });
    failures.push(...validation.failures);
    warnings.push(...validation.warnings);
    return {
        ok: failures.length === 0,
        manifestPath: resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST),
        manifest,
        failures,
        warnings,
        messages: failures.length === 0 ? ["Deploy check passed."] : failures
    };
}
export function initAgentFiles(options) {
    const projectDir = resolve(options.projectDir ?? process.cwd());
    const targets = options.agent === "all" ? ["generic", "claude", "cursor", "copilot"] : [options.agent];
    const written = [];
    for (const target of targets) {
        if (target === "generic")
            written.push(writeAgentFile(projectDir, "AGENTS.md", genericAgentText()));
        if (target === "claude")
            written.push(writeAgentFile(projectDir, ".claude/CLAUDE.md", genericAgentText("Claude")));
        if (target === "cursor")
            written.push(writeAgentFile(projectDir, ".cursor/rules/aura3d.mdc", genericAgentText("Cursor")));
        if (target === "copilot")
            written.push(writeAgentFile(projectDir, ".github/copilot-instructions.md", genericAgentText("GitHub Copilot")));
    }
    return written;
}
export function readAssetManifest(projectDir) {
    const manifestPath = resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST);
    if (!existsSync(manifestPath)) {
        return {
            schema: "aura3d.assets/1.0",
            assetBasePath: DEFAULT_AURA_ASSET_PUBLIC_PATH,
            outputDir: DEFAULT_AURA_ASSET_OUTPUT_DIR,
            typegen: DEFAULT_AURA_ASSET_TYPEGEN,
            assets: []
        };
    }
    const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
    if (parsed.schema !== "aura3d.assets/1.0")
        throw new Error(`Unsupported Aura3D asset manifest schema: ${String(parsed.schema)}`);
    return parsed;
}
export function writeAssetManifest(projectDir, manifest) {
    writeFileSync(resolve(projectDir, DEFAULT_AURA_ASSET_MANIFEST), `${JSON.stringify(manifest, null, 2)}\n`);
}
function inspectAssetFile(path, format) {
    if (format === "gltf")
        return inspectGltf(JSON.parse(readFileSync(path, "utf8")), dirname(path));
    if (format === "glb")
        return inspectGlb(readFileSync(path), dirname(path));
    return {
        materials: [],
        animations: [],
        textures: [],
        dependencies: [],
        bounds: undefined
    };
}
function inspectGlb(buffer, baseDir) {
    if (buffer.toString("utf8", 0, 4) !== "glTF")
        throw new Error("Invalid GLB header. Suggested fix: re-export the asset as binary glTF (.glb).");
    const length = buffer.readUInt32LE(8);
    if (length > buffer.length)
        throw new Error("Invalid GLB length. Suggested fix: run assets validate on the original export.");
    const chunkLength = buffer.readUInt32LE(12);
    const chunkType = buffer.toString("utf8", 16, 20);
    if (chunkType !== "JSON")
        throw new Error("Invalid GLB JSON chunk. Suggested fix: re-export the GLB.");
    const json = JSON.parse(buffer.toString("utf8", 20, 20 + chunkLength).trim());
    return inspectGltf(json, baseDir);
}
function inspectGltf(json, baseDir) {
    const dependencies = [
        ...(json.images ?? []).map((image) => image.uri).filter(isExternalUri),
        ...(json.buffers ?? []).map((buffer) => buffer.uri).filter(isExternalUri)
    ];
    if (baseDir) {
        const missing = dependencies.filter((dependency) => !existsSync(resolve(baseDir, dependency)));
        if (missing.length > 0) {
            throw new Error(`Aura3D assets add failed: referenced asset file missing: ${missing.join(", ")}. Suggested fix: keep external .bin and texture files beside the .gltf or export as .glb.`);
        }
    }
    return {
        bounds: extractBounds(json),
        materials: (json.materials ?? []).map((material, index) => material.name ?? `material-${index}`),
        animations: (json.animations ?? []).map((animation, index) => animation.name ?? `clip-${index}`),
        textures: (json.images ?? []).map((image, index) => image.uri ?? image.name ?? `image-${index}`),
        dependencies
    };
}
function isExternalUri(uri) {
    return typeof uri === "string" && uri.length > 0 && !uri.startsWith("data:");
}
function extractBounds(json) {
    let min;
    let max;
    for (const accessor of json.accessors ?? []) {
        if (!accessor.min || !accessor.max || accessor.min.length < 3 || accessor.max.length < 3)
            continue;
        min = min ? [Math.min(min[0], accessor.min[0]), Math.min(min[1], accessor.min[1]), Math.min(min[2], accessor.min[2])] : [accessor.min[0], accessor.min[1], accessor.min[2]];
        max = max ? [Math.max(max[0], accessor.max[0]), Math.max(max[1], accessor.max[1]), Math.max(max[2], accessor.max[2])] : [accessor.max[0], accessor.max[1], accessor.max[2]];
    }
    return min && max ? [round(max[0] - min[0]), round(max[1] - min[1]), round(max[2] - min[2])] : undefined;
}
function createAssetWarnings(path, inspection) {
    const warnings = [];
    const size = statSync(path).size;
    if (size > 25 * 1024 * 1024)
        warnings.push("asset exceeds 25 MB; consider compression before deployment");
    if (!inspection.bounds)
        warnings.push("bounds could not be extracted");
    if (inspection.textures.length === 0 && ["glb", "gltf"].includes(extname(path).slice(1).toLowerCase()))
        warnings.push("no texture references detected");
    return warnings;
}
function copyAssetDependencies(projectDir, sourcePath, outputDir, dependencies) {
    const sourceDir = dirname(sourcePath);
    for (const dependency of dependencies) {
        if (dependency.startsWith("data:"))
            continue;
        const sourceDependencyPath = resolve(sourceDir, dependency);
        const outputDependencyPath = resolve(projectDir, outputDir, dependency);
        mkdirSync(dirname(outputDependencyPath), { recursive: true });
        copyFileSync(sourceDependencyPath, outputDependencyPath);
    }
}
function writeThumbnail(projectDir, outputDir, publicPath, id, bounds) {
    const fileName = `${id}.thumb.svg`;
    const outputPath = resolve(projectDir, outputDir, fileName);
    mkdirSync(dirname(outputPath), { recursive: true });
    const label = `${id}${bounds ? ` ${bounds.join("x")}` : ""}`;
    writeFileSync(outputPath, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180"><rect width="320" height="180" fill="#101720"/><path d="M80 126 160 46l80 80-80 28z" fill="#77a7ff"/><text x="160" y="160" text-anchor="middle" font-family="Arial" font-size="18" fill="#f4f7fb">${escapeXml(label)}</text></svg>`);
    return `${publicPath}${fileName}`;
}
function inferAssetType(format) {
    if (["glb", "gltf"].includes(format))
        return "model";
    if (["png", "jpg", "jpeg", "webp", "ktx2"].includes(format))
        return "texture";
    if (["hdr", "exr"].includes(format))
        return "environment";
    if (["mp3", "wav", "ogg"].includes(format))
        return "audio";
    throw new Error(`Unsupported Aura3D asset format: ${format || "unknown"}. Suggested fix: use glb, gltf, png, jpg, webp, ktx2, hdr, exr, mp3, wav, or ogg.`);
}
function hashFile(path) {
    return createHash("sha256").update(readFileSync(path)).digest("hex");
}
function sortManifest(manifest) {
    return {
        ...manifest,
        assets: [...manifest.assets].sort((a, b) => a.id.localeCompare(b.id))
    };
}
function normalizePublicPath(path) {
    const withStart = path.startsWith("/") || path.startsWith("http") ? path : `/${path}`;
    return withStart.endsWith("/") ? withStart : `${withStart}/`;
}
function normalizeRelativePath(path) {
    return path.replaceAll("\\", "/").replace(/^\.\//, "");
}
function sanitizeAssetId(value) {
    return value.replace(/[^a-zA-Z0-9_]+(.)/g, (_, char) => char.toUpperCase()).replace(/^[^a-zA-Z_]+/, "") || "asset";
}
function round(value) {
    return Math.round(value * 1000) / 1000;
}
function writeAgentFile(projectDir, path, contents) {
    const output = resolve(projectDir, path);
    mkdirSync(dirname(output), { recursive: true });
    writeFileSync(output, contents);
    return output;
}
function genericAgentText(agent = "AI coding agent") {
    return `# Aura3D Instructions For ${agent}

Read ./llms.txt first, then ./docs/agents/README.md.

Use @aura3d/engine public imports only:
- createAuraApp
- scene
- model
- camera
- lights
- material
- effects
- timeline
- interactions
- defineAuraAssets

Do not invent asset paths or asset ids. Read ./src/aura-assets.ts after running:

npx @aura3d/cli@latest assets add ./assets/model.glb --name model

Use model(assets.model), not model("model").
Run npm run build and the template route-health/screenshot tests before claiming the scene is done.
`;
}
function escapeXml(value) {
    return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
}
//# sourceMappingURL=index.js.map