import { readFileSync } from 'fs';
import { join, basename } from 'path';
import { WorkspaceAccessService } from '../../services/workspace-access.js';
import { validateArtifactBundle } from '../../core/artifact-bundle.js';
import { artifactExportFilename, buildArtifactHtmlExport } from '../../core/artifact-export.js';
import { artifactLibrarySourcesForBundle } from '../../services/artifact-library-sources.js';
import { graphForRead as resolveGraphForRead } from './graph-api-access.js';
import { ArtifactRenderExportUnavailableError, artifactPngArchiveFilename, nameArtifactPngFiles, renderArtifactPdf, renderArtifactPng, renderArtifactPngFiles, zipArtifactPngFiles, zipArtifactFiles, } from '../../services/artifact-render-export.js';
import { resolveCanonicalBaseUrl, CanonicalUrlError } from './oauth-api.js';
import { exporterAccessibleArtifactAssets, mintArtifactAssetTokenForBundle } from '../artifact-asset-mint.js';
// Bounds on the self-contained HTML-export zip so one artifact can't force a
// huge set of R2 downloads / an enormous in-memory archive.
const HTML_EXPORT_MAX_ASSETS = 200;
const HTML_EXPORT_MAX_TOTAL_BYTES = 100 * 1024 * 1024;
function workspaceConfigForExport(workspaceManager) {
    try {
        // Keep this route aligned with OAuth's canonical-origin pinning without
        // exposing WorkspaceManager's whole mutable config object.
        const raw = JSON.parse(readFileSync(join(workspaceManager.getWorkspacePath(), '.studiograph', 'workspace.json'), 'utf-8'));
        return raw;
    }
    catch {
        return {};
    }
}
export async function registerArtifactExportApiRoutes(fastify, workspaceManager, authService, renderers = {}) {
    const accessService = new WorkspaceAccessService(workspaceManager, authService);
    const graphForRead = (req, reply, repoName) => resolveGraphForRead(accessService, req, reply, repoName);
    const renderPng = renderers.renderPng ?? renderArtifactPng;
    const renderPngFiles = renderers.renderPngFiles ?? (renderers.renderPng
        ? async (bundle, options) => [{ index: 0, bytes: await renderPng(bundle, options) }]
        : renderArtifactPngFiles);
    const renderPdf = renderers.renderPdf ?? renderArtifactPdf;
    const exportBaseUrl = (req, reply) => {
        try {
            return resolveCanonicalBaseUrl(workspaceConfigForExport(workspaceManager), req);
        }
        catch (err) {
            if (err instanceof CanonicalUrlError) {
                reply.status(503).send({ error: 'Artifact export base URL is not configured' });
                return null;
            }
            throw err;
        }
    };
    async function handleDownload(req, reply, format) {
        const { repo, id } = req.params;
        const graph = graphForRead(req, reply, repo);
        if (!graph)
            return;
        let entity;
        try {
            entity = graph.get('artifact', id);
        }
        catch (err) {
            return reply.status(404).send({ error: err.message ?? `Artifact not found: ${id}` });
        }
        if (!entity) {
            return reply.status(404).send({ error: `Artifact not found: ${id}` });
        }
        let rawBundle;
        try {
            rawBundle = JSON.parse(entity.document?.content ?? '');
        }
        catch {
            return reply.status(422).send({ error: 'Artifact body is not a valid JSON bundle' });
        }
        const validation = validateArtifactBundle(rawBundle);
        if (!validation.ok || !validation.bundle) {
            return reply.status(422).send({ error: `Invalid artifact bundle: ${validation.errors.join('; ')}` });
        }
        try {
            reply.header('Cache-Control', 'no-store');
            reply.header('Content-Disposition', `attachment; filename="${artifactExportFilename(validation.bundle, format)}"`);
            const userId = String(req.user?.id ?? '0');
            if (format === 'html') {
                // D11b export model: bundle every asset the EXPORTING USER can read —
                // the artifact's repo, workspace-wide stores, and any cross-repo asset
                // they have access to (per-asset authz; custody transfers to the
                // authenticated exporter, same trust model as a screenshot). Refs that
                // don't resolve render as labeled placeholders — never absolutized
                // back to the workspace (the export CSP carries no network sources,
                // so an absolute URL couldn't load anyway). No baseHref: a downloaded
                // file must not phone home.
                const user = req.user;
                const assets = exporterAccessibleArtifactAssets(workspaceManager, authService, user, validation.bundle);
                if (assets.length > HTML_EXPORT_MAX_ASSETS) {
                    return reply.status(413).send({ error: `Too many assets to bundle (${assets.length} > ${HTML_EXPORT_MAX_ASSETS})` });
                }
                // Download the accessible assets through their OWNING repo's graph,
                // enforcing the byte ceiling against the ACTUAL downloaded length
                // (index `size` is often null → can't be trusted for the cap).
                const assetEntries = [];
                const bundledKeys = new Set();
                let totalBytes = 0;
                for (const a of assets) {
                    let bytes;
                    try {
                        bytes = await workspaceManager.getGraph(a.repo).downloadAssetById(a.assetId, a.filename);
                    }
                    catch {
                        continue; // blob missing → ref renders as a placeholder
                    }
                    totalBytes += bytes.length;
                    if (totalBytes > HTML_EXPORT_MAX_TOTAL_BYTES) {
                        return reply.status(413).send({ error: 'Bundled assets exceed the export size limit' });
                    }
                    // assetId (UUID) dir + basename(filename) → safe, collision-free path.
                    assetEntries.push({ filename: `assets/${a.assetId}/${basename(a.filename)}`, bytes });
                    bundledKeys.add(`${a.assetId}/${a.filename}`);
                }
                const indexHtml = buildArtifactHtmlExport(validation.bundle, {
                    context: 'fullscreen',
                    includeRenderHostStyles: true,
                    librarySources: artifactLibrarySourcesForBundle(validation.bundle),
                    // Relative-mode resolution: bundled refs → ./assets/..., unbundled →
                    // placeholders, CSP = no network. URL rewrite + CSP from one value.
                    assets: { mode: 'relative', bundledKeys },
                });
                if (assetEntries.length === 0) {
                    // Nothing bundled (no refs, or none accessible) → single .html.
                    reply.header('Content-Type', 'text/html; charset=utf-8');
                    return reply.send(indexHtml);
                }
                const entries = [
                    { filename: 'index.html', bytes: Buffer.from(indexHtml, 'utf-8') },
                    ...assetEntries,
                ];
                reply.header('Content-Type', 'application/zip');
                reply.header('Content-Disposition', `attachment; filename="${artifactExportFilename(validation.bundle, 'html').replace(/\.html$/i, '.zip')}"`);
                return reply.send(await zipArtifactFiles(entries));
            }
            // PNG/PDF render in headless Chromium with no session cookie — mint an
            // artifact-asset token so the page's /api/assets image loads are
            // authorized (token-mode resolution). Only this path needs the canonical
            // base URL (the headless page resolves root-relative URLs against it);
            // the HTML export above is self-contained by construction.
            const baseUrl = exportBaseUrl(req, reply);
            if (!baseUrl)
                return;
            const { token: renderToken } = mintArtifactAssetTokenForBundle(workspaceManager, authService, repo, id, validation.bundle, userId);
            if (format === 'png') {
                const files = await renderPngFiles(validation.bundle, { baseUrl, assetToken: renderToken });
                if (files.length <= 1) {
                    reply.header('Content-Type', 'image/png');
                    return reply.send(files[0]?.bytes ?? Buffer.alloc(0));
                }
                const namedFiles = nameArtifactPngFiles(validation.bundle, files);
                reply.header('Content-Type', 'application/zip');
                reply.header('Content-Disposition', `attachment; filename="${artifactPngArchiveFilename(validation.bundle)}"`);
                return reply.send(await zipArtifactPngFiles(namedFiles));
            }
            reply.header('Content-Type', 'application/pdf');
            return reply.send(await renderPdf(validation.bundle, { baseUrl, assetToken: renderToken }));
        }
        catch (err) {
            if (err instanceof ArtifactRenderExportUnavailableError) {
                return reply.status(503).send({ error: err.message });
            }
            return reply.status(500).send({ error: `Failed to render ${format.toUpperCase()} export` });
        }
    }
    fastify.get('/api/repos/:repo/entities/artifact/:id/export/html', (req, reply) => handleDownload(req, reply, 'html'));
    fastify.get('/api/repos/:repo/entities/artifact/:id/export/png', (req, reply) => handleDownload(req, reply, 'png'));
    fastify.get('/api/repos/:repo/entities/artifact/:id/export/pdf', (req, reply) => handleDownload(req, reply, 'pdf'));
}
//# sourceMappingURL=artifact-export-api.js.map