// DSO IndexedDB Functions
const DSO_DB_NAME = 'AstroManagerDSO';
const DSO_MIN_DB_VERSION = 1;
const DSO_REQUIRED_STORES = ['dsoObjects', 'dsoMetadata'];

function logDsoStorageDiagnostic(level, message) {
    return;
}

function isAppleMobileWebView() {
    try {
        const userAgent = navigator.userAgent || '';
        const touchPoints = navigator.maxTouchPoints || 0;
        return /iPhone|iPad|iPod/i.test(userAgent)
            || (/Macintosh/i.test(userAgent) && touchPoints > 1);
    } catch {
        return false;
    }
}

function getDsoStorageBatchSize() {
    return isAppleMobileWebView() ? 200 : 1000;
}

function getDsoStorageYieldDelayMs() {
    return isAppleMobileWebView() ? 12 : 0;
}

function createDsoObjectStore(db) {
    const store = db.createObjectStore('dsoObjects', { keyPath: 'id' });
    store.createIndex('identifier', 'identifier', { unique: false });
    store.createIndex('constellation', 'constellation', { unique: false });
    store.createIndex('baseType', 'baseType', { unique: false });
    store.createIndex('magnitudeV', 'magnitudeV', { unique: false });
}

function ensureDsoSchema(db) {
    if (!db.objectStoreNames.contains('dsoObjects')) {
        createDsoObjectStore(db);
    }

    if (!db.objectStoreNames.contains('dsoMetadata')) {
        db.createObjectStore('dsoMetadata', { keyPath: 'key' });
    }
}

function hasRequiredDsoStores(db) {
    return DSO_REQUIRED_STORES.every(storeName => db.objectStoreNames.contains(storeName));
}

function openDsoDatabase(version = undefined) {
    return new Promise((resolve, reject) => {
        const request = version == null
            ? indexedDB.open(DSO_DB_NAME)
            : indexedDB.open(DSO_DB_NAME, version);

        request.onerror = () => reject(request.error);
        request.onsuccess = () => resolve(request.result);
        request.onupgradeneeded = (event) => {
            ensureDsoSchema(event.target.result);
        };
    });
}

window.__amDsoChunkStoreSession = null;

async function openDsoDatabaseEnsuringSchema() {
    let db = await openDsoDatabase();

    if (hasRequiredDsoStores(db)) {
        return db;
    }

    const repairedVersion = Math.max((db.version || DSO_MIN_DB_VERSION) + 1, DSO_MIN_DB_VERSION + 1);
    console.warn(`[DSO IndexedDB] Missing required stores detected. Repairing schema by upgrading DB to version ${repairedVersion}.`);
    db.close();

    db = await openDsoDatabase(repairedVersion);
    return db;
}

window.initializeDsoIndexedDb = async function () {
    // Request persistent storage
    if ('storage' in navigator && 'persist' in navigator.storage) {
        try {
            const isPersistent = await navigator.storage.persist();
            console.log('IndexedDB persistent storage:', isPersistent ? 'granted' : 'denied');
            logDsoStorageDiagnostic('info', `navigator.storage.persist result=${isPersistent ? 'granted' : 'denied'}`);
        } catch (error) {
            console.warn('Failed to request persistent storage:', error);
            logDsoStorageDiagnostic('warn', `navigator.storage.persist failed: ${error?.message || error}`);
        }
    }
    const db = await openDsoDatabaseEnsuringSchema();
    logDsoStorageDiagnostic('info', `IndexedDB initialized dbVersion=${db.version} requiredStores=${DSO_REQUIRED_STORES.join(',')}`);
    db.close();
};

