"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEmptyDevRuntimeAssetManifest = createEmptyDevRuntimeAssetManifest;
exports.buildLocalDevRuntimeAssetsUrl = buildLocalDevRuntimeAssetsUrl;
exports.buildLocalDevRuntimeAssetFileUrl = buildLocalDevRuntimeAssetFileUrl;
exports.buildDevRuntimeAssetManifest = buildDevRuntimeAssetManifest;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const semver_1 = require("semver");
const types_1 = require("@playdrop/types");
function createEmptyDevRuntimeAssetManifest() {
    return {
        response: {
            appVersionId: 0,
            appVersion: 'dev',
            assets: [],
            localAppMetadata: {
                type: 'GAME',
                surfaceTargets: Array.from(types_1.DEFAULT_APP_SURFACE_TARGETS),
                authMode: types_1.DEFAULT_APP_AUTH_MODE,
                controllerMode: types_1.DEFAULT_APP_CONTROLLER_MODE,
                previewable: types_1.DEFAULT_APP_PREVIEWABLE,
                assetSpecSupport: [],
                version: null,
            },
        },
        ownedAssetFiles: {},
    };
}
const CONTENT_TYPE_BY_EXTENSION = {
    '.html': 'text/html; charset=utf-8',
    '.js': 'application/javascript; charset=utf-8',
    '.mjs': 'application/javascript; charset=utf-8',
    '.cjs': 'application/javascript; charset=utf-8',
    '.css': 'text/css; charset=utf-8',
    '.json': 'application/json; charset=utf-8',
    '.png': 'image/png',
    '.jpg': 'image/jpeg',
    '.jpeg': 'image/jpeg',
    '.svg': 'image/svg+xml',
    '.webp': 'image/webp',
    '.glb': 'model/gltf-binary',
    '.gltf': 'model/gltf+json',
    '.wasm': 'application/wasm',
    '.mp4': 'video/mp4',
    '.webm': 'video/webm',
    '.ogg': 'audio/ogg',
    '.mp3': 'audio/mpeg',
    '.wav': 'audio/wav',
};
function inferContentType(filePath) {
    const extension = (0, node_path_1.extname)(filePath).toLowerCase();
    return CONTENT_TYPE_BY_EXTENSION[extension] ?? null;
}
function normalizeManifestFiles(fileManifest) {
    const files = fileManifest && typeof fileManifest === 'object' && Array.isArray(fileManifest.files)
        ? fileManifest.files
        : [];
    return files
        .filter((entry) => Boolean(entry && typeof entry === 'object'))
        .map((entry) => ({
        role: typeof entry.role === 'string' ? entry.role.trim().toLowerCase() : '',
        key: typeof entry.key === 'string' ? entry.key.trim() : '',
        contentType: typeof entry.contentType === 'string' ? entry.contentType : null,
        sizeBytes: typeof entry.sizeBytes === 'number' && Number.isFinite(entry.sizeBytes) ? entry.sizeBytes : null,
        sha256: typeof entry.sha256 === 'string' ? entry.sha256 : null,
    }))
        .filter((entry) => entry.role.length > 0);
}
function buildPublishedAssetFileUrl(apiBase, creatorUsername, assetName, revision, role) {
    const path = `/assets/${encodeURIComponent(creatorUsername)}/${encodeURIComponent(assetName)}/versions/${encodeURIComponent(`r${revision}`)}/file?role=${encodeURIComponent(role)}`;
    return new URL(path, apiBase).toString();
}
function buildSyntheticLocalAssetRef(creatorUsername, appName, ownedAssetName) {
    return (0, types_1.formatContentVersionRef)({
        kind: 'asset',
        creatorUsername: `${creatorUsername}-dev`,
        name: `${appName}-${ownedAssetName}`,
        revision: 1,
    });
}
function buildLocalDevRuntimeAssetsUrl(appBaseUrl) {
    const base = appBaseUrl.endsWith('/') ? appBaseUrl.slice(0, -1) : appBaseUrl;
    return `${base}/_playdrop/runtime-assets`;
}
function buildLocalDevRuntimeAssetFileUrl(appBaseUrl, ownedAssetName, role) {
    const base = appBaseUrl.endsWith('/') ? appBaseUrl.slice(0, -1) : appBaseUrl;
    return `${base}/_playdrop/runtime-assets/files/${encodeURIComponent(ownedAssetName)}?role=${encodeURIComponent(role)}`;
}
async function findAssetVersionByRevision(client, creatorUsername, assetName, revision) {
    let offset = 0;
    for (let page = 0; page < 200; page += 1) {
        const response = await client.listAssetVersions(creatorUsername, assetName, {
            limit: 100,
            offset,
        });
        const version = (response.versions ?? []).find((entry) => entry.revision === revision);
        if (version) {
            return version;
        }
        if (!response.pagination?.hasMore) {
            break;
        }
        offset += response.pagination.limit;
    }
    throw new Error(`dev_runtime_asset_version_not_found:${creatorUsername}/${assetName}@r${revision}`);
}
async function findPackVersion(client, creatorUsername, packName, versionString) {
    let offset = 0;
    for (let page = 0; page < 200; page += 1) {
        const response = await client.listAssetPackVersions(creatorUsername, packName, {
            limit: 100,
            offset,
        });
        const version = (response.versions ?? []).find((entry) => entry.version === versionString);
        if (version) {
            return version;
        }
        if (!response.pagination?.hasMore) {
            break;
        }
        offset += response.pagination.limit;
    }
    throw new Error(`dev_runtime_pack_version_not_found:${creatorUsername}/${packName}@${versionString}`);
}
function createPublishedAssetSummary(input) {
    const files = normalizeManifestFiles(input.version.fileManifest).map((file) => ({
        role: file.role,
        url: buildPublishedAssetFileUrl(input.apiBase, input.creatorUsername, input.assetName, input.revision, file.role),
        contentType: file.contentType ?? null,
        sizeBytes: file.sizeBytes ?? null,
        sha256: file.sha256 ?? null,
    }));
    if (files.length === 0) {
        throw new Error(`dev_runtime_asset_files_missing:${input.creatorUsername}/${input.assetName}@r${input.revision}`);
    }
    return {
        assetRef: (0, types_1.formatContentVersionRef)({
            kind: 'asset',
            creatorUsername: input.creatorUsername,
            name: input.assetName,
            revision: input.revision,
        }),
        runtimeKey: input.runtimeKey?.trim() || null,
        sourceType: input.sourceType,
        ...(input.sourcePackRef ? { sourcePackRef: input.sourcePackRef } : {}),
        files,
    };
}
async function resolvePublishedAssetSummary(client, apiBase, assetRef, options) {
    const parsed = (0, types_1.parseContentVersionRef)(assetRef);
    if (!parsed || parsed.kind !== 'asset') {
        throw new Error(`dev_runtime_asset_ref_invalid:${assetRef}`);
    }
    const version = await findAssetVersionByRevision(client, parsed.creatorUsername, parsed.name, parsed.revision);
    if (version.visibility !== 'PUBLIC') {
        throw new Error(`dev_runtime_asset_not_public:${assetRef}`);
    }
    return createPublishedAssetSummary({
        apiBase,
        creatorUsername: parsed.creatorUsername,
        assetName: parsed.name,
        revision: parsed.revision,
        version,
        runtimeKey: options.runtimeKey,
        sourceType: options.sourceType,
        sourcePackRef: options.sourcePackRef,
    });
}
function createLocalOwnedAssetSummary(task, creatorUsername, appBaseUrl, ownedAsset) {
    const files = {};
    const runtimeFiles = [];
    for (const [role, absolutePath] of Object.entries(ownedAsset.filePaths)) {
        const stats = (0, node_fs_1.statSync)(absolutePath);
        const contentType = inferContentType(absolutePath);
        files[role] = {
            absolutePath,
            contentType,
            sizeBytes: stats.size,
        };
        runtimeFiles.push({
            role,
            url: buildLocalDevRuntimeAssetFileUrl(appBaseUrl, ownedAsset.name, role),
            contentType,
            sizeBytes: stats.size,
            sha256: null,
        });
    }
    return {
        asset: {
            assetRef: buildSyntheticLocalAssetRef(creatorUsername, task.name, ownedAsset.name),
            runtimeKey: ownedAsset.runtimeKey?.trim() || null,
            sourceType: 'OWNED',
            files: runtimeFiles,
        },
        files,
    };
}
function assertRuntimeKeyAvailable(seenRuntimeKeys, runtimeKey, context) {
    const normalized = typeof runtimeKey === 'string' ? runtimeKey.trim() : '';
    if (!normalized) {
        return;
    }
    if (seenRuntimeKeys.has(normalized)) {
        throw new Error(`dev_runtime_key_duplicate:${normalized}:${context}`);
    }
    seenRuntimeKeys.add(normalized);
}
function buildLocalAppMetadata(task) {
    if (!task.type) {
        throw new Error(`dev_runtime_app_type_missing:${task.name}`);
    }
    return {
        type: task.type,
        surfaceTargets: task.surfaceTargets.length > 0 ? Array.from(task.surfaceTargets) : Array.from(types_1.DEFAULT_APP_SURFACE_TARGETS),
        authMode: task.authMode ?? types_1.DEFAULT_APP_AUTH_MODE,
        controllerMode: task.controllerMode ?? types_1.DEFAULT_APP_CONTROLLER_MODE,
        previewable: task.previewable ?? types_1.DEFAULT_APP_PREVIEWABLE,
        assetSpecSupport: task.assetSpecSupport.map((entry) => ({
            assetSpecRef: entry.assetSpec,
            versionRange: entry.versionRange,
            capabilities: Array.from(entry.capabilities),
        })),
        version: typeof task.version === 'string' && task.version.trim().length > 0 ? task.version.trim() : null,
    };
}
function assertCustomAssetVersionSupported(task, assetRef, version) {
    if (typeof version.assetSpecVersionRef !== 'string' || version.assetSpecVersionRef.trim().length === 0) {
        return;
    }
    const parsedAssetSpecVersion = (0, types_1.parseAssetSpecVersionRef)(version.assetSpecVersionRef);
    if (!parsedAssetSpecVersion) {
        throw new Error(`dev_runtime_asset_spec_invalid:${assetRef}:${version.assetSpecVersionRef}`);
    }
    const assetSpecFamilyRef = (0, types_1.formatAssetSpecFamilyRef)({
        kind: 'asset-spec',
        creatorUsername: parsedAssetSpecVersion.creatorUsername,
        name: parsedAssetSpecVersion.name,
    });
    const supported = task.assetSpecSupport.some((entry) => (entry.assetSpec === assetSpecFamilyRef
        && entry.capabilities.includes('READ')
        && (0, semver_1.satisfies)(parsedAssetSpecVersion.version, entry.versionRange, { includePrerelease: false })));
    if (!supported) {
        throw new Error(`dev_runtime_asset_spec_unsupported:${assetRef}:${version.assetSpecVersionRef}`);
    }
}
async function buildDevRuntimeAssetManifest(input) {
    const localAppMetadata = buildLocalAppMetadata(input.task);
    const assets = [];
    const ownedAssetFiles = {};
    const seenRuntimeKeys = new Set();
    const seenOwnedAssetNames = new Set();
    for (const ownedAsset of input.task.ownedAssets) {
        if (seenOwnedAssetNames.has(ownedAsset.name)) {
            throw new Error(`dev_runtime_owned_asset_duplicate:${ownedAsset.name}`);
        }
        seenOwnedAssetNames.add(ownedAsset.name);
        assertRuntimeKeyAvailable(seenRuntimeKeys, ownedAsset.runtimeKey, ownedAsset.name);
        const localAsset = createLocalOwnedAssetSummary(input.task, input.creatorUsername, input.appBaseUrl, ownedAsset);
        assets.push(localAsset.asset);
        ownedAssetFiles[ownedAsset.name] = localAsset.files;
    }
    for (const dependency of input.task.uses.assets) {
        assertRuntimeKeyAvailable(seenRuntimeKeys, dependency.runtimeKey, dependency.ref);
        const parsedDependencyRef = (0, types_1.parseContentVersionRef)(dependency.ref);
        if (!parsedDependencyRef || parsedDependencyRef.kind !== 'asset') {
            throw new Error(`dev_runtime_asset_ref_invalid:${dependency.ref}`);
        }
        const dependencyVersion = await findAssetVersionByRevision(input.client, parsedDependencyRef.creatorUsername, parsedDependencyRef.name, parsedDependencyRef.revision);
        assertCustomAssetVersionSupported(input.task, dependency.ref, dependencyVersion);
        assets.push(await resolvePublishedAssetSummary(input.client, input.apiBase, dependency.ref, {
            runtimeKey: dependency.runtimeKey,
            sourceType: 'DIRECT',
        }));
    }
    for (const packRef of input.task.uses.packs) {
        const parsedPackRef = (0, types_1.parseContentVersionRef)(packRef);
        if (!parsedPackRef || parsedPackRef.kind !== 'pack') {
            throw new Error(`dev_runtime_pack_ref_invalid:${packRef}`);
        }
        const packVersion = await findPackVersion(input.client, parsedPackRef.creatorUsername, parsedPackRef.name, parsedPackRef.version);
        if (packVersion.visibility !== 'PUBLIC') {
            throw new Error(`dev_runtime_pack_not_public:${packRef}`);
        }
        for (const member of packVersion.assets) {
            const memberRef = (0, types_1.formatContentVersionRef)({
                kind: 'asset',
                creatorUsername: member.creatorUsername,
                name: member.name,
                revision: member.revision,
            });
            const memberVersion = await findAssetVersionByRevision(input.client, member.creatorUsername, member.name, member.revision);
            assertCustomAssetVersionSupported(input.task, memberRef, memberVersion);
            assets.push(await resolvePublishedAssetSummary(input.client, input.apiBase, memberRef, {
                sourceType: 'PACK',
                sourcePackRef: packRef,
            }));
        }
    }
    return {
        response: {
            ...createEmptyDevRuntimeAssetManifest().response,
            assets,
            localAppMetadata,
        },
        ownedAssetFiles,
    };
}
