import { configStore } from "../store/store";
import { initCacheDB } from "./cache-db";
import { CACHE_ENABLED, CACHE_LIMITS } from "./constant";
// Chat
export async function getCachedChats() {
    if (!CACHE_ENABLED)
        return [];
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return [];
        const all = await db.getAllFromIndex('chats', 'by-business-shop', [businessId, shopId]);
        return all
            .sort((a, b) => {
            if (a.data.pin !== b.data.pin) {
                return a.data.pin ? -1 : 1;
            }
            const aTime = a.data.last_message_at ? new Date(a.data.last_message_at).getTime() : 0;
            const bTime = b.data.last_message_at ? new Date(b.data.last_message_at).getTime() : 0;
            return bTime - aTime;
        })
            .slice(0, CACHE_LIMITS.MAX_CACHED_CHATS)
            .map(entry => entry.data);
    }
    catch (error) {
        console.warn('[Cache] Failed to get cached chats:', error);
        return [];
    }
}
export async function persistChats(chats) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        const tx = db.transaction('chats', 'readwrite');
        const store = tx.objectStore('chats');
        for (const chat of chats) {
            await store.put({
                id: chat.id,
                data: chat,
                business_id: businessId,
                shop_id: shopId,
                updated_at: Date.now(),
            });
        }
        await tx.done;
        await removeOldChats();
    }
    catch (error) {
        console.warn('[Cache] Failed to persist chats:', error);
    }
}
export async function persistChat(chat) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        await db.put('chats', {
            id: chat.id,
            data: chat,
            business_id: businessId,
            shop_id: shopId,
            updated_at: Date.now(),
        });
    }
    catch (error) {
        console.warn('[Cache] Failed to persist chat:', error);
    }
}
async function removeOldChats() {
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        const all = await db.getAllFromIndex('chats', 'by-business-shop', [businessId, shopId]);
        if (all.length <= CACHE_LIMITS.MAX_CACHED_CHATS)
            return;
        const sorted = all.sort((a, b) => b.updated_at - a.updated_at);
        const toDelete = sorted.slice(CACHE_LIMITS.MAX_CACHED_CHATS);
        const tx = db.transaction('chats', 'readwrite');
        for (const entry of toDelete) {
            await tx.store.delete(entry.id);
        }
        await tx.done;
    }
    catch (error) {
        console.warn('[Cache] Failed to remove old chats:', error);
    }
}
export async function isChatCacheStale() {
    if (!CACHE_ENABLED)
        return true;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return true;
        const syncState = await db.get('sync_state', `chats-${businessId}-${shopId}`);
        if (!syncState)
            return true;
        return Date.now() - syncState.last_sync_at > CACHE_LIMITS.CHAT_STALE_MS;
    }
    catch {
        return true;
    }
}
export async function updateChatSyncState() {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        await db.put('sync_state', {
            last_sync_at: Date.now(),
            business_id: businessId,
            shop_id: shopId,
        }, `chats-${businessId}-${shopId}`);
    }
    catch (error) {
        console.warn('[Cache] Failed to update chat sync state:', error);
    }
}
// Message
export async function getCachedMessages(chatId) {
    if (!CACHE_ENABLED)
        return [];
    try {
        const db = await initCacheDB();
        const all = await db.getAllFromIndex('messages', 'by-chat', chatId);
        return all
            .sort((a, b) => a.message_at - b.message_at)
            .slice(-CACHE_LIMITS.MAX_MESSAGES_PER_CHAT)
            .map(entry => entry.data);
    }
    catch (error) {
        console.warn('[Cache] Failed to get cached messages:', error);
        return [];
    }
}
export async function persistMessages(chatId, messages) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        const tx = db.transaction('messages', 'readwrite');
        const store = tx.objectStore('messages');
        for (const msg of messages) {
            if (!msg.id)
                continue;
            const key = `${chatId}-${msg.id}`;
            await store.put({
                id: key,
                chat_id: chatId,
                data: msg,
                business_id: businessId,
                shop_id: shopId,
                message_at: new Date(msg.message_at).getTime(),
            });
        }
        await tx.done;
        await removeOldMessages(chatId);
    }
    catch (error) {
        console.warn('[Cache] Failed to persist messages:', error);
    }
}
export async function persistMessage(chatId, message) {
    if (!CACHE_ENABLED)
        return;
    // Most use without await for non-blocking
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId || !message.id)
            return;
        const key = `${chatId}-${message.id}`;
        await db.put('messages', {
            id: key,
            chat_id: chatId,
            data: message,
            business_id: businessId,
            shop_id: shopId,
            message_at: new Date(message.message_at).getTime(),
        });
    }
    catch (error) {
        console.warn('[Cache] Failed to persist message:', error);
    }
}
async function removeOldMessages(chatId) {
    try {
        const db = await initCacheDB();
        const all = await db.getAllFromIndex('messages', 'by-chat', chatId);
        if (all.length <= CACHE_LIMITS.MAX_MESSAGES_PER_CHAT)
            return;
        const sorted = all.sort((a, b) => a.message_at - b.message_at);
        const toDelete = sorted.slice(0, sorted.length - CACHE_LIMITS.MAX_MESSAGES_PER_CHAT);
        const tx = db.transaction('messages', 'readwrite');
        for (const entry of toDelete) {
            await tx.store.delete(entry.id);
        }
        await tx.done;
        // Invalidate pagination cursors since the cache was trimmed and the cursors
        // no longer match the oldest messages in the cache.
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (businessId && shopId) {
            await db.delete('sync_state', `messages-${chatId}`);
        }
    }
    catch (error) {
        console.warn('[Cache] Failed to remove old messages:', error);
    }
}
export async function updateMessageSyncState(chatId) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        await db.put('sync_state', {
            last_sync_at: Date.now(),
            business_id: businessId,
            shop_id: shopId,
        }, `messages-${chatId}`);
    }
    catch (error) {
        console.warn('[Cache] Failed to update message sync state:', error);
    }
}
export async function getMessageSyncState(chatId) {
    if (!CACHE_ENABLED)
        return null;
    try {
        const db = await initCacheDB();
        return await db.get('sync_state', `messages-${chatId}`);
    }
    catch {
        return null;
    }
}
export async function invalidateMessageSyncState(chatId) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        await db.delete('sync_state', `messages-${chatId}`);
    }
    catch (error) {
        console.warn('[Cache] Failed to invalidate message sync state:', error);
    }
}
export async function isMessageCacheStale(chatId) {
    if (!CACHE_ENABLED)
        return true;
    try {
        const db = await initCacheDB();
        const syncState = await db.get('sync_state', `messages-${chatId}`);
        if (!syncState)
            return true;
        return Date.now() - syncState.last_sync_at > CACHE_LIMITS.MESSAGE_STALE_MS;
    }
    catch {
        return true;
    }
}
// User
export async function getCachedUser(channel, userId) {
    if (!CACHE_ENABLED)
        return null;
    try {
        const db = await initCacheDB();
        const key = `${channel}-${userId}`;
        const entry = await db.get('users', key);
        if (!entry)
            return null;
        return entry.data;
    }
    catch (error) {
        console.warn('[Cache] Failed to get cached user:', error);
        return null;
    }
}
export async function persistUser(channel, userId, user) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        const key = `${channel}-${userId}`;
        await db.put('users', {
            id: key,
            data: user,
            business_id: businessId,
            shop_id: shopId,
            fetched_at: Date.now(),
        });
        await removeOldUsers();
    }
    catch (error) {
        console.warn('[Cache] Failed to persist user:', error);
    }
}
export async function invalidateCachedUser(channel, userId) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const key = `${channel}-${userId}`;
        await db.delete('users', key);
    }
    catch (error) {
        console.warn('[Cache] Failed to invalidate cached user:', error);
    }
}
async function removeOldUsers() {
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        const all = await db.getAllFromIndex('users', 'by-business-shop', [businessId, shopId]);
        if (all.length <= CACHE_LIMITS.MAX_CACHED_USERS)
            return;
        // Remove oldest-fetched entries first
        const sorted = all.sort((a, b) => a.fetched_at - b.fetched_at);
        const toDelete = sorted.slice(0, sorted.length - CACHE_LIMITS.MAX_CACHED_USERS);
        const tx = db.transaction('users', 'readwrite');
        for (const entry of toDelete) {
            await tx.store.delete(entry.id);
        }
        await tx.done;
    }
    catch (error) {
        console.warn('[Cache] Failed to remove old users:', error);
    }
}
//Tags, Statuses, Quick Messages
function getMetadataKey(type) {
    const businessId = configStore.get('businessId');
    const shopId = configStore.get('shopId');
    return `${type}-${businessId}-${shopId}`;
}
export async function getCachedMetadata(type) {
    if (!CACHE_ENABLED)
        return null;
    try {
        const db = await initCacheDB();
        const key = getMetadataKey(type);
        const entry = await db.get('metadata', key);
        if (!entry)
            return null;
        return entry.data;
    }
    catch (error) {
        console.warn(`[Cache] Failed to get cached metadata (${type}):`, error);
        return null;
    }
}
export async function persistMetadata(type, data) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        const key = getMetadataKey(type);
        await db.put('metadata', {
            id: key,
            data,
            business_id: businessId,
            shop_id: shopId,
            fetched_at: Date.now(),
        });
    }
    catch (error) {
        console.warn(`[Cache] Failed to persist metadata (${type}):`, error);
    }
}
export async function invalidateMetadata(type) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const key = getMetadataKey(type);
        await db.delete('metadata', key);
    }
    catch (error) {
        console.warn(`[Cache] Failed to invalidate metadata (${type}):`, error);
    }
}
// Media
export async function getCachedMedia(channel, sessionId, messageId) {
    if (!CACHE_ENABLED)
        return null;
    try {
        const db = await initCacheDB();
        const key = `${channel}:${sessionId}:${messageId}`;
        const entry = await db.get('media', key);
        if (!entry)
            return null;
        db.put('media', { ...entry, accessed_at: Date.now() }).catch(() => { });
        return { arrayBuffer: entry.data, contentType: entry.content_type };
    }
    catch (error) {
        console.warn('[Cache] Failed to get cached media:', error);
        return null;
    }
}
export async function persistMedia(channel, sessionId, messageId, arrayBuffer, contentType) {
    if (!CACHE_ENABLED)
        return;
    try {
        const db = await initCacheDB();
        const businessId = configStore.get('businessId');
        const shopId = configStore.get('shopId');
        if (!businessId || !shopId)
            return;
        const key = `${channel}:${sessionId}:${messageId}`;
        const now = Date.now();
        await db.put('media', {
            id: key,
            data: arrayBuffer,
            content_type: contentType,
            size: arrayBuffer.byteLength,
            business_id: businessId,
            shop_id: shopId,
            accessed_at: now,
            created_at: now,
        });
        removeMediaLRU().catch(() => { });
    }
    catch (error) {
        console.warn('[Cache] Failed to persist media:', error);
    }
}
async function removeMediaLRU() {
    try {
        const db = await initCacheDB();
        const allKeys = await db.getAllKeys('media');
        if (allKeys.length <= CACHE_LIMITS.MAX_MEDIA_CACHE_ENTRIES) {
            const allEntries = await db.getAll('media');
            const totalSize = allEntries.reduce((sum, entry) => sum + entry.size, 0);
            if (totalSize <= CACHE_LIMITS.MAX_MEDIA_CACHE_BYTES)
                return;
            const sorted = allEntries.sort((a, b) => a.accessed_at - b.accessed_at);
            let currentSize = totalSize;
            const tx = db.transaction('media', 'readwrite');
            for (const entry of sorted) {
                if (currentSize <= CACHE_LIMITS.MAX_MEDIA_CACHE_BYTES)
                    break;
                currentSize -= entry.size;
                await tx.store.delete(entry.id);
            }
            await tx.done;
            return;
        }
        const allEntries = await db.getAll('media');
        const sorted = allEntries.sort((a, b) => a.accessed_at - b.accessed_at);
        const toDelete = sorted.slice(0, sorted.length - CACHE_LIMITS.MAX_MEDIA_CACHE_ENTRIES);
        const tx = db.transaction('media', 'readwrite');
        for (const entry of toDelete) {
            await tx.store.delete(entry.id);
        }
        await tx.done;
    }
    catch (error) {
        console.warn('[Cache] Failed to remove media LRU:', error);
    }
}
// Clear cache
export async function clearAllCache() {
    try {
        const db = await initCacheDB();
        const storeNames = Array.from(db.objectStoreNames);
        if (storeNames.length > 0) {
            const tx = db.transaction(storeNames, 'readwrite');
            await Promise.all(storeNames.map((store) => tx.objectStore(store).clear()));
            await tx.done;
        }
    }
    catch (error) {
        console.warn('[Cache] Failed to clear cache:', error);
    }
}
