/**
 * Database operations for CRUD functionality
 */
import { sanitizeTags, parseMetadata, stringifyMetadata } from '../models/index.js';
import { SchemaCompatibility, ApiKeySecurity } from './compatibility.js';
export class DatabaseOperations {
    db;
    constructor(db) {
        this.db = db;
    }
    // User operations
    async createUser(user) {
        // Use the actual database columns including api_key_hash
        const sql = `
      INSERT INTO users (id, email, name, organization, api_key_hash, is_active, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    `;
        await this.db.execute(sql, [
            user.id,
            user.email,
            user.name || null,
            user.organization || null,
            user.apiKey ? ApiKeySecurity.hashApiKey(user.apiKey) : null, // Hash API key if provided
            user.isActive !== undefined ? (user.isActive ? 1 : 0) : 1,
            user.createdAt || new Date().toISOString(),
            user.updatedAt || new Date().toISOString(),
        ]);
        return user;
    }
    async getUserById(id) {
        const result = await this.db.execute('SELECT * FROM users WHERE id = ?', [id]);
        return result.rows.length > 0 ? this.mapRowToUser(result.rows[0]) : null;
    }
    async getUserByEmail(email) {
        const result = await this.db.execute('SELECT * FROM users WHERE email = ?', [email]);
        return result.rows.length > 0 ? this.mapRowToUser(result.rows[0]) : null;
    }
    async getUserByApiKey(apiKey) {
        // Hash the API key for comparison
        const hashedKey = ApiKeySecurity.hashApiKey(apiKey);
        // Query using api_key_hash column (the actual column in the database)
        const result = await this.db.execute('SELECT * FROM users WHERE api_key_hash = ?', [hashedKey]);
        return result.rows.length > 0 ? this.mapRowToUser(result.rows[0]) : null;
    }
    async updateUser(id, updates) {
        const fields = [];
        const values = [];
        for (const [key, value] of Object.entries(updates)) {
            if (key === 'id' || value === undefined)
                continue;
            const dbKey = key === 'isActive'
                ? 'is_active'
                : key === 'apiKey'
                    ? 'api_key_hash' // Use api_key_hash for new schema
                    : key === 'oauthProvider'
                        ? 'oauth_provider'
                        : key === 'oauthId'
                            ? 'oauth_id'
                            : key === 'createdAt'
                                ? 'created_at'
                                : key === 'updatedAt'
                                    ? 'updated_at'
                                    : key;
            fields.push(`${dbKey} = ?`);
            if (key === 'metadata') {
                values.push(stringifyMetadata(value));
            }
            else if (key === 'isActive') {
                values.push(value ? 1 : 0);
            }
            else if (key === 'apiKey') {
                // Hash API key before storing
                values.push(ApiKeySecurity.hashApiKey(value));
            }
            else {
                values.push(value);
            }
        }
        if (fields.length === 0)
            return this.getUserById(id);
        fields.push('updated_at = ?');
        values.push(new Date().toISOString());
        values.push(id);
        await this.db.execute(`UPDATE users SET ${fields.join(', ')} WHERE id = ?`, values);
        return this.getUserById(id);
    }
    async deleteUser(id) {
        const result = await this.db.execute('DELETE FROM users WHERE id = ?', [id]);
        return result.changes > 0;
    }
    // Entity operations
    async createEntity(entity) {
        // Use compatibility layer to map entity fields
        const mappedEntity = SchemaCompatibility.mapEntityForDatabase(entity);
        // Generate UUID for the entity (entities table uses UUID as primary key)
        const entityId = SchemaCompatibility.generateUUID();
        // Only use columns that actually exist in the database
        const sql = `
      INSERT INTO entities (id, user_id, name, entity_type, person_type, description,
                           first_name, last_name, company, title, contact_info,
                           notes, tags, metadata, active, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `;
        const result = await this.db.execute(sql, [
            entityId,
            mappedEntity.user_id || mappedEntity.userId || null,
            mappedEntity.name,
            mappedEntity.entity_type || mappedEntity.entityType,
            mappedEntity.person_type || mappedEntity.personType || null,
            mappedEntity.description || null,
            mappedEntity.first_name || null,
            mappedEntity.last_name || null,
            mappedEntity.company || null,
            mappedEntity.title || null,
            mappedEntity.contact_info || null, // Stores email/phone/address as JSON
            mappedEntity.notes || null,
            typeof mappedEntity.tags === 'string'
                ? mappedEntity.tags
                : JSON.stringify(sanitizeTags(mappedEntity.tags)),
            typeof mappedEntity.metadata === 'string'
                ? mappedEntity.metadata
                : stringifyMetadata(mappedEntity.metadata),
            mappedEntity.active !== undefined ? (mappedEntity.active ? 1 : 0) : 1,
            mappedEntity.created_at || mappedEntity.createdAt || new Date().toISOString(),
            mappedEntity.updated_at || mappedEntity.updatedAt || new Date().toISOString(),
        ]);
        return { ...entity, id: entityId };
    }
    async getEntityById(id) {
        const result = await this.db.execute('SELECT * FROM entities WHERE id = ?', [id]);
        return result.rows.length > 0 ? this.mapRowToEntity(result.rows[0]) : null;
    }
    async getEntitiesByUserId(userId, limit = 100) {
        const result = await this.db.execute('SELECT * FROM entities WHERE user_id = ? ORDER BY created_at DESC LIMIT ?', [userId, limit]);
        return result.rows.map(row => this.mapRowToEntity(row));
    }
    async searchEntities(userId, query, limit = 10) {
        const result = await this.db.execute(`
      SELECT e.* FROM entities e
      JOIN entities_fts fts ON e.id = fts.rowid
      WHERE e.user_id = ? AND entities_fts MATCH ?
      ORDER BY rank LIMIT ?
    `, [userId, query, limit]);
        return result.rows.map(row => this.mapRowToEntity(row));
    }
    // Memory operations
    async createMemory(memory) {
        // Use compatibility layer to map memory fields
        const mappedMemory = SchemaCompatibility.mapMemoryForDatabase(memory);
        // Generate a UUID for the TEXT PRIMARY KEY
        const memoryId = SchemaCompatibility.generateUUID();
        // Only use columns that actually exist in the database
        const sql = `
      INSERT INTO memories (id, user_id, title, content, memory_type, importance,
                           tags, entity_ids, embedding, metadata, is_archived, active,
                           created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `;
        await this.db.execute(sql, [
            memoryId,
            mappedMemory.user_id || mappedMemory.userId || null,
            mappedMemory.title,
            mappedMemory.content,
            mappedMemory.memory_type || mappedMemory.memoryType,
            mappedMemory.importance,
            typeof mappedMemory.tags === 'string'
                ? mappedMemory.tags
                : JSON.stringify(sanitizeTags(mappedMemory.tags)),
            typeof mappedMemory.entity_ids === 'string'
                ? mappedMemory.entity_ids
                : JSON.stringify(mappedMemory.entity_ids || mappedMemory.entityIds || []),
            typeof mappedMemory.embedding === 'string'
                ? mappedMemory.embedding
                : JSON.stringify(mappedMemory.embedding || []),
            typeof mappedMemory.metadata === 'string'
                ? mappedMemory.metadata
                : stringifyMetadata(mappedMemory.metadata),
            mappedMemory.is_archived !== undefined
                ? mappedMemory.is_archived
                : mappedMemory.isArchived
                    ? 1
                    : 0,
            mappedMemory.active !== undefined ? mappedMemory.active : 1,
            mappedMemory.created_at || mappedMemory.createdAt || new Date().toISOString(),
            mappedMemory.updated_at || mappedMemory.updatedAt || new Date().toISOString(),
        ]);
        return { ...memory, id: memoryId };
    }
    async getMemoryById(id) {
        const result = await this.db.execute('SELECT * FROM memories WHERE id = ?', [id]);
        return result.rows.length > 0 ? this.mapRowToMemory(result.rows[0]) : null;
    }
    async getMemoriesByUserId(userId, limit = 100) {
        const result = await this.db.execute('SELECT * FROM memories WHERE user_id = ? AND is_archived = 0 ORDER BY created_at DESC LIMIT ?', [userId, limit]);
        return result.rows.map(row => this.mapRowToMemory(row));
    }
    async searchMemories(userId, query, limit = 10) {
        try {
            // Tokenize query into words for multi-word search
            const words = query
                .trim()
                .split(/\s+/)
                .filter(w => w.length > 0);
            if (words.length === 0) {
                return [];
            }
            // For single word, search in title, content, AND metadata
            if (words.length === 1) {
                const result = await this.db.execute(`
          SELECT * FROM memories
          WHERE user_id = ? AND is_archived = 0
          AND (
            LOWER(title) LIKE LOWER(?) OR
            LOWER(content) LIKE LOWER(?) OR
            (metadata IS NOT NULL AND json_valid(metadata) AND EXISTS (
              SELECT 1 FROM json_each(metadata)
              WHERE LOWER(json_each.value) LIKE LOWER(?)
            ))
          )
          ORDER BY updated_at DESC
          LIMIT ?
        `, [userId, `%${words[0]}%`, `%${words[0]}%`, `%${words[0]}%`, limit]);
                return result.rows.map(row => this.mapRowToMemory(row));
            }
            // For multiple words, use OR logic to find memories containing ANY of the words
            // Search across title, content, AND metadata
            const conditions = words
                .map(() => `(
        LOWER(title) LIKE LOWER(?) OR
        LOWER(content) LIKE LOWER(?) OR
        (metadata IS NOT NULL AND json_valid(metadata) AND EXISTS (
          SELECT 1 FROM json_each(metadata)
          WHERE LOWER(json_each.value) LIKE LOWER(?)
        ))
      )`)
                .join(' OR ');
            const params = words.flatMap(w => [`%${w}%`, `%${w}%`, `%${w}%`]);
            const result = await this.db.execute(`
        SELECT * FROM memories
        WHERE user_id = ? AND is_archived = 0
        AND (${conditions})
        ORDER BY updated_at DESC
        LIMIT ?
      `, [userId, ...params, limit]);
            return result.rows.map(row => this.mapRowToMemory(row));
        }
        catch (error) {
            console.error('Search memories error:', error);
            // Return empty array on error rather than throwing
            return [];
        }
    }
    // Helper methods to map database rows to objects
    mapRowToUser(row) {
        return {
            id: row.id,
            email: row.email,
            name: row.name,
            organization: row.organization,
            apiKey: row.api_key_hash, // Use the actual column name from database
            oauthProvider: row.oauth_provider,
            oauthId: row.oauth_id,
            isActive: row.is_active !== undefined ? Boolean(row.is_active) : true,
            metadata: parseMetadata(row.metadata),
            createdAt: row.created_at,
            updatedAt: row.updated_at,
        };
    }
    mapRowToEntity(row) {
        // Use compatibility layer for proper field mapping
        return SchemaCompatibility.mapEntityFromDatabase(row);
    }
    mapRowToMemory(row) {
        // Use compatibility layer for proper field mapping
        return SchemaCompatibility.mapMemoryFromDatabase(row);
    }
}
//# sourceMappingURL=operations.js.map