import { Dexie } from 'dexie';
import React from 'react';

function useObservable(observableFactory, arg2, arg3) {
    // Resolve vars from overloading variants of this function:
    var deps;
    var defaultResult;
    if (typeof observableFactory === 'function') {
        deps = arg2 || [];
        defaultResult = arg3;
    }
    else {
        deps = [];
        defaultResult = arg2;
    }
    // Create a ref that keeps the state we need
    var monitor = React.useRef({
        hasResult: false,
        result: defaultResult,
        error: null,
    });
    // We control when component should rerender. Make triggerUpdate
    // as examplified on React's docs at:
    // https://reactjs.org/docs/hooks-faq.html#is-there-something-like-forceupdate
    var _a = React.useReducer(function (x) { return x + 1; }, 0); _a[0]; var triggerUpdate = _a[1];
    // Memoize the observable based on deps
    var observable = React.useMemo(function () {
        // Make it remember previous subscription's default value when
        // resubscribing.
        var observable = typeof observableFactory === 'function'
            ? observableFactory()
            : observableFactory;
        if (!observable || typeof observable.subscribe !== 'function') {
            if (observableFactory === observable) {
                throw new TypeError("Given argument to useObservable() was neither a valid observable nor a function.");
            }
            else {
                throw new TypeError("Observable factory given to useObservable() did not return a valid observable.");
            }
        }
        if (!monitor.current.hasResult &&
            typeof window !== 'undefined' // Don't do this in SSR
        ) {
            // Optimize for BehaviorSubject and other observables implementing getValue():
            if (typeof observable.hasValue !== 'function' || observable.hasValue()) {
                if (typeof observable.getValue === 'function') {
                    monitor.current.result = observable.getValue();
                    monitor.current.hasResult = true;
                }
                else {
                    // Find out if the observable has a current value: try get it by subscribing and
                    // unsubscribing synchronously
                    var subscription = observable.subscribe(function (val) {
                        monitor.current.result = val;
                        monitor.current.hasResult = true;
                    });
                    // Unsubscribe directly. We only needed any synchronous value if it was possible.
                    if (typeof subscription === 'function') {
                        subscription();
                    }
                    else {
                        subscription.unsubscribe();
                    }
                }
            }
        }
        return observable;
    }, deps);
    // Integrate with react devtools:
    React.useDebugValue(monitor.current.result);
    // Subscribe to the observable
    React.useEffect(function () {
        var subscription = observable.subscribe(function (val) {
            var current = monitor.current;
            if (current.error !== null || current.result !== val) {
                current.error = null;
                current.result = val;
                current.hasResult = true;
                triggerUpdate();
            }
        }, function (err) {
            var current = monitor.current;
            if (current.error !== err) {
                current.error = err;
                triggerUpdate();
            }
        });
        return typeof subscription === 'function'
            ? subscription // Support observables that return unsubscribe directly
            : subscription.unsubscribe.bind(subscription);
    }, deps);
    // Throw if observable has emitted error so that
    // an ErrorBoundrary can catch it
    if (monitor.current.error)
        throw monitor.current.error;
    // Return the current result
    return monitor.current.result;
}

function useLiveQuery(querier, deps, defaultResult) {
    return useObservable(function () { return Dexie.liveQuery(querier); }, deps || [], defaultResult);
}

function usePermissions(firstArg, table, obj) {
    if (!firstArg)
        throw new TypeError("Invalid arguments to usePermissions(): undefined or null");
    var db;
    if (arguments.length >= 3) {
        if (!('transaction' in firstArg)) {
            // Using ducktyping instead of instanceof in case there are multiple Dexie modules in app.
            // First arg is  ensures first arg is a Dexie instance
            throw new TypeError("Invalid arguments to usePermission(db, table, obj): 1st arg must be a Dexie instance");
        }
        if (typeof table !== 'string')
            throw new TypeError("Invalid arguments to usePermission(db, table, obj): 2nd arg must be string");
        if (!obj || typeof obj !== 'object')
            throw new TypeError("Invalid arguments to usePermission(db, table, obj): 3rd arg must be an object");
        db = firstArg;
    }
    else {
        if (firstArg instanceof Dexie)
            throw new TypeError("Invalid arguments to usePermission(db, table, obj): Missing table and obj arguments.");
        if (typeof firstArg.table === 'function' &&
            typeof firstArg.db === 'object') {
            db = firstArg.db;
            obj = firstArg;
            table = firstArg.table();
        }
        else {
            throw new TypeError("Invalid arguments to usePermissions(). " +
                "Expected usePermissions(entity: DexieCloudEntity) or " +
                "usePermissions(db: Dexie, table: string, obj: DexieCloudObject)");
        }
    }
    if (!('cloud' in db))
        throw new Error("usePermissions() is only for Dexie Cloud but there's no dexie-cloud-addon active in given db.");
    if (!('permissions' in db.cloud))
        throw new Error("usePermissions() requires a newer version of dexie-cloud-addon. Please upgrade it.");
    return useObservable(
    // @ts-ignore
    function () { return db.cloud.permissions(obj, table); }, [obj.realmId, obj.owner, table]);
}

var gracePeriod = 100; // 100 ms = grace period to optimize for unload/reload scenarios
var fr = typeof FinalizationRegistry !== 'undefined' && new FinalizationRegistry(function (doc) {
    // If coming here, react effect never ran. This is a fallback cleanup mechanism.
    var DexieYProvider = Dexie['DexieYProvider'];
    if (DexieYProvider)
        DexieYProvider.release(doc);
});
function useDocument(doc) {
    var _a, _b;
    if (!fr)
        throw new TypeError('FinalizationRegistry not supported.');
    var providerRef = React.useRef(null);
    var DexieYProvider = Dexie['DexieYProvider'];
    if (!DexieYProvider) {
        throw new Error('DexieYProvider is not available. Make sure `y-dexie` is installed and imported.');
    }
    var unregisterToken = undefined;
    if (doc) {
        if (doc !== ((_a = providerRef.current) === null || _a === void 0 ? void 0 : _a.doc)) {
            providerRef.current = DexieYProvider.load(doc, { gracePeriod: gracePeriod });
            unregisterToken = Object.create(null);
            fr.register(providerRef, doc, unregisterToken);
        }
    }
    else if ((_b = providerRef.current) === null || _b === void 0 ? void 0 : _b.doc) {
        providerRef.current = null;
    }
    React.useEffect(function () {
        if (doc) {
            // Doc is set or changed. Unregister provider from FinalizationRegistry
            // and instead take over from here to release the doc when component is unmounted
            // or when doc is changed. What we're doing here is to avoid relying on FinalizationRegistry
            // in all the normal cases and instead rely on React's lifecycle to release the doc.
            // But there can be situations when react never calls this effect and therefore, we
            // need to rely on FinalizationRegistry to release the doc as a fallback.
            // We cannot wait with loading the document until the effect happens, because the doc
            // could have been destroyed in the meantime.
            if (unregisterToken)
                fr.unregister(unregisterToken);
            var provider = DexieYProvider.for(doc);
            if (provider) {
                return function () {
                    DexieYProvider.release(doc);
                };
            }
            else {
                // Maybe the doc was destroyed in the meantime.
                // Can not happen if React and FinalizationRegistry works as we expect them to.
                // Except if a user had called DexieYProvider.release() on the doc
                throw new Error("FATAL. DexieYProvider.release() has been called somewhere in application code, making us lose the document.");
            }
        }
    }, [doc, unregisterToken]);
    return providerRef.current;
}

export { useDocument, useLiveQuery, useObservable, usePermissions };
//# sourceMappingURL=dexie-react-hooks.mjs.map
