import { useCallback, useEffect, useState } from 'react';

import type {
    MangoQuery,
    RxCollection,
    RxDocument,
} from '../../../types/index.d.ts';
import { isRxCollection } from '../../../rx-collection.ts';
import { useRxCollection } from './use-rx-collection.ts';
import { newRxError } from '../../../rx-error.ts';

export type UseRxQueryOptions<
    RxDocumentType = any,
    OrmMethods = {},
    StaticMethods = {},
    InstanceCreationOptions = {},
    Reactivity = unknown,
> = {
    collection:
    | string
    | RxCollection<
        RxDocumentType,
        OrmMethods,
        StaticMethods,
        InstanceCreationOptions,
        Reactivity
    >;
    query: MangoQuery<RxDocumentType>;
};

export type UseRxQueryResult<RxDocumentType = any, OrmMethods = {}> = {
    results: RxDocument<RxDocumentType, OrmMethods>[];
    loading: boolean;
    error: string | null;
};

/**
 * React hook to query an RxDB collection with Mango queries.
 *
 * @param {UseRxQueryOptions<RxDocumentType, OrmMethods, StaticMethods, InstanceCreationOptions, Reactivity>} options - Options for the query.
 * @param {string|RxCollection} options.collection - The collection name or instance to query.
 * @param {MangoQuery<RxDocumentType>} options.query - The Mango query to execute.
 * @param {boolean} [options.live] - Whether to subscribe to live query updates.
 *
 * @returns {UseRxQueryResult<RxDocumentType, OrmMethods>} The query result, loading state, and error.
 */
export function useRxQueryBase<
    RxDocumentType = any,
    OrmMethods = {},
    StaticMethods = {},
    InstanceCreationOptions = {},
    Reactivity = unknown,
>({
    collection,
    query,
    live,
}: UseRxQueryOptions<
    RxDocumentType,
    OrmMethods,
    StaticMethods,
    InstanceCreationOptions,
    Reactivity
> & { live: boolean }): UseRxQueryResult<RxDocumentType, OrmMethods> {
    const [results, setResults] = useState(
        [] as RxDocument<RxDocumentType, OrmMethods>[],
    );
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState<string | null>(null);

    let dbCollection: RxCollection<
        RxDocumentType,
        OrmMethods,
        StaticMethods,
        InstanceCreationOptions,
        Reactivity
    > | null;
    if (typeof collection === 'string') {
        dbCollection = useRxCollection<
            RxDocumentType,
            OrmMethods,
            StaticMethods,
            InstanceCreationOptions,
            Reactivity
        >(collection);
    } else {
        if (!isRxCollection(collection)) {
            throw newRxError('R3', {
                collection
            });
        }
        dbCollection = collection;
    }
    (window as any).collection = dbCollection;

    const emitResults = (res: RxDocument<RxDocumentType, OrmMethods>[]) => {
        setResults(res);
        if (loading) {
            setLoading(false);
        }
    };

    const emitError = (e: Error) => {
        setError(e.message);
        if (loading) {
            setLoading(false);
        }
    };

    const runQuery = useCallback(async () => {
        if (dbCollection == null) {
            return;
        }
        setError(null);
        setLoading(loading);
        const rxQuery = dbCollection.find(query);
        if (live) {
            const subscription = rxQuery.$.subscribe({
                next: (res: RxDocument<RxDocumentType, OrmMethods>[]) => emitResults(res),
                error: (err: Error) => emitError(err),
            });

            return () => {
                subscription.unsubscribe();
            };
        } else {
            try {
                emitResults(await rxQuery.exec());
            } catch (e) {
                emitError(e as Error);
            }
        }
    }, [dbCollection, query]);

    useEffect(() => {
        if (collection == null) {
            return;
        }

        runQuery();
    }, [runQuery]);

    return { results, loading, error };
}



export function useRxQuery<
    RxDocumentType = any,
    OrmMethods = {},
    StaticMethods = {},
    InstanceCreationOptions = {},
    Reactivity = unknown,
>({
    collection,
    query,
}: UseRxQueryOptions<
    RxDocumentType,
    OrmMethods,
    StaticMethods,
    InstanceCreationOptions,
    Reactivity
>): UseRxQueryResult<RxDocumentType, OrmMethods> {
    return useRxQueryBase({
        collection,
        query,
        live: false
    });
}
