#![allow(deprecated)]

use_prelude!();

use std::sync::Weak;

use ffi_sdk::COrderByParam;

use super::event::CollectionsEvent;
use crate::{
    error::DittoError, store::collection::pending_cursor_operation::*, subscription::Subscription,
};

trait_alias! {
    /// A closure which is called on each event relating to changes in the known
    /// about collections.
    ///
    /// Ditto may call this handler in parallel such as when a second invocation
    /// happens before the first invocation has completed. This typically occurs
    /// when the closure makes slow, external calls.
    pub
    trait CollectionsEventHandler =
        FnMut(CollectionsEvent) // callable sequentially
        + Send // can be dropped in another thread
        + 'static // cannot dangle
}

/// Use [`ditto.store().collections()`] to query information about Ditto collections themselves.
///
/// They allow chaining of further collections-related functions. You can either call `exec` on the
/// object to get an array of `Collection`s as an immediate return value, or you can establish
/// either a live query or a subscription, which both work over time.
///
/// A live query, established by calling `observe_local`, will notify you every time there's a
/// change in the collections that the device knows about.
///
/// A subscription, established by calling `subscribe`, will act as a signal to other peers that
/// the device connects to that you would like to receive updates from them about the collections
/// that they know about.
///
/// Typically, an app would set up a `subscribe` in some part of the application which is long-lived
/// to ensure the device receives updates from the mesh. These updates will be automatically
/// received and written into the local store. Elsewhere, where you need to use this data, an
/// `observe_local` can be used to notify you of the data, and all subsequent changes to the data.
///
/// [`ditto.store().collections()`]: crate::store::Store::collections
#[deprecated(
    since = "4.14.4",
    note = "Will be removed in SDK 5.0. Use DQL queries instead"
)]
pub struct PendingCollectionsOperation<'order_by> {
    pub(super) ditto: Weak<BoxedDitto>,
    pub(super) pending_cursor_operation: PendingCursorOperation<'order_by>,
}

impl<'order_by> PendingCollectionsOperation<'order_by> {
    pub(crate) fn new(ditto: Weak<BoxedDitto>) -> Self {
        #[allow(deprecated)]
        let pending_cursor_operation =
            PendingCursorOperation::new(ditto.clone(), char_p::new("__collections"), "true", None);
        Self {
            ditto,
            pending_cursor_operation,
        }
    }

    /// Limit the number of documents that get returned when querying a
    /// collection for matching documents.
    pub fn limit(&mut self, limit: i32) -> &mut Self {
        self.pending_cursor_operation.limit(limit);
        self
    }

    /// Offset the resulting set of matching documents. This is useful if you
    /// aren’t interested in the first N matching documents for one reason
    /// or another. For example, you might already have queried the
    /// collection and obtained the first 20 matching documents and so you might
    /// want to run the same query as you did previously but ignore the first 20
    /// matching documents, and that is where you would use offset.
    pub fn offset(&mut self, offset: u32) -> &mut Self {
        self.pending_cursor_operation.offset(offset);
        self
    }

    // FIXME: To bring this in line with the other SDKs this should accept a single
    // "order_by" expression, which should then be added to the `order_by` vec
    /// Sort the documents that match the query provided in the preceding
    /// find-like function call.
    ///
    /// Documents that are missing the field to sort by will appear at
    /// the beginning of the results when sorting in ascending order.
    pub fn sort(&mut self, sort_param: Vec<COrderByParam<'order_by>>) -> &mut Self {
        self.pending_cursor_operation.sort(sort_param);
        self
    }

    /// Execute the query generated by the preceding function chaining and
    /// return the list of matching documents. This occurs immediately.
    pub fn exec(&self) -> Result<Vec<Collection>, DittoError> {
        let docs = self.pending_cursor_operation.exec()?;
        Ok(PendingCollectionsOperation::collections_from_docs(
            docs,
            self.ditto.clone(),
        ))
    }

    /// Enables you to subscribe to changes that occur on a collection. Having a subscription acts
    /// as a signal to others that you are interested in receiving updates when local or remote
    /// changes are made to documents that match the query generated by the chain of operations that
    /// precedes the call to subscribe. The returned [`Subscription`] object must be kept in scope
    /// for as long as you want to keep receiving updates.
    pub fn subscribe(&self) -> Subscription {
        self.pending_cursor_operation.subscribe()
    }

    /// Enables you to listen for changes that occur on a collection. This won’t subscribe to
    /// receive changes made remotely by others and so it will only fire updates when a local change
    /// is made. If you want to receive remotely performed updates as well then also call subscribe
    /// with the relevant query. The returned [`LiveQuery`](crate::prelude::Subscription) object
    /// must be kept in scope for as long as you want the provided `Handler` to be called when an
    /// update occurs.
    pub fn observe_local<Handler>(&self, handler: Handler) -> Result<LiveQuery, DittoError>
    where
        Handler: CollectionsEventHandler,
    {
        let ditto = self.ditto.clone();
        let mut handler = handler;
        let handler = move |docs: Vec<BoxedDocument>, event: LiveQueryEvent| {
            let colls_ev = PendingCollectionsOperation::collections_event_from_docs_and_event(
                docs,
                event,
                ditto.clone(),
            );
            handler(colls_ev);
        };
        self.pending_cursor_operation.observe_local(handler)
    }

    fn collections_from_docs(docs: Vec<BoxedDocument>, ditto: Weak<BoxedDitto>) -> Vec<Collection> {
        docs.iter()
            .filter_map(|doc| {
                doc.get::<String>("name")
                    .ok()
                    .map(|coll_name| Collection::new(ditto.clone(), coll_name))
            })
            .collect()
    }

    fn collections_event_from_docs_and_event(
        docs: Vec<BoxedDocument>,
        event: LiveQueryEvent,
        ditto: Weak<BoxedDitto>,
    ) -> CollectionsEvent {
        match event {
            #[allow(deprecated)]
            LiveQueryEvent::Initial => CollectionsEvent::initial(
                PendingCollectionsOperation::collections_from_docs(docs, ditto),
            ),
            #[allow(deprecated)]
            LiveQueryEvent::Update {
                old_documents,
                insertions,
                deletions,
                updates,
                moves,
            } => CollectionsEvent::update(
                PendingCollectionsOperation::collections_from_docs(docs, ditto.clone()),
                PendingCollectionsOperation::collections_from_docs(old_documents, ditto),
                insertions,
                deletions,
                updates,
                moves,
            ),
        }
    }
}
