/**
 * Normalise rule: `drop-trivial-passthrough`.
 *
 * Phase 4 of `2026-05-13-NORMALISE-PHASE.md` §5 Rule 3. Removes
 * Controller.cured operations whose declared body is just the framework
 * default — a single look-up / insert / update / delete with no
 * preconditions, no published events, and no branching.
 *
 * Rationale: SpecVerse's realize phase auto-emits standard CURVED
 * implementations for every Model. When an explicit `cured.retrieve:`
 * block exists in the spec, realize uses it INSTEAD of the framework
 * default. If the explicit body is semantically identical to the
 * default ("Look up X by id, return X or null"), declaring it is
 * noise — drop it and let inference fill in the default at realize
 * time.
 *
 * Before (post-Rule-2):
 *
 *   ClubController:
 *     model: Club
 *     cured:
 *       retrieve:
 *         steps:
 *           - Look up Club by id
 *           - Return Club or null
 *         requires: [id is provided]
 *
 * After:
 *
 *   (Whole ClubController removed — synthesised by Rule 2,
 *    body was trivial, no other content remains.)
 *
 * Triviality criteria (ALL must hold):
 *   - `steps:` has ≤ 2 entries
 *   - No `publishes:` field, or `publishes: []`
 *   - Step text matches the operation's verb pattern:
 *       retrieve  →  look up / find / fetch / retrieve / select
 *       create    →  create / insert / store / persist
 *       update    →  update / set / modify
 *       delete    →  delete / remove
 *
 * Cleanup after dropping ops:
 *   - If `cured` becomes empty, delete it
 *   - If the Controller is now empty except for `model:` (i.e. we
 *     synthesised it via Rule 2 and there's nothing else), drop the
 *     whole controller. Be conservative: keep controllers that have
 *     `actions:`, `subscriptions:`, `path:`, or other content the user
 *     might have added.
 *   - If the component's controllers map is empty, delete it.
 *
 * NOT in scope:
 *   - Service operations (would require knowing the inference default
 *     applies, which it doesn't for filtered/predicate queries)
 *   - retrieve_many / list operations (default inference may or may not
 *     match the explicit body)
 *   - Bodies with non-trivial preconditions/postconditions (those carry
 *     real spec intent worth preserving even when steps look default)
 */
const VERB_KEYWORDS = {
    retrieve: /\b(look\s*up|find|fetch|retrieve|select|get|read|query|load|pull)\b/i,
    create: /\b(create|insert|store|persist|add|save)\b/i,
    update: /\b(update|set|modify|change|patch)\b/i,
    delete: /\b(delete|remove|destroy|drop|erase)\b/i,
};
/**
 * Patterns indicating that a step is a pure description of default
 * behavior — "Return X", "Throw NotFoundError", "Yield records". Steps
 * matching this MAY appear alongside a verb-keyword step without
 * disqualifying the body from being trivial. Used to allow common
 * second-step language like "Return Club or null".
 */
const TRIVIAL_FOLLOWUP_RE = /^\s*(return|throw|yield|raise)\b/i;
/**
 * Patterns indicating non-default behavior — side effects, validation
 * logic, business operations. Any step matching one of these
 * disqualifies the body. The intent is to catch real spec content
 * (`Validate permissions`, `Notify subscribers`, `Audit access`) that
 * a developer wouldn't want silently discarded.
 */
const FORBIDDEN_STEP_RE = /\b(validate|notify|publish|log|audit|check|verify|ensure|calculate|compute|enrich|transform|merge|join|filter|sort|paginate)\b/i;
/**
 * Is the body of a cured op trivial enough to drop? Examines steps +
 * publishes + the keyword match for the curved op.
 *
 * Trivial rules:
 *   - No `publishes:` (event-driven side effect = non-trivial)
 *   - ≤2 steps
 *   - 0 steps → trivial (already minimal)
 *   - 1 step → trivial as long as it doesn't contain FORBIDDEN keywords
 *     (a single step IS the operation; verb-keyword check is meaningless
 *     because LLMs often collapse "look up X / return X" into one step
 *     like "Return Club by id")
 *   - 2 steps → require at least one step matches the verb keyword AND
 *     the other matches TRIVIAL_FOLLOWUP (e.g. "Return X or null"); no
 *     step may match FORBIDDEN
 */
