//! Lance scalar index management
//!
//! Creates and manages scalar indexes on Lance dataset columns
//! to accelerate filter queries (WHERE clauses).
//!
//! ## Index Strategy
//!
//! Lance 2.0 supports scalar indexes via the `DatasetIndexExt` trait.
//! The engine automatically selects the optimal index structure (BTree/Bitmap)
//! based on column cardinality and data distribution.
//!
//! ## Indexed Columns
//!
//! - `id` — unique node identifier (high cardinality)
//! - `created_at` — timestamp for time-range queries
//! - `last_accessed_at` — timestamp for recency queries
//! - `importance` — float score for threshold filtering
//! - `access_count` — integer for frequency queries
//! - `content_type` — enum string for type filtering
//! - `sem_axis_0/1/2` — semantic axes for gate filtering

use lance::Dataset;

/// Definition of which columns should be indexed.
#[derive(Debug, Clone)]
pub struct ScalarIndexDef {
    /// Column name in the Lance dataset
    pub column: String,
    /// Description (for logging)
    pub description: String,
}

/// Default scalar index definitions for the MemoryNode schema.
pub fn default_scalar_indexes() -> Vec<ScalarIndexDef> {
    vec![
        ScalarIndexDef {
            column: "id".to_string(),
            description: "Node ID (unique, high cardinality)".to_string(),
        },
        ScalarIndexDef {
            column: "created_at".to_string(),
            description: "Creation timestamp for time-range queries".to_string(),
        },
        ScalarIndexDef {
            column: "last_accessed_at".to_string(),
            description: "Last access timestamp for recency queries".to_string(),
        },
        ScalarIndexDef {
            column: "importance".to_string(),
            description: "Importance score for threshold filtering".to_string(),
        },
        ScalarIndexDef {
            column: "access_count".to_string(),
            description: "Access count for frequency queries".to_string(),
        },
        ScalarIndexDef {
            column: "content_type".to_string(),
            description: "Content type enum (low cardinality)".to_string(),
        },
        ScalarIndexDef {
            column: "sem_axis_0".to_string(),
            description: "Semantic axis 0 for gate filtering".to_string(),
        },
        ScalarIndexDef {
            column: "sem_axis_1".to_string(),
            description: "Semantic axis 1 for gate filtering".to_string(),
        },
        ScalarIndexDef {
            column: "sem_axis_2".to_string(),
            description: "Semantic axis 2 for gate filtering".to_string(),
        },
    ]
}

/// Create scalar indexes on a Lance dataset.
///
/// Uses `DatasetIndexExt::create_index` with `IndexType::Scalar`.
/// Lance automatically selects the optimal index structure.
///
/// This is idempotent — if an index already exists on a column, it is skipped.
/// Returns the number of newly created indexes.
pub async fn ensure_scalar_indexes(
    dataset: &mut Dataset,
    indexes: &[ScalarIndexDef],
) -> Result<usize, String> {
    use lance_index::IndexType;
    use lance_index::scalar::ScalarIndexParams;
    use lance_index::traits::DatasetIndexExt;

    let mut created = 0;

    // Get schema field names to check column existence
    let field_names: Vec<String> = dataset
        .schema()
        .fields
        .iter()
        .map(|f| f.name.clone())
        .collect();

    // Load existing indexes once to check for duplicates
    let existing_indices = dataset.load_indices().await.unwrap_or_default();

    for idx_def in indexes {
        // Check if the column exists in the schema
        if !field_names.contains(&idx_def.column) {
            continue;
        }

        // Check if index already exists for this column
        let schema = dataset.schema();
        let already_indexed = existing_indices.iter().any(|idx| {
            idx.fields.iter().any(|&field_id| {
                schema.fields.iter().any(|f| {
                    f.id == field_id as i32 && f.name == idx_def.column
                })
            })
        });

        if already_indexed {
            continue;
        }

        // Create scalar index
        let params = ScalarIndexParams::default();
        let result = dataset
            .create_index(
                &[&idx_def.column],
                IndexType::Scalar,
                None,
                &params,
                true, // replace
            )
            .await;

        match result {
            Ok(_) => {
                created += 1;
            }
            Err(e) => {
                eprintln!(
                    "[aifastdb] Warning: Failed to create scalar index on '{}': {}",
                    idx_def.column, e
                );
            }
        }
    }

    Ok(created)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_scalar_indexes() {
        let indexes = default_scalar_indexes();
        assert!(indexes.len() >= 9);

        let columns: Vec<&str> = indexes.iter().map(|i| i.column.as_str()).collect();
        assert!(columns.contains(&"id"));
        assert!(columns.contains(&"created_at"));
        assert!(columns.contains(&"importance"));
        assert!(columns.contains(&"content_type"));
        assert!(columns.contains(&"sem_axis_0"));
        assert!(columns.contains(&"sem_axis_1"));
        assert!(columns.contains(&"sem_axis_2"));
    }
}
