import pDefer from "p-defer";
import { v4 as uuid } from "uuid";
import * as messages from "./sqlite3-messages.worker.js";
import { create as createDatabase } from "./sqlite3.wasm.js";
import {} from "./types.js";
const DEFAULT_PROTOCOL = "clone";
const EMPTY_ENCODE_METRICS = {
    encodeMs: 0,
    valueCount: 0,
    blobValueCount: 0,
    blobBytes: 0,
};
const getProtocol = (options) => options?.protocol ?? DEFAULT_PROTOCOL;
class ProxyStatement {
    send;
    databaseId;
    statementId;
    sql;
    options;
    id;
    needsReset = false;
    constructor(send, databaseId, statementId, sql, options) {
        this.send = send;
        this.databaseId = databaseId;
        this.statementId = statementId;
        this.sql = sql;
        this.options = options;
        this.id = statementId;
    }
    async bind(values) {
        const encoded = messages.encodeValues(values, getProtocol(this.options));
        await this.send({
            type: "bind",
            values: encoded.values ?? [],
            id: uuid(),
            databaseId: this.databaseId,
            statementId: this.statementId,
        }, {
            requestType: "bind",
            sql: this.sql,
            ...encoded.metrics,
        });
        this.needsReset = true;
        return this;
    }
    async finalize() {
        await this.send({
            type: "finalize",
            id: uuid(),
            databaseId: this.databaseId,
            statementId: this.statementId,
        }, {
            requestType: "finalize",
            sql: this.sql,
            ...EMPTY_ENCODE_METRICS,
        });
        this.needsReset = false;
    }
    async get(values) {
        const encoded = messages.encodeValues(values, getProtocol(this.options));
        const result = await this.send({
            type: "get",
            values: encoded.values,
            id: uuid(),
            databaseId: this.databaseId,
            statementId: this.statementId,
        }, {
            requestType: "get",
            sql: this.sql,
            ...encoded.metrics,
        });
        this.needsReset = false;
        return result;
    }
    async run(values) {
        const encoded = messages.encodeValues(values, getProtocol(this.options));
        await this.send({
            type: "run-statement",
            values: encoded.values ?? [],
            id: uuid(),
            databaseId: this.databaseId,
            statementId: this.statementId,
        }, {
            requestType: "run-statement",
            sql: this.sql,
            ...encoded.metrics,
        });
        this.needsReset = false;
    }
    async reset() {
        if (!this.needsReset) {
            return this;
        }
        await this.send({
            type: "reset",
            id: uuid(),
            databaseId: this.databaseId,
            statementId: this.statementId,
        }, {
            requestType: "reset",
            sql: this.sql,
            ...EMPTY_ENCODE_METRICS,
        });
        this.needsReset = false;
        return this;
    }
    async all(values) {
        const encoded = messages.encodeValues(values, getProtocol(this.options));
        const result = await this.send({
            type: "all",
            values: encoded.values ?? [],
            id: uuid(),
            databaseId: this.databaseId,
            statementId: this.statementId,
        }, {
            requestType: "all",
            sql: this.sql,
            ...encoded.metrics,
        });
        this.needsReset = false;
        return result;
    }
}
class ProxyDatabase {
    postMessage;
    options;
    statements = new Map();
    databaseId;
    directory;
    constructor(postMessage, options) {
        this.postMessage = postMessage;
        this.options = options;
    }
    async send(message, metrics) {
        const startedAt = performance.now();
        const protocol = getProtocol(this.options);
        const shouldProfile = Boolean(this.options?.profile || this.options?.onProfile);
        const requestType = metrics?.requestType ?? message.type;
        try {
            const response = await this.postMessage({
                ...message,
                protocol,
                profile: shouldProfile,
            });
            this.options?.onProfile?.({
                requestType,
                protocol,
                databaseId: this.databaseId,
                databaseDirectory: this.directory,
                sql: metrics?.sql ??
                    ("sql" in message && typeof message.sql === "string"
                        ? message.sql
                        : undefined),
                clientEncodeMs: metrics?.encodeMs ?? 0,
                clientRoundTripMs: performance.now() - startedAt,
                valueCount: metrics?.valueCount ?? 0,
                blobValueCount: metrics?.blobValueCount ?? 0,
                blobBytes: metrics?.blobBytes ?? 0,
                worker: response.timing,
            });
            return response.result;
        }
        catch (error) {
            const responseError = error;
            this.options?.onProfile?.({
                requestType,
                protocol,
                databaseId: this.databaseId,
                databaseDirectory: this.directory,
                sql: metrics?.sql ??
                    ("sql" in message && typeof message.sql === "string"
                        ? message.sql
                        : undefined),
                clientEncodeMs: metrics?.encodeMs ?? 0,
                clientRoundTripMs: performance.now() - startedAt,
                valueCount: metrics?.valueCount ?? 0,
                blobValueCount: metrics?.blobValueCount ?? 0,
                blobBytes: metrics?.blobBytes ?? 0,
                worker: responseError?.timing,
            });
            if (responseError?.type === "error") {
                throw new Error(responseError.message);
            }
            throw error;
        }
    }
    async init(directory) {
        this.databaseId = uuid();
        this.directory = directory;
        return this.send({
            type: "create",
            directory,
            pragmas: this.options?.pragmas,
            databaseId: this.databaseId,
            id: uuid(),
        });
    }
    async exec(sql) {
        return this.send({
            type: "exec",
            sql,
            id: uuid(),
            databaseId: this.databaseId,
        });
    }
    async prepare(sql, id) {
        if (id != null) {
            const prev = this.statements.get(id);
            if (prev) {
                await prev.reset();
                return prev;
            }
        }
        const statementId = await this.send({
            type: "prepare",
            sql,
            id: uuid(),
            databaseId: this.databaseId,
        });
        const statement = new ProxyStatement(this.send.bind(this), this.databaseId, statementId, sql, this.options);
        this.statements.set(statementId, statement);
        if (id != null) {
            this.statements.set(id, statement);
        }
        return statement;
    }
    async open() {
        return this.send({ type: "open", id: uuid(), databaseId: this.databaseId });
    }
    async close() {
        return this.send({
            type: "close",
            id: uuid(),
            databaseId: this.databaseId,
        });
    }
    async drop() {
        return this.send({
            type: "drop",
            id: uuid(),
            databaseId: this.databaseId,
        });
    }
    async status() {
        return this.send({
            type: "status",
            id: uuid(),
            databaseId: this.databaseId,
        });
    }
}
let initialized = undefined;
const init = async () => {
    if (initialized) {
        return initialized;
    }
    const worker = new Worker(new URL("/peerbit/sqlite3/sqlite3.worker.min.js", import.meta.url), { type: "module" });
    const resolvers = {};
    const postMessage = (message) => {
        const promise = new Promise((resolve, reject) => {
            resolvers[message.id] = { resolve, reject };
        });
        worker.postMessage(message);
        return promise.finally(() => delete resolvers[message.id]);
    };
    const isReady = pDefer();
    worker.onmessage = async (ev) => {
        const message = ev.data;
        if (message.type === "ready") {
            isReady.resolve();
            return;
        }
        const resolver = resolvers[message.id];
        if (!resolver) {
            return;
        }
        if (message.type === "error") {
            resolver.reject(message);
        }
        else {
            resolver.resolve(message);
        }
    };
    const create = async (directory, options) => {
        const db = new ProxyDatabase(postMessage, options);
        await isReady.promise;
        await db.init(directory);
        await db.open();
        return db;
    };
    return (initialized = {
        create,
        close: () => {
            initialized = undefined;
            worker.terminate();
        },
    });
};
const create = (directory, options) => {
    if (directory) {
        // persist the database
        return init().then((creator) => creator.create(directory, options));
    }
    return createDatabase(directory, options);
};
export { create };
//# sourceMappingURL=sqlite3.browser.js.map