/**
 * Behavior AI Service — session-based Claude conversation for generating
 * TypeScript function bodies for unmatched behavior steps.
 *
 * Architecture (mirrors specverse-app-demo/src/ai/ai-service.ts):
 * - First call: spawns Claude with --session-id <uuid> --system-prompt <prompt>
 * - Subsequent calls: --resume <uuid> for cached context (98% token savings)
 * - All steps for a controller share one session — Claude builds understanding
 *   of the spec across calls, generating coherent code
 * - Loads system prompt from assets/prompts/core/standard/v9/behavior.prompt.yaml
 *
 * Used by ai-behaviors-generator at realize time.
 */
import { spawn, execSync } from 'child_process';
import { randomUUID } from 'crypto';
import { existsSync, readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import yaml from 'js-yaml';
/**
 * Load behavior prompt from engines/assets/prompts/.
 */
function loadBehaviorPrompt() {
    try {
        // Find the engines package root by walking up from this file
        const __filename = fileURLToPath(import.meta.url);
        const __dirname = dirname(__filename);
        // dist/ai/behavior-ai-service.js or src/ai/behavior-ai-service.ts
        // Walk up to find assets/prompts
        const candidates = [
            join(__dirname, '..', '..', 'assets', 'prompts', 'core', 'standard', 'v9', 'behavior.prompt.yaml'),
            join(__dirname, '..', '..', '..', 'assets', 'prompts', 'core', 'standard', 'v9', 'behavior.prompt.yaml'),
        ];
        for (const path of candidates) {
            if (existsSync(path)) {
                const content = yaml.load(readFileSync(path, 'utf8'));
                const role = content?.system?.role || '';
                const context = content?.system?.context || '';
                const userTemplate = content?.user?.template || '';
                return {
                    system: `${role}\n\n${context}`.trim(),
                    userTemplate,
                };
            }
        }
    }
    catch {
        // Fall through to default
    }
    return null;
}
const DEFAULT_SYSTEM_PROMPT = `You are a SpecVerse behavior code generator.
Generate TypeScript function bodies for behavior steps that don't match convention patterns.
Use the global \`prisma\` client. Output only the function body, no fences, no explanation.`;
const DEFAULT_USER_TEMPLATE = `Generate a TypeScript function body for: "{{step}}"
Context: {{modelName}}.{{operationName}}, parameters: {{parameterNames}}
Output ONLY the function body code.`;
/**
 * Session-based AI service for generating behavior implementations.
 */
export class BehaviorAIService {
    claudePath;
    model;
    timeout;
    sessionId = null;
    initialized = false;
    currentProcess = null;
    systemPrompt;
    userTemplate;
    constructor(options = {}) {
        this.claudePath = options.claudePath || this.detectClaudePath();
        this.model = options.model || null;
        this.timeout = options.timeout || 60000;
        const prompt = loadBehaviorPrompt();
        this.systemPrompt = prompt?.system || DEFAULT_SYSTEM_PROMPT;
        this.userTemplate = prompt?.userTemplate || DEFAULT_USER_TEMPLATE;
    }
    detectClaudePath() {
        const home = process.env.HOME || '';
        const candidates = [
            join(home, '.claude', 'local', 'claude'),
            'claude',
        ];
        for (const candidate of candidates) {
            if (candidate === 'claude')
                return candidate;
            if (existsSync(candidate))
                return candidate;
        }
        return 'claude';
    }
    /**
     * Check if Claude CLI is available.
     */
    get isAvailable() {
        try {
            execSync(`${this.claudePath} --version`, { stdio: 'ignore', timeout: 5000 });
            return true;
        }
        catch {
            return false;
        }
    }
    /**
     * Start a new session for a controller. All step generations within the
     * controller will share this session, accumulating spec context.
     */
    startSession(controllerName) {
        this.sessionId = randomUUID();
        this.initialized = false;
        return this.sessionId;
    }
    /**
     * Generate a function body for a behavior step.
     * First call creates the session with system prompt; subsequent calls resume.
     */
    async generateBehavior(ctx) {
        if (!this.sessionId) {
            this.startSession(ctx.modelName);
        }
        if (!this.isAvailable)
            return null;
        const userPrompt = this.buildUserPrompt(ctx);
        const args = ['--print'];
        if (!this.initialized) {
            args.push('--session-id', this.sessionId);
            args.push('--system-prompt', this.systemPrompt);
        }
        else {
            args.push('--resume', this.sessionId);
        }
        if (this.model)
            args.push('--model', this.model);
        args.push('-p', userPrompt);
        return new Promise((resolve) => {
            const proc = spawn(this.claudePath, args, {
                stdio: ['ignore', 'pipe', 'pipe'],
                env: { ...process.env },
            });
            this.currentProcess = proc;
            let output = '';
            const timer = setTimeout(() => {
                proc.kill();
                resolve(null);
            }, this.timeout);
            proc.stdout?.on('data', (data) => { output += data.toString(); });
            proc.on('close', (code) => {
                clearTimeout(timer);
                this.currentProcess = null;
                if (code !== 0) {
                    resolve(null);
                    return;
                }
                this.initialized = true;
                resolve(this.extractCode(output));
            });
            proc.on('error', () => {
                clearTimeout(timer);
                this.currentProcess = null;
                resolve(null);
            });
        });
    }
    /**
     * End the current session.
     */
    endSession() {
        if (this.currentProcess) {
            this.currentProcess.kill();
            this.currentProcess = null;
        }
        this.sessionId = null;
        this.initialized = false;
    }
    buildUserPrompt(ctx) {
        const inputVars = ctx.parameterNames.length > 0 ? ctx.parameterNames.join(', ') : 'none';
        const inputSignature = ctx.parameterNames.length > 0
            ? `{ ${ctx.parameterNames.map(n => `${n}: any`).join('; ')} }`
            : 'Record<string, never>';
        const returnType = ctx.returnType || 'any';
        return this.userTemplate
            .replace(/\{\{step\}\}/g, ctx.step)
            .replace(/\{\{modelName\}\}/g, ctx.modelName)
            .replace(/\{\{operationName\}\}/g, ctx.operationName)
            .replace(/\{\{functionName\}\}/g, ctx.functionName)
            .replace(/\{\{parameterNames\}\}/g, inputVars)
            .replace(/\{\{inputVars\}\}/g, inputVars)
            .replace(/\{\{inputSignature\}\}/g, inputSignature)
            .replace(/\{\{returnType\}\}/g, returnType)
            .replace(/\{\{availableModels\}\}/g, ctx.availableModels.join(', ') || 'none');
    }
    extractCode(output) {
        let code = output.trim();
        // Strip markdown fences
        const codeBlock = code.match(/```(?:typescript|ts|javascript|js)?\n([\s\S]*?)```/);
        if (codeBlock)
            code = codeBlock[1].trim();
        // Strip "function ... { ... }" wrapper if model included it
        const funcMatch = code.match(/^(?:export\s+)?(?:async\s+)?function\s+\w+\([^)]*\)[^{]*\{([\s\S]*)\}\s*$/);
        if (funcMatch)
            code = funcMatch[1].trim();
        return code || null;
    }
}
//# sourceMappingURL=behavior-ai-service.js.map