window.storeDsoObjects = async function (jsonData) {
    try {
        const objects = JSON.parse(jsonData);
        logDsoStorageDiagnostic('info', `storeDsoObjects invoked objectCount=${Array.isArray(objects) ? objects.length : 0}`);
        
        console.log(`Received ${objects.length} DSO objects for storage`);
        
        // Validate that objects have the required 'id' field
        if (objects.length > 0) {
            const firstObj = objects[0];
            console.log('First object structure:', Object.keys(firstObj));
            
            if (!firstObj.id && !firstObj.Id) {
                console.error('Objects missing id field. First object:', firstObj);
                throw new Error('DSO objects missing required id field for IndexedDB key path');
            }
            
            // Ensure all objects have lowercase 'id' field for IndexedDB keyPath
            objects.forEach((obj, index) => {
                if (obj.Id && !obj.id) {
                    obj.id = obj.Id; // Copy Id to id for IndexedDB compatibility
                }
                if (!obj.id) {
                    console.error(`Object at index ${index} missing id:`, obj);
                    throw new Error(`Object at index ${index} missing required id field`);
                }
            });
        }
        
        // Ensure the database and metadata store exist before writing
        await window.initializeDsoIndexedDb();
        
        // Open database for writing
        const db = await openDsoDatabaseEnsuringSchema();

        try {
            // Clear existing data first in its own transaction
            await new Promise((resolve, reject) => {
                const clearTransaction = db.transaction(['dsoObjects'], 'readwrite');
                const clearStore = clearTransaction.objectStore('dsoObjects');
                const clearRequest = clearStore.clear();

                clearRequest.onerror = () => reject(clearRequest.error);
                clearTransaction.oncomplete = () => resolve();
                clearTransaction.onerror = () => reject(clearTransaction.error);
            });

            const batchSize = getDsoStorageBatchSize();
            const yieldDelayMs = getDsoStorageYieldDelayMs();
            console.log(`Storing ${objects.length} DSO objects in IndexedDB...`);
            logDsoStorageDiagnostic('info', `storeDsoObjects writing objectCount=${objects.length} batchSize=${batchSize} yieldDelayMs=${yieldDelayMs}`);

            // Store objects in batches so each transaction can complete cleanly.
            for (let i = 0; i < objects.length; i += batchSize) {
                const batch = objects.slice(i, i + batchSize);

                await new Promise((resolve, reject) => {
                    const batchTransaction = db.transaction(['dsoObjects'], 'readwrite');
                    const batchStore = batchTransaction.objectStore('dsoObjects');

                    batchTransaction.oncomplete = () => resolve();
                    batchTransaction.onerror = () => reject(batchTransaction.error);

                    batch.forEach((obj, batchIndex) => {
                        try {
                            const request = batchStore.put(obj);
                            request.onerror = () => {
                                console.error(`Error adding object at batch index ${batchIndex}:`, obj, request.error);
                                reject(request.error);
                            };
                        } catch (error) {
                            console.error(`Exception adding object at batch index ${batchIndex}:`, obj, error);
                            reject(error);
                        }
                    });
                });

                console.log(`Stored batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(objects.length / batchSize)}`);
                logDsoStorageDiagnostic('info', `storeDsoObjects stored batch endingAt=${Math.min(i + batch.length, objects.length)}`);

                await new Promise((resolve) => setTimeout(resolve, yieldDelayMs));
            }
        } finally {
            db.close();
        }

        console.log('DSO objects stored successfully in IndexedDB');
        logDsoStorageDiagnostic('info', 'storeDsoObjects completed successfully');
        return true;
    } catch (error) {
        console.error('Error clearing DSO objects:', error);
        logDsoStorageDiagnostic('error', `storeDsoObjects failed: ${error?.name || 'Error'} ${error?.message || error}`);
        throw error;
    }
};

window.beginDsoObjectsStore = async function (totalCount = 0) {
    try {
        const normalizedTotalCount = Number(totalCount) || 0;

        if (window.__amDsoChunkStoreSession?.db) {
            try {
                window.__amDsoChunkStoreSession.db.close();
            } catch {
                // Ignore stale close failures
            }
        }

        await window.initializeDsoIndexedDb();
        const db = await openDsoDatabaseEnsuringSchema();
        logDsoStorageDiagnostic('info', `beginDsoObjectsStore opened db version=${db.version} totalCount=${normalizedTotalCount}`);

        await new Promise((resolve, reject) => {
            const clearTransaction = db.transaction(['dsoObjects'], 'readwrite');
            const clearStore = clearTransaction.objectStore('dsoObjects');
            const clearRequest = clearStore.clear();

            clearRequest.onerror = () => reject(clearRequest.error);
            clearTransaction.oncomplete = () => resolve();
            clearTransaction.onerror = () => reject(clearTransaction.error);
        });

        window.__amDsoChunkStoreSession = {
            db,
            totalCount: normalizedTotalCount,
            storedCount: 0
        };

        logDsoStorageDiagnostic('info', `beginDsoObjectsStore cleared existing DSO objects totalCount=${normalizedTotalCount}`);
        return true;
    } catch (error) {
        logDsoStorageDiagnostic('error', `beginDsoObjectsStore failed: ${error?.name || 'Error'} ${error?.message || error}`);
        throw error;
    }
};

