/**
 * Section content validation — shared by the admin editor's `validatePage`,
 * the REST `/page-sections/validate` endpoint, and the MCP
 * `validate_section_content` tool.
 *
 * Deterministic (no LLM, no I/O). Validates a SINGLE section's content
 * against its registry field schema. Page-level rules (title/path, "at
 * least one visible section", SEO warnings) live in the admin's
 * `validatePage` because they operate on the whole `EditorPage`.
 *
 * Messages are intentionally label-prefix-free (e.g. `"Heading" is
 * required.`) so callers can prepend their own context (`${sectionName}:
 * ...`) without double-labelling.
 */
import { getSectionType } from './registry.js';
import { resolveRepeaterField } from './fields.js';
const URL_OK = /^(https?:\/\/|\/|#|mailto:|tel:)/i;
function isBlank(v) {
    return v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
}
/** Label/URL field pairs: the URL must be valid when its label is filled in. */
const BUTTON_PAIRS = [
    ['primaryButtonLabel', 'primaryButtonUrl'],
    ['secondaryButtonLabel', 'secondaryButtonUrl'],
    ['buttonLabel', 'buttonUrl'],
];
/**
 * Validate one section's content + settings against its type schema.
 *
 * An unknown `sectionType` is a hard error (the agent asked for a type with
 * no renderer). Required fields, button URL validity, and image alt text
 * are enforced; everything else is advisory.
 */
export function validateSectionContent(sectionType, content, _settings) {
    const errors = [];
    const warnings = [];
    const def = getSectionType(sectionType);
    if (!def) {
        errors.push({
            sectionType,
            message: `Unknown section type "${sectionType}". It has no registered renderer.`,
        });
        return { valid: false, errors, warnings };
    }
    const data = content ?? {};
    for (const field of def.fields) {
        const repeater = resolveRepeaterField(field);
        if (repeater) {
            validateRepeater(sectionType, field.key, repeater, data[field.key], errors, warnings);
            continue;
        }
        if (field.required && isBlank(data[field.key])) {
            errors.push({ sectionType, field: field.key, message: `"${field.label}" is required.` });
        }
    }
    for (const [labelKey, urlKey] of BUTTON_PAIRS) {
        const hasLabel = !isBlank(data[labelKey]);
        const url = data[urlKey];
        if (hasLabel && !isBlank(url) && typeof url === 'string' && !URL_OK.test(url.trim())) {
            errors.push({ sectionType, field: urlKey, message: `"${url}" is not a valid URL.` });
        }
        // A configured URL with no label is a soft issue — the button won't render.
        if (!hasLabel && !isBlank(data[urlKey])) {
            warnings.push({
                sectionType,
                field: labelKey,
                message: `A URL is set for "${urlKey}" but its button label is empty, so the button will not render.`,
            });
        }
    }
    // Image alt text required when an image is set (accessibility).
    if (!isBlank(data.imageUrl) && isBlank(data.imageAlt)) {
        errors.push({
            sectionType,
            field: 'imageAlt',
            message: 'image needs alt text for accessibility.',
        });
    }
    return { valid: errors.length === 0, errors, warnings };
}
/**
 * Validate one repeater field's rows against its sub-field schema. Covers the
 * generic `repeater` type and the `stats` / `gallery` shorthands (resolved to
 * the same row model). Required sub-fields are hard errors; an image row
 * missing alt text is an advisory warning (accessibility). `minRows` is
 * advisory; exceeding `maxRows` is an error (the editor enforces the cap, so
 * over-cap data only arrives via the API/agent).
 */
function validateRepeater(sectionType, key, repeater, value, errors, warnings) {
    const rows = Array.isArray(value) ? value : [];
    const mediaField = repeater.fields.find((f) => f.type === 'media');
    const altField = repeater.fields.find((f) => f.key === 'alt');
    rows.forEach((raw, i) => {
        if (!raw || typeof raw !== 'object')
            return;
        const row = raw;
        for (const sub of repeater.fields) {
            if (sub.required && isBlank(row[sub.key])) {
                errors.push({
                    sectionType,
                    field: `${key}[${i}].${sub.key}`,
                    message: `"${sub.label}" is required in ${repeater.rowLabel} ${i + 1}.`,
                });
            }
        }
        // An image row with a set image but no alt text is an accessibility gap.
        if (mediaField && altField && !isBlank(row[mediaField.key]) && isBlank(row[altField.key])) {
            warnings.push({
                sectionType,
                field: `${key}[${i}].${altField.key}`,
                message: `${repeater.rowLabel} ${i + 1} is missing alt text for accessibility.`,
            });
        }
    });
    if (typeof repeater.minRows === 'number' && rows.length < repeater.minRows) {
        warnings.push({
            sectionType,
            field: key,
            message: `Add at least ${repeater.minRows} ${repeater.rowLabel.toLowerCase()}(s).`,
        });
    }
    if (typeof repeater.maxRows === 'number' && rows.length > repeater.maxRows) {
        errors.push({
            sectionType,
            field: key,
            message: `No more than ${repeater.maxRows} ${repeater.rowLabel.toLowerCase()}(s) are allowed.`,
        });
    }
}
//# sourceMappingURL=validate.js.map