// interface MyJsonObject {
//   id: string;
//   name: string;
//   email: string;
// }
// Efficiently storing data in indexdb: https://stackoverflow.com/a/62975917
const postStoreName = "posts";
const tombstoneStoreName = "tombstones";
const followingStoreName = "following";
const profileStoreName = "profiles";
let keyBase = "dandelion_posts_v1_";
let key = "";
let version = 3;
async function upgrade_0to1(db, _transaction) {
    let postsStore = db.createObjectStore(postStoreName, { keyPath: "id", autoIncrement: true });
    postsStore.createIndex("datetimeIndex", "post_timestamp", { unique: false });
    postsStore.createIndex("postIDIndex", "data.post_id", { unique: true });
}
async function upgrade_1to2(_db, _transaction) {
    // Was broken for some clients — index creation moved to upgrade_2to3
}
async function upgrade_2to3(_db, transaction) {
    const postsStore = transaction.objectStore(postStoreName);
    if (!postsStore.indexNames.contains("postReplyIndex")) {
        postsStore.createIndex("postReplyIndex", "data.reply_to_id", { unique: false });
    }
}
// let following = ['b38b623c-c3fa-4351-9cab-50233c99fa4e'];
// let profiles = [
//     {
//         id: 'b38b623c-c3fa-4351-9cab-50233c99fa4e',
//         name: 'Robert Anderberg',
//         handle: 'bobbydigitales',
//         avatar_image: new ArrayBuffer(1024),
//         banner_image: new ArrayBuffer(1024),
//         description: "A very nice person who never does anything wrong.",
//     }];
// let tombstones = ['b38b623c-c3fa-4351-9cab-50233c99fa4e'];
// async function upgrade_1to2(db: IDBDatabase) {
//     let followingStore = db.createObjectStore(followingStoreName, { keyPath: "id", autoIncrement: true });
//     let profileStore = db.createObjectStore(profileStoreName, { keyPath: "id", autoIncrement: true });
//     let tombstoneStore = db.createObjectStore(tombstoneStoreName, { keyPath: "id", autoIncrement: true });
//     tombstoneStore.createIndex("postIDIndex", "data.post_id", { unique: true });
// }
// async function upgrade_1to2(db: IDBDatabase) {
//     console.log("Upgrading database from 1 to 2");
//     console.log("Converting all image arraybuffers to Blobs")
//     // TODO convert all images from arraybuffers to blobs
//     let knownUsers = [...(await indexedDB.databases())].map((db) => db.name?.replace('user_', ''));
//     if (knownUsers.length === 0) {
//         return;
//     }
//     for (const userID of knownUsers as string[]) {
//         console.log(`Converting images for user ${userID}`)
//         let posts = await getAllData(userID);
//         for (const post of posts) {
//             let image_data = post.data.image_data;
//             if (image_data) {
//                 let blob = new Blob([image_data as ArrayBuffer]);
//                 post.data.image_data = blob;
//                 // TODO get the format of a new post right
//                 addData(userID, post);
//             }
//         }
//     }
// }
// async function upgrade_2to3(db: IDBDatabase) {
//     let followingStore = db.createObjectStore(followingStoreName, { keyPath: "id", autoIncrement: true });
// }
let upgrades = new Map([
    [0, upgrade_0to1],
    [1, upgrade_1to2],
    [2, upgrade_2to3],
]);
export function openDatabase(userID) {
    const dbName = `user_${userID}`;
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(dbName, version);
        request.onerror = (event) => {
            const errorEvent = event;
            reject(`Database error: ${errorEvent.target.error?.message}`);
        };
        request.onupgradeneeded = async (event) => {
            const db = event.target.result;
            const transaction = event.target.transaction;
            for (let v = event.oldVersion; v < (event.newVersion ?? version); v++) {
                const upgradeFunction = upgrades.get(v);
                if (!upgradeFunction) {
                    throw new Error(`db: Don't have an upgrade function to go from version ${v} to version ${v + 1}`);
                }
                await upgradeFunction(db, transaction);
            }
        };
        request.onsuccess = (event) => {
            const db = event.target.result;
            resolve(db);
        };
    });
}
async function getDBTransactionStore(userID, mode = "readonly") {
    const db = await openDatabase(userID);
    const transaction = db.transaction(postStoreName, mode);
    const store = transaction.objectStore(postStoreName);
    return { db, transaction, store };
}
export async function addData(userID, data) {
    try {
        const { db, transaction, store } = await getDBTransactionStore(userID, "readwrite");
        const addRequest = store.add({ post_timestamp: data.post_timestamp, data: data });
        addRequest.onsuccess = (e) => {
            // console.log('Data has been added:', (e.target as IDBRequest).result);
        };
        addRequest.onerror = (event) => {
            // Use a type assertion to access the specific properties of IDBRequest error event
            const errorEvent = event;
            console.error('Error in adding data:', errorEvent.target.error?.message);
        };
    }
    catch (error) {
        console.error('Error in opening database:', error);
    }
}
export async function deleteData(userID, postID) {
    try {
        const { db, transaction, store } = await getDBTransactionStore(userID, "readwrite");
        const index = store.index("postIDIndex");
        const getRequest = index.getKey(postID);
        getRequest.onerror = e => console.log(e.target.error);
        getRequest.onsuccess = e => {
            const key = e.target.result;
            if (key === undefined) {
                console.error("Post not found");
                return null;
            }
            const deleteRequest = store.delete(key);
            deleteRequest.onerror = e => { console.error(e.target.error); return false; };
            deleteRequest.onsuccess = () => true;
        };
    }
    catch (error) {
        console.error('Error in opening database:', error);
    }
}
export async function clearData(userID) {
    try {
        const { db, transaction, store } = await getDBTransactionStore(userID, "readwrite");
        const clearRequest = store.clear();
        clearRequest.onsuccess = (e) => {
            // console.log('Data has been added:', (e.target as IDBRequest).result);
        };
        clearRequest.onerror = (event) => {
            // Use a type assertion to access the specific properties of IDBRequest error event
            const errorEvent = event;
            console.error('Error in clearing data:', errorEvent.target.error?.message);
        };
    }
    catch (error) {
        console.error('Error in opening database:', error);
    }
}
// TODO - this function can return before the data is stored!
export async function addDataArray(userID, array) {
    try {
        const { db, transaction, store } = await getDBTransactionStore(userID, "readwrite");
        let completionPromise = new Promise((resolve, reject) => {
            transaction.oncomplete = (event) => {
                resolve();
            };
            transaction.onerror = (event) => {
                console.error('Error in adding data:', event);
                reject();
            };
        });
        // let count = 0;
        array.reverse();
        for (let data of array) {
            const addRequest = store.add({ post_timestamp: data.post_timestamp, data: data });
            // addRequest.onsuccess = (e: Event) => {
            //     // console.log('Data has been added:', (e.target as IDBRequest).result);
            // };
            // addRequest.onerror = (event: Event) => {
            //     // Use a type assertion to access the specific properties of IDBRequest error event
            //     const errorEvent = event as IDBRequestEvent;
            //     console.error('Error in adding data:', errorEvent.target.error?.message);
            // };
            // count++;
            // if (count % 100 === 0) {
            //     console.log(`Added ${count} posts...`);
            // }
        }
        return completionPromise;
    }
    catch (error) {
        console.error('Error in opening database:', error);
    }
}
export async function checkPostIds(userID, post_ids) {
    try {
        const { db, transaction, store } = await getDBTransactionStore(userID);
        const index = store.index("postIDIndex");
        transaction.oncomplete = () => {
            // console.log("Transaction completed successfully");
            db.close();
        };
        transaction.onerror = (event) => {
            console.error("Transaction error:", event.target.error);
            db.close();
        };
        let postIdsNeeded = [];
        for (let id of post_ids) {
            try {
                let havePost = await new Promise((resolve, reject) => {
                    const getRequest = index.getKey(id);
                    getRequest.onerror = (e) => {
                        console.log(e.target.error);
                        reject(e);
                    };
                    getRequest.onsuccess = async (e) => {
                        const key = e.target.result;
                        resolve(key !== undefined);
                    };
                });
                // console.log(post.post_id, havePost);
                if (!havePost) {
                    postIdsNeeded.push(id);
                }
            }
            catch (error) {
                console.error("Error processing post:", error);
            }
        }
        // console.log(`checkPostIds need ${postIdsNeeded.length} posts`);
        return postIdsNeeded;
    }
    catch (error) {
        console.error("Error in opening database:", error);
    }
}
export async function mergeDataArray(userID, array) {
    try {
        const { db, transaction, store } = await getDBTransactionStore(userID, "readwrite");
        const index = store.index("postIDIndex");
        transaction.oncomplete = () => {
            // console.log("Transaction completed successfully");
            db.close();
        };
        transaction.onerror = (event) => {
            console.error("Transaction error:", event.target.error);
            db.close();
        };
        let postsToWrite = [];
        for (let post of array) {
            try {
                let havePost = await new Promise((resolve, reject) => {
                    const getRequest = index.getKey(post.post_id);
                    getRequest.onerror = (e) => {
                        console.log(e.target.error);
                        reject(e);
                    };
                    getRequest.onsuccess = async (e) => {
                        const key = e.target.result;
                        resolve(key !== undefined);
                    };
                });
                // console.log(post.post_id, havePost);
                if (!havePost) {
                    postsToWrite.push(post);
                }
            }
            catch (error) {
                console.error("Error processing post:", error);
            }
        }
        console.log(`Writing ${postsToWrite.length} posts`);
        await addDataArray(userID, postsToWrite);
    }
    catch (error) {
        console.error("Error in opening database:", error);
    }
}
export async function getPostForUser(userID, postID) {
    const { store } = await getDBTransactionStore(userID);
    const index = store.index("postIDIndex");
    return new Promise((resolve, reject) => {
        const getPostRequest = index.get(postID);
        getPostRequest.onsuccess = () => {
            resolve(getPostRequest.result);
        };
        getPostRequest.onerror = () => {
            console.error('Transaction failed:', getPostRequest.error?.message);
            reject(getPostRequest.error);
        };
    });
}
export async function getData(userID, lowerID, upperID) {
    const { store } = await getDBTransactionStore(userID);
    const keyRangeValue = IDBKeyRange.bound(lowerID, upperID);
    const index = store.index("datetimeIndex");
    return new Promise((resolve, reject) => {
        const getAllRequest = index.getAll(keyRangeValue);
        getAllRequest.onsuccess = () => {
            // let records = [];
            // for (let record of getAllRequest.result) {
            //     records.push(record);
            // }
            // // const records = getAllRequest.result.map((item: any) => item.data);
            resolve(getAllRequest.result);
        };
        getAllRequest.onerror = () => {
            console.error('Transaction failed:', getAllRequest.error?.message);
            reject(getAllRequest.error);
        };
    });
}
export async function getAllData(userID) {
    const { store } = await getDBTransactionStore(userID);
    return new Promise((resolve, reject) => {
        const getRequest = store.getAll();
        getRequest.onsuccess = () => {
            if (getRequest.result) {
                //   console.log('Retrieved data:', getRequest.result.jsonData);
                // resolve(getRequest.result.jsonData as any);
                resolve(getRequest.result);
            }
            else {
                console.log('No data record found for key', key);
                resolve(undefined); // explicitly resolve with undefined when no data is found
            }
        };
        getRequest.onerror = (event) => {
            // Use a type assertion to access the specific properties of IDBRequest error event
            const errorEvent = event;
            console.error('Transaction failed:', errorEvent.target.error?.message);
            reject(errorEvent.target.error); // reject the promise if there's an error
        };
    });
}
export async function getAllIds(userID) {
    const { store } = await getDBTransactionStore(userID);
    const index = store.index("postIDIndex");
    let keys = [];
    return new Promise((resolve, reject) => {
        let request = index.openKeyCursor();
        request.onsuccess = (event) => {
            let cursor = event.target.result;
            if (cursor) {
                keys.push(cursor.key);
                cursor.continue();
            }
            else {
                resolve(keys);
            }
        };
        request.onerror = (event) => {
            reject(event);
        };
    });
}
export async function buildReplyCountMap() {
    const children = new Map();
    const knownUsers = [...(await indexedDB.databases())]
        .map(db => db.name?.replace('user_', '')).filter(Boolean);
    for (const userID of knownUsers) {
        try {
            const { store } = await getDBTransactionStore(userID);
            const index = store.index("postReplyIndex");
            const replies = await new Promise((resolve, reject) => {
                const req = index.getAll();
                req.onsuccess = () => resolve(req.result);
                req.onerror = () => reject(req.error);
            });
            for (const r of replies) {
                const parentID = r.data.reply_to_id;
                const replyID = r.data.post_id;
                if (parentID && replyID) {
                    if (!children.has(parentID))
                        children.set(parentID, []);
                    children.get(parentID).push(replyID);
                }
            }
        }
        catch (_) { }
    }
    const direct = new Map();
    for (const [parentID, kids] of children) {
        direct.set(parentID, kids.length);
    }
    const countDescendants = (postID) => {
        const kids = children.get(postID) ?? [];
        return kids.reduce((sum, kid) => sum + 1 + countDescendants(kid), 0);
    };
    const recursive = new Map();
    for (const postID of children.keys()) {
        recursive.set(postID, countDescendants(postID));
    }
    return { direct, recursive };
}
export async function getPostById(postID) {
    const knownUsers = [...(await indexedDB.databases())]
        .map(db => db.name?.replace('user_', '')).filter(Boolean);
    for (const userID of knownUsers) {
        try {
            const { store } = await getDBTransactionStore(userID);
            const index = store.index("postIDIndex");
            const result = await new Promise((resolve, reject) => {
                const req = index.get(postID);
                req.onsuccess = () => resolve(req.result);
                req.onerror = () => reject(req.error);
            });
            if (result)
                return result;
        }
        catch (_) { }
    }
    return undefined;
}
export async function getRepliesForPost(postID) {
    const knownUsers = [...(await indexedDB.databases())]
        .map(db => db.name?.replace('user_', '')).filter(Boolean);
    let replies = [];
    for (const userID of knownUsers) {
        try {
            const { store } = await getDBTransactionStore(userID);
            const index = store.index("postReplyIndex");
            const results = await new Promise((resolve, reject) => {
                const req = index.getAll(postID);
                req.onsuccess = () => resolve(req.result);
                req.onerror = () => reject(req.error);
            });
            replies = replies.concat(results);
        }
        catch (_) { }
    }
    return replies;
}
export async function getPostsByIds(userID, postIDs) {
    const { store } = await getDBTransactionStore(userID);
    const index = store.index("postIDIndex");
    let posts = [];
    for (const postID of postIDs) {
        const post = await new Promise((resolve, reject) => {
            let request = index.get(postID);
            request.onsuccess = (event) => {
                resolve(event.target.result); // Resolve with the post
            };
            request.onerror = (event) => {
                reject(event); // Reject if any error occurs
            };
        });
        if (post) {
            posts.push(post); // Add the post to the result array if found
        }
    }
    return posts; // Return the array of posts
}
//# sourceMappingURL=db.js.map