window.appendDsoObjectsChunk = async function (jsonData, offset = 0, totalCount = 0) {
    try {
        const objects = JSON.parse(jsonData);
        const normalizedTotalCount = Number(totalCount) || (Array.isArray(objects) ? objects.length : 0);
        const normalizedOffset = Number(offset) || 0;

        logDsoStorageDiagnostic(
            'info',
            `appendDsoObjectsChunk invoked offset=${normalizedOffset} count=${Array.isArray(objects) ? objects.length : 0} totalCount=${normalizedTotalCount}`);

        if (!Array.isArray(objects) || objects.length === 0) {
            return normalizedOffset;
        }

        let session = window.__amDsoChunkStoreSession;
        if (!session?.db) {
            await window.beginDsoObjectsStore(normalizedTotalCount);
            session = window.__amDsoChunkStoreSession;
        }

        for (let index = 0; index < objects.length; index++) {
            const obj = objects[index];
            if (obj.Id && !obj.id) {
                obj.id = obj.Id;
            }

            if (!obj.id) {
                throw new Error(`Chunk object at absolute index ${normalizedOffset + index} is missing its id field.`);
            }
        }

        logDsoStorageDiagnostic('info', `storeDsoObjectsChunk writing transaction start offset=${normalizedOffset} count=${objects.length}`);
        await new Promise((resolve, reject) => {
            const transaction = session.db.transaction(['dsoObjects'], 'readwrite');
            const store = transaction.objectStore('dsoObjects');

            transaction.oncomplete = () => resolve();
            transaction.onerror = () => reject(transaction.error);

            objects.forEach((obj, chunkIndex) => {
                const request = store.put(obj);
                request.onerror = () => reject(request.error ?? new Error(`Failed to write object at chunk index ${chunkIndex}.`));
            });
        });

        const endExclusive = normalizedOffset + objects.length;
        session.storedCount = endExclusive;
        logDsoStorageDiagnostic('info', `storeDsoObjectsChunk stored range=${normalizedOffset}-${endExclusive}/${normalizedTotalCount}`);
        return endExclusive;
    } catch (error) {
        logDsoStorageDiagnostic('error', `storeDsoObjectsChunk failed: ${error?.name || 'Error'} ${error?.message || error}`);
        throw error;
    }
};

window.completeDsoObjectsStore = async function (expectedTotalCount = 0) {
    const normalizedExpectedTotalCount = Number(expectedTotalCount) || 0;
    const session = window.__amDsoChunkStoreSession;

    try {
        if (!session?.db) {
            throw new Error('No active DSO chunk store session exists.');
        }

        const count = await new Promise((resolve, reject) => {
            const transaction = session.db.transaction(['dsoObjects'], 'readonly');
            const store = transaction.objectStore('dsoObjects');
            const request = store.count();

            request.onerror = () => reject(request.error);
            request.onsuccess = () => resolve(request.result);
            transaction.onerror = () => reject(transaction.error);
        });

        logDsoStorageDiagnostic('info', `completeDsoObjectsStore counted stored=${count} expected=${normalizedExpectedTotalCount}`);

        if (normalizedExpectedTotalCount > 0 && count !== normalizedExpectedTotalCount) {
            throw new Error(`Stored DSO object count mismatch. Expected ${normalizedExpectedTotalCount}, found ${count}.`);
        }

        return count;
    } catch (error) {
        logDsoStorageDiagnostic('error', `completeDsoObjectsStore failed: ${error?.name || 'Error'} ${error?.message || error}`);
        throw error;
    } finally {
        if (session?.db) {
            try {
                session.db.close();
                logDsoStorageDiagnostic('info', 'completeDsoObjectsStore closed db');
            } catch {
                // Ignore close failures
            }
        }

        window.__amDsoChunkStoreSession = null;
    }
};

window.abortDsoObjectsStore = async function () {
    const session = window.__amDsoChunkStoreSession;
    if (session?.db) {
        try {
            session.db.close();
            logDsoStorageDiagnostic('warn', 'abortDsoObjectsStore closed active db session');
        } catch {
            // Ignore close failures
        }
    }

    window.__amDsoChunkStoreSession = null;
    return true;
};

async function reportDsoProgress(progressCallback, message, percent) {
    if (!progressCallback || typeof progressCallback.invokeMethodAsync !== 'function') {
        return;
    }

    try {
        await progressCallback.invokeMethodAsync('OnProgress', message, percent);
    } catch (error) {
        console.warn('Failed to report DSO progress to .NET:', error);
    }
}

