/**
 * SpecVerse Code Realization
 *
 * Main entry point for code generation (realization) from SpecVerse specifications.
 * Transforms minimal specs into production-ready implementations.
 */
// Types (TemplateContext, etc.)
export * from './types/index.js';
// Utilities
export * from './utils/index.js';
// Generators
export * from './generators/index.js';
// Library
export { InstanceFactoryLibrary, createDefaultLibrary } from './library/library.js';
export { createResolver } from './library/resolver.js';
// Code generator
export { createCodeGenerator } from './engines/code-generator.js';
import { writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
import { dirname, join, basename } from 'path';
import { fileURLToPath } from 'url';
import { createDefaultLibrary } from './library/library.js';
import { createResolver } from './library/resolver.js';
import { createCodeGenerator } from './engines/code-generator.js';
import { loadManifest } from './utils/manifest-loader.js';
class SpecVerseRealizeEngine {
    name = 'realize';
    version = '3.5.2';
    capabilities = ['realize', 'code-generation', 'manifest-resolution', 'instance-factories'];
    library = null;
    resolver = null;
    codeGenerator = null;
    manifest = null;
    initialized = false;
    async initialize(config) {
        const workingDir = config?.workingDir || process.cwd();
        // Load instance factory library
        this.library = await createDefaultLibrary(workingDir);
        // Load and resolve manifest if provided
        if (config?.manifestPath) {
            this.manifest = loadManifest(config.manifestPath);
            this.resolver = createResolver(this.library, this.manifest);
        }
        // Create code generator
        this.codeGenerator = createCodeGenerator();
        this.initialized = true;
    }
    getInfo() {
        return { name: this.name, version: this.version, capabilities: this.capabilities };
    }
    resolve(capability) {
        if (!this.resolver)
            throw new Error('Realize engine not initialized with manifest.');
        return this.resolver.resolveCapability(capability);
    }
    async generate(resolved, template, context) {
        if (!this.codeGenerator)
            throw new Error('Realize engine not initialized.');
        return this.codeGenerator.generateFromTemplate(resolved, template, context);
    }
    /**
     * Realize all code from an AI-optimized spec.
     * This is the full pipeline equivalent of the hand-written CLI's `case 'all':`.
     */
    async realizeAll(spec, outputDir) {
        if (!this.initialized || !this.resolver || !this.codeGenerator) {
            throw new Error('Realize engine not initialized. Call initialize() with manifestPath.');
        }
        const files = [];
        const errors = [];
        const allModels = Object.values(spec.models || {});
        // L3 verification (Quint guards) is NOT run here. It lives in
        // `validate --verify` so users can opt into it explicitly and run
        // it on any spec — raw or inferred — without also generating code.
        // See engines/src/inference/verification.ts.
        const writeOutput = (output) => {
            // A generator can signal "skip this file" by returning an empty string.
            if (output.code === '')
                return;
            const dir = dirname(output.filePath);
            if (!existsSync(dir))
                mkdirSync(dir, { recursive: true });
            writeFileSync(output.filePath, output.code, 'utf-8');
            files.push(basename(output.filePath));
        };
        // Prefer compiled .js in dist/libs/ over .ts in libs/ (Node v24+ can't strip types from node_modules)
        const resolveGenPath = (tsPath) => {
            // Try dist/libs/ compiled JS first
            if (tsPath.includes('/libs/')) {
                const distJsPath = tsPath.replace('/libs/', '/dist/libs/').replace(/\.tsx?$/, '.js');
                if (existsSync(distJsPath))
                    return distJsPath;
            }
            // Fall back to .js alongside .ts
            const jsPath = tsPath.replace(/\.tsx?$/, '.js');
            if (existsSync(jsPath))
                return jsPath;
            // Fall back to .ts (works in dev, fails in node_modules on Node v24+)
            if (existsSync(tsPath))
                return tsPath;
            return null;
        };
        const tryResolve = (capability) => {
            try {
                return this.resolver.resolveCapability(capability);
            }
            catch {
                return null;
            }
        };
        // 1. ORM Schema
        const ormResolved = tryResolve('orm.schema');
        if (ormResolved?.instanceFactory?.codeTemplates?.schema) {
            try {
                const output = await this.codeGenerator.generateFromTemplate(ormResolved, 'schema', { spec, models: allModels }, { outputDir });
                writeOutput(output);
                console.log(`   ✅ ORM schema: ${output.filePath}`);
            }
            catch (e) {
                errors.push(`ORM: ${e.message}`);
            }
        }
        // 1.5 Event infrastructure (types + bus + websocket bridge)
        const eventResolved = tryResolve('messaging.events');
        if (eventResolved?.instanceFactory?.codeTemplates) {
            const eventFiles = [];
            // Generate in dependency order: types first, then bus, then websocket
            const orderedTemplates = ['types', 'bus', 'websocket'].filter(t => eventResolved.instanceFactory.codeTemplates[t]);
            // Also generate any other templates (publisher, subscriber)
            for (const t of Object.keys(eventResolved.instanceFactory.codeTemplates)) {
                if (!orderedTemplates.includes(t))
                    orderedTemplates.push(t);
            }
            for (const templateName of orderedTemplates) {
                try {
                    const output = await this.codeGenerator.generateFromTemplate(eventResolved, templateName, { spec, models: allModels, manifest: this.manifest }, { outputDir });
                    writeOutput(output);
                    eventFiles.push(basename(output.filePath));
                }
                catch (e) {
                    errors.push(`Event ${templateName}: ${e.message}`);
                }
            }
            if (eventFiles.length)
                console.log(`   ✅ Events: ${eventFiles.join(', ')}`);
        }
        // 2. Controllers (per model) — use spec controllers if available, else generate from model
        const ctrlResolved = tryResolve('service.controller');
        if (ctrlResolved?.instanceFactory?.codeTemplates?.controllers) {
            // Build controller lookup from spec
            const specControllers = Array.isArray(spec.controllers)
                ? spec.controllers
                : Object.values(spec.controllers || {});
            const controllerLookup = {};
            for (const c of specControllers)
                controllerLookup[c.name] = c;
            for (const model of allModels) {
                try {
                    const ctrlName = `${model.name}Controller`;
                    const specCtrl = controllerLookup[ctrlName];
                    const controller = specCtrl ? {
                        ...specCtrl,
                        model: specCtrl.modelReference || specCtrl.model || model.name,
                        modelReference: specCtrl.modelReference || specCtrl.model || model.name,
                        cured: specCtrl.cured || { create: {}, retrieve: {}, update: {}, validate: {}, evolve: {}, delete: {} },
                    } : {
                        name: ctrlName,
                        model: model.name,
                        modelReference: model.name,
                        cured: { create: {}, retrieve: {}, update: {}, validate: {}, evolve: {}, delete: {} },
                    };
                    const output = await this.codeGenerator.generateFromTemplate(ctrlResolved, 'controllers', { spec, model, controller, models: allModels }, { outputDir });
                    writeOutput(output);
                }
                catch (e) {
                    errors.push(`Controller ${model.name}: ${e.message}`);
                }
            }
            console.log(`   ✅ Controllers: ${allModels.length} controller(s)`);
            // 2.5 AI Behaviors — generate .ai.ts files for unmatched steps
            try {
                const aiBehaviorsGenPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'services', 'templates', 'prisma', 'ai-behaviors-generator.ts');
                const aiBehaviorsGen = resolveGenPath(aiBehaviorsGenPath);
                if (aiBehaviorsGen) {
                    const { default: generateAiBehaviors } = await import(aiBehaviorsGen);
                    let aiBehaviorCount = 0;
                    for (const model of allModels) {
                        const ctrlName = `${model.name}Controller`;
                        const specCtrl = controllerLookup[ctrlName];
                        if (specCtrl?.actions && Object.keys(specCtrl.actions).length > 0) {
                            const code = await generateAiBehaviors({ spec, model, controller: specCtrl, models: allModels });
                            if (code) {
                                const filePath = join(outputDir, 'backend', 'src', 'behaviors', `${ctrlName}.ai.ts`);
                                const dir = dirname(filePath);
                                if (!existsSync(dir))
                                    mkdirSync(dir, { recursive: true });
                                writeFileSync(filePath, code);
                                aiBehaviorCount++;
                            }
                        }
                    }
                    if (aiBehaviorCount > 0) {
                        console.log(`   ✅ AI Behaviors: ${aiBehaviorCount} file(s) — review in behaviors/*.ai.ts`);
                    }
                }
            }
            catch (e) {
                errors.push(`AI Behaviors: ${e.message}`);
            }
        }
        // 3. Services
        const servicesList = Array.isArray(spec.services) ? spec.services : Object.values(spec.services || {});
        if (ctrlResolved?.instanceFactory?.codeTemplates?.services) {
            // Load helpers for pre-scanning unmatched steps + generating AI behavior files
            let scanUnmatched = null;
            let generateAiFile = null;
            try {
                const scanPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'services', 'templates', 'prisma', 'step-conventions.ts');
                const scanResolved = resolveGenPath(scanPath);
                if (scanResolved) {
                    const mod = await import(scanResolved);
                    scanUnmatched = mod.matchStep;
                }
                const aiGenPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'services', 'templates', 'prisma', 'ai-behaviors-generator.ts');
                const aiResolved = resolveGenPath(aiGenPath);
                if (aiResolved) {
                    const mod = await import(aiResolved);
                    generateAiFile = mod.generateAiBehaviorsFile;
                }
            }
            catch { /* non-fatal */ }
            /**
             * Pre-scan a service's operations to determine which steps don't match
             * conventions and will need AI-generated behavior functions. Runs the
             * same matchStep logic the service-generator uses, but in this pipeline's
             * module context so we don't need cross-module state.
             */
            const scanServiceUnmatched = (service) => {
                if (!scanUnmatched)
                    return [];
                const operations = service.operations || {};
                const entries = Array.isArray(operations)
                    ? operations.map((op) => [op.name, op])
                    : Object.entries(operations);
                const unmatched = [];
                const seenFunctions = new Set();
                for (const [opName, operation] of entries) {
                    const steps = operation.steps || operation.implementation?.steps || [];
                    const parameterNames = Object.keys(operation.parameters || {});
                    const declaredVars = new Set();
                    // Simulate precondition variable declarations
                    const preconditions = operation.requires || operation.preconditions || [];
                    for (const pc of preconditions) {
                        const m = typeof pc === 'string' ? pc.match(/^(\w+)\s+(?:exists|is\s+\w+)$/i) : null;
                        if (m)
                            declaredVars.add(m[1].charAt(0).toLowerCase() + m[1].slice(1));
                    }
                    for (let i = 0; i < steps.length; i++) {
                        const stepInput = steps[i];
                        const stepText = typeof stepInput === 'string' ? stepInput : stepInput?.step;
                        const stepAs = typeof stepInput === 'object' ? stepInput?.as : undefined;
                        const stepReturns = typeof stepInput === 'object' ? stepInput?.returns : undefined;
                        if (typeof stepText !== 'string')
                            continue;
                        const ctx = {
                            modelName: service.name.replace(/Service$/, ''),
                            prismaModel: service.name.replace(/Service$/, '').charAt(0).toLowerCase() + service.name.replace(/Service$/, '').slice(1),
                            serviceName: service.name,
                            operationName: opName,
                            stepNum: i + 1,
                            parameterNames,
                            declaredVars,
                            resultName: stepAs,
                        };
                        const result = scanUnmatched(stepText, ctx);
                        if (!result.matched && result.functionName && !seenFunctions.has(result.functionName)) {
                            seenFunctions.add(result.functionName);
                            unmatched.push({
                                step: stepText,
                                functionName: result.functionName,
                                operationName: opName,
                                parameterNames,
                                inputs: result.inputs || [],
                                returns: stepReturns,
                                modelName: service.name, // AI file uses service name as owner
                            });
                        }
                    }
                }
                return unmatched;
            };
            let svcAiCount = 0;
            for (const service of servicesList) {
                try {
                    const svcName = service.name || 'Service';
                    const output = await this.codeGenerator.generateFromTemplate(ctrlResolved, 'services', { spec, service: { name: svcName, ...service } }, { outputDir });
                    writeOutput(output);
                    // Pre-scan this service for unmatched steps and generate AI behaviors file if any
                    if (generateAiFile) {
                        const unmatched = scanServiceUnmatched(service);
                        if (unmatched.length > 0) {
                            const aiFile = await generateAiFile({
                                ownerName: svcName,
                                unmatchedFunctions: unmatched,
                                availableModels: allModels.map((m) => m.name),
                                spec,
                            });
                            if (aiFile) {
                                const filePath = join(outputDir, 'backend', 'src', 'behaviors', `${svcName}.ai.ts`);
                                const dir = dirname(filePath);
                                if (!existsSync(dir))
                                    mkdirSync(dir, { recursive: true });
                                writeFileSync(filePath, aiFile);
                                svcAiCount++;
                            }
                        }
                    }
                }
                catch (e) {
                    errors.push(`Service: ${e.message}`);
                }
            }
            console.log(`   ✅ Services: ${servicesList.length} service(s)${svcAiCount > 0 ? ` (${svcAiCount} AI behaviors file${svcAiCount > 1 ? 's' : ''})` : ''}`);
        }
        // 4. Routes (per model) — use spec controllers for endpoint data
        const routeResolved = tryResolve('api.rest');
        if (routeResolved?.instanceFactory?.codeTemplates?.routes) {
            const specControllers2 = Array.isArray(spec.controllers)
                ? spec.controllers
                : Object.values(spec.controllers || {});
            const ctrlLookup2 = {};
            for (const c of specControllers2)
                ctrlLookup2[c.name] = c;
            for (const model of allModels) {
                try {
                    const ctrlName = `${model.name}Controller`;
                    const specCtrl = ctrlLookup2[ctrlName];
                    const controller = specCtrl || {
                        name: ctrlName,
                        model: model.name,
                        modelReference: model.name,
                        cured: { create: {}, retrieve: {}, update: {}, validate: {}, evolve: {}, delete: {} },
                    };
                    const output = await this.codeGenerator.generateFromTemplate(routeResolved, 'routes', { spec, model, controller, models: allModels }, { outputDir });
                    writeOutput(output);
                }
                catch (e) {
                    errors.push(`Route ${model.name}: ${e.message}`);
                }
            }
            console.log(`   ✅ Routes: ${allModels.length} route handler(s)`);
        }
        // 4.5 Service routes — HTTP endpoints for service operations
        //     POST /api/services/{ServiceName}/{operationName}
        //     Mirrors app-demo's dynamic runtime URL shape (see
        //     specverse-app-demo/src/api/http-server.ts:611) so smoke-parity
        //     tests can hit both backends identically.
        if (routeResolved?.instanceFactory?.codeTemplates?.serviceRoutes && servicesList.length > 0) {
            let svcRouteCount = 0;
            for (const service of servicesList) {
                try {
                    const svcName = service.name || 'Service';
                    const output = await this.codeGenerator.generateFromTemplate(routeResolved, 'serviceRoutes', { spec, service: { name: svcName, ...service }, models: allModels }, { outputDir });
                    writeOutput(output);
                    svcRouteCount++;
                }
                catch (e) {
                    errors.push(`Service route ${service?.name}: ${e.message}`);
                }
            }
            if (svcRouteCount > 0)
                console.log(`   ✅ Service routes: ${svcRouteCount} file(s)`);
        }
        // 5. Views, Forms, Hooks
        const viewsResolved = tryResolve('ui.components');
        if (viewsResolved && spec.views) {
            const views = Array.isArray(spec.views) ? spec.views : Object.values(spec.views);
            let viewCount = 0, formCount = 0, hookCount = 0;
            for (const viewData of views) {
                const modelRef = viewData.primaryModel || viewData.model;
                const model = modelRef ? allModels.find((m) => m.name === modelRef) : undefined;
                // View component
                if (viewsResolved.instanceFactory.codeTemplates?.components) {
                    try {
                        const output = await this.codeGenerator.generateFromTemplate(viewsResolved, 'components', { spec, view: viewData, model, models: allModels }, { outputDir });
                        writeOutput(output);
                        viewCount++;
                    }
                    catch (e) {
                        errors.push(`View ${viewData?.name || 'unknown'}: ${e.message}`);
                    }
                }
                // Form
                if (model && viewsResolved.instanceFactory.codeTemplates?.forms) {
                    try {
                        const output = await this.codeGenerator.generateFromTemplate(viewsResolved, 'forms', { spec, model, view: viewData, models: allModels }, { outputDir });
                        writeOutput(output);
                        formCount++;
                    }
                    catch (e) {
                        errors.push(`Form ${model?.name || 'unknown'}: ${e.message}`);
                    }
                }
                // Hook
                if (model && viewsResolved.instanceFactory.codeTemplates?.hooks) {
                    try {
                        const output = await this.codeGenerator.generateFromTemplate(viewsResolved, 'hooks', { spec, model, view: viewData, models: allModels }, { outputDir });
                        writeOutput(output);
                        hookCount++;
                    }
                    catch (e) {
                        errors.push(`Hook ${model?.name || 'unknown'}: ${e.message}`);
                    }
                }
            }
            if (viewCount)
                console.log(`   ✅ Views: ${viewCount} component(s)`);
            if (formCount)
                console.log(`   ✅ Forms: ${formCount} form(s)`);
            if (hookCount)
                console.log(`   ✅ Hooks: ${hookCount} hook(s)`);
        }
        // 6. Types (per model)
        if (viewsResolved?.instanceFactory?.codeTemplates?.types) {
            for (const model of allModels) {
                try {
                    const output = await this.codeGenerator.generateFromTemplate(viewsResolved, 'types', { spec, model, models: allModels }, { outputDir });
                    writeOutput(output);
                }
                catch (e) {
                    errors.push(`Type ${model?.name || 'unknown'}: ${e.message}`);
                }
            }
            console.log(`   ✅ Types: ${allModels.length} model type(s)`);
        }
        // 7. Scaffolding (package.json, tsconfig, etc.)
        const scaffoldResolved = tryResolve('project.scaffold');
        if (scaffoldResolved?.instanceFactory?.codeTemplates) {
            const scaffoldFiles = [];
            for (const [templateName] of Object.entries(scaffoldResolved.instanceFactory.codeTemplates)) {
                try {
                    const output = await this.codeGenerator.generateFromTemplate(scaffoldResolved, templateName, { spec, models: allModels, manifest: this.manifest, instanceFactories: [] }, { outputDir });
                    writeOutput(output);
                    scaffoldFiles.push(basename(output.filePath));
                }
                catch (e) {
                    errors.push(`Scaffold: ${e.message}`);
                }
            }
            if (scaffoldFiles.length)
                console.log(`   ✅ Project scaffolding: ${scaffoldFiles.join(', ')}`);
        }
        // 8. Backend app entry point + Fastify server with route wiring
        const appResolved = tryResolve('app.entrypoint');
        if (appResolved?.instanceFactory?.codeTemplates) {
            const appFiles = [];
            for (const [templateName] of Object.entries(appResolved.instanceFactory.codeTemplates)) {
                try {
                    const output = await this.codeGenerator.generateFromTemplate(appResolved, templateName, { spec, models: allModels, manifest: this.manifest }, { outputDir });
                    writeOutput(output);
                    appFiles.push(basename(output.filePath));
                }
                catch (e) {
                    errors.push(`App ${templateName}: ${e.message}`);
                }
            }
            // Generate Fastify server with auto-wired routes
            try {
                const serverGenPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'controllers', 'templates', 'fastify', 'server-generator.ts');
                const serverGen = resolveGenPath(serverGenPath);
                if (serverGen) {
                    const genPath = serverGen;
                    const { default: generateServer } = await import(genPath);
                    const serverCode = generateServer({ spec, models: allModels });
                    const serverPath = join(outputDir, 'backend', 'src', 'main.ts');
                    const serverDir = dirname(serverPath);
                    if (!existsSync(serverDir))
                        mkdirSync(serverDir, { recursive: true });
                    writeFileSync(serverPath, serverCode);
                    appFiles.push('main.ts');
                }
            }
            catch (e) {
                errors.push(`Server: ${e.message}`);
            }
            if (appFiles.length)
                console.log(`   ✅ Backend application: ${appFiles.join(', ')}`);
        }
        // 9. Frontend app
        const frontendResolved = tryResolve('app.frontend');
        if (frontendResolved?.instanceFactory?.codeTemplates) {
            const frontendFiles = [];
            for (const [templateName] of Object.entries(frontendResolved.instanceFactory.codeTemplates)) {
                try {
                    const output = await this.codeGenerator.generateFromTemplate(frontendResolved, templateName, { spec, models: allModels, views: spec.views, manifest: this.manifest }, { outputDir });
                    writeOutput(output);
                    frontendFiles.push(basename(output.filePath));
                }
                catch (e) {
                    errors.push(`Frontend ${templateName}: ${e.message}`);
                }
            }
            if (frontendFiles.length)
                console.log(`   ✅ Frontend application: ${frontendFiles.join(', ')}`);
            // 9a. Generate shared view utilities and Tailwind config.
            // The frontend factory may declare outputStructure=standalone with
            // frontendDir="." — in that case views/lib live at the output root
            // instead of under `frontend/`.
            const frontendCfg = frontendResolved?.configuration || frontendResolved?.instanceFactory?.configuration || {};
            const frontendOutputStructure = frontendCfg.outputStructure || 'monorepo';
            const frontendRelDir = frontendOutputStructure === 'standalone'
                ? '.'
                : (frontendCfg.frontendDir || 'frontend');
            const frontendDir = frontendRelDir === '.' ? outputDir : join(outputDir, frontendRelDir);
            try {
                const sharedUtilsGen = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'views', 'templates', 'react', 'shared-utils-generator.ts');
                const sharedUtilsResolved = resolveGenPath(sharedUtilsGen);
                if (sharedUtilsResolved) {
                    const genPath = sharedUtilsResolved;
                    const { default: generateSharedUtils } = await import(genPath);
                    const result = generateSharedUtils({ spec });
                    const libDir = join(frontendDir, 'src', 'lib');
                    if (!existsSync(libDir))
                        mkdirSync(libDir, { recursive: true });
                    for (const file of result.files) {
                        writeFileSync(join(libDir, file.path), file.content);
                    }
                }
                // Tailwind config
                writeFileSync(join(frontendDir, 'tailwind.config.js'), `import path from 'path';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);

/** @type {import('tailwindcss').Config} */
export default {
  darkMode: 'class',
  content: [
    './index.html',
    './src/**/*.{js,ts,jsx,tsx}',
    path.join(path.dirname(require.resolve('@specverse/runtime/package.json')), 'dist/**/*.js'),
  ],
  theme: { extend: {} },
  plugins: [],
};
`);
                writeFileSync(join(frontendDir, 'postcss.config.js'), `export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
`);
            }
            catch (e) {
                errors.push(`SharedUtils: ${e.message}`);
            }
        }
        // 9b. Quint guards are no longer emitted to the generated backend.
        // They are L3 verification assertions about the SPEC, not runtime
        // checks on the user's data — so they run at realize time (see the
        // L3 verification gate at the start of realizeAll) and don't ship
        // to the user's project.
        // 10. CLI commands (Commander.js)
        try {
            const cliDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'cli', 'templates', 'commander');
            const entryGen = join(cliDir, 'cli-entry-generator.ts');
            const cmdGen = join(cliDir, 'command-generator.ts');
            const entryGenResolved = resolveGenPath(entryGen);
            const cmdGenResolved = resolveGenPath(cmdGen);
            if (entryGenResolved && cmdGenResolved) {
                // Extract commands from spec — check each component for a commands section
                const components = spec.components || [];
                const componentList = Array.isArray(components)
                    ? components
                    : Object.entries(components).map(([name, c]) => ({ name, ...c }));
                let commands = [];
                let cliComponentVersion;
                for (const comp of componentList) {
                    if (comp.commands) {
                        commands = Array.isArray(comp.commands)
                            ? comp.commands
                            : Object.entries(comp.commands).map(([name, def]) => ({ name, ...def }));
                        cliComponentVersion = comp.version;
                        break;
                    }
                }
                if (commands.length > 0) {
                    const cliOutputDir = join(outputDir, 'backend', 'src', 'cli');
                    if (!existsSync(cliOutputDir))
                        mkdirSync(cliOutputDir, { recursive: true });
                    // Generate entry point
                    const { default: generateCLIEntry } = await import(entryGenResolved);
                    const entryCode = generateCLIEntry({ spec, commands, componentVersion: cliComponentVersion });
                    const entryPath = join(cliOutputDir, 'index.ts');
                    writeFileSync(entryPath, entryCode);
                    // Generate individual command files
                    const { default: generateCommand } = await import(cmdGenResolved);
                    const cmdOutputDir = join(cliOutputDir, 'commands');
                    if (!existsSync(cmdOutputDir))
                        mkdirSync(cmdOutputDir, { recursive: true });
                    // The root command's subcommands are the actual CLI commands
                    const rootCommand = commands[0];
                    const subcommands = rootCommand.subcommands || {};
                    let cmdCount = 0;
                    for (const [cmdName, cmdDef] of Object.entries(subcommands)) {
                        try {
                            const code = generateCommand({ spec, command: { name: cmdName, ...cmdDef } });
                            writeFileSync(join(cmdOutputDir, `${cmdName}.ts`), code);
                            cmdCount++;
                        }
                        catch (e) {
                            errors.push(`CLI ${cmdName}: ${e.message}`);
                        }
                    }
                    console.log(`   ✅ CLI: ${cmdCount} command(s) generated`);
                    files.push('index.ts', ...Object.keys(subcommands).map(n => `${n}.ts`));
                }
            }
        }
        catch (e) {
            errors.push(`CLI generation: ${e.message}`);
        }
        // 11. Developer tools (VSCode extension, MCP server)
        // Only generate when the manifest maps tools.vscode or tools.mcp capabilities
        try {
            const toolsDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'libs', 'instance-factories', 'tools', 'templates');
            // VSCode extension — only if manifest maps tools.vscode
            if (tryResolve('tools.vscode')) {
                const vscodeGen = resolveGenPath(join(toolsDir, 'vscode', 'vscode-extension-generator.ts'));
                if (vscodeGen) {
                    try {
                        const { default: generateVSCodeExtension } = await import(vscodeGen);
                        const result = generateVSCodeExtension({ spec, outputDir, models: allModels });
                        console.log(`   ✅ VSCode extension: ${result}`);
                    }
                    catch (e) {
                        errors.push(`VSCode: ${e.message}`);
                    }
                }
            }
            // MCP server — only if manifest maps tools.mcp
            if (tryResolve('tools.mcp')) {
                const mcpGen = resolveGenPath(join(toolsDir, 'mcp', 'mcp-server-generator.ts'));
                if (mcpGen) {
                    try {
                        const { default: generateMCPServer } = await import(mcpGen);
                        const result = generateMCPServer({ spec, outputDir, models: allModels });
                        console.log(`   ✅ MCP server: ${result}`);
                    }
                    catch (e) {
                        errors.push(`MCP: ${e.message}`);
                    }
                }
            }
        }
        catch (e) {
            errors.push(`Tools: ${e.message}`);
        }
        // 12. Ship assets (templates, examples, schema) from engine packages
        try {
            const assetsCopied = await this.copyAssets(outputDir);
            if (assetsCopied.length > 0) {
                console.log(`   ✅ Assets: ${assetsCopied.join(', ')}`);
            }
        }
        catch (e) {
            errors.push(`Assets: ${e.message}`);
        }
        // 13. UI contract tests (Phase 1 of the inference test framework).
        // Rules live in engines/src/inference/ui-contracts/. Each rule is
        // a pure function (spec) => TestCase[]; the translator renders each
        // rule's output into a single .spec.ts file under
        // generated/code/tests/contract/. A Playwright config and the
        // test:contract npm script are emitted alongside.
        try {
            const { generateContractTests, normalizeSpec } = await import('../inference/ui-contracts/index.js');
            const normalized = normalizeSpec(spec);
            const contractFiles = generateContractTests(normalized);
            if (contractFiles.length > 0) {
                const contractDir = join(outputDir, 'tests', 'contract');
                if (!existsSync(contractDir))
                    mkdirSync(contractDir, { recursive: true });
                let totalTests = 0;
                for (const f of contractFiles) {
                    writeFileSync(join(contractDir, f.filename), f.contents, 'utf-8');
                    totalTests += f.testCount;
                }
                // Playwright config — written once at the output root. Uses
                // the tests/contract directory exclusively; behavioral tests
                // (if any) live under tests/e2e/ and are handled by a
                // separate playwright.behavioral.config.ts (not auto-generated).
                const playwrightConfigPath = join(outputDir, 'playwright.contract.config.ts');
                const playwrightConfig = `/**
 * Playwright config for auto-generated UI contract tests.
 * DO NOT EDIT — regenerated every \`spv realize\` run.
 *
 * Behavioral tests (hand-written) live under tests/e2e/ and use a
 * separate playwright.config.ts that you maintain yourself.
 */
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests/contract',
  timeout: 30_000,
  expect: { timeout: 10_000 },
  reporter: 'list',
  use: {
    baseURL: process.env.SPECVERSE_FRONTEND_URL || 'http://localhost:5173',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { browserName: 'chromium' },
    },
  ],
});
`;
                writeFileSync(playwrightConfigPath, playwrightConfig, 'utf-8');
                console.log(`   ✅ UI contract tests: ${contractFiles.length} file(s), ${totalTests} test case(s)`);
            }
        }
        catch (e) {
            errors.push(`UI contract tests: ${e.message}`);
        }
        // 14. Implementation conformance checks (Phase 5 of the inference
        // test framework). Static, grep-level checks that verify the
        // current React-Query-based implementation honors the design
        // contract. Runs in milliseconds, no browser needed. When the
        // runtime is rewritten in a different state library, this script
        // gets rewritten too — the design-contract Playwright tests above
        // are the stable layer, these checks are the short-lived ratchet
        // that enforces "no escape hatches in the current implementation".
        try {
            const scriptsDir = join(outputDir, 'scripts');
            if (!existsSync(scriptsDir))
                mkdirSync(scriptsDir, { recursive: true });
            const conformanceScript = `#!/usr/bin/env node
/**
 * Implementation conformance checks — auto-generated.
 * DO NOT EDIT — regenerated every \`spv realize\` run.
 *
 * Verifies the current React-Query-based runtime implementation
 * doesn't have escape hatches that could violate the design
 * contract:
 *   1. no-raw-fetch-in-views — view components go through apiClient
 *   2. no-bare-usequery-in-views — view components use named hooks
 *   3. websocket-invalidates-cache — WS events trigger cache refetch
 *   4. mutation-hooks-invalidate — mutations invalidate after success
 *
 * Exits 1 on any failure. Fast (grep-level, no browser).
 */
import { readFileSync, readdirSync, statSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const root = join(__dirname, '..');
let failed = false;

function walk(dir, pattern, exclude = []) {
  const hits = [];
  const visit = (d) => {
    let stat;
    try { stat = statSync(d); } catch { return; }
    if (!stat.isDirectory()) return;
    for (const entry of readdirSync(d)) {
      const p = join(d, entry);
      if (exclude.some(e => p.includes(e))) continue;
      let s;
      try { s = statSync(p); } catch { continue; }
      if (s.isDirectory()) visit(p);
      else if (/\\.(ts|tsx|js|jsx|mjs)$/.test(entry)) {
        const lines = readFileSync(p, 'utf-8').split('\\n');
        lines.forEach((line, i) => {
          if (pattern.test(line)) hits.push({ file: p, line: i + 1, text: line.trim() });
        });
      }
    }
  };
  visit(dir);
  return hits;
}

function report(name, ok, msg, hits) {
  console.log(\`\${ok ? '\\u2713' : '\\u2717'} \${name}\${msg ? ' \\u2014 ' + msg : ''}\`);
  if (!ok) {
    failed = true;
    if (hits) {
      for (const h of hits.slice(0, 5)) {
        console.log(\`    \${h.file}:\${h.line}  \${h.text.slice(0, 120)}\`);
      }
      if (hits.length > 5) console.log(\`    ... and \${hits.length - 5} more\`);
    }
  }
}

const frontendSrc = join(root, 'frontend', 'src');
const componentsDir = join(frontendSrc, 'components');
const pagesDir = join(frontendSrc, 'pages');

// 1. no-raw-fetch-in-views
{
  const hits = [
    ...walk(componentsDir, /\\bfetch\\s*\\(/),
    ...walk(pagesDir, /\\bfetch\\s*\\(/),
  ];
  report(
    'no-raw-fetch-in-views',
    hits.length === 0,
    hits.length === 0 ? '' : \`found \${hits.length} raw fetch() call(s) outside apiClient\`,
    hits
  );
}

// 2. no-bare-usequery-in-views — allow useQuery only inside hooks/useApi
{
  const hits = walk(frontendSrc, /\\buseQuery\\s*\\(/, ['hooks/useApi', 'lib/']);
  report(
    'no-bare-usequery-in-views',
    hits.length === 0,
    hits.length === 0 ? '' : \`found \${hits.length} bare useQuery() call(s) outside useApi\`,
    hits
  );
}

// 3. websocket-invalidates-cache — runtime must wire WS → invalidate
{
  const candidates = [
    join(root, 'node_modules', '@specverse', 'runtime', 'dist', 'runtime', 'views', 'react', 'hooks', 'useEntitySync.js'),
  ];
  let content = null;
  for (const path of candidates) {
    try { content = readFileSync(path, 'utf-8'); break; } catch { /* keep trying */ }
  }
  if (!content) {
    report('websocket-invalidates-cache', false, 'useEntitySync hook not found in @specverse/runtime');
  } else if (!/invalidateEntities/.test(content)) {
    report('websocket-invalidates-cache', false, 'useEntitySync does not call invalidateEntities on event receipt');
  } else {
    report('websocket-invalidates-cache', true, '');
  }
}

// 4. mutation-hooks-invalidate — useApi.ts must invalidate on success
{
  const useApiPath = join(frontendSrc, 'hooks', 'useApi.ts');
  let content = null;
  try { content = readFileSync(useApiPath, 'utf-8'); } catch { /* ignore */ }
  if (!content) {
    report('mutation-hooks-invalidate', false, 'frontend/src/hooks/useApi.ts not found');
  } else if (!/useExecuteOperationMutation/.test(content)) {
    report('mutation-hooks-invalidate', false, 'useExecuteOperationMutation not exported from useApi.ts');
  } else if (!/invalidateQueries/.test(content)) {
    report('mutation-hooks-invalidate', false, 'useApi.ts does not call invalidateQueries on mutation success');
  } else {
    report('mutation-hooks-invalidate', true, '');
  }
}

if (failed) {
  console.log('\\n\\u2717 conformance checks failed');
  process.exit(1);
}
console.log('\\n\\u2713 all conformance checks passed');
`;
            writeFileSync(join(scriptsDir, 'test-conformance.mjs'), conformanceScript, 'utf-8');
            console.log(`   ✅ UI contract conformance: scripts/test-conformance.mjs`);
        }
        catch (e) {
            errors.push(`UI conformance: ${e.message}`);
        }
        console.log(`\n✅ All code generated in: ${outputDir}`);
        if (errors.length) {
            console.warn(`⚠️  ${errors.length} warning(s) during generation`);
            for (const err of errors)
                console.warn(`  ${err}`);
        }
        return { files, errors };
    }
    /**
     * Copy static assets (templates, examples, schema) from engine packages
     * into the output directory so the generated project is self-contained.
     */
    async copyAssets(outputDir) {
        const copied = [];
        const copyDir = (src, dest, label) => {
            if (!existsSync(src))
                return;
            if (!existsSync(dest))
                mkdirSync(dest, { recursive: true });
            const copyRecursive = (s, d) => {
                for (const entry of readdirSync(s)) {
                    const srcPath = join(s, entry);
                    const destPath = join(d, entry);
                    if (statSync(srcPath).isDirectory()) {
                        if (!existsSync(destPath))
                            mkdirSync(destPath, { recursive: true });
                        copyRecursive(srcPath, destPath);
                    }
                    else {
                        copyFileSync(srcPath, destPath);
                    }
                }
            };
            copyRecursive(src, dest);
            copied.push(label);
        };
        // Assets live in engines/assets/ (co-located with this package)
        const enginesAssets = this.resolvePackageAssets('@specverse/engines', 'assets');
        if (enginesAssets) {
            copyDir(join(enginesAssets, 'examples'), join(outputDir, 'examples'), 'examples');
            copyDir(join(enginesAssets, 'prompts'), join(outputDir, 'prompts'), 'prompts');
        }
        // Copy composed schema from engine-entities (single source of truth)
        const thisDir = dirname(fileURLToPath(import.meta.url));
        const schemaCandidates = [];
        try {
            const { createRequire } = await import('module');
            const require = createRequire(import.meta.url);
            const entitiesPkg = dirname(require.resolve('@specverse/entities/package.json'));
            schemaCandidates.push(join(entitiesPkg, 'schema', 'SPECVERSE-SCHEMA.json'));
        }
        catch { /* engine-entities not available */ }
        schemaCandidates.push(join(thisDir, '..', 'schema', 'SPECVERSE-SCHEMA.json'), join(thisDir, '../..', 'schema', 'SPECVERSE-SCHEMA.json'));
        for (const schemaFile of schemaCandidates) {
            if (existsSync(schemaFile)) {
                const destSchema = join(outputDir, 'backend', 'schema');
                if (!existsSync(destSchema))
                    mkdirSync(destSchema, { recursive: true });
                copyFileSync(schemaFile, join(destSchema, 'SPECVERSE-SCHEMA.json'));
                copied.push('schema');
                break;
            }
        }
        return copied;
    }
    resolvePackageAssets(packageName, subdir) {
        const thisDir = dirname(fileURLToPath(import.meta.url));
        const isSelf = packageName === '@specverse/engines' || packageName === '@specverse/engine-realize';
        const candidates = [];
        if (isSelf) {
            // For our own package, look relative to this file
            candidates.push(join(thisDir, '..', subdir));
            candidates.push(join(thisDir, '../..', subdir));
        }
        // Try via node_modules (workspace layout and npm install)
        for (let i = 2; i <= 5; i++) {
            const up = Array(i).fill('..').join('/');
            candidates.push(join(thisDir, up, 'node_modules', ...packageName.split('/'), subdir));
        }
        candidates.push(join(process.cwd(), 'node_modules', ...packageName.split('/'), subdir));
        for (const candidate of candidates) {
            if (existsSync(candidate))
                return candidate;
        }
        return null;
    }
}
export const engine = new SpecVerseRealizeEngine();
export default engine;
export { SpecVerseRealizeEngine };
//# sourceMappingURL=index.js.map