/**
 * Adaptive Pattern Engine for UAP
 *
 * Tracks pattern outcomes per task category and returns patterns sorted
 * by historical success rate. Enables the system to learn which patterns
 * work best for different types of tasks over time.
 *
 * Now with SQLite persistence — outcomes survive across process restarts.
 */
import Database from 'better-sqlite3';
import { existsSync, mkdirSync } from 'fs';
import { dirname } from 'path';
/**
 * Ensure the pattern_outcomes table exists in the given database.
 */
function ensurePatternOutcomesSchema(db) {
    db.exec(`
    CREATE TABLE IF NOT EXISTS pattern_outcomes (
      pattern_id TEXT NOT NULL,
      task_category TEXT NOT NULL,
      uses INTEGER NOT NULL DEFAULT 0,
      successes INTEGER NOT NULL DEFAULT 0,
      updated_at TEXT NOT NULL DEFAULT (datetime('now')),
      PRIMARY KEY (pattern_id, task_category)
    );
    CREATE INDEX IF NOT EXISTS idx_pattern_outcomes_category
      ON pattern_outcomes(task_category);
    CREATE TABLE IF NOT EXISTS agent_pattern_outcomes (
      agent_id TEXT NOT NULL,
      pattern_id TEXT NOT NULL,
      task_category TEXT NOT NULL,
      uses INTEGER NOT NULL DEFAULT 0,
      successes INTEGER NOT NULL DEFAULT 0,
      updated_at TEXT NOT NULL DEFAULT (datetime('now')),
      PRIMARY KEY (agent_id, pattern_id, task_category)
    );
    CREATE INDEX IF NOT EXISTS idx_agent_pattern_agent
      ON agent_pattern_outcomes(agent_id);
    CREATE INDEX IF NOT EXISTS idx_agent_pattern_category
      ON agent_pattern_outcomes(task_category);
  `);
}
/**
 * AdaptivePatternEngine learns which patterns work best for different
 * task categories by tracking success/failure outcomes.
 *
 * Supports optional SQLite persistence via `attachDb()`.
 */
