import { decodeCursor, encodeCursor } from '../../../storage/core/storageValidation.js';
import { ErrorHandler } from '../../../utils/internal/error-handler/errorHandler.js';
import { logger } from '../../../utils/internal/logger.js';
const TABLE_NAME = 'kv_store';
const DEFAULT_LIST_LIMIT = 1000;
/** Escapes SQL LIKE wildcard characters (`%` and `_`) in a prefix string. */
function escapeLikePattern(prefix) {
    return prefix.replace(/[%_\\]/g, '\\$&');
}
export class SupabaseProvider {
    client;
    constructor(client) {
        this.client = client;
    }
    getClient() {
        return this.client;
    }
    async get(tenantId, key, context) {
        return await ErrorHandler.tryCatch(async () => {
            const { data, error } = await this.getClient()
                .from(TABLE_NAME)
                .select('value, expires_at')
                .eq('tenant_id', tenantId)
                .eq('key', key)
                .single();
            if (error) {
                if (error.code === 'PGRST116') {
                    // "Not found" error code from PostgREST
                    return null;
                }
                throw error;
            }
            if (data.expires_at && new Date(data.expires_at).getTime() < Date.now()) {
                await this.delete(tenantId, key, context);
                logger.debug(`[SupabaseProvider] Key expired and removed: ${key} for tenant: ${tenantId}`, context);
                return null;
            }
            return data.value;
        }, {
            operation: 'SupabaseProvider.get',
            context,
            input: { tenantId, key },
        });
    }
    async set(tenantId, key, value, context, options) {
        return await ErrorHandler.tryCatch(async () => {
            // Fix: Check for undefined instead of truthy to handle ttl=0 correctly
            const expires_at = options?.ttl !== undefined
                ? new Date(Date.now() + options.ttl * 1000).toISOString()
                : null;
            const { error } = await this.getClient()
                .from(TABLE_NAME)
                .upsert({
                tenant_id: tenantId,
                key,
                value: value,
                expires_at,
            });
            if (error)
                throw error;
        }, {
            operation: 'SupabaseProvider.set',
            context,
            input: { tenantId, key },
        });
    }
    async delete(tenantId, key, context) {
        return await ErrorHandler.tryCatch(async () => {
            const { error, count } = await this.getClient()
                .from(TABLE_NAME)
                .delete({ count: 'exact' })
                .eq('tenant_id', tenantId)
                .eq('key', key);
            if (error)
                throw error;
            return (count ?? 0) > 0;
        }, {
            operation: 'SupabaseProvider.delete',
            context,
            input: { tenantId, key },
        });
    }
    async list(tenantId, prefix, context, options) {
        return await ErrorHandler.tryCatch(async () => {
            const now = new Date().toISOString();
            const limit = options?.limit ?? DEFAULT_LIST_LIMIT;
            let query = this.getClient()
                .from(TABLE_NAME)
                .select('key')
                .eq('tenant_id', tenantId)
                .like('key', `${escapeLikePattern(prefix)}%`)
                .or(`expires_at.is.null,expires_at.gt.${now}`)
                .order('key', { ascending: true })
                .limit(limit + 1); // Fetch one extra to determine if there are more results
            // Apply cursor-based pagination with opaque cursors
            if (options?.cursor) {
                // Decode and validate cursor
                const lastKey = decodeCursor(options.cursor, tenantId, context);
                query = query.gt('key', lastKey);
            }
            const { data, error } = await query;
            if (error)
                throw error;
            const keys = data?.map((item) => item.key) ?? [];
            const hasMore = keys.length > limit;
            const resultKeys = hasMore ? keys.slice(0, limit) : keys;
            const nextCursor = hasMore && resultKeys.length > 0
                ? encodeCursor(resultKeys[resultKeys.length - 1], tenantId)
                : undefined;
            return {
                keys: resultKeys,
                nextCursor,
            };
        }, {
            operation: 'SupabaseProvider.list',
            context,
            input: { tenantId, prefix, options },
        });
    }
    async getMany(tenantId, keys, context) {
        return await ErrorHandler.tryCatch(async () => {
            if (keys.length === 0) {
                return new Map();
            }
            const { data, error } = await this.getClient()
                .from(TABLE_NAME)
                .select('key, value, expires_at')
                .eq('tenant_id', tenantId)
                .in('key', keys);
            if (error)
                throw error;
            const results = new Map();
            for (const row of data ?? []) {
                if (!row.expires_at || new Date(row.expires_at).getTime() >= Date.now()) {
                    results.set(row.key, row.value);
                }
                else {
                    // Clean up expired entries
                    await this.delete(tenantId, row.key, context);
                }
            }
            return results;
        }, {
            operation: 'SupabaseProvider.getMany',
            context,
            input: { tenantId, keyCount: keys.length },
        });
    }
    async setMany(tenantId, entries, context, options) {
        return await ErrorHandler.tryCatch(async () => {
            if (entries.size === 0) {
                return;
            }
            // Fix: Check for undefined instead of truthy to handle ttl=0 correctly
            const expires_at = options?.ttl !== undefined
                ? new Date(Date.now() + options.ttl * 1000).toISOString()
                : null;
            const rows = Array.from(entries.entries()).map(([key, value]) => ({
                tenant_id: tenantId,
                key,
                value: value,
                expires_at,
            }));
            const { error } = await this.getClient().from(TABLE_NAME).upsert(rows);
            if (error)
                throw error;
        }, {
            operation: 'SupabaseProvider.setMany',
            context,
            input: { tenantId, entryCount: entries.size },
        });
    }
    async deleteMany(tenantId, keys, context) {
        return await ErrorHandler.tryCatch(async () => {
            if (keys.length === 0) {
                return 0;
            }
            const { error, count } = await this.getClient()
                .from(TABLE_NAME)
                .delete({ count: 'exact' })
                .eq('tenant_id', tenantId)
                .in('key', keys);
            if (error)
                throw error;
            return count ?? 0;
        }, {
            operation: 'SupabaseProvider.deleteMany',
            context,
            input: { tenantId, keyCount: keys.length },
        });
    }
    async clear(tenantId, context) {
        return await ErrorHandler.tryCatch(async () => {
            const { error, count } = await this.getClient()
                .from(TABLE_NAME)
                .delete({ count: 'exact' })
                .eq('tenant_id', tenantId);
            if (error)
                throw error;
            logger.info(`[SupabaseProvider] Cleared ${count ?? 0} keys for tenant: ${tenantId}`, context);
            return count ?? 0;
        }, {
            operation: 'SupabaseProvider.clear',
            context,
            input: { tenantId },
        });
    }
}
//# sourceMappingURL=supabaseProvider.js.map