function isTrivialBody(body, curvedOp) {
    if (!body || typeof body !== 'object') {
        return true;
    }
    const b = body;
    const publishes = b.publishes;
    if (Array.isArray(publishes) && publishes.length > 0)
        return false;
    const steps = b.steps;
    if (!steps)
        return true;
    if (!Array.isArray(steps))
        return false;
    if (steps.length > 2)
        return false;
    if (steps.length === 0)
        return true;
    const verbRe = VERB_KEYWORDS[curvedOp];
    if (!verbRe)
        return false;
    // Quick guard: any step containing forbidden business-logic keywords
    // disqualifies the body regardless of step count.
    for (const step of steps) {
        if (typeof step !== 'string')
            return false;
        if (FORBIDDEN_STEP_RE.test(step))
            return false;
    }
    // Single-step body — trivial as long as no forbidden keywords appear.
    if (steps.length === 1)
        return true;
    // Two-step body — require ≥1 verb-keyword match, others must be
    // trivial follow-ups (Return / Throw etc).
    let sawVerb = false;
    for (const step of steps) {
        const s = step;
        if (verbRe.test(s))
            sawVerb = true;
        else if (TRIVIAL_FOLLOWUP_RE.test(s))
            continue;
        else
            return false;
    }
    return sawVerb;
}
/**
 * Detect drop candidates by walking spec.components[*].controllers[*].cured[*].
 */
function detectDropMatches(spec) {
    const out = [];
    const components = spec.components;
    if (!components)
        return out;
    for (const [componentName, componentBody] of Object.entries(components)) {
        const controllers = componentBody?.controllers;
        if (!controllers)
            continue;
        for (const [controllerName, controllerBody] of Object.entries(controllers)) {
            const cured = controllerBody?.cured;
            if (!cured)
                continue;
            for (const [curvedOp, body] of Object.entries(cured)) {
                if (isTrivialBody(body, curvedOp)) {
                    out.push({ componentName, controllerName, curvedOp });
                }
            }
        }
    }
    return out;
}
function applyDropMatch(spec, data) {
    const components = spec.components;
    const component = components[data.componentName];
    const controllers = component.controllers;
    const controller = controllers[data.controllerName];
    const cured = controller.cured;
    delete cured[data.curvedOp];
    // Clean up empty cured.
    if (Object.keys(cured).length === 0) {
        delete controller.cured;
    }
    // Drop the controller only when its remaining content is just `model:`
    // (a synthesised shell from Rule 2). Conservatively keep controllers
    // that have any of: actions, subscriptions, path, description, or
    // other fields the user might have authored.
    const remainingKeys = Object.keys(controller);
    const isJustModelShell = remainingKeys.length === 1 && remainingKeys[0] === 'model';
    const isEmpty = remainingKeys.length === 0;
    if (isJustModelShell || isEmpty) {
        delete controllers[data.controllerName];
        if (Object.keys(controllers).length === 0) {
            delete component.controllers;
        }
    }
    return spec;
}
export const DROP_TRIVIAL_PASSTHROUGH_RULE = {
    id: 'drop-trivial-passthrough',
    summary: 'Remove explicit cured: operations whose body is just the framework default (≤2 steps, verb-keyword match, no events). Inference re-emits the default at realize time.',
    ordering: 20,
    enabledByDefault: true,
    appliesWhen: (ctx) => ctx.source !== 'hand-authored',
    detect: (spec) => {
        const matches = [];
        for (const data of detectDropMatches(spec)) {
            matches.push({
                description: `${data.componentName}.${data.controllerName}.cured.${data.curvedOp} — trivial body, drop (inference will emit framework default)`,
                data,
            });
        }
        return matches;
    },
    transform: (spec, match) => {
        return applyDropMatch(spec, match.data);
    },
};
export const __internal = {
    isTrivialBody,
    detectDropMatches,
};
//# sourceMappingURL=drop-trivial-passthrough.js.map