"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateFormFieldsFromAjvSchema = generateFormFieldsFromAjvSchema;
const defaultArrayPlaceholder = (arrayDepth) => `index${arrayDepth + 1}`;
const DEFAULT_SKIP_PROPERTIES = ['when'];
function resolveOptions(options) {
    return {
        arrayIndexPlaceholder: options?.arrayIndexPlaceholder ?? defaultArrayPlaceholder,
        skipProperties: options?.skipProperties ?? DEFAULT_SKIP_PROPERTIES,
        wrapInRootSection: options?.wrapInRootSection,
        rootSectionTitle: options?.rootSectionTitle,
        generalSectionTitle: options?.generalSectionTitle,
    };
}
// ─── Title helpers ────────────────────────────────────────────────────────────
function formatKeyAsTitle(key) {
    if (!key)
        return '';
    // strip leading underscores/dashes, then split on camelCase and separators
    const clean = key.replace(/^[_-]+/, '');
    return clean
        .replace(/([a-z])([A-Z])/g, '$1 $2')
        .replace(/[_-]+/g, ' ')
        .replace(/\s+/g, ' ')
        .trim()
        .replace(/^\w/, (c) => c.toUpperCase());
}
function resolveTitle(schema, key) {
    const s = schema;
    if (typeof s['title'] === 'string' && s['title'])
        return s['title'];
    if (typeof s['optionName'] === 'string' && s['optionName'])
        return s['optionName'];
    return formatKeyAsTitle(key);
}
// ─── Path helpers ─────────────────────────────────────────────────────────────
const joinPath = (base, key) => {
    if (!key)
        return base;
    return base ? `${base}.${key}` : key;
};
function makeArrayIndexPlaceholder(pathPrefix, fieldName, arrayDepth, placeholder) {
    const base = placeholder(arrayDepth);
    const fullPath = joinPath(pathPrefix, fieldName);
    const safeSuffix = fullPath.replace(/[^\w]/g, '_');
    return safeSuffix ? `${base}_${safeSuffix}` : base;
}
function oneOfDiscriminatorKey(pathPrefix, fieldName) {
    const safeName = fieldName.replace(/[^\w]/g, '_');
    return joinPath(pathPrefix, `__oneOf_${safeName}`);
}
// ─── When helpers ─────────────────────────────────────────────────────────────
function sanitizeWhen(when) {
    if (!when?.length)
        return undefined;
    const result = [];
    for (const token of when) {
        if (token.field) {
            if ((token.operator === '===' || token.operator === '!==') &&
                token.value !== undefined) {
                result.push(token);
            }
            continue;
        }
        if ((token.operator === '&&' || token.operator === '||') && result.length > 0) {
            result.push(token);
        }
    }
    while (result.length > 0 && !result[result.length - 1]?.field) {
        result.pop();
    }
    return result.length > 0 ? result : undefined;
}
function mergeWhen(existing, extra) {
    const left = sanitizeWhen(existing);
    const right = sanitizeWhen(extra);
    if (!right?.length)
        return left;
    if (!left?.length)
        return right;
    return sanitizeWhen([...left, { operator: '&&' }, ...right]);
}
function parseShowIfToWhen(showIf) {
    if (!showIf)
        return undefined;
    const parts = showIf.trim().split(/\s+/);
    if (parts.length !== 3)
        return undefined;
    const [field, op, raw] = parts;
    if (op !== '===' && op !== '!==')
        return undefined;
    let value = raw;
    if (raw === 'true')
        value = true;
    else if (raw === 'false')
        value = false;
    else if ((raw.startsWith('"') && raw.endsWith('"')) ||
        (raw.startsWith("'") && raw.endsWith("'"))) {
        value = raw.slice(1, -1);
    }
    return [{ field, operator: op, value }];
}
function withWhen(field, when) {
    if (!when?.length)
        return field;
    return { ...field, when };
}
function applyBranchWhenToFields(fields, branchWhen) {
    if (!branchWhen?.length)
        return fields;
    return fields.map((field) => ({
        ...field,
        when: mergeWhen(field.when, branchWhen),
    }));
}
// ─── Sort helpers ─────────────────────────────────────────────────────────────
function sortGeneratedFieldsSectionsLast(fields) {
    const withSortedChildren = fields.map((field) => {
        if (field.type === 'section') {
            return { ...field, fields: sortGeneratedFieldsSectionsLast(field.fields) };
        }
        return field;
    });
    const nonSection = [];
    const sections = [];
    for (const field of withSortedChildren) {
        if (field.type === 'section')
            sections.push(field);
        else
            nonSection.push(field);
    }
    return [...nonSection, ...sections];
}
// ─── Schema type helpers ──────────────────────────────────────────────────────
function isTextArea(schema) {
    const s = schema;
    return s['inputType'] === 'textarea' || s['contentType'] === 'yfm';
}
// ─── Converters ───────────────────────────────────────────────────────────────
function convertScalar(fieldPath, schema, when) {
    const s = schema;
    const title = resolveTitle(schema, fieldPath.split('.').pop() ?? fieldPath);
    const type = schema.type;
    const enumValues = s['enum'] ?? undefined;
    if (enumValues?.length || (!type && enumValues)) {
        return [
            withWhen({
                type: 'select',
                name: fieldPath,
                title,
                options: (enumValues ?? []).map((e) => ({
                    value: String(e),
                    content: String(e),
                })),
            }, when),
        ];
    }
    if (type === 'string') {
        if (isTextArea(schema)) {
            return [withWhen({ type: 'textArea', name: fieldPath, title }, when)];
        }
        return [withWhen({ type: 'textInput', name: fieldPath, title }, when)];
    }
    if (type === 'number' || type === 'integer') {
        return [withWhen({ type: 'textInput', name: fieldPath, title }, when)];
    }
    if (type === 'boolean') {
        return [withWhen({ type: 'switch', name: fieldPath, title }, when)];
    }
    return [];
}
function convertVariantFields(variant, parentPath, arrayDepth, opts) {
    const s = variant;
    if (variant.type === 'object' && s['properties']) {
        return convertProperties(Object.entries(s['properties']), parentPath, arrayDepth, opts);
    }
    return convertScalar(parentPath, variant, undefined);
}
function convertOneOrAnyOf(key, variants, fieldPath, title, when, pathPrefix, arrayDepth, opts) {
    const metaKey = oneOfDiscriminatorKey(pathPrefix, key);
    const variantOptions = variants.map((v, i) => {
        const s = v;
        const value = s['optionName'] ??
            s['type'] ??
            String(i);
        const content = s['optionName'] ??
            s['title'] ??
            s['type'] ??
            String(i);
        return { value, content };
    });
    const discriminator = {
        type: 'segmentedRadioGroup',
        name: metaKey,
        title,
        options: variantOptions,
    };
    const branchFields = variants.flatMap((variant, i) => {
        const branchWhen = [
            { field: metaKey, operator: '===', value: variantOptions[i].value },
        ];
        const fields = convertVariantFields(variant, fieldPath, arrayDepth, opts);
        return applyBranchWhenToFields(fields, branchWhen);
    });
    const section = {
        type: 'section',
        title,
        fields: [discriminator, ...branchFields],
    };
    return [withWhen(section, when)];
}
function convertArray(key, schema, fieldPath, title, when, pathPrefix, arrayDepth, opts) {
    const items = schema['items'];
    if (!items)
        return [];
    const ph = makeArrayIndexPlaceholder(pathPrefix, key, arrayDepth, opts.arrayIndexPlaceholder);
    const itemPath = `${fieldPath}[{{${ph}}}]`;
    const itemsType = items['type'];
    const itemsEnum = items['enum'];
    let innerFields;
    if (itemsType === 'string' || itemsEnum) {
        innerFields = [{ type: 'textInput', name: itemPath, title }];
    }
    else if (itemsType === 'object') {
        const itemProps = items['properties'];
        innerFields = itemProps
            ? convertProperties(Object.entries(itemProps), itemPath, arrayDepth + 1, opts)
            : [];
    }
    else {
        // items with oneOf/anyOf or other complex types — delegate to convertProperty
        innerFields = convertProperty('', items, itemPath, arrayDepth + 1, opts);
    }
    const section = {
        type: 'section',
        index: ph,
        withAddButton: true,
        title,
        itemTitle: `${title} {{${ph}}}`,
        fields: innerFields,
    };
    return [withWhen(section, when)];
}
function convertProperty(key, schema, pathPrefix, arrayDepth, opts) {
    const s = schema;
    const fieldPath = joinPath(pathPrefix, key);
    const title = resolveTitle(schema, key);
    const showIf = s['showIf'] ?? undefined;
    const when = parseShowIfToWhen(showIf);
    const schemaEnum = s['enum'];
    const schemaType = schema.type;
    // oneOf / anyOf
    if (s['oneOf']) {
        return convertOneOrAnyOf(key, s['oneOf'], fieldPath, title, when, pathPrefix, arrayDepth, opts);
    }
    if (s['anyOf']) {
        return convertOneOrAnyOf(key, s['anyOf'], fieldPath, title, when, pathPrefix, arrayDepth, opts);
    }
    // enum without explicit type
    if (schemaEnum && !schemaType) {
        return [
            withWhen({
                type: 'select',
                name: fieldPath,
                title,
                options: schemaEnum.map((e) => ({ value: String(e), content: String(e) })),
            }, when),
        ];
    }
    switch (schemaType) {
        case 'string': {
            if (schemaEnum) {
                return [
                    withWhen({
                        type: 'select',
                        name: fieldPath,
                        title,
                        options: schemaEnum.map((e) => ({
                            value: String(e),
                            content: String(e),
                        })),
                    }, when),
                ];
            }
            if (isTextArea(schema)) {
                return [withWhen({ type: 'textArea', name: fieldPath, title }, when)];
            }
            return [withWhen({ type: 'textInput', name: fieldPath, title }, when)];
        }
        case 'number':
        case 'integer':
            return [withWhen({ type: 'textInput', name: fieldPath, title }, when)];
        case 'boolean':
            return [withWhen({ type: 'switch', name: fieldPath, title }, when)];
        case 'object': {
            const props = s['properties'];
            if (!props)
                return [];
            return [
                withWhen({
                    type: 'section',
                    title,
                    fields: convertProperties(Object.entries(props), fieldPath, arrayDepth, opts),
                }, when),
            ];
        }
        case 'array':
            return convertArray(key, schema, fieldPath, title, when, pathPrefix, arrayDepth, opts);
        default:
            return [];
    }
}
function convertProperties(entries, pathPrefix, arrayDepth, opts) {
    return entries
        .filter(([key]) => !opts.skipProperties.includes(key))
        .flatMap(([key, schema]) => convertProperty(key, schema, pathPrefix, arrayDepth, opts));
}
function generateFormFieldsFromAjvSchema(schema, options) {
    const opts = resolveOptions(options);
    const props = schema['properties'];
    if (!props)
        return [];
    const inner = sortGeneratedFieldsSectionsLast(convertProperties(Object.entries(props), '', 0, opts));
    if (opts.wrapInRootSection) {
        return [
            {
                type: 'section',
                title: opts.rootSectionTitle ?? 'Settings',
                fields: inner,
            },
        ];
    }
    const nonSections = inner.filter((f) => f.type !== 'section');
    const sections = inner.filter((f) => f.type === 'section');
    if (nonSections.length === 0)
        return sections;
    const generalSection = {
        type: 'section',
        title: opts.generalSectionTitle ?? 'General',
        fields: nonSections,
    };
    return [generalSection, ...sections];
}
//# sourceMappingURL=generateFormFieldsFromAjv.js.map