async function fetchDsoOfflinePayload(apiUrl) {
    const normalizedApiUrl = apiUrl.endsWith('/') ? apiUrl : `${apiUrl}/`;

    let response = await fetch(normalizedApiUrl + 'api/admin/dso-static/dso-data.json');
    if (!response.ok) {
        response = await fetch(normalizedApiUrl + 'api/admin/dso-offline/data');
    }

    if (!response.ok) {
        throw new Error('Failed to fetch the offline DSO database from the server.');
    }

    return await response.json();
}

window.dsoDirectFetchAndStore = async function (apiUrl, version, progressCallback = null) {
    let db = null;

    try {
        await reportDsoProgress(progressCallback, 'Connecting to server...', 5);
        logDsoStorageDiagnostic('info', `dsoDirectFetchAndStore starting apiUrl=${apiUrl} version=${version || '(null)'}`);

        const objects = await fetchDsoOfflinePayload(apiUrl);
        if (!Array.isArray(objects) || objects.length === 0) {
            throw new Error('The downloaded offline DSO database was empty.');
        }

        await reportDsoProgress(progressCallback, `Downloaded ${objects.length} objects. Preparing storage...`, 20);
        logDsoStorageDiagnostic('info', `dsoDirectFetchAndStore downloaded objectCount=${objects.length}`);

        for (let index = 0; index < objects.length; index++) {
            const obj = objects[index];
            if (obj.Id && !obj.id) {
                obj.id = obj.Id;
            }

            if (!obj.id) {
                throw new Error(`Offline DSO object at index ${index} is missing its id field.`);
            }
        }

        await window.initializeDsoIndexedDb();
        db = await openDsoDatabaseEnsuringSchema();

        await new Promise((resolve, reject) => {
            const clearTransaction = db.transaction(['dsoObjects'], 'readwrite');
            const clearStore = clearTransaction.objectStore('dsoObjects');
            const clearRequest = clearStore.clear();

            clearRequest.onerror = () => reject(clearRequest.error);
            clearTransaction.oncomplete = () => resolve();
            clearTransaction.onerror = () => reject(clearTransaction.error);
        });

        const batchSize = getDsoStorageBatchSize();
        const yieldDelayMs = getDsoStorageYieldDelayMs();
        await reportDsoProgress(progressCallback, 'Storing data locally...', 35);
        logDsoStorageDiagnostic('info', `dsoDirectFetchAndStore writing batchSize=${batchSize} yieldDelayMs=${yieldDelayMs}`);
        for (let i = 0; i < objects.length; i += batchSize) {
            const batch = objects.slice(i, i + batchSize);

            await new Promise((resolve, reject) => {
                const transaction = db.transaction(['dsoObjects'], 'readwrite');
                const store = transaction.objectStore('dsoObjects');

                transaction.oncomplete = () => resolve();
                transaction.onerror = () => reject(transaction.error);

                batch.forEach((obj) => store.put(obj));
            });

            const stored = Math.min(i + batch.length, objects.length);
            const percent = 35 + Math.round((stored / objects.length) * 60);
            await reportDsoProgress(progressCallback, `Stored ${stored}/${objects.length} objects...`, percent);
            logDsoStorageDiagnostic('info', `dsoDirectFetchAndStore stored ${stored}/${objects.length}`);

            // Yield between batches so mobile WebViews remain responsive.
            await new Promise((resolve) => setTimeout(resolve, yieldDelayMs));
        }

        if (version) {
            await new Promise((resolve, reject) => {
                const metadataTransaction = db.transaction(['dsoMetadata'], 'readwrite');
                const metadataStore = metadataTransaction.objectStore('dsoMetadata');
                const request = metadataStore.put({ key: 'databaseVersion', value: version });

                request.onerror = () => reject(request.error);
                metadataTransaction.oncomplete = () => resolve();
                metadataTransaction.onerror = () => reject(metadataTransaction.error);
            });
        }

        await reportDsoProgress(progressCallback, 'Finalizing...', 98);
        logDsoStorageDiagnostic('info', 'dsoDirectFetchAndStore completed successfully');
        return objects.length;
    } catch (error) {
        console.error('Direct DSO fetch-and-store failed:', error);
        logDsoStorageDiagnostic('error', `dsoDirectFetchAndStore failed: ${error?.name || 'Error'} ${error?.message || error}`);
        throw error;
    } finally {
        if (db) {
            db.close();
        }
    }
};

