/**
 * Structural-check library for the verify stage. Each check is a
 * (parsedSpec) -> StructuralCheckResult function. Checks are SpecVerse-
 * semantic invariants that go beyond schema validation: things that
 * are syntactically valid YAML matching the JSON schema, but
 * semantically odd (e.g. an action with no steps and no cured shorthand).
 *
 * Design: each check is pure, fast, and reads only the parsed spec.
 * No filesystem I/O, no LLM calls.
 *
 * Adding a new check:
 *   1. Define the check function below
 *   2. Add it to ALL_CHECKS at the bottom
 *   3. Add a unit test in __tests__/verify-checks.test.ts
 */
function* iterateActions(spec) {
    const components = spec.components || {};
    for (const [componentName, comp] of Object.entries(components)) {
        for (const ownerKind of ['controllers', 'services']) {
            const owners = comp[ownerKind] || {};
            for (const [ownerName, owner] of Object.entries(owners)) {
                const actions = owner.actions || {};
                for (const [actionName, action] of Object.entries(actions)) {
                    yield {
                        componentName,
                        ownerName,
                        ownerKind: ownerKind === 'controllers' ? 'controller' : 'service',
                        actionName,
                        action,
                        path: `components.${componentName}.${ownerKind}.${ownerName}.actions.${actionName}`,
                    };
                }
            }
        }
    }
}
function* iterateModels(spec) {
    const components = spec.components || {};
    for (const [componentName, comp] of Object.entries(components)) {
        const models = comp.models || {};
        for (const [modelName, model] of Object.entries(models)) {
            yield {
                componentName,
                modelName,
                model,
                path: `components.${componentName}.models.${modelName}`,
            };
        }
    }
}
/** Each action either declares step bullets or is shorthand-mapped via cured. */
export function checkActionsHaveStepsOrCured(spec) {
    const violations = [];
    for (const { action, path } of iterateActions(spec)) {
        const hasSteps = Array.isArray(action.steps) && action.steps.length > 0;
        const hasCured = action.cured && typeof action.cured === 'object';
        const hasInline = typeof action.implementation === 'string' || action.body;
        if (!hasSteps && !hasCured && !hasInline) {
            violations.push({ path, issue: 'action declares no steps, cured shorthand, or inline body' });
        }
    }
    return {
        name: 'every-action-has-steps-or-cured',
        description: 'Each action declares either an explicit steps list or a cured shorthand or an inline body.',
        ok: violations.length === 0,
        violations,
    };
}
/** Every model has at least one attribute marked as primary key (id, isPrimary, or auto). */
export function checkModelsHavePrimaryKey(spec) {
    const violations = [];
    for (const { model, path, modelName } of iterateModels(spec)) {
        const attrs = model.attributes || {};
        let hasPk = false;
        for (const [attrName, attrSpec] of Object.entries(attrs)) {
            // Primary if name is 'id' OR attrSpec contains 'isPrimary' OR 'auto=' modifier.
            const text = typeof attrSpec === 'string' ? attrSpec : (attrSpec?.modifiers?.join?.(' ') ?? '');
            if (attrName === 'id') {
                hasPk = true;
                break;
            }
            if (/\bisPrimary\b/.test(text)) {
                hasPk = true;
                break;
            }
            if (/\bauto\s*=\s*(uuid|nanoid|cuid|increment)/.test(text)) {
                hasPk = true;
                break;
            }
        }
        // Composite-key shape (`keys: [a, b]` on the model itself).
        if (!hasPk && Array.isArray(model.keys) && model.keys.length > 0)
            hasPk = true;
        if (!hasPk) {
            violations.push({ path, issue: `model ${modelName} has no detectable primary key` });
        }
    }
    return {
        name: 'every-model-has-primary-key',
        description: 'Every model declares a primary key (id attribute, isPrimary modifier, auto-id, or composite keys).',
        ok: violations.length === 0,
        violations,
    };
}
/** Every emit references a declared event. */
export function checkEmitsReferenceDeclaredEvents(spec) {
    const violations = [];
    // Build the set of declared event names from `events:` blocks across components.
    const declaredEvents = new Set();
    const components = spec.components || {};
    for (const comp of Object.values(components)) {
        const events = comp.events || {};
        for (const eventName of Object.keys(events))
            declaredEvents.add(eventName);
    }
    // Top-level events: block too (some specs declare globally).
    for (const eventName of Object.keys(spec.events || {}))
        declaredEvents.add(eventName);
    for (const { action, path } of iterateActions(spec)) {
        const emits = action.emits || action.publishes || [];
        if (!Array.isArray(emits))
            continue;
        for (const emit of emits) {
            const eventName = typeof emit === 'string' ? emit : emit?.event ?? emit?.name;
            if (!eventName)
                continue;
            // Strip any qualifier prefix (e.g. 'on.UserCreated' -> 'UserCreated').
            const cleanName = eventName.replace(/^[a-z]+\./, '');
            if (!declaredEvents.has(cleanName)) {
                violations.push({
                    path: `${path}.emits`,
                    issue: `event '${cleanName}' is emitted but not declared in any events: block`,
                });
            }
        }
    }
    return {
        name: 'every-emit-references-declared-event',
        description: 'Every action that emits an event references a declared event in events: blocks.',
        ok: violations.length === 0,
        violations,
    };
}
/** No two model declarations with the same name across components (collision risk at realize). */
export function checkNoDuplicateModelNames(spec) {
    const violations = [];
    const seen = new Map();
    for (const { componentName, modelName, path } of iterateModels(spec)) {
        if (!seen.has(modelName))
            seen.set(modelName, []);
        seen.get(modelName).push(componentName);
    }
    for (const [modelName, components] of seen.entries()) {
        if (components.length > 1) {
            violations.push({
                path: `models.${modelName}`,
                issue: `model '${modelName}' declared in multiple components: ${components.join(', ')}`,
            });
        }
    }
    return {
        name: 'no-duplicate-model-names',
        description: 'No model name is declared in more than one component.',
        ok: violations.length === 0,
        violations,
    };
}
/** Every action has at least one step bullet OR explicit cured shorthand. Also checks step bullets are non-empty strings. */
export function checkStepBulletsWellFormed(spec) {
    const violations = [];
    for (const { action, path } of iterateActions(spec)) {
        const steps = Array.isArray(action.steps) ? action.steps : [];
        for (let i = 0; i < steps.length; i++) {
            const s = steps[i];
            const text = typeof s === 'string' ? s : s?.step;
            if (typeof text !== 'string' || text.trim() === '') {
                violations.push({ path: `${path}.steps[${i}]`, issue: 'step bullet is empty or non-string' });
            }
            else if (text.length > 250) {
                violations.push({ path: `${path}.steps[${i}]`, issue: `step bullet is unusually long (${text.length} chars) — likely should be split into multiple steps` });
            }
        }
    }
    return {
        name: 'step-bullets-well-formed',
        description: 'Each step bullet is a non-empty string of reasonable length (<250 chars).',
        ok: violations.length === 0,
        violations,
    };
}
export const ALL_CHECKS = [
    checkActionsHaveStepsOrCured,
    checkModelsHavePrimaryKey,
    checkEmitsReferenceDeclaredEvents,
    checkNoDuplicateModelNames,
    checkStepBulletsWellFormed,
];
/** Run every check against the parsed spec and return all results. */
export function runAllChecks(spec) {
    return ALL_CHECKS.map((check) => check(spec));
}
//# sourceMappingURL=verify-checks.js.map