"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAssetRemix = createAssetRemix;
exports.createPackRemix = createPackRemix;
exports.createRemixContent = createRemixContent;
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const types_1 = require("@playdrop/types");
const commandContext_1 = require("../commandContext");
const http_1 = require("../http");
const messages_1 = require("../messages");
const init_1 = require("./init");
const CATALOGUE_FILENAME = 'catalogue.json';
const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
const ALLOWED_CATALOGUE_TOP_LEVEL_KEYS = new Set(['apps', 'assets', 'assetPacks']);
const MIME_TYPE_TO_EXTENSION = {
    'image/png': '.png',
    'image/jpeg': '.jpg',
    'image/webp': '.webp',
    'image/gif': '.gif',
    'audio/mpeg': '.mp3',
    'audio/wav': '.wav',
    'audio/ogg': '.ogg',
    'video/mp4': '.mp4',
    'video/webm': '.webm',
    'model/gltf+json': '.gltf',
    'model/gltf-binary': '.glb',
    'application/json': '.json',
};
function inferExtensionFromCandidate(candidate) {
    try {
        if (candidate.startsWith('http://') || candidate.startsWith('https://')) {
            return (0, node_path_1.extname)(new URL(candidate).pathname);
        }
    }
    catch {
        return (0, node_path_1.extname)(candidate);
    }
    return (0, node_path_1.extname)(candidate);
}
async function ensureWorkspaceCataloguePath() {
    const path = (0, node_path_1.resolve)(process.cwd(), CATALOGUE_FILENAME);
    if ((0, node_fs_1.existsSync)(path) && (0, node_fs_1.statSync)(path).isFile()) {
        return path;
    }
    const initSummary = await (0, init_1.init)('.', { quiet: true });
    if (!initSummary) {
        process.exitCode = process.exitCode || 1;
        throw new Error('workspace_init_failed');
    }
    if (!(0, node_fs_1.existsSync)(path) || !(0, node_fs_1.statSync)(path).isFile()) {
        (0, messages_1.printErrorWithHelp)('Project bootstrap did not create catalogue.json.', ['Run "playdrop project init ." and confirm the workspace files were created before retrying.'], { command: 'project create', includeGeneralHelp: false });
        process.exitCode = 1;
        throw new Error('workspace_init_failed');
    }
    return path;
}
function readWorkspaceCatalogue(path) {
    let parsed;
    try {
        parsed = JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
    }
    catch {
        throw new Error(`Invalid catalogue.json at ${path}.`);
    }
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error(`Invalid catalogue.json at ${path}.`);
    }
    const catalogue = parsed;
    if (Object.prototype.hasOwnProperty.call(catalogue, LEGACY_CATALOGUE_VERSION_KEY)) {
        throw new Error(`catalogue.json at ${path} must not define the legacy top-level version field.`);
    }
    const unsupportedKey = Object.keys(catalogue).find((key) => !ALLOWED_CATALOGUE_TOP_LEVEL_KEYS.has(key));
    if (unsupportedKey) {
        throw new Error(`catalogue.json at ${path} contains unsupported top-level key "${unsupportedKey}".`);
    }
    return catalogue;
}
function writeWorkspaceCatalogue(path, catalogue) {
    (0, node_fs_1.writeFileSync)(path, `${JSON.stringify(catalogue, null, 2)}\n`);
}
function assertWorkspaceRootCanOwnEntries(cataloguePath) {
    const rootDir = (0, node_path_1.resolve)(cataloguePath, '..');
    const rootCataloguePath = (0, node_path_1.join)(rootDir, CATALOGUE_FILENAME);
    const stack = [rootDir];
    while (stack.length > 0) {
        const current = stack.pop();
        const entries = (0, node_fs_1.readdirSync)(current, { withFileTypes: true });
        for (const entry of entries) {
            if (entry.name.startsWith('.') || entry.name === 'node_modules') {
                continue;
            }
            const absolutePath = (0, node_path_1.join)(current, entry.name);
            if (entry.isDirectory()) {
                stack.push(absolutePath);
                continue;
            }
            if (entry.isFile() && entry.name === CATALOGUE_FILENAME && absolutePath !== rootCataloguePath) {
                throw new Error(`catalogue.json at ${cataloguePath} must stay {} because nested catalogue.json files already exist below this directory.`);
            }
        }
    }
}
async function createAuthenticatedClient(commandLabel) {
    const ctx = await (0, commandContext_1.resolveAuthenticatedEnvironmentContext)(commandLabel, commandLabel, { workspacePath: process.cwd() });
    if (!ctx) {
        throw new Error('missing_token');
    }
    return ctx.client;
}
function ensureUniqueEntryName(catalogue, kind, name) {
    const entries = kind === 'asset' ? catalogue.assets : catalogue.assetPacks;
    const exists = Array.isArray(entries)
        && entries.some((entry) => typeof entry?.name === 'string' && entry.name.trim() === name);
    if (exists) {
        throw new Error(`catalogue.json already contains ${kind} "${name}".`);
    }
}
function parseAssetManifestFiles(value) {
    if (!value || typeof value !== 'object' || Array.isArray(value)) {
        throw new Error('Asset version is missing fileManifest.');
    }
    const manifest = value;
    if (!Array.isArray(manifest.files) || manifest.files.length === 0) {
        throw new Error('Asset version is missing fileManifest.files.');
    }
    return manifest.files.filter((entry) => {
        return Boolean(entry && typeof entry.role === 'string' && entry.role.trim().length > 0);
    });
}
function inferExtensionFromManifestEntry(entry) {
    const candidates = [entry.key, entry.url].filter((value) => typeof value === 'string' && value.length > 0);
    for (const candidate of candidates) {
        const extension = inferExtensionFromCandidate(candidate);
        if (extension) {
            return extension;
        }
    }
    const mimeType = typeof entry.contentType === 'string' ? entry.contentType.trim().toLowerCase() : '';
    return MIME_TYPE_TO_EXTENSION[mimeType] ?? '';
}
function buildLocalFilename(entry, usedNames) {
    const sourceName = typeof entry.key === 'string' && entry.key.length > 0
        ? (0, node_path_1.basename)(entry.key)
        : typeof entry.url === 'string' && entry.url.length > 0
            ? (0, node_path_1.basename)(entry.url)
            : '';
    const baseName = sourceName && sourceName !== '/'
        ? sourceName.replace(/[^A-Za-z0-9._-]+/g, '-')
        : `${entry.role.replace(/[^A-Za-z0-9._-]+/g, '-').toLowerCase()}${inferExtensionFromManifestEntry(entry)}`;
    let candidate = baseName || `${entry.role.toLowerCase()}${inferExtensionFromManifestEntry(entry)}`;
    let counter = 2;
    while (usedNames.has(candidate)) {
        const extension = (0, node_path_1.extname)(candidate);
        const stem = extension ? candidate.slice(0, -extension.length) : candidate;
        candidate = `${stem}-${counter}${extension}`;
        counter += 1;
    }
    usedNames.add(candidate);
    return candidate;
}
async function downloadAssetFilesForRemix(client, targetDir, creatorUsername, assetName, revision, manifestEntries) {
    const files = {};
    const usedNames = new Set();
    (0, node_fs_1.mkdirSync)(targetDir, { recursive: false });
    for (const entry of manifestEntries) {
        const blob = await client.downloadAssetFile(creatorUsername, assetName, revision, { role: entry.role });
        const buffer = Buffer.from(await blob.arrayBuffer());
        const localFilename = buildLocalFilename(entry, usedNames);
        const absolutePath = (0, node_path_1.resolve)(targetDir, localFilename);
        (0, node_fs_1.writeFileSync)(absolutePath, buffer);
        files[entry.role] = `${(0, node_path_1.basename)(targetDir)}/${localFilename}`;
    }
    return files;
}
async function createAssetRemix(name, remixRef) {
    const parsedRef = (0, types_1.parseContentVersionRef)(remixRef);
    if (!parsedRef || parsedRef.kind !== 'asset') {
        (0, messages_1.printErrorWithHelp)('The --remix value must be an asset version ref like "asset:creator/name@r4".', ['Use the exact public asset version ref you want to remix.'], { command: 'project create asset' });
        process.exitCode = 1;
        return;
    }
    const canonicalRef = (0, types_1.formatContentVersionRef)(parsedRef);
    const client = await createAuthenticatedClient('project create asset');
    const cataloguePath = await ensureWorkspaceCataloguePath();
    const catalogue = readWorkspaceCatalogue(cataloguePath);
    assertWorkspaceRootCanOwnEntries(cataloguePath);
    ensureUniqueEntryName(catalogue, 'asset', name);
    const detail = await client.fetchAssetBySlug(parsedRef.creatorUsername, parsedRef.name);
    const versions = await client.listAssetVersions(parsedRef.creatorUsername, parsedRef.name, { limit: 200, offset: 0 });
    const version = versions.versions.find((entry) => entry.revision === parsedRef.revision);
    if (!version) {
        throw new Error(`Could not find public version ${canonicalRef}.`);
    }
    if (version.visibility !== 'PUBLIC') {
        throw new Error(`Remix source ${canonicalRef} must be public.`);
    }
    const manifestEntries = parseAssetManifestFiles(version.fileManifest);
    const targetDir = (0, node_path_1.resolve)(process.cwd(), name);
    if ((0, node_fs_1.existsSync)(targetDir)) {
        throw new Error(`Cannot create ${targetDir} because it already exists.`);
    }
    const files = await downloadAssetFilesForRemix(client, targetDir, parsedRef.creatorUsername, parsedRef.name, parsedRef.revision, manifestEntries);
    const assets = Array.isArray(catalogue.assets) ? [...catalogue.assets] : [];
    assets.push({
        name,
        displayName: detail.asset.displayName,
        category: detail.asset.category,
        subcategory: version.subcategory,
        format: version.format,
        remix: canonicalRef,
        files,
    });
    catalogue.assets = assets;
    writeWorkspaceCatalogue(cataloguePath, catalogue);
    console.log(`Created asset remix "${name}" from ${canonicalRef}.`);
    console.log(`Downloaded ${manifestEntries.length} file(s) into ${(0, node_path_1.basename)(targetDir)}/ and added the asset entry to ${CATALOGUE_FILENAME}.`);
    console.log('Next steps:');
    console.log(` - Review ${CATALOGUE_FILENAME} and update displayName, description, or pricing as needed.`);
    console.log(` - Edit files inside ${(0, node_path_1.basename)(targetDir)}/ or replace them before running "playdrop project publish ${name}".`);
}
async function createPackRemix(name, remixRef) {
    const parsedRef = (0, types_1.parseContentVersionRef)(remixRef);
    if (!parsedRef || parsedRef.kind !== 'pack') {
        (0, messages_1.printErrorWithHelp)('The --remix value must be a pack version ref like "pack:creator/name@1.2.3".', ['Use the exact public asset pack version ref you want to remix.'], { command: 'project create pack' });
        process.exitCode = 1;
        return;
    }
    const canonicalRef = (0, types_1.formatContentVersionRef)(parsedRef);
    const client = await createAuthenticatedClient('project create pack');
    const cataloguePath = await ensureWorkspaceCataloguePath();
    const catalogue = readWorkspaceCatalogue(cataloguePath);
    assertWorkspaceRootCanOwnEntries(cataloguePath);
    ensureUniqueEntryName(catalogue, 'pack', name);
    const detail = await client.fetchAssetPackBySlug(parsedRef.creatorUsername, parsedRef.name);
    const versions = await client.listAssetPackVersions(parsedRef.creatorUsername, parsedRef.name, { limit: 200, offset: 0 });
    const version = versions.versions.find((entry) => entry.version === parsedRef.version);
    if (!version) {
        throw new Error(`Could not find public version ${canonicalRef}.`);
    }
    if (version.visibility !== 'PUBLIC') {
        throw new Error(`Remix source ${canonicalRef} must be public.`);
    }
    const assetPacks = Array.isArray(catalogue.assetPacks) ? [...catalogue.assetPacks] : [];
    assetPacks.push({
        name,
        version: '1.0.0',
        remix: canonicalRef,
        assets: version.assets.map((assetRef) => `asset:${assetRef.creatorUsername}/${assetRef.name}@r${assetRef.revision}`),
    });
    catalogue.assetPacks = assetPacks;
    writeWorkspaceCatalogue(cataloguePath, catalogue);
    console.log(`Created asset pack remix "${name}" from ${canonicalRef}.`);
    console.log(`Added the asset pack entry to ${CATALOGUE_FILENAME} with ${version.assets.length} referenced asset(s).`);
    console.log('Next steps:');
    console.log(` - Review ${CATALOGUE_FILENAME} and edit the assets list before running "playdrop project publish ${name}".`);
    console.log(` - Add listing art or releaseNotes in ${CATALOGUE_FILENAME} if you want to publish the pack.`);
    if (version.downloadUrl) {
        console.log(` - Source download: ${version.downloadUrl}`);
    }
}
async function createRemixContent(kind, name, remixRef) {
    try {
        if (kind === 'asset') {
            await createAssetRemix(name, remixRef);
            return;
        }
        await createPackRemix(name, remixRef);
    }
    catch (error) {
        if (error instanceof http_1.CLIUnsupportedClientError) {
            process.exitCode = 1;
            return;
        }
        if (error instanceof types_1.UnsupportedClientError) {
            (0, http_1.handleUnsupportedError)(error, `Create ${kind} remix`);
            process.exitCode = 1;
            return;
        }
        if (error instanceof types_1.ApiError) {
            (0, messages_1.printErrorWithHelp)(`Failed to fetch remix source (status ${error.status}).`, ['Confirm the remix ref is correct and publicly accessible, then retry.'], { command: `project create ${kind}` });
            process.exitCode = 1;
            return;
        }
        if (error instanceof TypeError) {
            (0, messages_1.printNetworkIssue)(`Could not reach the Playdrop API to create the ${kind} remix.`, `project create ${kind}`);
            process.exitCode = 1;
            return;
        }
        if (error instanceof Error && ['workspace_init_failed', 'missing_env', 'unsupported_env', 'missing_token'].includes(error.message)) {
            return;
        }
        if (error instanceof Error) {
            (0, messages_1.printErrorWithHelp)(error.message, ['Fix the issue above, then retry the command.'], {
                command: `project create ${kind}`,
                includeGeneralHelp: false,
            });
            process.exitCode = 1;
            return;
        }
        throw error;
    }
}
