/**
 * Rule: action-buttons-present
 *
 * For every CURED operation supported by a controller, the
 * corresponding model's form view should expose a visible action
 * trigger (button, menu item, etc.) labeled with the operation name.
 *
 * CURED = create / update / retrieve / validate / evolve / delete.
 * The spec declares which ones a controller supports via
 * `cured: { create: {}, retrieve: {}, ... }`.
 *
 * Walker UX convention (Phase 4A, memory R16c): list views render
 * data only. Mutation actions live on the form view. Create is
 * visible whenever the form is on screen; Update / Delete / Evolve
 * are gated on a selected entity, so we seed one via API + click
 * its row before asserting. Retrieve + validate fire implicitly and
 * have no user-facing button.
 *
 * Models with required `belongsTo` relationships have their parents
 * seeded first (recursively) and the parent's id threaded into the
 * FK field via `$expr:varName.id`.
 *
 * One TestCase per (controller, cured-operation) pair.
 */
export const RULE_ID = 'action-buttons-present';
const CURED_LABELS = new Set([
    'create',
    'retrieve',
    'update',
    'validate',
    'evolve',
    'delete',
]);
/**
 * Build a minimal-but-valid body for createEntity against a model.
 * Covers required non-auto business attributes with type-based
 * defaults. Skips metadata + relationship categories — relationship
 * FK values are provided separately by the seed-chain builder.
 */
function synthesizeBody(model) {
    const out = {};
    for (const attr of model.attributes || []) {
        if (!attr?.name)
            continue;
        if (attr.category === 'metadata' || attr.category === 'relationship')
            continue;
        if (!attr.required)
            continue;
        if (attr.auto)
            continue;
        out[attr.name] = defaultValueForAttribute(attr);
    }
    return out;
}
function defaultValueForAttribute(attr) {
    const typeRaw = String(attr.type || 'String');
    const type = typeRaw.replace(/[^A-Za-z].*$/, '').toLowerCase();
    switch (type) {
        case 'integer':
        case 'int':
        case 'number':
            return 1;
        case 'float':
        case 'double':
        case 'decimal':
            return 1.0;
        case 'boolean':
        case 'bool':
            return false;
        case 'datetime':
        case 'timestamp':
            return '2026-01-01T00:00:00.000Z';
        case 'date':
            return '2026-01-01';
        case 'uuid':
            return '00000000-0000-0000-0000-000000000000';
        default:
            return `test-${attr.name}`;
    }
}
/**
 * Walk a model's belongsTo relationships and emit createEntity steps
 * for every dependency, seeding leaves first. Each seed binds to a
 * unique variable name; FK fields on downstream models reference
 * those variables via `$expr:varName.id`.
 *
 * Returns the ordered step list + the variable name bound to the
 * final (target) model.
 *
 * Cycles are guarded: if A → B → A, the second visit skips. Cyclic
 * required-FK specs would be unsatisfiable anyway.
 */
function buildSeedChain(model, modelByName) {
    const steps = [];
    const bindings = new Map(); // modelName -> varName
    const inProgress = new Set();
    function seed(m) {
        const existing = bindings.get(m.name);
        if (existing)
            return existing;
        if (inProgress.has(m.name)) {
            // Cycle — fall back to bare body without the FK (will likely
            // fail at the backend, but the test will surface the issue
            // rather than the rule crashing).
            return `seed_${m.name}`;
        }
        inProgress.add(m.name);
        const body = synthesizeBody(m);
        for (const rel of m.relationships || []) {
            if (!isBelongsTo(rel))
                continue;
            if (!rel.target)
                continue;
            const parent = modelByName.get(rel.target);
            if (!parent)
                continue;
            const parentVar = seed(parent);
            body[`${rel.name}Id`] = `$expr:${parentVar}.id`;
        }
        const myVar = `seed_${m.name}`;
        steps.push({
            action: 'createEntity',
            modelName: m.name,
            data: body,
            bindAs: myVar,
        });
        bindings.set(m.name, myVar);
        inProgress.delete(m.name);
        return myVar;
    }
    const targetVar = seed(model);
    return { steps, targetVar };
}
function isBelongsTo(rel) {
    return String(rel.type || '').toLowerCase() === 'belongsto';
}
export const actionButtonsPresent = (spec) => {
    const cases = [];
    const modelByName = new Map();
    for (const m of spec.models || [])
        modelByName.set(m.name, m);
    for (const controller of spec.controllers || []) {
        const cured = controller.cured;
        if (!cured)
            continue;
        const modelName = controller.model || controller.name.replace(/Controller$/, '');
        const model = modelByName.get(modelName);
        if (!model)
            continue;
        for (const opName of Object.keys(cured)) {
            if (!CURED_LABELS.has(opName))
                continue;
            if (opName === 'retrieve' || opName === 'validate')
                continue;
            const steps = [
                { action: 'bootRuntime' },
                { action: 'navigateToModelForm', modelName },
            ];
            if (opName !== 'create') {
                // Seed an entity (plus any belongsTo parents) via API, then
                // click its row to put the form into update mode where
                // Update / Delete / Evolve render.
                const { steps: seedSteps, targetVar } = buildSeedChain(model, modelByName);
                steps.push(...seedSteps);
                steps.push({ action: 'selectEntityInList', idExpr: `${targetVar}.id` });
            }
            steps.push({ action: 'expectActionButton', modelName, actionLabel: opName });
            cases.push({
                ruleId: RULE_ID,
                specElement: `${controller.name}.${opName}`,
                name: `[${RULE_ID}] ${modelName}.${opName} action button is visible`,
                steps,
            });
        }
    }
    return cases;
};
//# sourceMappingURL=action-buttons-present.js.map