window.getDsoObjects = async function () {
    try {
        const db = await openDsoDatabaseEnsuringSchema();

        const transaction = db.transaction(['dsoObjects'], 'readonly');
        const store = transaction.objectStore('dsoObjects');
        
        const objects = await new Promise((resolve, reject) => {
            const request = store.getAll();
            request.onerror = () => reject(request.error);
            request.onsuccess = () => resolve(request.result);
        });

        db.close();
        return JSON.stringify(objects);
        
    } catch (error) {
        console.error('Failed to retrieve DSO objects:', error);
        return null;
    }
};

window.clearDsoObjects = async function () {
    try {
        const db = await openDsoDatabaseEnsuringSchema();

        const transaction = db.transaction(['dsoObjects'], 'readwrite');
        const store = transaction.objectStore('dsoObjects');
        
        await new Promise((resolve, reject) => {
            const request = store.clear();
            request.onerror = () => reject(request.error);
            request.onsuccess = () => resolve();
        });

        db.close();
        console.log('DSO objects cleared from IndexedDB');
        return true;
        
    } catch (error) {
        console.error('Failed to clear DSO objects:', error);
        return false;
    }
};

window.getDsoObjectCount = async function () {
    try {
        const db = await openDsoDatabaseEnsuringSchema();

        const transaction = db.transaction(['dsoObjects'], 'readonly');
        const store = transaction.objectStore('dsoObjects');
        
        const count = await new Promise((resolve, reject) => {
            const request = store.count();
            request.onerror = () => reject(request.error);
            request.onsuccess = () => resolve(request.result);
        });

        db.close();
        logDsoStorageDiagnostic('info', `getDsoObjectCount returned count=${count}`);
        return count;
        
    } catch (error) {
        console.error('Failed to get DSO object count:', error);
        logDsoStorageDiagnostic('error', `getDsoObjectCount failed: ${error?.name || 'Error'} ${error?.message || error}`);
        return 0;
    }
};

// Get DSO database version from IndexedDB
window.getDsoVersion = async function () {
    try {
        const db = await openDsoDatabaseEnsuringSchema();

        // Check if dsoMetadata store exists (may not exist in older DB versions)
        if (!db.objectStoreNames.contains('dsoMetadata')) {
            db.close();
            return null;
        }

        const transaction = db.transaction(['dsoMetadata'], 'readonly');
        const store = transaction.objectStore('dsoMetadata');
        
        const result = await new Promise((resolve, reject) => {
            const request = store.get('databaseVersion');
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });

        db.close();
        logDsoStorageDiagnostic('info', `getDsoVersion returned value=${result ? result.value : '(null)'}`);
        return result ? result.value : null;
    } catch (error) {
        console.error('Error getting DSO database version:', error);
        logDsoStorageDiagnostic('error', `getDsoVersion failed: ${error?.name || 'Error'} ${error?.message || error}`);
        return null;
    }
};

// Set DSO database version in IndexedDB
window.setDsoVersion = async function (version) {
    try {
        // First ensure the database has the dsoMetadata store
        await window.initializeDsoIndexedDb();
        logDsoStorageDiagnostic('info', `setDsoVersion starting version=${version}`);
        
        const db = await openDsoDatabaseEnsuringSchema();

        // Check if dsoMetadata store exists
        if (!db.objectStoreNames.contains('dsoMetadata')) {
            db.close();
            console.warn('dsoMetadata store not found, cannot set version');
            return;
        }

        try {
            const transaction = db.transaction(['dsoMetadata'], 'readwrite');
            const store = transaction.objectStore('dsoMetadata');
            
            await new Promise((resolve, reject) => {
                const request = store.put({ key: 'databaseVersion', value: version });
                request.onerror = () => reject(request.error);
                transaction.oncomplete = () => resolve();
                transaction.onerror = () => reject(transaction.error);
            });
        } finally {
            db.close();
        }

        console.log(`DSO database version set to: ${version}`);
        logDsoStorageDiagnostic('info', `setDsoVersion completed version=${version}`);
    } catch (error) {
        console.error('Error setting DSO database version:', error);
        logDsoStorageDiagnostic('error', `setDsoVersion failed: ${error?.name || 'Error'} ${error?.message || error}`);
        throw error;
    }
};

window.getDsoMetadataValue = async function (key) {
    try {
        if (!key) {
            return null;
        }

        const db = await openDsoDatabaseEnsuringSchema();

        if (!db.objectStoreNames.contains('dsoMetadata')) {
            db.close();
            return null;
        }

        const transaction = db.transaction(['dsoMetadata'], 'readonly');
        const store = transaction.objectStore('dsoMetadata');

        const result = await new Promise((resolve, reject) => {
            const request = store.get(key);
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });

        db.close();
        return result ? result.value : null;
    } catch (error) {
        console.error(`Error getting DSO metadata value for key ${key}:`, error);
        return null;
    }
};

