/**
 * Claude-mem to brain.db migration.
 * Imports observations from ~/.claude-mem/claude-mem.db into brain_observations.
 * Also creates brain_decisions from decision-typed observations and
 * brain_learnings from session_summaries with learned fields.
 *
 * @epic T5149
 * @task T5143
 */
import { existsSync } from 'node:fs';
import { createRequire } from 'node:module';
import { getClaudeMemDbPath } from '../paths.js';
import { getBrainDb, getBrainNativeDb } from '../store/brain-sqlite.js';
import { ensureFts5Tables, rebuildFts5Index } from './brain-search.js';
/** Type-safe wrapper for StatementSync.all(). */
function typedAll(db, sql) {
    return db.prepare(sql).all();
}
// Runtime-load node:sqlite via createRequire (same pattern as node-sqlite-adapter.ts)
const _require = createRequire(import.meta.url);
const { DatabaseSync } = _require('node:sqlite');
/** Valid observation types in the BRAIN schema. */
const VALID_OBSERVATION_TYPES = new Set([
    'discovery',
    'change',
    'feature',
    'bugfix',
    'decision',
    'refactor',
]);
/**
 * Map a claude-mem observation type to a valid BRAIN observation type.
 * Falls back to 'discovery' for unrecognized types.
 */
function mapObservationType(type) {
    if (VALID_OBSERVATION_TYPES.has(type)) {
        return type;
    }
    return 'discovery';
}
/**
 * Parse a learned field from claude-mem session_summaries.
 * The field may be a JSON array of strings or a plain string.
 */
function parseLearned(learned) {
    try {
        const parsed = JSON.parse(learned);
        if (Array.isArray(parsed)) {
            return parsed.filter((s) => typeof s === 'string' && s.trim()).join('; ');
        }
        if (typeof parsed === 'string') {
            return parsed;
        }
        return String(parsed);
    }
    catch {
        // Not valid JSON — treat as plain string
        return learned;
    }
}
/**
 * Migrate observations from claude-mem's SQLite database into CLEO brain.db.
 *
 * Reads from ~/.claude-mem/claude-mem.db (or a custom path) and inserts into:
 * - brain_observations (all observations, prefixed CM-)
 * - brain_decisions (decision-typed observations, prefixed CMD-)
 * - brain_learnings (session summaries with learned field, prefixed CML-)
 *
 * Idempotent: skips rows whose ID already exists in brain.db.
 * After all inserts, rebuilds FTS5 indexes.
 *
 * @param projectRoot - The CLEO project root (for brain.db resolution)
 * @param options - Migration options
 */
