/**
 * `componentBlock` field type — validates a stored block instance
 * against an extracted Manifest. Lives in its own file so the larger
 * validation rules (per-prop coercion, allow-list enforcement) can grow
 * without bloating the registry index.
 *
 * Stored-value shape:
 *   { component: 'Hero', props: { title: 'Welcome', alignment: 'center' } }
 */
function isPlainObject(v) {
    return v !== null && typeof v === 'object' && !Array.isArray(v);
}
function isInlineManifest(m) {
    if (!isPlainObject(m))
        return false;
    if (m.version !== 1)
        return false;
    if (typeof m.generatedAt !== 'string')
        return false;
    if (!Array.isArray(m.components))
        return false;
    // Verify every component entry has the per-component shape we'll
    // iterate later. Without this, a malformed entry with `props` missing
    // would crash `validateComponentBlockValue` with a TypeError on
    // `for (const propSpec of spec.props)`, surfacing as a 500 rather
    // than a friendly validation error.
    for (const c of m.components) {
        if (!isPlainObject(c))
            return false;
        if (typeof c.name !== 'string')
            return false;
        if (!Array.isArray(c.props))
            return false;
    }
    return true;
}
function isStoredBlockValue(v) {
    // `props` must be a *plain* object — arrays pass the `typeof obj`
    // check but break the per-prop lookup later.
    if (!isPlainObject(v))
        return false;
    return typeof v.component === 'string' && isPlainObject(v.props);
}
/**
 * Validate a stored block value against the manifest declared on its
 * field. Returns `true` on success or a human-readable error string.
 *
 * Performs structural checks only — the per-prop type validation is
 * shared with the admin form generator in the next slice (PR #2) and
 * lives in `@actuate-media/component-blocks`. Pulling that here would
 * couple cms-core to the extractor, which we want to keep optional.
 */
export function validateComponentBlockValue(value, field) {
    if (field.type !== 'componentBlock')
        return true;
    const blockField = field;
    if (!isStoredBlockValue(value)) {
        return 'Component block value must be { component: string; props: object }.';
    }
    // Allow inline manifest only; path-based manifests are resolved by
    // the loader at config-load time and replaced with their inline form
    // before validation runs. This keeps the validator pure and testable.
    if (!isInlineManifest(blockField.manifest)) {
        return 'Component block field has no resolved manifest (was it loaded?).';
    }
    const manifest = blockField.manifest;
    const spec = manifest.components.find((c) => c.name === value.component);
    if (!spec) {
        const known = manifest.components.map((c) => c.name).join(', ') || '<none>';
        return `Unknown component '${value.component}'. Manifest knows: ${known}.`;
    }
    if (blockField.allow && !blockField.allow.includes(value.component)) {
        return `Component '${value.component}' is not in the allow-list for this field.`;
    }
    // Validate each prop declared in the manifest. Required props must
    // have a value; optional props are checked only when supplied.
    // Per-type coercion (e.g. parseInt for numeric strings) is deferred
    // to the admin form generator slice; this layer just rejects outright
    // shape mismatches so bad data never reaches the database.
    for (const propSpec of spec.props) {
        const v = value.props[propSpec.name];
        if (v === undefined || v === null) {
            if (propSpec.required) {
                return `Missing required prop '${propSpec.name}' for component '${value.component}'.`;
            }
            continue;
        }
        const typeCheckError = validatePropTypeShape(v, propSpec.type, propSpec.name);
        if (typeCheckError)
            return typeCheckError;
    }
    return true;
}
/**
 * Lightweight runtime check that a stored prop value matches its
 * declared PropType shape. Deliberately permissive — we only flag
 * outright type mismatches (number vs string) and unknown enum values.
 * The richer schema-aware validator (with coercion and migration hints)
 * lives in `@actuate-media/component-blocks` and is wired in PR #2.
 */
function validatePropTypeShape(value, type, propName) {
    if (!type || typeof type !== 'object')
        return null;
    const t = type;
    switch (t.kind) {
        case 'string':
            return typeof value === 'string' ? null : `Prop '${propName}' must be a string.`;
        case 'number':
            return typeof value === 'number' ? null : `Prop '${propName}' must be a number.`;
        case 'boolean':
            return typeof value === 'boolean' ? null : `Prop '${propName}' must be a boolean.`;
        case 'enum': {
            const values = t.values ?? [];
            return values.includes(value)
                ? null
                : `Prop '${propName}' must be one of: ${values.join(', ')}.`;
        }
        case 'array':
            return Array.isArray(value) ? null : `Prop '${propName}' must be an array.`;
        case 'object':
            return value !== null && typeof value === 'object' && !Array.isArray(value)
                ? null
                : `Prop '${propName}' must be an object.`;
        default:
            return null;
    }
}
export const componentBlockFieldType = {
    type: 'componentBlock',
    label: 'Component Block',
    defaultConfig: {
        type: 'componentBlock',
        label: 'Component Block',
    },
    validate: validateComponentBlockValue,
};
//# sourceMappingURL=component-block.js.map