window.setDsoMetadataValue = async function (key, value) {
    try {
        if (!key) {
            return false;
        }

        await window.initializeDsoIndexedDb();
        const db = await openDsoDatabaseEnsuringSchema();

        if (!db.objectStoreNames.contains('dsoMetadata')) {
            db.close();
            return false;
        }

        try {
            const transaction = db.transaction(['dsoMetadata'], 'readwrite');
            const store = transaction.objectStore('dsoMetadata');

            await new Promise((resolve, reject) => {
                const request = store.put({ key, value });
                request.onerror = () => reject(request.error);
                transaction.oncomplete = () => resolve();
                transaction.onerror = () => reject(transaction.error);
            });
        } finally {
            db.close();
        }

        return true;
    } catch (error) {
        console.error(`Error setting DSO metadata value for key ${key}:`, error);
        return false;
    }
};

window.removeDsoMetadataValue = async function (key) {
    try {
        if (!key) {
            return false;
        }

        await window.initializeDsoIndexedDb();
        const db = await openDsoDatabaseEnsuringSchema();

        if (!db.objectStoreNames.contains('dsoMetadata')) {
            db.close();
            return false;
        }

        try {
            const transaction = db.transaction(['dsoMetadata'], 'readwrite');
            const store = transaction.objectStore('dsoMetadata');

            await new Promise((resolve, reject) => {
                const request = store.delete(key);
                request.onerror = () => reject(request.error);
                transaction.oncomplete = () => resolve();
                transaction.onerror = () => reject(transaction.error);
            });
        } finally {
            db.close();
        }

        return true;
    } catch (error) {
        console.error(`Error removing DSO metadata value for key ${key}:`, error);
        return false;
    }
};

window.removeDsoMetadataValuesByPrefix = async function (prefix) {
    try {
        if (!prefix) {
            return 0;
        }

        await window.initializeDsoIndexedDb();
        const db = await openDsoDatabaseEnsuringSchema();

        if (!db.objectStoreNames.contains('dsoMetadata')) {
            db.close();
            return 0;
        }

        let deletedCount = 0;

        try {
            const transaction = db.transaction(['dsoMetadata'], 'readwrite');
            const store = transaction.objectStore('dsoMetadata');

            await new Promise((resolve, reject) => {
                const request = store.openCursor();
                request.onsuccess = () => {
                    const cursor = request.result;
                    if (!cursor) {
                        resolve();
                        return;
                    }

                    if (typeof cursor.key === 'string' && cursor.key.startsWith(prefix)) {
                        const deleteRequest = cursor.delete();
                        deleteRequest.onsuccess = () => {
                            deletedCount++;
                            cursor.continue();
                        };
                        deleteRequest.onerror = () => reject(deleteRequest.error);
                        return;
                    }

                    cursor.continue();
                };
                request.onerror = () => reject(request.error);
                transaction.onerror = () => reject(transaction.error);
            });
        } finally {
            db.close();
        }

        return deletedCount;
    } catch (error) {
        console.error(`Error removing DSO metadata values for prefix ${prefix}:`, error);
        return 0;
    }
};

// Fast search function that filters in IndexedDB - avoids loading all objects into WASM
window.searchDsoObjects = async function (filterJson) {
    try {
        const filter = JSON.parse(filterJson);
        const db = await openDsoDatabaseEnsuringSchema();

        if (!db) return JSON.stringify({ results: [], totalCount: 0 });

        const transaction = db.transaction(['dsoObjects'], 'readonly');
        const store = transaction.objectStore('dsoObjects');
        
        // Get all objects using cursor for memory efficiency
        const matchingObjects = [];
        let totalScanned = 0;
        
        await new Promise((resolve, reject) => {
            const request = store.openCursor();
            
            request.onsuccess = (event) => {
                const cursor = event.target.result;
                if (cursor) {
                    totalScanned++;
                    const obj = cursor.value;
                    
                    // Apply filters
                    if (matchesFilter(obj, filter)) {
                        matchingObjects.push(obj);
                    }
                    cursor.continue();
                } else {
                    resolve();
                }
            };
            request.onerror = () => reject(request.error);
        });

        db.close();
        
        // Sort results
        sortResults(matchingObjects, filter.sortBy, filter.sortAscending);
        
        // Apply pagination
        const page = filter.page || 1;
        const pageSize = filter.pageSize || 20;
        const startIndex = (page - 1) * pageSize;
        const pagedResults = matchingObjects.slice(startIndex, startIndex + pageSize);
        
        console.log(`[DSO Search] Scanned ${totalScanned}, matched ${matchingObjects.length}, returning ${pagedResults.length}`);
        
        return JSON.stringify({
            results: pagedResults,
            totalCount: matchingObjects.length
        });
    } catch (error) {
        console.error('[DSO Search] Error:', error);
        return JSON.stringify({ results: [], totalCount: 0, error: error.message });
    }
};

