/**
 * V3.1 Inference Engine - Entry Point
 * Exports complete inference engine functionality
 */
// Core inference engine
export * from './core/index.js';
// Logical inference capabilities
export * from './logical/index.js';
// Deployment inference capabilities
export * from './deployment/index.js';
// Comprehensive inference engine
export { ComprehensiveInferenceEngine } from './comprehensive-engine.js';
// Dev specly generator
export { generateDevSpecly } from './dev-specly-generator.js';
// Specly format converter
import { SpeclyConverter } from './core/specly-converter.js';
// Dev specly generator
import { generateDevSpecly } from './dev-specly-generator.js';
// Quint → TypeScript transpiler
export { transpileEntityGuards, extractStateVariables, transpileQuintFile, generateGuardsModule, transpilePhase2Guard } from './quint-transpiler.js';
export { verifySpec, buildSnapshot, formatVerificationResult } from './verification.js';
export { transpileActions, generateActionsModule } from './quint-transpiler.js';
import { ComprehensiveInferenceEngine } from './comprehensive-engine.js';
class SpecVerseInferenceEngine {
    name = 'inference';
    version = '3.5.2';
    capabilities = ['infer', 'logical-inference', 'deployment-inference', 'rule-engine'];
    _engine = null;
    async initialize(config) {
        this._engine = new ComprehensiveInferenceEngine(config?.options || {}, config?.debug || false);
    }
    getInfo() {
        return { name: this.name, version: this.version, capabilities: this.capabilities };
    }
    async loadRules() {
        if (!this._engine)
            throw new Error('Inference engine not initialized.');
        return this._engine.loadRules();
    }
    async infer(ast, options) {
        if (!this._engine)
            throw new Error('Inference engine not initialized.');
        await this._engine.loadRules();
        const components = ast.components || [];
        // Run inference on EVERY component that has models — not just the first.
        // Pre-fix, only `findIndex(...models)` ran inference on a single component
        // and the rest were passthrough. Multi-component specs (idle-meta with
        // 8 domains) had CRUD ops + behavior promotion only on the first
        // component; downstream PlayerController, GameController etc. shipped
        // with empty `cured:` because the user spec only declared `actions:`,
        // and inference never filled in the CRUD half. This loop closes that
        // gap — each model-bearing component gets its own inference pass + merge.
        const inferTargetIndices = [];
        for (let i = 0; i < components.length; i++) {
            if (components[i].models && components[i].models.length > 0) {
                inferTargetIndices.push(i);
            }
        }
        const inferredComponents = new Map();
        const allDeployments = {};
        let firstResult = null; // kept for backward-compat return shape
        const aggregatedValidation = {
            valid: true, errors: [], warnings: [],
        };
        const aggregatedStats = {
            modelsProcessed: 0, controllersGenerated: 0, servicesGenerated: 0,
            eventsGenerated: 0, viewsGenerated: 0,
        };
        for (const inferTargetIndex of inferTargetIndices) {
            const targetComp = components[inferTargetIndex];
            // Convert AST ModelSpec[] to inference ModelDefinition[]
            const models = (targetComp.models || []).map((model) => ({
                name: model.name,
                attributes: (model.attributes || []).map((attr) => ({
                    name: attr.name,
                    type: attr.type || 'String',
                    required: attr.required || false,
                    unique: attr.unique || false,
                    default: attr.default,
                    auto: attr.auto,
                    category: attr.category,
                })),
                relationships: (model.relationships || []).map((rel) => ({
                    name: rel.name,
                    type: rel.type,
                    targetModel: rel.target,
                    cascadeDelete: rel.cascade || false,
                })),
                lifecycle: model.lifecycles?.[0] ? {
                    name: model.lifecycles[0].name,
                    states: model.lifecycles[0].states || [],
                    transitions: Object.entries(model.lifecycles[0].transitions || {}).map(([name, trans]) => ({
                        name, from: trans.from, to: trans.to, conditions: trans.condition ? [trans.condition] : [],
                    })),
                } : undefined,
                behaviors: model.behaviors ? Object.entries(model.behaviors).map(([name, b]) => ({
                    name, description: b.description, parameters: b.parameters, returns: b.returns,
                    requires: b.requires, ensures: b.ensures, publishes: b.publishes,
                    steps: b.steps,
                    // Routing overrides — preserved through the AST→ModelDefinition
                    // conversion so the inference engine's controller-generator can
                    // promote them onto the resulting customActions, and the route
                    // generator can mount the action at the explicit path/method.
                    // Same lesson as steps (TODO #48): if it's not in this map, it's
                    // silently dropped.
                    path: b.path,
                    method: b.method,
                })) : [],
                profiles: [],
                metadata: model.metadata || {},
                // Phase 2 — carry model-level constraints (already convention-processed
                // by the parser into `{ on, requires: { type:'guard', body, … } }`)
                // through the AST→ModelDefinition conversion. Same lesson as steps /
                // routing above: not in this map → silently dropped, and `spv realize`
                // emits no `<Model>.guards.ts`. logical-engine + specly-converter then
                // preserve + serialize them downstream.
                constraints: model.constraints,
                // Same lesson for model-level composite uniqueness (`unique: [[a, b]]`):
                // not in this map → dropped, and `spv realize` emits no Prisma
                // `@@unique`. Carried through logical-engine + specly-converter below.
                unique: model.unique,
            }));
            // Collect extension entity data
            const extensionData = {};
            if (targetComp.promotions)
                extensionData.promotions = targetComp.promotions;
            // Pass user-declared views / services / controllers into inference so
            // generators can honor them. Without this, user declarations were
            // silently dropped and only auto-generated entries survived — e.g.
            // declared DashboardView entries vanished because the view-generator
            // does not auto-generate dashboards in essentials-only mode.
            // The parser's AST gives these as arrays of {name, ...} objects;
            // downstream generators expect a name-keyed object, so normalize.
            const toNameKeyed = (v) => {
                if (!v)
                    return undefined;
                if (Array.isArray(v)) {
                    const out = {};
                    for (const item of v) {
                        if (item && item.name)
                            out[item.name] = item;
                    }
                    return Object.keys(out).length > 0 ? out : undefined;
                }
                return (typeof v === 'object') ? v : undefined;
            };
            const userViews = toNameKeyed(targetComp.views);
            const userServices = toNameKeyed(targetComp.services);
            const userControllers = toNameKeyed(targetComp.controllers);
            if (userViews)
                extensionData.userSpecifiedViews = userViews;
            if (userServices)
                extensionData.userSpecifiedServices = userServices;
            if (userControllers)
                extensionData.userSpecifiedControllers = userControllers;
            const compName = targetComp.name || `Component${inferTargetIndex}`;
            const result = await this._engine.inferCompleteSpecification(models, compName, options?.targetEnvironment || 'development', extensionData);
            if (!firstResult)
                firstResult = result;
            // Aggregate validation + statistics across all inferred components
            aggregatedValidation.errors.push(...(result.validation?.errors || []));
            aggregatedValidation.warnings.push(...(result.validation?.warnings || []));
            if (!result.validation?.valid)
                aggregatedValidation.valid = false;
            aggregatedStats.modelsProcessed += result.statistics?.modelsProcessed || 0;
            aggregatedStats.controllersGenerated += result.statistics?.controllersGenerated || 0;
            aggregatedStats.servicesGenerated += result.statistics?.servicesGenerated || 0;
            aggregatedStats.eventsGenerated += result.statistics?.eventsGenerated || 0;
            aggregatedStats.viewsGenerated += result.statistics?.viewsGenerated || 0;
            Object.assign(allDeployments, result.deployments || {});
            // Merge inferred result with original component metadata and passthrough sections
            const inferredComponent = { ...result.component, name: compName };
            if (targetComp.version)
                inferredComponent.version = String(targetComp.version);
            if (targetComp.description)
                inferredComponent.description = targetComp.description;
            // Copy / merge sections from the original component into the inferred one.
            //   - For map-shaped sections (controllers / services / views / events / etc.),
            //     merge by key so user-declared entries coexist with inferred ones.
            //     User-declared values WIN on key collision (preserve user intent).
            //   - For non-map sections (deployments, conventions, etc.) preserve the
            //     prior behavior: inferred takes priority unless empty.
            const MAP_SECTIONS = new Set(['controllers', 'services', 'views', 'events']);
            // The parser emits map-shaped sections (controllers/services/views/events)
            // as arrays of { name, ...rest } in the AST, while the inference engine
            // emits them as name-keyed objects. Normalize arrays to objects before
            // merging so user-declared entries (with different names from inferred ones)
            // survive into the final component.
            const normalizeToMap = (v) => {
                if (!v)
                    return null;
                if (Array.isArray(v)) {
                    const out = {};
                    for (const item of v) {
                        if (item && item.name)
                            out[item.name] = item;
                    }
                    return out;
                }
                return (typeof v === 'object') ? v : null;
            };
            for (const [key, value] of Object.entries(targetComp)) {
                if (key === 'name' || key === 'models' || value == null)
                    continue;
                const existing = inferredComponent[key];
                const isEmpty = !existing || (typeof existing === 'object' && !Array.isArray(existing) && Object.keys(existing).length === 0)
                    || (Array.isArray(existing) && existing.length === 0);
                if (isEmpty) {
                    inferredComponent[key] = value;
                }
                else if (MAP_SECTIONS.has(key)) {
                    // Both arrays-of-named-items and name-keyed objects normalize to a map.
                    // Merge user keys + inferred keys. On collision, the user spec wins
                    // FIELD-by-FIELD — so a bare `PollController: { model: Poll }` in the
                    // user spec doesn't wipe the inferred PollController's cured ops.
                    // Inferred fields the user didn't explicitly set survive.
                    const userMap = normalizeToMap(value);
                    const existingMap = normalizeToMap(existing);
                    if (userMap && existingMap) {
                        const merged = { ...existingMap };
                        for (const [entryKey, userEntry] of Object.entries(userMap)) {
                            const existingEntry = existingMap[entryKey];
                            if (existingEntry && typeof userEntry === 'object' && !Array.isArray(userEntry)
                                && typeof existingEntry === 'object' && !Array.isArray(existingEntry)) {
                                // Per-entry merge: user fields override inferred ones IFF
                                // they're non-empty. Empty objects / arrays / null come from
                                // the parser auto-filling placeholder fields the user spec
                                // didn't actually declare — preserving the inferred value
                                // is the right semantic for "user didn't say anything about
                                // this field".
                                const finalEntry = { ...existingEntry };
                                for (const [fieldKey, fieldVal] of Object.entries(userEntry)) {
                                    const isEmptyObj = fieldVal && typeof fieldVal === 'object'
                                        && !Array.isArray(fieldVal) && Object.keys(fieldVal).length === 0;
                                    const isEmptyArr = Array.isArray(fieldVal) && fieldVal.length === 0;
                                    if (fieldVal == null || isEmptyObj || isEmptyArr)
                                        continue;
                                    finalEntry[fieldKey] = fieldVal;
                                }
                                merged[entryKey] = finalEntry;
                            }
                            else {
                                merged[entryKey] = userEntry;
                            }
                        }
                        inferredComponent[key] = merged;
                    }
                }
            }
            inferredComponents.set(inferTargetIndex, inferredComponent);
        }
        // Build YAML output: components header written once, each component appended
        let yamlOutput = 'components:\n';
        for (let i = 0; i < components.length; i++) {
            const comp = components[i];
            const compName = comp.name || `Component${i}`;
            const inferredForThisComp = inferredComponents.get(i);
            if (inferredForThisComp) {
                // This component had models — serialize the inferred result via SpeclyConverter
                // SpeclyConverter.toSpecly() outputs "components:\n  Name:\n  ..." so we strip the header
                const fullSpecly = SpeclyConverter.toSpecly(inferredForThisComp, compName);
                const withoutHeader = fullSpecly.replace(/^components:\n/, '');
                yamlOutput += withoutHeader + '\n';
            }
            else {
                // Passthrough component — serialize all its sections via SpeclyConverter
                const passComp = {};
                for (const [key, value] of Object.entries(comp)) {
                    if (key !== 'name' && value != null) {
                        passComp[key] = value;
                    }
                }
                const fullSpecly = SpeclyConverter.toSpecly(passComp, compName);
                const withoutHeader = fullSpecly.replace(/^components:\n/, '');
                yamlOutput += withoutHeader + '\n';
            }
        }
        // Append deployments — aggregated across all inferred components
        if (Object.keys(allDeployments).length > 0) {
            const yaml = await import('js-yaml');
            yamlOutput += '\ndeployments:\n';
            yamlOutput += yaml.dump(allDeployments, { indent: 2, lineWidth: -1, noRefs: true })
                .split('\n').map((l) => l ? '  ' + l : '').join('\n');
        }
        else if (ast.deployments && ast.deployments.length > 0) {
            // Preserve original deployments
            const yaml = await import('js-yaml');
            const deploymentsObj = {};
            for (const dep of ast.deployments) {
                const { name, ...rest } = dep;
                if (name)
                    deploymentsObj[name] = rest;
            }
            yamlOutput += '\ndeployments:\n';
            yamlOutput += yaml.dump(deploymentsObj, { indent: 2, lineWidth: -1, noRefs: true })
                .split('\n').map((l) => l ? '  ' + l : '').join('\n');
        }
        else if (ast.deployments !== undefined) {
            // Preserve empty deployments
            yamlOutput += '\ndeployments: {}\n';
        }
        // Generate dev.specly for runtime DevShell — built from the FIRST inferred
        // component for backward compatibility (DevShell consumes a single component
        // shape today). A future change can extend this to aggregate all inferred
        // components.
        const firstInferred = inferTargetIndices.length > 0
            ? inferredComponents.get(inferTargetIndices[0])
            : undefined;
        const devYaml = firstInferred
            ? generateDevSpecly(firstInferred, firstInferred.name)
            : undefined;
        return {
            component: firstInferred || {},
            deployments: allDeployments,
            yaml: yamlOutput.trim(),
            devYaml,
            validation: aggregatedValidation,
            statistics: {
                ...aggregatedStats,
                rulesApplied: firstResult?.statistics?.totalRulesApplied || 0,
                processingTimeMs: firstResult?.statistics?.processingTimeMs || 0,
            },
        };
    }
}
export const engine = new SpecVerseInferenceEngine();
export default engine;
//# sourceMappingURL=index.js.map