/**
 * Build metadata generator for non-Agentuity framework deployments.
 *
 * For Agentuity native apps, the existing metadata-generator.ts creates
 * full BuildMetadata with routes, agents, assets from the Vite pipeline.
 *
 * For other frameworks (Next.js, Vite, SvelteKit, etc.), this module
 * generates a BuildMetadata from the BuildResult + LaunchMetadata,
 * with empty routes/agents and assets enumerated from the static dir.
 */
import { randomBytes } from 'node:crypto';
import { existsSync, readdirSync, statSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { basename, join, relative } from 'node:path';
import { getContentType } from '@agentuity/server';
import { runtimeKind, runtimeVersion } from "./node-compat/runtime-info.js";
import { buildGitTags, getGitInfo } from "./utils/git.js";
import { getVersion } from "./version.js";
/**
 * Determine if an asset should be compressed with gzip for CDN upload.
 */
function shouldCompress(contentType) {
    const compressible = [
        'text/',
        'application/javascript',
        'application/json',
        'application/xml',
        'application/xhtml+xml',
        'image/svg+xml',
    ];
    return compressible.some((prefix) => contentType.startsWith(prefix));
}
const BUILD_INFRA_FILES = new Set([
    'launch.json',
    'package.json',
    'package-lock.json',
    'npm-shrinkwrap.json',
    'pnpm-lock.yaml',
    'yarn.lock',
    'bun.lock',
    'bun.lockb',
    '_serve.js',
]);
/**
 * Recursively enumerate static assets from a directory.
 */
function enumerateAssets(dir, baseDir) {
    const assets = [];
    if (!existsSync(dir))
        return assets;
    const entries = readdirSync(dir, { withFileTypes: true });
    for (const entry of entries) {
        const fullPath = join(dir, entry.name);
        if (entry.isDirectory()) {
            // Skip dot-directories and node_modules
            if (entry.name.startsWith('.') || entry.name === 'node_modules')
                continue;
            assets.push(...enumerateAssets(fullPath, baseDir));
        }
        else {
            // Skip dot-files and build/runtime metadata files.
            if (entry.name.startsWith('.') || BUILD_INFRA_FILES.has(entry.name))
                continue;
            const stats = statSync(fullPath);
            if (stats.size === 0)
                continue;
            const relativePath = relative(baseDir, fullPath);
            const contentType = getContentType(entry.name);
            const asset = {
                filename: relativePath,
                kind: getAssetKind(entry.name),
                contentType,
                size: stats.size,
            };
            if (shouldCompress(contentType)) {
                asset.contentEncoding = 'gzip';
            }
            assets.push(asset);
        }
    }
    return assets;
}
/**
 * Lowercase + kebab-case a string for use as a fallback project name.
 * Collapses runs of non-alphanumeric characters into a single `-` and
 * trims leading/trailing dashes. If the input has no usable characters
 * (e.g. deploying from `/`), falls back to `project-<shortsha>` so the
 * deployment still gets a unique, readable slug.
 */
function kebabize(input) {
    const out = input
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '');
    if (out)
        return out;
    return `project-${randomBytes(4).toString('hex')}`;
}
/**
 * Classify an asset by file extension.
 */
function getAssetKind(filename) {
    const ext = filename.split('.').pop()?.toLowerCase();
    switch (ext) {
        case 'js':
        case 'mjs':
        case 'cjs':
            return 'script';
        case 'css':
            return 'stylesheet';
        case 'html':
            return 'page';
        case 'json':
            return 'data';
        case 'svg':
        case 'png':
        case 'jpg':
        case 'jpeg':
        case 'gif':
        case 'webp':
        case 'avif':
        case 'ico':
            return 'image';
        case 'woff':
        case 'woff2':
        case 'ttf':
        case 'otf':
        case 'eot':
            return 'font';
        case 'map':
            return 'sourcemap';
        default:
            return 'static';
    }
}
/**
 * Generate BuildMetadata for a non-Agentuity framework deployment.
 *
 * Creates the metadata the API expects, with:
 * - Empty routes and agents (non-Agentuity apps don't have them)
 * - Static assets enumerated from BuildResult.staticDir
 * - Launch metadata for the backend to know how to start the app
 * - Standard project/deployment info
 */
export async function generateDeployMetadata(options) {
    const { buildResult, packageResult, projectDir, projectId, orgId, deploymentId, deploymentConfig, deploymentOptions, registeredProjectName, logger, } = options;
    // Read package.json for project info
    let pkgContents = {};
    const pkgPath = join(projectDir, 'package.json');
    if (existsSync(pkgPath)) {
        try {
            pkgContents = JSON.parse(await readFile(pkgPath, 'utf-8'));
        }
        catch (error) {
            logger.warn(`Failed to read package.json: ${error}`);
        }
    }
    // Enumerate static assets if we have a static directory
    const assets = [];
    if (buildResult.staticDir && existsSync(buildResult.staticDir)) {
        const outputDir = buildResult.outputDir;
        const staticAssets = enumerateAssets(buildResult.staticDir, outputDir);
        assets.push(...staticAssets);
        logger.debug(`Found ${assets.length} static assets in ${buildResult.staticDir}`);
    }
    // Build git info
    const gitInfo = await getGitInfo(projectDir, logger);
    const metadata = {
        routes: [],
        agents: [],
        assets,
        project: {
            id: projectId,
            // Once a project is registered, its name is owned by the cloud, not
            // package.json. Renaming package.json must not try to rename the
            // cloud project (the server enforces name uniqueness within an org
            // and will reject the deploy if the new name collides).
            name: registeredProjectName ?? pkgContents.name ?? kebabize(basename(projectDir)),
            version: pkgContents.version,
            description: pkgContents.description,
            keywords: pkgContents.keywords,
            orgId,
        },
        deployment: {
            ...deploymentConfig,
            id: deploymentId,
            date: new Date().toISOString(),
            build: {
                // Reports the host runtime in the same `bun: <ver>` field for
                // backwards compatibility with the existing metadata schema.
                // Under Bun this is `Bun.version`; under Node it's the Node
                // version with a 'node-' prefix so consumers can disambiguate.
                bun: runtimeKind() === 'bun' ? runtimeVersion() : `node-${runtimeVersion()}`,
                agentuity: getVersion(),
                arch: process.arch,
                platform: process.platform,
            },
            git: gitInfo,
        },
        launch: packageResult.launch,
    };
    // Build git tags
    if (metadata.deployment.git) {
        metadata.deployment.git.tags = buildGitTags(metadata.deployment.git);
    }
    // Merge deployment options (CI info, etc.)
    if (deploymentOptions) {
        const git = { ...(metadata.deployment.git ?? {}), ...deploymentOptions };
        if (deploymentOptions.pullRequestNumber) {
            git.pull_request = {
                number: deploymentOptions.pullRequestNumber,
                url: deploymentOptions.pullRequestUrl,
            };
            delete git.pullRequestNumber;
            delete git.pullRequestUrl;
        }
        metadata.deployment.git = git;
    }
    return metadata;
}
//# sourceMappingURL=deploy-metadata.js.map