/**
 * Shared contract for agent-produced canvas artifacts.
 *
 * V1 is deliberately text-only: HTML, CSS, JS, SVG, JSON, and plain text files.
 * Binary assets and network-dependent files are follow-up work, because this
 * bundle is persisted in chat message metadata and rendered in a sandboxed
 * iframe.
 */
export const ARTIFACT_BUNDLE_SCHEMA_VERSION = 1;
export const ARTIFACT_BUNDLE_MAX_BYTES = 512 * 1024;
export const ARTIFACT_KINDS = ['html', 'document', 'slides', 'social', 'app', 'custom'];
export const ARTIFACT_VIEWPORT_MODES = ['fixed', 'responsive', 'paged'];
export const ARTIFACT_FULLSCREEN_FITS = ['contain', 'width', 'none'];
export const ARTIFACT_DISPLAY_BACKGROUNDS = ['neutral', 'transparent'];
export const ARTIFACT_LIBRARY_KEYS = ['three', 'd3', 'motion'];
const TEXT_EXTENSIONS = new Set([
    '.html', '.htm', '.css', '.js', '.mjs', '.svg', '.json', '.txt', '.md',
]);
const ID_RE = /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,127}$/;
const PATH_RE = /^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,255}$/;
function isRecord(value) {
    return !!value && typeof value === 'object' && !Array.isArray(value);
}
function byteLength(value) {
    return new TextEncoder().encode(JSON.stringify(value)).length;
}
function extensionOf(path) {
    const dot = path.lastIndexOf('.');
    return dot === -1 ? '' : path.slice(dot).toLowerCase();
}
function isSafePath(path) {
    if (!PATH_RE.test(path))
        return false;
    if (path.includes('..'))
        return false;
    if (path.includes('//'))
        return false;
    if (/^[a-z][a-z0-9+.-]*:/i.test(path))
        return false;
    return true;
}
function normalizeString(value) {
    return typeof value === 'string' && value.trim() ? value.trim() : null;
}
function inferArtifactLibraries(files) {
    const text = Object.entries(files)
        .filter(([path]) => /\.(html?|m?js|css|svg)$/i.test(path))
        .map(([, content]) => content)
        .join('\n');
    const inferred = [];
    if (/\b(?:window\.)?THREE\b/.test(text) || /\bfrom\s+['"]three(?:\/[^'"]*)?['"]/.test(text)) {
        inferred.push('three');
    }
    if (/\b(?:window\.)?d3\b/.test(text) || /\bfrom\s+['"]d3(?:\/[^'"]*)?['"]/.test(text)) {
        inferred.push('d3');
    }
    if (/\b(?:window\.)?Motion\b/.test(text)
        || /\bmotion\.(?:animate|spring|inView|scroll)\b/.test(text)
        || /\bfrom\s+['"]motion(?:\/[^'"]*)?['"]/.test(text)) {
        inferred.push('motion');
    }
    return inferred;
}
export function validateArtifactBundle(input) {
    const errors = [];
    const byteSize = byteLength(input);
    if (byteSize > ARTIFACT_BUNDLE_MAX_BYTES) {
        errors.push(`Artifact bundle exceeds ${ARTIFACT_BUNDLE_MAX_BYTES} bytes`);
    }
    if (!isRecord(input)) {
        return { ok: false, errors: ['Artifact bundle must be an object', ...errors], byteSize };
    }
    if (input.schemaVersion !== ARTIFACT_BUNDLE_SCHEMA_VERSION) {
        errors.push(`schemaVersion must be ${ARTIFACT_BUNDLE_SCHEMA_VERSION}`);
    }
    const artifactId = normalizeString(input.artifactId);
    if (!artifactId || !ID_RE.test(artifactId)) {
        errors.push('artifactId must be a stable alphanumeric id');
    }
    const title = normalizeString(input.title);
    if (!title)
        errors.push('title is required');
    const kind = normalizeString(input.kind);
    if (!kind || !ARTIFACT_KINDS.includes(kind)) {
        errors.push(`kind must be one of: ${ARTIFACT_KINDS.join(', ')}`);
    }
    const viewport = input.viewport;
    let normalizedViewport = null;
    if (!isRecord(viewport)) {
        errors.push('viewport is required');
    }
    else {
        const width = viewport.width;
        const height = viewport.height;
        const mode = viewport.mode;
        if (typeof width !== 'number' || !Number.isFinite(width) || width < 120 || width > 10000) {
            errors.push('viewport.width must be between 120 and 10000');
        }
        if (typeof height !== 'number' || !Number.isFinite(height) || height < 120 || height > 10000) {
            errors.push('viewport.height must be between 120 and 10000');
        }
        if (typeof mode !== 'string' || !ARTIFACT_VIEWPORT_MODES.includes(mode)) {
            errors.push(`viewport.mode must be one of: ${ARTIFACT_VIEWPORT_MODES.join(', ')}`);
        }
        if (typeof width === 'number' && typeof height === 'number' && typeof mode === 'string') {
            normalizedViewport = {
                width: Math.round(width),
                height: Math.round(height),
                mode: mode,
            };
        }
    }
    const entrypoint = normalizeString(input.entrypoint);
    if (!entrypoint || !isSafePath(entrypoint)) {
        errors.push('entrypoint must be a safe relative file path');
    }
    const files = input.files;
    const normalizedFiles = {};
    if (!isRecord(files)) {
        errors.push('files must be an object of text file paths to string contents');
    }
    else {
        const entries = Object.entries(files);
        if (entries.length === 0)
            errors.push('files must contain at least one file');
        for (const [path, content] of entries) {
            if (!isSafePath(path)) {
                errors.push(`file path "${path}" must be a safe relative path`);
                continue;
            }
            const ext = extensionOf(path);
            if (!TEXT_EXTENSIONS.has(ext)) {
                errors.push(`file "${path}" must be a supported text file`);
            }
            if (typeof content !== 'string') {
                errors.push(`file "${path}" must be a string`);
                continue;
            }
            normalizedFiles[path] = content;
        }
    }
    if (entrypoint && !Object.prototype.hasOwnProperty.call(normalizedFiles, entrypoint)) {
        errors.push('entrypoint must exist in files');
    }
    const declaredLibraries = new Set();
    if (input.libraries !== undefined) {
        if (!Array.isArray(input.libraries)) {
            errors.push('libraries must be an array when provided');
        }
        else {
            for (const library of input.libraries) {
                if (typeof library !== 'string' || !ARTIFACT_LIBRARY_KEYS.includes(library)) {
                    errors.push(`libraries must contain only: ${ARTIFACT_LIBRARY_KEYS.join(', ')}`);
                    continue;
                }
                declaredLibraries.add(library);
            }
        }
    }
    let display;
    if (input.display !== undefined) {
        if (!isRecord(input.display)) {
            errors.push('display must be an object when provided');
        }
        else {
            display = {};
            if (input.display.fullscreenFit !== undefined) {
                if (typeof input.display.fullscreenFit !== 'string'
                    || !ARTIFACT_FULLSCREEN_FITS.includes(input.display.fullscreenFit)) {
                    errors.push(`display.fullscreenFit must be one of: ${ARTIFACT_FULLSCREEN_FITS.join(', ')}`);
                }
                else {
                    display.fullscreenFit = input.display.fullscreenFit;
                }
            }
            if (input.display.background !== undefined) {
                if (typeof input.display.background !== 'string'
                    || !ARTIFACT_DISPLAY_BACKGROUNDS.includes(input.display.background)) {
                    errors.push(`display.background must be one of: ${ARTIFACT_DISPLAY_BACKGROUNDS.join(', ')}`);
                }
                else {
                    display.background = input.display.background;
                }
            }
            if (input.display.fullscreenViewport !== undefined) {
                if (!isRecord(input.display.fullscreenViewport)) {
                    errors.push('display.fullscreenViewport must be an object when provided');
                }
                else {
                    const width = input.display.fullscreenViewport.width;
                    const height = input.display.fullscreenViewport.height;
                    if (typeof width !== 'number' || !Number.isFinite(width) || width < 120 || width > 10000) {
                        errors.push('display.fullscreenViewport.width must be between 120 and 10000');
                    }
                    if (typeof height !== 'number' || !Number.isFinite(height) || height < 120 || height > 10000) {
                        errors.push('display.fullscreenViewport.height must be between 120 and 10000');
                    }
                    if (typeof width === 'number' && typeof height === 'number') {
                        display.fullscreenViewport = {
                            width: Math.round(width),
                            height: Math.round(height),
                        };
                    }
                }
            }
            if (input.display.controls !== undefined) {
                if (!isRecord(input.display.controls)) {
                    errors.push('display.controls must be an object when provided');
                }
                else {
                    const controls = {};
                    let hasControls = false;
                    if (input.display.controls.stepper !== undefined) {
                        if (!isRecord(input.display.controls.stepper)) {
                            errors.push('display.controls.stepper must be an object when provided');
                        }
                        else {
                            const stepper = {};
                            if (input.display.controls.stepper.selector !== undefined) {
                                const selector = normalizeString(input.display.controls.stepper.selector);
                                if (!selector || selector.length > 180) {
                                    errors.push('display.controls.stepper.selector must be a non-empty string up to 180 characters');
                                }
                                else {
                                    stepper.selector = selector;
                                }
                            }
                            if (input.display.controls.stepper.label !== undefined) {
                                const label = normalizeString(input.display.controls.stepper.label);
                                if (!label || label.length > 40) {
                                    errors.push('display.controls.stepper.label must be a non-empty string up to 40 characters');
                                }
                                else {
                                    stepper.label = label;
                                }
                            }
                            controls.stepper = stepper;
                            hasControls = true;
                        }
                    }
                    if (input.display.controls.reset !== undefined) {
                        if (typeof input.display.controls.reset !== 'boolean') {
                            errors.push('display.controls.reset must be a boolean when provided');
                        }
                        else {
                            controls.reset = input.display.controls.reset;
                            hasControls = true;
                        }
                    }
                    if (hasControls)
                        display.controls = controls;
                }
            }
            if (Object.keys(display).length === 0)
                display = undefined;
        }
    }
    let source;
    if (input.source !== undefined) {
        if (!isRecord(input.source)) {
            errors.push('source must be an object when provided');
        }
        else {
            source = {};
            for (const key of ['repo', 'entityType', 'entityId', 'skillId']) {
                if (input.source[key] !== undefined) {
                    const value = normalizeString(input.source[key]);
                    if (!value)
                        errors.push(`source.${key} must be a non-empty string`);
                    else
                        source[key] = value;
                }
            }
        }
    }
    if (errors.length > 0 || !artifactId || !title || !kind || !entrypoint || !normalizedViewport) {
        return { ok: false, errors, byteSize };
    }
    const libraries = Array.from(new Set([...declaredLibraries, ...inferArtifactLibraries(normalizedFiles)]));
    return {
        ok: true,
        errors: [],
        byteSize,
        bundle: {
            schemaVersion: ARTIFACT_BUNDLE_SCHEMA_VERSION,
            artifactId,
            title,
            kind: kind,
            viewport: normalizedViewport,
            ...(display ? { display } : {}),
            ...(libraries.length > 0 ? { libraries } : {}),
            files: normalizedFiles,
            entrypoint,
            ...(source ? { source } : {}),
        },
    };
}
export function assertArtifactBundle(input) {
    const result = validateArtifactBundle(input);
    if (!result.ok || !result.bundle) {
        throw new Error(result.errors.join('; ') || 'Invalid artifact bundle');
    }
    return result.bundle;
}
//# sourceMappingURL=artifact-bundle.js.map