export async function migrateClaudeMem(projectRoot, options = {}) {
    const sourcePath = options.sourcePath ?? getClaudeMemDbPath();
    const dryRun = options.dryRun ?? false;
    const batchSize = options.batchSize ?? 100;
    const result = {
        observationsImported: 0,
        observationsSkipped: 0,
        learningsImported: 0,
        decisionsImported: 0,
        errors: [],
        dryRun,
    };
    // Validate source database exists
    if (!existsSync(sourcePath)) {
        throw new Error(`claude-mem database not found at: ${sourcePath}\n` +
            `Expected location: ~/.claude-mem/claude-mem.db\n` +
            `Use --source <path> to specify a custom location.`);
    }
    // Open source DB read-only
    let sourceDb;
    try {
        sourceDb = new DatabaseSync(sourcePath, {
            readOnly: true,
        });
    }
    catch (err) {
        throw new Error(`Failed to open claude-mem database at ${sourcePath}: ${err instanceof Error ? err.message : String(err)}`);
    }
    try {
        // Initialize brain.db (ensures tables exist via migrations)
        await getBrainDb(projectRoot);
        const nativeDb = getBrainNativeDb();
        if (!nativeDb) {
            throw new Error('Failed to initialize brain.db — getBrainNativeDb() returned null');
        }
        // Ensure FTS5 tables exist for the rebuild at the end
        ensureFts5Tables(nativeDb);
        // --- Phase 1: Migrate observations ---
        const observations = typedAll(sourceDb, 'SELECT * FROM observations ORDER BY id');
        // Process observations in batches
        for (let i = 0; i < observations.length; i += batchSize) {
            const batch = observations.slice(i, i + batchSize);
            if (!dryRun) {
                nativeDb.prepare('BEGIN IMMEDIATE').run();
            }
            try {
                for (const row of batch) {
                    const obsId = `CM-${row.id}`;
                    // Check if already imported (idempotent)
                    const existing = nativeDb
                        .prepare('SELECT id FROM brain_observations WHERE id = ?')
                        .get(obsId);
                    if (existing) {
                        result.observationsSkipped++;
                        continue;
                    }
                    if (dryRun) {
                        result.observationsImported++;
                        // Also count decisions in dry run
                        if (row.type === 'decision') {
                            result.decisionsImported++;
                        }
                        continue;
                    }
                    const mappedType = mapObservationType(row.type);
                    const projectTag = options.project ?? row.project ?? null;
                    nativeDb
                        .prepare(`
            INSERT INTO brain_observations (
              id, type, title, subtitle, narrative,
              facts_json, concepts_json, project,
              files_read_json, files_modified_json,
              source_type, created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
          `)
                        .run(obsId, mappedType, row.title, row.subtitle ?? null, row.narrative ?? null, row.facts ?? null, row.concepts ?? null, projectTag, row.files_read ?? null, row.files_modified ?? null, 'claude-mem', row.created_at ?? null, row.updated_at ?? null);
                    result.observationsImported++;
                    // For decision-typed observations, also create a brain_decisions entry
                    if (row.type === 'decision') {
                        const decId = `CMD-${row.id}`;
                        const existingDec = nativeDb
                            .prepare('SELECT id FROM brain_decisions WHERE id = ?')
                            .get(decId);
                        if (!existingDec) {
                            nativeDb
                                .prepare(`
                INSERT INTO brain_decisions (
                  id, type, decision, rationale, confidence, created_at
                ) VALUES (?, ?, ?, ?, ?, ?)
              `)
                                .run(decId, 'tactical', row.title, row.narrative ?? 'Imported from claude-mem', 'medium', row.created_at ?? null);
                            result.decisionsImported++;
                        }
                    }
                }
                if (!dryRun) {
                    nativeDb.prepare('COMMIT').run();
                }
            }
            catch (err) {
                if (!dryRun) {
                    try {
                        nativeDb.prepare('ROLLBACK').run();
                    }
                    catch {
                        // Ignore rollback errors
                    }
                }
                result.errors.push(`Batch starting at observation ${batch[0]?.id ?? '?'}: ` +
                    `${err instanceof Error ? err.message : String(err)}`);
            }
        }
        // --- Phase 2: Migrate session_summaries (learned -> brain_learnings) ---
        let summaries = [];
        try {
            summaries = typedAll(sourceDb, 'SELECT * FROM session_summaries ORDER BY id');
        }
        catch {
            // session_summaries table may not exist in all claude-mem versions
            // This is not an error — just skip
        }
        for (let i = 0; i < summaries.length; i += batchSize) {
            const batch = summaries.slice(i, i + batchSize);
            if (!dryRun) {
                nativeDb.prepare('BEGIN IMMEDIATE').run();
            }
            try {
                for (const row of batch) {
                    if (!row.learned || !row.learned.trim()) {
                        continue;
                    }
                    const learnId = `CML-${row.id}`;
                    // Check if already imported
                    const existing = nativeDb
                        .prepare('SELECT id FROM brain_learnings WHERE id = ?')
                        .get(learnId);
                    if (existing) {
                        // Don't count as skipped — these are separate from observations
                        continue;
                    }
                    const insight = parseLearned(row.learned);
                    if (!insight.trim()) {
                        continue;
                    }
                    if (dryRun) {
                        result.learningsImported++;
                        continue;
                    }
                    const source = row.session_id
                        ? `claude-mem session ${row.session_id}`
                        : 'claude-mem session';
                    nativeDb
                        .prepare(`
            INSERT INTO brain_learnings (
              id, insight, source, confidence, actionable, created_at
            ) VALUES (?, ?, ?, ?, ?, ?)
          `)
                        .run(learnId, insight, source, 0.5, 0, // false
                    row.created_at ?? null);
                    result.learningsImported++;
                }
                if (!dryRun) {
                    nativeDb.prepare('COMMIT').run();
                }
            }
            catch (err) {
                if (!dryRun) {
                    try {
                        nativeDb.prepare('ROLLBACK').run();
                    }
                    catch {
                        // Ignore rollback errors
                    }
                }
                result.errors.push(`Session summary batch starting at ${batch[0]?.id ?? '?'}: ` +
                    `${err instanceof Error ? err.message : String(err)}`);
            }
        }
        // --- Phase 3: Rebuild FTS5 indexes ---
        if (!dryRun &&
            (result.observationsImported > 0 ||
                result.learningsImported > 0 ||
                result.decisionsImported > 0)) {
            try {
                rebuildFts5Index(nativeDb);
            }
            catch (err) {
                result.errors.push(`FTS5 rebuild warning: ${err instanceof Error ? err.message : String(err)}`);
            }
        }
    }
    finally {
        // Close source DB (read-only, safe to close)
        try {
            sourceDb.close();
        }
        catch {
            // Ignore close errors
        }
    }
    return result;
}
//# sourceMappingURL=claude-mem-migration.js.map