/**
 * Shared helpers for rules that need sample data / seedability
 * analysis. Kept separate from test-case-types.ts so rules can
 * import pure utilities without pulling in the TestStep union.
 */
/**
 * Pick the attribute to use as the entity's human-readable display
 * value in assertions. Preference order:
 *   1. First required business String named 'title'/'name'/'label'
 *   2. First required business String attribute
 *   3. First business String attribute
 *   4. null — caller should skip this rule for this model
 */
export function pickDisplayAttribute(model) {
    const attrs = model.attributes || [];
    const business = attrs.filter(a => a.category !== 'metadata' && a.category !== 'relationship');
    const strings = business.filter(a => !a.type || /^String$|^Email$/i.test(a.type));
    const named = strings.find(a => /^(title|name|label)$/i.test(a.name) && a.required);
    if (named)
        return named;
    const requiredStr = strings.find(a => a.required);
    if (requiredStr)
        return requiredStr;
    if (strings.length > 0)
        return strings[0];
    return null;
}
/**
 * Build a seed-data payload for a model that's plausible enough for
 * the backend to accept. Fills in every required business attribute
 * with a marker-tagged value so contract-test-created entities are
 * easy to identify.
 *
 * Returns null if the model has any required belongsTo relationship —
 * seeding parent entities is out of scope for v1 state-sync rules.
 * Phase 5+ may add a topological-seed helper that handles dependents.
 */
export function buildSeedData(model, marker) {
    // If the model has a required belongsTo relationship, we'd need to
    // seed the parent first. v1 skips these; only top-level models get
    // state-sync rules.
    const rels = model.relationships || [];
    const hasRequiredBelongsTo = rels.some(r => /belongsTo/i.test(String(r.type || '')));
    if (hasRequiredBelongsTo)
        return null;
    const data = {};
    const attrs = model.attributes || [];
    for (const attr of attrs) {
        if (attr.category === 'metadata')
            continue;
        if (attr.category === 'relationship')
            continue;
        if (!attr.required && !isLifecycleField(attr))
            continue;
        data[attr.name] = sampleValueFor(attr, marker);
    }
    return data;
}
function isLifecycleField(attr) {
    // Heuristic: `status` / `state` attributes with an enumerated
    // value list belong to a lifecycle. The parser usually strips
    // the value list from the type at normalization time, so we
    // rely on naming convention here. Good enough for v1.
    return /^(status|state)$/i.test(attr.name);
}
function sampleValueFor(attr, marker) {
    const type = (attr.type || 'String').trim();
    // Lifecycle/enum fields: pick a plausible initial state. For poll-app
    // it's 'draft'. Future work: parse the enum list from the spec and
    // pick the first value.
    if (isLifecycleField(attr))
        return 'draft';
    if (/^Integer$|^Int$|^Number$|^Float$/i.test(type))
        return 0;
    if (/^Boolean$/i.test(type))
        return false;
    if (/^DateTime$|^Date$/i.test(type))
        return new Date().toISOString();
    // String, Email, UUID → tag with the marker so assertions find it
    return `${marker}-${attr.name}`;
}
/**
 * Extract the list of lifecycle state names from a model's lifecycles
 * block. Returns null if the model has no lifecycle.
 */
export function getLifecycleStates(model) {
    const lifecycles = model.lifecycles;
    if (!lifecycles)
        return null;
    const first = Object.values(lifecycles)[0];
    if (!first)
        return null;
    // Form 1: { flow: 'draft -> open -> closed' }
    if (typeof first.flow === 'string') {
        return first.flow.split('->').map((s) => s.trim()).filter(Boolean);
    }
    // Form 2: { states: [...] }
    if (Array.isArray(first.states)) {
        return first.states.map((s) => (typeof s === 'string' ? s : s?.name)).filter(Boolean);
    }
    return null;
}
//# sourceMappingURL=_shared.js.map