//! Indexes for the storage engine.
//!
//! This module contains the definition of the indexes used by the storage
//! engine and provides a convenient conversion from less useful to more useful
//! indexes by default.
//!
//! Each storage engine can use this implementation or have their own
use nostr_rs_types::types::{filter::TagValue, Addr, Filter, Id, Kind};
use std::collections::HashSet;

/// Indexes for the storage engine.
#[derive(Debug, Clone)]
pub enum Index {
    /// Index by tag.
    Tag(String, HashSet<TagValue>),
    /// Index by id.
    Id(Vec<Id>),
    /// Index by author.
    Author(Vec<Id>),
    /// Index by kind.
    Kind(Vec<Kind>),
    /// Table scan.
    TableScan,
}

impl Index {
    /// Splits the index into a list of single indexes.
    pub fn split(self) -> Vec<SingleIndex> {
        match self {
            Index::Tag(tag, tags) => tags
                .into_iter()
                .map(|tag_value| SingleIndex::Tag(tag.clone(), tag_value))
                .collect(),
            Index::Id(ids) => ids.into_iter().map(SingleIndex::Id).collect(),
            Index::Author(authors) => authors.into_iter().map(SingleIndex::Author).collect(),
            Index::Kind(kinds) => kinds.into_iter().map(SingleIndex::Kind).collect(),
            Index::TableScan => vec![SingleIndex::AllUpdates],
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum SingleIndex {
    Author(Id),
    Id(Id),
    Kind(Kind),
    Tag(String, TagValue),
    AllUpdates,
}

impl From<&mut Filter> for Index {
    fn from(query: &mut Filter) -> Self {
        if !query.ids.is_empty() {
            Index::Id(std::mem::take(&mut query.ids))
        } else if !query.authors.is_empty() {
            Index::Author(std::mem::take(&mut query.authors))
        } else if !query.tags.is_empty() {
            // find the tag with fewer options to iterate over
            // and convert that tag as the index.
            // This block of code is slightly different, because only an index is needed, instead of a list of indexes
            // to iterate over, therefore the index with fewer options is selected.
            let mut tag_with_fewer_opts = query
                .tags
                .iter()
                .map(|(tag, values)| (tag.to_owned(), values.len()))
                .collect::<Vec<_>>();

            tag_with_fewer_opts.sort_by_key(|(_, len)| *len);
            let (key, _) = tag_with_fewer_opts.remove(0);

            if let Some(tags) = query.tags.remove(&key) {
                Index::Tag(key, tags)
            } else {
                Index::TableScan
            }
        } else if !query.kinds.is_empty() {
            Index::Kind(std::mem::take(&mut query.kinds))
        } else {
            Index::TableScan
        }
    }
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use super::*;
    use std::collections::HashMap;

    #[test]
    fn test_index_preference_ids_over_others() {
        let mut filter: Filter = serde_json::from_value(json!({
            "ids": ["42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5"],
            "authors": ["42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5"],
            "#r": ["foo"],
            "kinds": [1]
        }))
        .expect("filter");

        let index = Index::from(&mut filter);
        assert!(matches!(index, Index::Id(_)));
    }

    #[test]
    fn test_index_preference_authors_over_tags_and_kinds() {
        let mut filter: Filter = serde_json::from_value(json!({
            "authors": ["42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5"],
            "#r": ["foo"],
            "kinds": [1]
        }))
        .expect("filter");

        let index = Index::from(&mut filter);
        assert!(matches!(index, Index::Author(_)));
    }

    #[test]
    fn test_index_preference_tags_over_kinds() {
        let mut filter: Filter = serde_json::from_value(json!({
            "#r": ["foo"],
            "kinds": [1]
        }))
        .expect("filter");

        let index = Index::from(&mut filter);
        assert!(matches!(index, Index::Tag(_, _)));
    }

    #[test]
    fn test_index_preference_kinds_when_others_are_empty() {
        let mut filter: Filter = serde_json::from_value(json!({
            "kinds": [1]
        }))
        .expect("filter");

        let index = Index::from(&mut filter);
        assert!(matches!(index, Index::Kind(_)));
    }
}
