
use std::sync::Arc;
use pyo3::prelude::*;
use rustc_hash::FxHashMap;
use smol_str::SmolStr;

use crate::index::{Indexable, PyQueryExpr, core::{query::{query_ops::{QueryExpr, kwargs_to_query}}, structures::{hybrid_set::HybridSetOps, string_interner::INTERNER}}, types::StrId};
use crate::index::interfaces::filtered_index::FilteredIndex;
use crate::index::core::index::IndexAPI;
use crate::index::core::stored_item::StoredItem;
use crate::index::value::PyValue;

#[pyclass]
pub struct Index {
    pub inner: Arc<IndexAPI>
}

#[pymethods]
impl Index {
    #[new]
    pub fn new() -> Self {
        let index = IndexAPI::new(None);
        Self {
            inner: Arc::new(index)
        }
    }

    pub fn collect(&self, py: Python) -> PyResult<Vec<Py<Indexable>>> {
        self.inner.collect(py)
    }

    #[pyo3(signature = (**kwargs))]
    pub fn reduced<'py>(
        &self,
        py: Python,
        kwargs: Option<FxHashMap<String, pyo3::Bound<'py, PyAny>>>,
    ) -> PyResult<FilteredIndex> {
        let eq_query = kwargs_to_query(kwargs);
        py.allow_threads(move || {
            Ok(self.inner.reduced(eq_query))
        })
    }

    #[pyo3(signature = (**kwargs))]
    pub fn reduce<'py>(
        &self,
        py: Python,
        kwargs: Option<FxHashMap<String, pyo3::Bound<'py, PyAny>>>,
    ) -> PyResult<()> {
        let eq_query = kwargs_to_query(kwargs);
        py.allow_threads(move || {
            self.inner.reduce(eq_query);
        });
        Ok(())
    }

    #[pyo3(signature = (**kwargs))]
    pub fn get_by_attribute<'py>(
        &self,
        py: Python,
        kwargs: Option<FxHashMap<String, pyo3::Bound<'py, PyAny>>>,
    ) -> PyResult<Vec<Py<Indexable>>> {
        let eq_query = kwargs_to_query(kwargs);
        py.allow_threads(move || {
            self.inner.reduced(eq_query)
        }).collect(py)
    }

    pub fn add_object_many(&self, py: Python, objs: Vec<PyRef<Indexable>>) -> PyResult<()> {
        
        let raw_objs: Vec<(Indexable, Py<Indexable>)> = objs.into_iter().map(|obj| {
            (
                Indexable::from_py_ref(&obj, py),
                obj.into_pyobject(py).unwrap().unbind()
            )
        }).collect();

        py.allow_threads(|| {
            let weak_index = Arc::downgrade(&self.inner);
            self.inner.add_object_many(weak_index, raw_objs);
        });

        Ok(())

    }

    pub fn add_object(&self, py: Python, py_ref: PyRef<Indexable>) -> PyResult<()> {

        let rust_handle = Arc::new(Indexable::from_py_ref(&py_ref, py));
        let py_handle = Arc::new(py_ref.into_pyobject(py)?.unbind());

        py.allow_threads(move ||{
            let weak_index = Arc::downgrade(&self.inner);
            rust_handle.add_index(weak_index.clone());
            let stored_item = StoredItem::new(py_handle, rust_handle.clone());
            // i dont like this clone - need to remove
            let py_val_hashmap = rust_handle.get_py_values();
            self.inner.add_object(weak_index, rust_handle.id, stored_item, py_val_hashmap);
        });


        Ok(())
    }

    pub fn reduced_query(
        &self,
        py: Python,
        query: PyQueryExpr,
    ) -> PyResult<FilteredIndex> {
        py.allow_threads(move || {
            Ok(self.inner.reduced_query(query))
        })
    }

    pub fn union_with(&self, py: Python, other: &Index) -> PyResult<()>{
        py.allow_threads(|| {
            self.inner.union_with(&other.inner)
        })
    }

}