/**
 * File-upload handling for form submissions.
 *
 * Validation is defense-in-depth: declared MIME allowlist + magic-byte sniffing
 * (so a renamed `.exe` claiming `image/png` is rejected) + a size cap. Files are
 * never stored inline in the DB — they go through the configured storage adapter
 * and we persist only metadata + the storage key needed to mint a download link.
 */
import { getDB } from '../db.js';
import { checkMagicBytes, validateMimeType } from '../security/upload.js';
/** Conservative default allowlist for form attachments (images + common docs). */
export const FORM_UPLOAD_ALLOWED_MIME_TYPES = [
    'image/jpeg',
    'image/png',
    'image/gif',
    'image/webp',
    'image/avif',
    'application/pdf',
    'text/plain',
    'text/csv',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
];
export const DEFAULT_MAX_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
function sanitizeFileName(name) {
    const base = name.split(/[/\\]/).pop() ?? 'file';
    return base.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 120) || 'file';
}
/**
 * Validate every incoming file against its field's rules. Returns all errors
 * (not just the first) so the submitter can fix everything in one round-trip.
 */
export function validateSubmissionFiles(fields, incoming, options = {}) {
    const errors = [];
    const fieldByKey = new Map(fields.map((f) => [f.key, f]));
    const globalMax = options.maxSizeBytes ?? DEFAULT_MAX_FILE_BYTES;
    const globalAllowed = options.allowedMimeTypes ?? FORM_UPLOAD_ALLOWED_MIME_TYPES;
    for (const file of incoming) {
        const field = fieldByKey.get(file.fieldKey);
        if (!field || field.type !== 'file') {
            errors.push({ fieldKey: file.fieldKey, message: 'Unexpected file upload.' });
            continue;
        }
        const rules = field.validationRules ?? {};
        const maxBytes = rules.maxFileSizeMB
            ? Math.min(globalMax, rules.maxFileSizeMB * 1024 * 1024)
            : globalMax;
        if (file.size > maxBytes) {
            errors.push({
                fieldKey: file.fieldKey,
                message: `${field.label || field.key} exceeds the ${Math.round(maxBytes / (1024 * 1024))}MB limit.`,
            });
            continue;
        }
        const allowed = rules.fileTypes && rules.fileTypes.length > 0
            ? rules.fileTypes.filter((t) => globalAllowed.includes(t))
            : globalAllowed;
        if (!validateMimeType(file.mimeType, allowed)) {
            errors.push({
                fieldKey: file.fieldKey,
                message: `${field.label || field.key} has an unsupported file type "${file.mimeType || 'unknown'}".`,
            });
            continue;
        }
        const magic = checkMagicBytes(file.buffer, file.mimeType);
        if (!magic.valid) {
            errors.push({
                fieldKey: file.fieldKey,
                message: `${field.label || field.key} contents do not match the declared type.`,
            });
        }
    }
    return { ok: errors.length === 0, errors };
}
/**
 * Upload validated files via the storage adapter and persist metadata rows.
 * Must be called *after* the submission row exists (files reference it). When
 * no storage adapter is configured the upload is skipped and the file is
 * dropped with a warning rather than failing the whole submission.
 */
export async function persistSubmissionFiles(formId, submissionId, files) {
    if (files.length === 0)
        return [];
    const { getStorageAdapter } = await import('../storage/index.js');
    const storage = getStorageAdapter();
    if (!storage) {
        if (process.env.NODE_ENV !== 'test') {
            console.warn('[Actuate CMS] Form upload received but no storage adapter is configured; file dropped.');
        }
        return [];
    }
    const db = getDB();
    const created = [];
    for (const file of files) {
        const safeName = sanitizeFileName(file.fileName);
        const storageKey = `actuate/forms/${formId}/${submissionId}/${Date.now()}-${safeName}`;
        let publicUrl = null;
        try {
            publicUrl = await storage.upload(storageKey, file.buffer, file.mimeType);
        }
        catch (err) {
            if (process.env.NODE_ENV !== 'test') {
                console.error('[Actuate CMS] Form file upload failed:', err);
            }
            continue;
        }
        const row = await db.formSubmissionFile.create({
            data: {
                submissionId,
                fieldKey: file.fieldKey,
                fileName: safeName,
                mimeType: file.mimeType,
                sizeBytes: file.size,
                storageKey,
                publicUrl,
            },
        });
        created.push({
            id: String(row.id),
            submissionId,
            fieldKey: file.fieldKey,
            mediaAssetId: row.mediaAssetId ?? null,
            fileName: safeName,
            mimeType: file.mimeType,
            sizeBytes: file.size,
            storageKey,
            publicUrl,
            secureDownloadUrl: null,
            createdAt: row.createdAt instanceof Date ? row.createdAt.toISOString() : new Date().toISOString(),
        });
    }
    return created;
}
//# sourceMappingURL=files.js.map