export class AdaptivePatternEngine {
    /**
     * Map<patternId, Map<taskCategory, PatternOutcome>>
     * Tracks per-pattern, per-category success rates.
     */
    outcomes = new Map();
    /**
     * Optional content store: Map<patternId, content>
     * Populated when outcomes are recorded so getAdaptedPatterns can return content.
     */
    patternContent = new Map();
    /**
     * Optional SQLite database for persistence.
     */
    db = null;
    flushStmt = null;
    flushAgentStmt = null;
    /**
     * Attach a SQLite database for persistent outcome storage.
     * Loads existing outcomes from the database into memory.
     */
    attachDb(dbPath) {
        const dir = dirname(dbPath);
        if (!existsSync(dir)) {
            mkdirSync(dir, { recursive: true });
        }
        this.db = new Database(dbPath);
        this.db.pragma('journal_mode = WAL');
        this.db.pragma('synchronous = NORMAL');
        this.db.pragma('busy_timeout = 5000');
        ensurePatternOutcomesSchema(this.db);
        // Prepare the upsert statement for flushing
        this.flushStmt = this.db.prepare(`
      INSERT INTO pattern_outcomes (pattern_id, task_category, uses, successes, updated_at)
      VALUES (?, ?, ?, ?, datetime('now'))
      ON CONFLICT(pattern_id, task_category) DO UPDATE SET
        uses = excluded.uses,
        successes = excluded.successes,
        updated_at = excluded.updated_at
    `);
        // Prepare per-agent upsert statement
        this.flushAgentStmt = this.db.prepare(`
      INSERT INTO agent_pattern_outcomes (agent_id, pattern_id, task_category, uses, successes, updated_at)
      VALUES (?, ?, ?, ?, ?, datetime('now'))
      ON CONFLICT(agent_id, pattern_id, task_category) DO UPDATE SET
        uses = agent_pattern_outcomes.uses + excluded.uses,
        successes = agent_pattern_outcomes.successes + excluded.successes,
        updated_at = excluded.updated_at
    `);
        // Load existing outcomes from DB into memory
        this.loadFromDb();
    }
    /**
     * Load all persisted outcomes into the in-memory maps.
     */
    loadFromDb() {
        if (!this.db)
            return;
        const rows = this.db
            .prepare('SELECT pattern_id, task_category, uses, successes FROM pattern_outcomes')
            .all();
        for (const row of rows) {
            if (!this.outcomes.has(row.pattern_id)) {
                this.outcomes.set(row.pattern_id, new Map());
            }
            this.outcomes.get(row.pattern_id).set(row.task_category, {
                uses: row.uses,
                successes: row.successes,
            });
            // Set default content if not already present
            if (!this.patternContent.has(row.pattern_id)) {
                this.patternContent.set(row.pattern_id, row.pattern_id);
            }
        }
    }
    /**
     * Flush a single outcome to the database.
     */
    flushOutcome(patternId, taskCategory, outcome, agentId) {
        if (!this.db || !this.flushStmt)
            return;
        try {
            this.flushStmt.run(patternId, taskCategory, outcome.uses, outcome.successes);
            // Also write per-agent row if agent_id is provided
            if (agentId && this.flushAgentStmt) {
                this.flushAgentStmt.run(agentId, patternId, taskCategory, 1, outcome.successes > 0 ? 1 : 0);
            }
        }
        catch {
            // Non-fatal: DB write failure doesn't break in-memory operation
        }
    }
    /**
     * Record the outcome of applying a pattern to a task.
     *
     * @param patternId - Identifier of the pattern (e.g. "P12")
     * @param success - Whether the task completed successfully
     * @param taskCategory - Category of the task (e.g. "security", "refactor")
     * @param agentId - Optional agent identifier for per-agent tracking
     */
    recordPatternOutcome(patternId, success, taskCategory, agentId) {
        if (!this.outcomes.has(patternId)) {
            this.outcomes.set(patternId, new Map());
        }
        const categoryMap = this.outcomes.get(patternId);
        if (!categoryMap.has(taskCategory)) {
            categoryMap.set(taskCategory, { uses: 0, successes: 0 });
        }
        const outcome = categoryMap.get(taskCategory);
        outcome.uses += 1;
        if (success) {
            outcome.successes += 1;
        }
        // Store a default content string if not already present
        if (!this.patternContent.has(patternId)) {
            this.patternContent.set(patternId, patternId);
        }
        // Persist to SQLite immediately
        this.flushOutcome(patternId, taskCategory, outcome, agentId);
    }
    /**
     * Set content for a pattern (optional, for richer results).
     */
    setPatternContent(patternId, content) {
        this.patternContent.set(patternId, content);
    }
    /**
     * Get patterns sorted by success rate for a given task category.
     *
     * @param taskCategory - Category to filter by
     * @param limit - Maximum number of patterns to return (default: 10)
     * @returns Patterns sorted by success rate descending
     */
    getAdaptedPatterns(taskCategory, limit = 10) {
        const results = [];
        for (const [patternId, categoryMap] of this.outcomes) {
            const outcome = categoryMap.get(taskCategory);
            if (!outcome)
                continue;
            const successRate = outcome.uses > 0 ? outcome.successes / outcome.uses : 0;
            results.push({
                id: patternId,
                content: this.patternContent.get(patternId) || patternId,
                successRate,
            });
        }
        results.sort((a, b) => b.successRate - a.successRate);
        return results.slice(0, limit);
    }
    /**
     * Get aggregated stats for all tracked patterns across all categories.
     *
     * @returns Map of patternId to aggregate stats
     */
    getPatternStats() {
        const stats = {};
        for (const [patternId, categoryMap] of this.outcomes) {
            let totalUses = 0;
            let totalSuccesses = 0;
            for (const outcome of categoryMap.values()) {
                totalUses += outcome.uses;
                totalSuccesses += outcome.successes;
            }
            stats[patternId] = {
                uses: totalUses,
                successes: totalSuccesses,
                rate: totalUses > 0 ? totalSuccesses / totalUses : 0,
            };
        }
        return stats;
    }
    /**
     * Close the database connection if attached.
     */
    close() {
        if (this.db) {
            this.db.close();
            this.db = null;
            this.flushStmt = null;
        }
    }
}
// Singleton
let instance = null;
export function getAdaptivePatternEngine(dbPath) {
    if (!instance) {
        instance = new AdaptivePatternEngine();
        // Default to the coordination DB path if none provided
        const resolvedPath = dbPath || './agents/data/coordination/coordination.db';
        instance.attachDb(resolvedPath);
    }
    else if (dbPath && !instance['db']) {
        // If instance exists but has no DB attached, attach now
        instance.attachDb(dbPath);
    }
    return instance;
}
/**
 * Reset the singleton (for testing).
 */
export function resetAdaptivePatternEngine() {
    if (instance) {
        instance.close();
        instance = null;
    }
}
//# sourceMappingURL=adaptive-patterns.js.map