// Helper function to check if object matches filter criteria
function matchesFilter(obj, filter) {
    // Search term filter (case-insensitive)
    if (filter.searchTerm && filter.searchTerm.trim()) {
        if (!matchesSearchTerms(obj, filter.searchTerm)) {
            return false;
        }
    }

    // Catalog filter
    if (filter.catalogs && filter.catalogs.length > 0) {
        const searchVector = (obj.CombinedSearchVector || obj.combinedSearchVector || '').toLowerCase();
        const identifier = (obj.Identifier || obj.identifier || '').toLowerCase();

        const hasCatalogMatch = filter.catalogs.some(catalog => {
            const catalogLower = (catalog || '').toLowerCase();
            return hasExactCatalogMatch(searchVector, catalogLower) || identifier.startsWith(catalogLower);
        });

        if (!hasCatalogMatch) {
            return false;
        }
    }
    
    // Constellation filter
    if (filter.constellations && filter.constellations.length > 0) {
        const constellation = obj.Constellation || obj.constellation;
        if (!constellation || !filter.constellations.includes(constellation)) {
            return false;
        }
    }
    
    // Object type filter
    if (filter.objectTypes && filter.objectTypes.length > 0) {
        const baseType = obj.BaseType || obj.baseType;
        if (!baseType || !filter.objectTypes.includes(baseType)) {
            return false;
        }
    }

    // Right ascension filter (stored in degrees, filter comes in hours)
    if (filter.raRange && filter.raRange.length === 2) {
        const raMinHours = filter.raRange[0];
        const raMaxHours = filter.raRange[1];

        if (raMinHours > 0 || raMaxHours < 24) {
            const ra = obj.RightAscension ?? obj.rightAscension ?? 0;
            const raMin = raMinHours * 15;
            const raMax = raMaxHours * 15;

            if (raMinHours > raMaxHours) {
                if (!(ra >= raMin || ra <= raMax)) {
                    return false;
                }
            } else if (ra < raMin || ra > raMax) {
                return false;
            }
        }
    }

    // Declination filter
    if (filter.decRange && filter.decRange.length === 2) {
        const decMin = filter.decRange[0];
        const decMax = filter.decRange[1];

        if (decMin > -90 || decMax < 90) {
            const dec = obj.Declination ?? obj.declination ?? 0;
            if (dec < decMin || dec > decMax) {
                return false;
            }
        }
    }
    
    // Magnitude filter
    const magnitude = obj.MagnitudeV ?? obj.magnitudeV;
    if (!filter.includeUnknownMagnitude && (magnitude == null)) {
        return false;
    }
    if (filter.minMagnitude != null && magnitude != null && magnitude < filter.minMagnitude) {
        return false;
    }
    if (filter.maxMagnitude != null && magnitude != null && magnitude > filter.maxMagnitude) {
        return false;
    }
    
    // Size filter
    const size = obj.SizeMaxArcmin ?? obj.sizeMaxArcmin;
    if (!filter.includeUnknownSize && (size == null)) {
        return false;
    }
    if (filter.minSize != null && size != null && size < filter.minSize) {
        return false;
    }
    if (filter.maxSize != null && size != null && size > filter.maxSize) {
        return false;
    }
    
    return true;
}

