/**
 * Rule: lifecycle-path-walkable
 *
 * For every model with a lifecycle, derive two behavioral scenarios:
 *
 *   1. Positive — create the entity in its initial state, then `evolve`
 *      along each declared transition, asserting the state after every
 *      hop (e.g. Poll: create→draft, evolve→open, evolve→closed).
 *   2. Negative — create the entity, then attempt an UNDECLARED jump
 *      (e.g. draft → closed) and assert it is rejected.
 *
 * Both are derived purely from the spec's `lifecycles:` block — zero
 * authoring. Models needing a parent seeded first (required belongsTo)
 * are skipped in v1 (Phase 2 adds topological seeding).
 */
import { parseLifecyclePaths, findUndeclaredJump, buildTopLevelPayload, opAvailable } from '../_shared.js';
export const RULE_ID = 'lifecycle-path-walkable';
export const lifecyclePathWalkable = (spec) => {
    const cases = [];
    for (const model of spec.models) {
        // Skip models whose controller can't evolve — the scenario wouldn't be
        // runnable (no transition endpoint to drive).
        if (!opAvailable(spec, model.name, 'evolve'))
            continue;
        for (const path of parseLifecyclePaths(model)) {
            const marker = `sc-lcwalk-${model.name}`;
            const payload = buildTopLevelPayload(model, marker);
            if (!payload)
                continue; // needs parent seeding — out of v1 scope
            // 1. Positive: walk the declared path.
            const walkSteps = [
                {
                    op: 'create',
                    model: model.name,
                    payload,
                    bindAs: 'e',
                    expect: { kind: 'state', field: path.field, value: path.initial },
                },
            ];
            let current = path.initial;
            for (const t of path.transitions) {
                if (t.from !== current)
                    continue; // follow the linear chain from initial
                walkSteps.push({
                    op: 'evolve',
                    model: model.name,
                    idExpr: 'e.id',
                    payload: { toState: t.to },
                    expect: { kind: 'state', field: path.field, value: t.to },
                });
                current = t.to;
            }
            cases.push({
                ruleId: RULE_ID,
                specElement: `${model.name}.${path.field}`,
                name: `[${RULE_ID}] ${model.name}.${path.field} walks ${path.states.join(' -> ')}`,
                steps: walkSteps,
            });
            // 2. Negative: an undeclared jump is rejected.
            const jump = findUndeclaredJump(path);
            if (jump) {
                cases.push({
                    ruleId: RULE_ID,
                    specElement: `${model.name}.${path.field}`,
                    name: `[${RULE_ID}] ${model.name}.${path.field} rejects undeclared ${jump.from} -> ${jump.to}`,
                    steps: [
                        {
                            op: 'create',
                            model: model.name,
                            payload,
                            bindAs: 'e',
                            expect: { kind: 'ok' },
                        },
                        {
                            op: 'evolve',
                            model: model.name,
                            idExpr: 'e.id',
                            payload: { toState: jump.to },
                            expect: { kind: 'rejected' },
                        },
                    ],
                });
            }
        }
    }
    return cases;
};
//# sourceMappingURL=lifecycle-path-walkable.js.map