/**
 * SPEC-14 form field width helpers.
 * Uniform 12-column grid (`grid-cols-1 md:grid-cols-12`); no tiers.
 * `getDefaultSpan` returns 1..12; `resolveFieldSpan` honors `widget.span` when declared;
 * `applyAutoExpand` expands the last field of any incomplete final row per container.
 */
import { WidgetType } from './widget-type.js';
const MIN_SPAN = 1;
const MAX_SPAN = 12;
/**
 * Default column span for a property when no `widget.span` override is
 * declared. The table is type-based; `maxLength` is consulted ONLY for
 * `varchar` (the one type where character length is a useful proxy of
 * typical UI width).
 */
export function getDefaultSpan(property) {
    // Relational widgets — dispatched in form-body.eta — have their own
    // sizing intent that overrides the underlying primitive type.
    const widgetType = property.widget?.type;
    if (widgetType === WidgetType.GRID_SELECT_ELEMENT)
        return 12;
    if (widgetType === WidgetType.GRID_SELECT_MULTIPLE_ELEMENTS)
        return 12;
    if (widgetType === WidgetType.ASYNC_SEARCH_SELECT)
        return 6;
    if (widgetType === WidgetType.SELECT || widgetType === WidgetType.MULTIPLE_SELECT)
        return 4;
    switch (property.type) {
        case 'array': {
            if (property.arrayOptions?.type === 'enum') {
                const optionCount = property.arrayOptions.enumOptions?.length ?? 0;
                return optionCount > 5 ? 4 : 3;
            }
            return 12;
        }
        case 'bigint':
        case 'decimal':
        case 'float':
        case 'int':
        case 'smallint': {
            return 4;
        }
        case 'boolean':
        case 'date':
        case 'timestamp': {
            return 3;
        }
        case 'char':
        case 'varchar': {
            const maxLen = property.maxLength ?? property.length;
            if (maxLen === undefined || maxLen > 80)
                return 12;
            if (maxLen <= 30)
                return 4;
            return 6;
        }
        case 'enum': {
            const optionCount = property.enumOptions?.length ?? 0;
            return optionCount > 5 ? 4 : 3;
        }
        case 'id': {
            // FK with a relational widget is handled above; bare id without widget
            // is not rendered (filtered by form-body.eta) — defensive default.
            return 4;
        }
        case 'password': {
            return 6;
        }
        case 'text': {
            return 12;
        }
        default: {
            return 12;
        }
    }
}
/**
 * Resolves final desktop span: honors `widget.span` when declared, else `getDefaultSpan`.
 * Out-of-range overrides are clamped to [1, 12] with a `console.warn` (defense in depth — the
 * JSON schema also validates the range, but this catches pre-schema YAMLs and custom loaders).
 */
export function resolveFieldSpan(property) {
    const explicit = property.widget?.span;
    if (typeof explicit === 'number') {
        if (explicit < MIN_SPAN || explicit > MAX_SPAN) {
            console.warn(`[SPEC-14] widget.span out of range for property '${property.name}': ${explicit}. Clamping to [${MIN_SPAN}, ${MAX_SPAN}].`);
            return Math.max(MIN_SPAN, Math.min(MAX_SPAN, explicit));
        }
        return explicit;
    }
    return getDefaultSpan(property);
}
/**
 * Auto-expand pass for one container (form, group, or tab). Walks left-to-right tracking a row
 * accumulator, wraps on overflow, and expands the last field to fill any incomplete final row.
 * Pure — invoked once per container, returns a new array of the same length.
 */
export function applyAutoExpand(spans) {
    if (spans.length === 0)
        return [];
    const result = [...spans];
    let rowAcc = 0;
    for (const span of spans) {
        if (rowAcc + span > MAX_SPAN)
            rowAcc = 0; // wrap to a fresh row
        rowAcc += span;
        if (rowAcc === MAX_SPAN)
            rowAcc = 0; // row exactly full → reset for next
    }
    // Final row incomplete? Expand the last field to fill the remainder.
    if (rowAcc > 0 && rowAcc < MAX_SPAN) {
        const lastIdx = spans.length - 1;
        const remaining = MAX_SPAN - (rowAcc - result[lastIdx]);
        result[lastIdx] = remaining;
    }
    return result;
}