function parseSearchComponents(searchTerm) {
    const searchComponents = [];
    const catalogPrefixes = ['m', 'ngc', 'ic', 'messier', 'sh', 'abell', 'pk'];
    const normalizedTerm = (searchTerm || '').toLowerCase().trim();

    let catalogMatched = false;

    for (const prefix of catalogPrefixes) {
        if (normalizedTerm.startsWith(prefix + ' ') && normalizedTerm.length > prefix.length + 1) {
            const number = normalizedTerm.substring(prefix.length + 1).trim();
            if (/^\d+$/.test(number)) {
                searchComponents.push(prefix, number);
                catalogMatched = true;
                break;
            }
        } else if (normalizedTerm.startsWith(prefix) && normalizedTerm.length > prefix.length) {
            const number = normalizedTerm.substring(prefix.length).trimStart();
            if (/^\d+$/.test(number)) {
                searchComponents.push(prefix, number);
                catalogMatched = true;
                break;
            }
        }
    }

    if (!catalogMatched && normalizedTerm.length > 0) {
        searchComponents.push(...normalizedTerm.split(/\s+/).filter(Boolean));
    }

    return searchComponents;
}

function hasWordStartingWith(text, prefix) {
    if (!text || !prefix) return false;
    return text
        .split(/[\s\-_]+/)
        .filter(Boolean)
        .some(word => word.startsWith(prefix));
}

function hasExactWord(text, word) {
    if (!text || !word) return false;
    return text
        .split(/[\s\-_]+/)
        .filter(Boolean)
        .some(candidate => candidate === word);
}

function hasExactCatalogMatch(searchVector, catalog) {
    if (!searchVector || !catalog) return false;
    const catalogLower = catalog.toLowerCase().replace(/ /g, '_');
    const terms = searchVector.toLowerCase().split(/\s+/).filter(Boolean);
    return terms.some(term => term === catalogLower);
}

function matchesSearchTerms(obj, searchTerm) {
    const searchComponents = parseSearchComponents(searchTerm);
    if (searchComponents.length === 0) return true;

    const deepSearch = searchTerm.endsWith('+') || searchTerm.endsWith('*') || searchComponents.length === 1;
    const identifier = (obj.Identifier || obj.identifier || '').toLowerCase();
    const searchVector = (obj.CombinedSearchVector || obj.combinedSearchVector || '').toLowerCase();
    const searchableText = `${identifier} ${searchVector}`.trim();

    if (deepSearch) {
        return searchComponents.some(component =>
            identifier.startsWith(component) ||
            searchVector.includes(component) ||
            hasWordStartingWith(searchableText, component));
    }

    return searchComponents.every(component =>
        identifier === component ||
        hasExactWord(searchVector, component));
}

// Helper function to sort results
function sortResults(results, sortBy, ascending) {
    const direction = ascending ? 1 : -1;
    
    results.sort((a, b) => {
        let valA, valB;
        
        switch (sortBy) {
            case 'RightAscension':
            case 1:
                valA = a.RightAscension ?? a.rightAscension ?? 0;
                valB = b.RightAscension ?? b.rightAscension ?? 0;
                break;
            case 'Declination':
            case 2:
                valA = a.Declination ?? a.declination ?? 0;
                valB = b.Declination ?? b.declination ?? 0;
                break;
            case 'ObjectType':
            case 3:
                valA = a.BaseType || a.baseType || '';
                valB = b.BaseType || b.baseType || '';
                return direction * valA.localeCompare(valB);
            case 'ObjectName':
            case 4:
                valA = a.Identifier || a.identifier || '';
                valB = b.Identifier || b.identifier || '';
                return direction * valA.localeCompare(valB);
            case 'Size':
            case 5:
                valA = a.SizeMaxArcmin ?? a.sizeMaxArcmin ?? 0;
                valB = b.SizeMaxArcmin ?? b.sizeMaxArcmin ?? 0;
                break;
            case 'Magnitude':
            case 6:
                valA = a.MagnitudeV ?? a.magnitudeV ?? 999;
                valB = b.MagnitudeV ?? b.magnitudeV ?? 999;
                break;
            case 'Popularity':
            case 0:
            default:
                valA = a.Popularity ?? a.popularity ?? 0;
                valB = b.Popularity ?? b.popularity ?? 0;
                break;
        }
        
        return direction * (valA - valB);
    });
}

// Fast check if DSO objects exist without counting
window.hasDsoObjects = async function () {
    try {
        const db = await openDsoDatabaseEnsuringSchema();

        const transaction = db.transaction(['dsoObjects'], 'readonly');
        const store = transaction.objectStore('dsoObjects');
        
        // Just get the first key to check if any data exists
        const hasData = await new Promise((resolve, reject) => {
            const request = store.openCursor();
            request.onsuccess = () => resolve(request.result !== null);
            request.onerror = () => reject(request.error);
        });

        db.close();
        return hasData;
    } catch (error) {
        console.error('Error checking if DSO objects exist:', error);
        return false;
    }
};
