//! FilterExpr — AST-based filter expression tree
//!
//! Provides a recursive expression tree that supports complex filter
//! combinations: AND, OR, NOT, comparison operators, IN, LIKE, BETWEEN,
//! IsNull, Contains, etc.
//!
//! ## Design Goals
//!
//! 1. **Expressive**: Support arbitrary nesting of AND/OR/NOT
//! 2. **Lance push-down**: Convert to Lance SQL predicates for columnar push-down
//! 3. **Backward compatible**: Existing `QueryFilter` can be converted to `FilterExpr`
//! 4. **Evaluable in-memory**: Can evaluate against `MemoryNode` for non-Lance backends
//!
//! ## Example
//!
//! ```rust,no_run
//! use aifastdb_core::storage::filter_expr::{FilterExpr, FilterField};
//!
//! // importance >= 0.5 AND (tag = "rust" OR tag = "database")
//! let expr = FilterExpr::and(vec![
//!     FilterExpr::gte(FilterField::Importance, 0.5.into()),
//!     FilterExpr::or(vec![
//!         FilterExpr::tag_eq("rust"),
//!         FilterExpr::tag_eq("database"),
//!     ]),
//! ]);
//! ```

use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::types::{ContentType, MemoryNode};

// ═══════════════════════════════════════════════════════════════════
// FilterField — which column / field the predicate targets
// ═══════════════════════════════════════════════════════════════════

/// Identifies which field of a `MemoryNode` a filter predicate targets.
///
/// For Lance push-down, each variant maps to a Lance column name.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum FilterField {
    /// Node ID (Lance column: `id`, STRING)
    Id,
    /// Content text (Lance column: `content`, STRING)
    Content,
    /// Content type enum (Lance column: `content_type`, STRING)
    ContentType,
    /// Created-at timestamp in ms (Lance column: `created_at`, INT64)
    CreatedAt,
    /// Last-accessed-at timestamp in ms (Lance column: `last_accessed_at`, INT64)
    LastAccessedAt,
    /// Access count (Lance column: `access_count`, UINT32)
    AccessCount,
    /// Importance score (Lance column: `importance`, FLOAT64)
    Importance,
    /// Parent ID (Lance column: `parent_id`, STRING, nullable)
    ParentId,
    /// Tag membership (special: uses tag index / tags JSON column)
    Tag,
    /// Semantic axis 0 (Lance column: `sem_axis_0`, FLOAT32)
    SemanticAxis0,
    /// Semantic axis 1 (Lance column: `sem_axis_1`, FLOAT32)
    SemanticAxis1,
    /// Semantic axis 2 (Lance column: `sem_axis_2`, FLOAT32)
    SemanticAxis2,
    /// Metadata field (dot-notation key, e.g. `"author"`)
    Metadata(String),
}

impl FilterField {
    /// Convert to Lance SQL column name for predicate push-down.
    pub fn to_lance_column(&self) -> String {
        match self {
            Self::Id => "id".to_string(),
            Self::Content => "content".to_string(),
            Self::ContentType => "content_type".to_string(),
            Self::CreatedAt => "created_at".to_string(),
            Self::LastAccessedAt => "last_accessed_at".to_string(),
            Self::AccessCount => "access_count".to_string(),
            Self::Importance => "importance".to_string(),
            Self::ParentId => "parent_id".to_string(),
            Self::Tag => "tags_json".to_string(),
            Self::SemanticAxis0 => "sem_axis_0".to_string(),
            Self::SemanticAxis1 => "sem_axis_1".to_string(),
            Self::SemanticAxis2 => "sem_axis_2".to_string(),
            Self::Metadata(key) => format!("metadata_json.{}", key),
        }
    }
}

// ═══════════════════════════════════════════════════════════════════
// FilterValue — typed values for comparison
// ═══════════════════════════════════════════════════════════════════

/// A typed value used in filter comparisons.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum FilterValue {
    /// String value
    String(String),
    /// Integer value (i64)
    Int(i64),
    /// Unsigned integer (u64)
    UInt(u64),
    /// Float value (f64)
    Float(f64),
    /// Boolean value
    Bool(bool),
    /// Null sentinel
    Null,
    /// List of values (for IN operator)
    List(Vec<FilterValue>),
}

impl FilterValue {
    /// Convert to Lance SQL literal string.
    pub fn to_lance_literal(&self) -> String {
        match self {
            Self::String(s) => format!("'{}'", s.replace('\'', "''")),
            Self::Int(i) => i.to_string(),
            Self::UInt(u) => u.to_string(),
            Self::Float(f) => format!("{:.10}", f),
            Self::Bool(b) => if *b { "TRUE".to_string() } else { "FALSE".to_string() },
            Self::Null => "NULL".to_string(),
            Self::List(items) => {
                let parts: Vec<String> = items.iter().map(|v| v.to_lance_literal()).collect();
                format!("({})", parts.join(", "))
            }
        }
    }

    /// Try to extract as f64 for numeric comparisons.
    pub fn as_f64(&self) -> Option<f64> {
        match self {
            Self::Int(i) => Some(*i as f64),
            Self::UInt(u) => Some(*u as f64),
            Self::Float(f) => Some(*f),
            _ => None,
        }
    }

    /// Try to extract as string.
    pub fn as_str(&self) -> Option<&str> {
        match self {
            Self::String(s) => Some(s.as_str()),
            _ => None,
        }
    }

    /// Try to extract as i64.
    pub fn as_i64(&self) -> Option<i64> {
        match self {
            Self::Int(i) => Some(*i),
            Self::UInt(u) => Some(*u as i64),
            Self::Float(f) => Some(*f as i64),
            _ => None,
        }
    }
}

// Convenient From impls
impl From<&str> for FilterValue {
    fn from(s: &str) -> Self { Self::String(s.to_string()) }
}
impl From<String> for FilterValue {
    fn from(s: String) -> Self { Self::String(s) }
}
impl From<i64> for FilterValue {
    fn from(v: i64) -> Self { Self::Int(v) }
}
impl From<i32> for FilterValue {
    fn from(v: i32) -> Self { Self::Int(v as i64) }
}
impl From<u64> for FilterValue {
    fn from(v: u64) -> Self { Self::UInt(v) }
}
impl From<u32> for FilterValue {
    fn from(v: u32) -> Self { Self::UInt(v as u64) }
}
impl From<f64> for FilterValue {
    fn from(v: f64) -> Self { Self::Float(v) }
}
impl From<f32> for FilterValue {
    fn from(v: f32) -> Self { Self::Float(v as f64) }
}
impl From<bool> for FilterValue {
    fn from(v: bool) -> Self { Self::Bool(v) }
}

// ═══════════════════════════════════════════════════════════════════
// FilterExpr — the recursive AST
// ═══════════════════════════════════════════════════════════════════

/// A recursive filter expression tree.
///
/// Leaf nodes are comparison predicates (Eq, Gt, Lt, etc.),
/// interior nodes are logical operators (And, Or, Not).
///
/// This structure can be:
/// 1. Evaluated in-memory against `MemoryNode`
/// 2. Compiled to Lance SQL predicate string for push-down
/// 3. Constructed from the legacy `QueryFilter` for backward compatibility
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum FilterExpr {
    // ── Logical combinators ──────────────────────────────────────
    /// All children must be true
    And(Vec<FilterExpr>),
    /// At least one child must be true
    Or(Vec<FilterExpr>),
    /// Negation
    Not(Box<FilterExpr>),

    // ── Comparison predicates ────────────────────────────────────
    /// field == value
    Eq(FilterField, FilterValue),
    /// field != value
    Ne(FilterField, FilterValue),
    /// field > value
    Gt(FilterField, FilterValue),
    /// field >= value
    Gte(FilterField, FilterValue),
    /// field < value
    Lt(FilterField, FilterValue),
    /// field <= value
    Lte(FilterField, FilterValue),

    // ── Set / range predicates ───────────────────────────────────
    /// field IN (v1, v2, ...)
    In(FilterField, Vec<FilterValue>),
    /// field NOT IN (v1, v2, ...)
    NotIn(FilterField, Vec<FilterValue>),
    /// field BETWEEN low AND high (inclusive)
    Between(FilterField, FilterValue, FilterValue),

    // ── String predicates ────────────────────────────────────────
    /// field LIKE pattern (SQL LIKE: % = any, _ = single char)
    Like(FilterField, String),
    /// field contains substring (in-memory only; Lance: LIKE '%sub%')
    Contains(FilterField, String),
    /// field starts with prefix
    StartsWith(FilterField, String),
    /// field ends with suffix
    EndsWith(FilterField, String),

    // ── Null checks ──────────────────────────────────────────────
    /// field IS NULL
    IsNull(FilterField),
    /// field IS NOT NULL
    IsNotNull(FilterField),

    // ── Special: always true / always false ──────────────────────
    /// Matches everything (no filter)
    True,
    /// Matches nothing
    False,
}

impl FilterExpr {
    // ── Convenience constructors ─────────────────────────────────

    /// AND combinator (flattens nested ANDs).
    pub fn and(children: Vec<FilterExpr>) -> Self {
        let mut flat = Vec::new();
        for child in children {
            match child {
                Self::And(inner) => flat.extend(inner),
                Self::True => {} // skip True in AND
                other => flat.push(other),
            }
        }
        match flat.len() {
            0 => Self::True,
            1 => flat.into_iter().next().unwrap(),
            _ => Self::And(flat),
        }
    }

    /// OR combinator (flattens nested ORs).
    pub fn or(children: Vec<FilterExpr>) -> Self {
        let mut flat = Vec::new();
        for child in children {
            match child {
                Self::Or(inner) => flat.extend(inner),
                Self::False => {} // skip False in OR
                other => flat.push(other),
            }
        }
        match flat.len() {
            0 => Self::False,
            1 => flat.into_iter().next().unwrap(),
            _ => Self::Or(flat),
        }
    }

    /// NOT combinator (simplifies double negation).
    pub fn not(expr: FilterExpr) -> Self {
        match expr {
            Self::Not(inner) => *inner, // double negation elimination
            Self::True => Self::False,
            Self::False => Self::True,
            other => Self::Not(Box::new(other)),
        }
    }

    // ── Comparison shortcuts ─────────────────────────────────────

    pub fn eq(field: FilterField, value: FilterValue) -> Self { Self::Eq(field, value) }
    pub fn ne(field: FilterField, value: FilterValue) -> Self { Self::Ne(field, value) }
    pub fn gt(field: FilterField, value: FilterValue) -> Self { Self::Gt(field, value) }
    pub fn gte(field: FilterField, value: FilterValue) -> Self { Self::Gte(field, value) }
    pub fn lt(field: FilterField, value: FilterValue) -> Self { Self::Lt(field, value) }
    pub fn lte(field: FilterField, value: FilterValue) -> Self { Self::Lte(field, value) }
    pub fn between(field: FilterField, low: FilterValue, high: FilterValue) -> Self {
        Self::Between(field, low, high)
    }
    pub fn is_in(field: FilterField, values: Vec<FilterValue>) -> Self { Self::In(field, values) }
    pub fn contains(field: FilterField, substr: impl Into<String>) -> Self {
        Self::Contains(field, substr.into())
    }
    pub fn like(field: FilterField, pattern: impl Into<String>) -> Self {
        Self::Like(field, pattern.into())
    }
    pub fn starts_with(field: FilterField, prefix: impl Into<String>) -> Self {
        Self::StartsWith(field, prefix.into())
    }
    pub fn ends_with(field: FilterField, suffix: impl Into<String>) -> Self {
        Self::EndsWith(field, suffix.into())
    }

    // ── Domain-specific shortcuts ────────────────────────────────

    /// Filter by tag equality (Tag == "value").
    pub fn tag_eq(tag: impl Into<String>) -> Self {
        Self::Eq(FilterField::Tag, FilterValue::String(tag.into()))
    }

    /// Filter by any of the given tags (OR combination).
    pub fn tag_any(tags: &[&str]) -> Self {
        Self::or(tags.iter().map(|t| Self::tag_eq(*t)).collect())
    }

    /// Filter by all of the given tags (AND combination).
    pub fn tag_all(tags: &[&str]) -> Self {
        Self::and(tags.iter().map(|t| Self::tag_eq(*t)).collect())
    }

    /// Filter by content type.
    pub fn content_type(ct: ContentType) -> Self {
        Self::Eq(FilterField::ContentType, FilterValue::String(format!("{:?}", ct)))
    }

    /// Filter by importance >= threshold.
    pub fn min_importance(threshold: f64) -> Self {
        Self::Gte(FilterField::Importance, FilterValue::Float(threshold))
    }

    /// Filter by created_at time range [start, end].
    pub fn time_range(start_ms: i64, end_ms: i64) -> Self {
        Self::Between(
            FilterField::CreatedAt,
            FilterValue::Int(start_ms),
            FilterValue::Int(end_ms),
        )
    }

    /// Filter by parent ID.
    pub fn parent_id(id: impl Into<String>) -> Self {
        Self::Eq(FilterField::ParentId, FilterValue::String(id.into()))
    }

    /// Semantic axis 0 range filter.
    pub fn sem_axis_0_range(min: f32, max: f32) -> Self {
        Self::Between(FilterField::SemanticAxis0, FilterValue::Float(min as f64), FilterValue::Float(max as f64))
    }

    /// Semantic axis 1 range filter.
    pub fn sem_axis_1_range(min: f32, max: f32) -> Self {
        Self::Between(FilterField::SemanticAxis1, FilterValue::Float(min as f64), FilterValue::Float(max as f64))
    }

    /// Semantic axis 2 range filter.
    pub fn sem_axis_2_range(min: f32, max: f32) -> Self {
        Self::Between(FilterField::SemanticAxis2, FilterValue::Float(min as f64), FilterValue::Float(max as f64))
    }

    /// Metadata key == value.
    pub fn metadata_eq(key: impl Into<String>, value: impl Into<String>) -> Self {
        Self::Eq(
            FilterField::Metadata(key.into()),
            FilterValue::String(value.into()),
        )
    }

    // ── Check if empty (matches everything) ──────────────────────

    /// Returns true if this expression matches everything.
    pub fn is_always_true(&self) -> bool {
        matches!(self, Self::True)
    }

    // ═══════════════════════════════════════════════════════════════
    // In-memory evaluation against MemoryNode
    // ═══════════════════════════════════════════════════════════════

    /// Evaluate this filter expression against a `MemoryNode`.
    ///
    /// Returns `true` if the node matches the filter.
    pub fn evaluate(&self, node: &MemoryNode) -> bool {
        match self {
            // ── Logical ──
            Self::And(children) => children.iter().all(|c| c.evaluate(node)),
            Self::Or(children) => children.iter().any(|c| c.evaluate(node)),
            Self::Not(inner) => !inner.evaluate(node),
            Self::True => true,
            Self::False => false,

            // ── Comparison ──
            Self::Eq(field, value) => self.eval_cmp(node, field, value, |a, b| a == b, |a, b| a == b),
            Self::Ne(field, value) => self.eval_cmp(node, field, value, |a, b| a != b, |a, b| a != b),
            Self::Gt(field, value) => self.eval_cmp(node, field, value, |a, b| a > b, |a, b| a > b),
            Self::Gte(field, value) => self.eval_cmp(node, field, value, |a, b| a >= b, |a, b| a >= b),
            Self::Lt(field, value) => self.eval_cmp(node, field, value, |a, b| a < b, |a, b| a < b),
            Self::Lte(field, value) => self.eval_cmp(node, field, value, |a, b| a <= b, |a, b| a <= b),

            // ── Set / Range ──
            Self::In(field, values) => {
                let node_val = self.extract_field_str(node, field);
                let node_num = self.extract_field_f64(node, field);
                values.iter().any(|v| {
                    if let (Some(ref nv), Some(sv)) = (&node_val, v.as_str()) {
                        nv == sv
                    } else if let (Some(nn), Some(sn)) = (node_num, v.as_f64()) {
                        (nn - sn).abs() < 1e-10
                    } else {
                        false
                    }
                })
            }
            Self::NotIn(field, values) => {
                !Self::In(field.clone(), values.clone()).evaluate(node)
            }
            Self::Between(field, low, high) => {
                if let Some(val) = self.extract_field_f64(node, field) {
                    let lo = low.as_f64().unwrap_or(f64::NEG_INFINITY);
                    let hi = high.as_f64().unwrap_or(f64::INFINITY);
                    val >= lo && val <= hi
                } else {
                    false
                }
            }

            // ── String predicates ──
            Self::Like(field, pattern) => {
                if let Some(val) = self.extract_field_str(node, field) {
                    sql_like_match(&val, pattern)
                } else {
                    false
                }
            }
            Self::Contains(field, substr) => {
                if let Some(val) = self.extract_field_str(node, field) {
                    val.contains(substr.as_str())
                } else {
                    false
                }
            }
            Self::StartsWith(field, prefix) => {
                if let Some(val) = self.extract_field_str(node, field) {
                    val.starts_with(prefix.as_str())
                } else {
                    false
                }
            }
            Self::EndsWith(field, suffix) => {
                if let Some(val) = self.extract_field_str(node, field) {
                    val.ends_with(suffix.as_str())
                } else {
                    false
                }
            }

            // ── Null checks ──
            Self::IsNull(field) => self.is_field_null(node, field),
            Self::IsNotNull(field) => !self.is_field_null(node, field),
        }
    }

    // ── Helper: extract string representation of a field ─────────

    fn extract_field_str(&self, node: &MemoryNode, field: &FilterField) -> Option<String> {
        match field {
            FilterField::Id => Some(node.id.clone()),
            FilterField::Content => Some(node.content.clone()),
            FilterField::ContentType => Some(format!("{:?}", node.content_type)),
            FilterField::ParentId => node.parent_id.clone(),
            FilterField::Tag => {
                // For Tag field, treat as the joined tags string
                Some(node.tags.join(","))
            }
            FilterField::Metadata(key) => {
                node.metadata.get(key).map(|v| match v {
                    Value::String(s) => s.clone(),
                    other => other.to_string(),
                })
            }
            _ => None, // Numeric fields are not strings
        }
    }

    // ── Helper: extract numeric representation of a field ────────

    fn extract_field_f64(&self, node: &MemoryNode, field: &FilterField) -> Option<f64> {
        match field {
            FilterField::CreatedAt => Some(node.created_at as f64),
            FilterField::LastAccessedAt => Some(node.last_accessed_at as f64),
            FilterField::AccessCount => Some(node.access_count as f64),
            FilterField::Importance => Some(node.computed_importance()),
            FilterField::SemanticAxis0 => node.semantic_axes.as_ref().map(|a| a.axis_0 as f64),
            FilterField::SemanticAxis1 => node.semantic_axes.as_ref().map(|a| a.axis_1 as f64),
            FilterField::SemanticAxis2 => node.semantic_axes.as_ref().map(|a| a.axis_2 as f64),
            _ => None,
        }
    }

    // ── Helper: check if field is null ───────────────────────────

    fn is_field_null(&self, node: &MemoryNode, field: &FilterField) -> bool {
        match field {
            FilterField::ParentId => node.parent_id.is_none(),
            FilterField::SemanticAxis0 | FilterField::SemanticAxis1 | FilterField::SemanticAxis2 => {
                node.semantic_axes.is_none()
            }
            FilterField::Metadata(key) => !node.metadata.contains_key(key),
            _ => false, // Required fields are never null
        }
    }

    // ── Helper: generic comparison ───────────────────────────────

    fn eval_cmp(
        &self,
        node: &MemoryNode,
        field: &FilterField,
        value: &FilterValue,
        str_cmp: impl Fn(&str, &str) -> bool,
        num_cmp: impl Fn(f64, f64) -> bool,
    ) -> bool {
        // Special case: Tag field — check membership
        if matches!(field, FilterField::Tag) {
            if let FilterValue::String(tag) = value {
                let has_tag = node.tags.iter().any(|t| t == tag);
                return str_cmp(if has_tag { tag.as_str() } else { "" }, tag.as_str());
            }
            return false;
        }

        // Try numeric comparison first
        if let (Some(node_num), Some(val_num)) = (self.extract_field_f64(node, field), value.as_f64()) {
            return num_cmp(node_num, val_num);
        }

        // Try string comparison
        if let (Some(node_str), Some(val_str)) = (self.extract_field_str(node, field), value.as_str()) {
            return str_cmp(&node_str, val_str);
        }

        false
    }

    // ═══════════════════════════════════════════════════════════════
    // Lance SQL predicate generation
    // ═══════════════════════════════════════════════════════════════

    /// Convert this filter expression to a Lance SQL predicate string.
    ///
    /// Returns `None` if the expression is `True` (no filter needed).
    /// Returns `Some("FALSE")` if the expression is `False`.
    ///
    /// ## Example Output
    ///
    /// ```text
    /// "importance >= 0.5 AND (tags_json LIKE '%rust%' OR tags_json LIKE '%database%')"
    /// ```
    pub fn to_lance_sql(&self) -> Option<String> {
        match self {
            Self::True => None,
            Self::False => Some("FALSE".to_string()),

            Self::And(children) => {
                let parts: Vec<String> = children.iter()
                    .filter_map(|c| c.to_lance_sql())
                    .collect();
                if parts.is_empty() {
                    None
                } else if parts.len() == 1 {
                    Some(parts.into_iter().next().unwrap())
                } else {
                    Some(parts.iter().map(|p| format!("({})", p)).collect::<Vec<_>>().join(" AND "))
                }
            }

            Self::Or(children) => {
                let parts: Vec<String> = children.iter()
                    .filter_map(|c| c.to_lance_sql())
                    .collect();
                if parts.is_empty() {
                    None
                } else if parts.len() == 1 {
                    Some(parts.into_iter().next().unwrap())
                } else {
                    Some(format!("({})", parts.iter().map(|p| format!("({})", p)).collect::<Vec<_>>().join(" OR ")))
                }
            }

            Self::Not(inner) => {
                inner.to_lance_sql().map(|sql| format!("NOT ({})", sql))
            }

            // ── Comparison predicates ──
            Self::Eq(field, value) => {
                // Special case: Tag uses LIKE for membership check
                if matches!(field, FilterField::Tag) {
                    if let FilterValue::String(tag) = value {
                        return Some(format!("tags_json LIKE '%\"{}\",%' OR tags_json LIKE '%,\"{}\"%' OR tags_json LIKE '[\"{}\"]'",
                            tag, tag, tag));
                    }
                }
                Some(format!("{} = {}", field.to_lance_column(), value.to_lance_literal()))
            }
            Self::Ne(field, value) => {
                Some(format!("{} != {}", field.to_lance_column(), value.to_lance_literal()))
            }
            Self::Gt(field, value) => {
                Some(format!("{} > {}", field.to_lance_column(), value.to_lance_literal()))
            }
            Self::Gte(field, value) => {
                Some(format!("{} >= {}", field.to_lance_column(), value.to_lance_literal()))
            }
            Self::Lt(field, value) => {
                Some(format!("{} < {}", field.to_lance_column(), value.to_lance_literal()))
            }
            Self::Lte(field, value) => {
                Some(format!("{} <= {}", field.to_lance_column(), value.to_lance_literal()))
            }

            // ── Set / Range ──
            Self::In(field, values) => {
                let list = FilterValue::List(values.clone());
                Some(format!("{} IN {}", field.to_lance_column(), list.to_lance_literal()))
            }
            Self::NotIn(field, values) => {
                let list = FilterValue::List(values.clone());
                Some(format!("{} NOT IN {}", field.to_lance_column(), list.to_lance_literal()))
            }
            Self::Between(field, low, high) => {
                Some(format!("{} BETWEEN {} AND {}",
                    field.to_lance_column(),
                    low.to_lance_literal(),
                    high.to_lance_literal()))
            }

            // ── String predicates ──
            Self::Like(field, pattern) => {
                Some(format!("{} LIKE '{}'", field.to_lance_column(), pattern.replace('\'', "''")))
            }
            Self::Contains(field, substr) => {
                Some(format!("{} LIKE '%{}%'", field.to_lance_column(), substr.replace('\'', "''")))
            }
            Self::StartsWith(field, prefix) => {
                Some(format!("{} LIKE '{}%'", field.to_lance_column(), prefix.replace('\'', "''")))
            }
            Self::EndsWith(field, suffix) => {
                Some(format!("{} LIKE '%{}'", field.to_lance_column(), suffix.replace('\'', "''")))
            }

            // ── Null checks ──
            Self::IsNull(field) => {
                Some(format!("{} IS NULL", field.to_lance_column()))
            }
            Self::IsNotNull(field) => {
                Some(format!("{} IS NOT NULL", field.to_lance_column()))
            }
        }
    }
}

// ═══════════════════════════════════════════════════════════════════
// QueryFilter → FilterExpr conversion (backward compatibility)
// ═══════════════════════════════════════════════════════════════════

use super::backend::QueryFilter;

impl From<&QueryFilter> for FilterExpr {
    /// Convert a legacy `QueryFilter` into the new `FilterExpr` AST.
    fn from(qf: &QueryFilter) -> Self {
        let mut conditions = Vec::new();

        // Tags → OR combination of tag_eq
        if let Some(ref tags) = qf.tags {
            if !tags.is_empty() {
                let tag_exprs: Vec<FilterExpr> = tags.iter()
                    .map(|t| FilterExpr::tag_eq(t.as_str()))
                    .collect();
                conditions.push(FilterExpr::or(tag_exprs));
            }
        }

        // Content type
        if let Some(ct) = qf.content_type {
            conditions.push(FilterExpr::content_type(ct));
        }

        // Parent ID
        if let Some(ref pid) = qf.parent_id {
            conditions.push(FilterExpr::parent_id(pid.as_str()));
        }

        // Min importance
        if let Some(min_imp) = qf.min_importance {
            conditions.push(FilterExpr::min_importance(min_imp));
        }

        // Time range
        if let Some((start, end)) = qf.time_range {
            conditions.push(FilterExpr::time_range(start, end));
        }

        // Metadata filters
        if let Some(ref meta_filters) = qf.metadata {
            for mf in meta_filters {
                let val_str = match &mf.value {
                    Value::String(s) => s.clone(),
                    other => other.to_string(),
                };
                conditions.push(FilterExpr::metadata_eq(&mf.key, val_str));
            }
        }

        // Semantic gate filter
        if let Some(ref gate) = qf.semantic_gate {
            if gate.is_active() {
                if let Some((min, max)) = gate.axis_0_range {
                    conditions.push(FilterExpr::sem_axis_0_range(min, max));
                }
                if let Some((min, max)) = gate.axis_1_range {
                    conditions.push(FilterExpr::sem_axis_1_range(min, max));
                }
                if let Some((min, max)) = gate.axis_2_range {
                    conditions.push(FilterExpr::sem_axis_2_range(min, max));
                }
            }
        }

        FilterExpr::and(conditions)
    }
}

// ═══════════════════════════════════════════════════════════════════
// SQL LIKE pattern matching (in-memory)
// ═══════════════════════════════════════════════════════════════════

/// Simple SQL LIKE pattern matching.
///
/// `%` matches zero or more characters, `_` matches exactly one character.
fn sql_like_match(text: &str, pattern: &str) -> bool {
    let text_chars: Vec<char> = text.chars().collect();
    let pattern_chars: Vec<char> = pattern.chars().collect();
    like_match_recursive(&text_chars, 0, &pattern_chars, 0)
}

fn like_match_recursive(text: &[char], ti: usize, pattern: &[char], pi: usize) -> bool {
    if pi == pattern.len() {
        return ti == text.len();
    }
    if pattern[pi] == '%' {
        // Skip consecutive %
        let mut next_pi = pi;
        while next_pi < pattern.len() && pattern[next_pi] == '%' {
            next_pi += 1;
        }
        if next_pi == pattern.len() {
            return true; // trailing % matches everything
        }
        // Try matching remaining pattern at every position
        for start in ti..=text.len() {
            if like_match_recursive(text, start, pattern, next_pi) {
                return true;
            }
        }
        return false;
    }
    if ti >= text.len() {
        return false;
    }
    if pattern[pi] == '_' || pattern[pi] == text[ti] {
        return like_match_recursive(text, ti + 1, pattern, pi + 1);
    }
    false
}

// ═══════════════════════════════════════════════════════════════════
// Tests
// ═══════════════════════════════════════════════════════════════════

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::MemoryNode;
    use crate::types::memory::SemanticAxes;

    fn test_node() -> MemoryNode {
        let mut node = MemoryNode::new("Hello World, this is a test content");
        node.tags = vec!["rust".to_string(), "database".to_string()];
        node.importance = 0.8;
        // Use recent timestamps so importance doesn't decay
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as i64;
        node.created_at = now - 1000;
        node.last_accessed_at = now;
        node.access_count = 5;
        node.parent_id = Some("parent-1".to_string());
        node.metadata.insert("author".to_string(), Value::String("Alice".to_string()));
        node.semantic_axes = Some(SemanticAxes { axis_0: 0.5, axis_1: 0.3, axis_2: 0.8 });
        node
    }

    // ── Basic comparison tests ───────────────────────────────────

    #[test]
    fn test_eq_tag() {
        let node = test_node();
        assert!(FilterExpr::tag_eq("rust").evaluate(&node));
        assert!(!FilterExpr::tag_eq("python").evaluate(&node));
    }

    #[test]
    fn test_tag_any_all() {
        let node = test_node();
        assert!(FilterExpr::tag_any(&["rust", "python"]).evaluate(&node));
        assert!(FilterExpr::tag_all(&["rust", "database"]).evaluate(&node));
        assert!(!FilterExpr::tag_all(&["rust", "python"]).evaluate(&node));
    }

    #[test]
    fn test_importance_filter() {
        let node = test_node();
        // importance=0.8, access_count=5 → computed_importance ≈ min(0.8*(1+ln5), 1.0) = 1.0
        assert!(FilterExpr::min_importance(0.5).evaluate(&node));
        assert!(FilterExpr::min_importance(0.99).evaluate(&node)); // clamps to 1.0
        // A node with 0 importance should fail
        let mut low_node = MemoryNode::new("low importance");
        low_node.importance = 0.1;
        low_node.access_count = 1;
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as i64;
        low_node.last_accessed_at = now;
        assert!(!FilterExpr::min_importance(0.5).evaluate(&low_node));
    }

    #[test]
    fn test_time_range() {
        let node = test_node();
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as i64;
        // created_at = now - 1000, so it should be in [now-2000, now]
        assert!(FilterExpr::time_range(now - 2000, now).evaluate(&node));
        // Should NOT be in [now+1000, now+3000]
        assert!(!FilterExpr::time_range(now + 1000, now + 3000).evaluate(&node));
    }

    #[test]
    fn test_between() {
        let node = test_node();
        let expr = FilterExpr::between(
            FilterField::AccessCount,
            FilterValue::Int(3),
            FilterValue::Int(10),
        );
        assert!(expr.evaluate(&node));

        let expr = FilterExpr::between(
            FilterField::AccessCount,
            FilterValue::Int(10),
            FilterValue::Int(20),
        );
        assert!(!expr.evaluate(&node));
    }

    // ── Logical combinator tests ─────────────────────────────────

    #[test]
    fn test_and_or_not() {
        let node = test_node();

        // rust AND importance >= 0.5
        let expr = FilterExpr::and(vec![
            FilterExpr::tag_eq("rust"),
            FilterExpr::min_importance(0.5),
        ]);
        assert!(expr.evaluate(&node));

        // python OR database
        let expr = FilterExpr::or(vec![
            FilterExpr::tag_eq("python"),
            FilterExpr::tag_eq("database"),
        ]);
        assert!(expr.evaluate(&node));

        // NOT python
        let expr = FilterExpr::not(FilterExpr::tag_eq("python"));
        assert!(expr.evaluate(&node));

        // NOT rust
        let expr = FilterExpr::not(FilterExpr::tag_eq("rust"));
        assert!(!expr.evaluate(&node));
    }

    #[test]
    fn test_nested_logic() {
        let node = test_node();

        // (tag=rust OR tag=python) AND importance >= 0.5 AND NOT (parent_id = "other")
        let expr = FilterExpr::and(vec![
            FilterExpr::or(vec![
                FilterExpr::tag_eq("rust"),
                FilterExpr::tag_eq("python"),
            ]),
            FilterExpr::min_importance(0.5),
            FilterExpr::not(FilterExpr::parent_id("other")),
        ]);
        assert!(expr.evaluate(&node));
    }

    // ── IN / NOT IN tests ────────────────────────────────────────

    #[test]
    fn test_in_not_in() {
        let node = test_node();

        let expr = FilterExpr::is_in(
            FilterField::AccessCount,
            vec![FilterValue::Int(3), FilterValue::Int(5), FilterValue::Int(7)],
        );
        assert!(expr.evaluate(&node));

        let expr = FilterExpr::NotIn(
            FilterField::AccessCount,
            vec![FilterValue::Int(3), FilterValue::Int(5), FilterValue::Int(7)],
        );
        assert!(!expr.evaluate(&node));
    }

    // ── String predicate tests ───────────────────────────────────

    #[test]
    fn test_contains() {
        let node = test_node();
        assert!(FilterExpr::contains(FilterField::Content, "test content").evaluate(&node));
        assert!(!FilterExpr::contains(FilterField::Content, "xyz").evaluate(&node));
    }

    #[test]
    fn test_starts_ends_with() {
        let node = test_node();
        assert!(FilterExpr::starts_with(FilterField::Content, "Hello").evaluate(&node));
        assert!(FilterExpr::ends_with(FilterField::Content, "content").evaluate(&node));
        assert!(!FilterExpr::starts_with(FilterField::Content, "World").evaluate(&node));
    }

    #[test]
    fn test_like() {
        let node = test_node();
        assert!(FilterExpr::like(FilterField::Content, "Hello%").evaluate(&node));
        assert!(FilterExpr::like(FilterField::Content, "%test%").evaluate(&node));
        assert!(FilterExpr::like(FilterField::Content, "Hello World%content").evaluate(&node));
        assert!(!FilterExpr::like(FilterField::Content, "Goodbye%").evaluate(&node));
    }

    // ── Null check tests ─────────────────────────────────────────

    #[test]
    fn test_null_checks() {
        let node = test_node();
        assert!(!FilterExpr::IsNull(FilterField::ParentId).evaluate(&node));
        assert!(FilterExpr::IsNotNull(FilterField::ParentId).evaluate(&node));

        let mut node2 = MemoryNode::new("no parent");
        node2.parent_id = None;
        assert!(FilterExpr::IsNull(FilterField::ParentId).evaluate(&node2));
    }

    // ── Metadata filter tests ────────────────────────────────────

    #[test]
    fn test_metadata_eq() {
        let node = test_node();
        assert!(FilterExpr::metadata_eq("author", "Alice").evaluate(&node));
        assert!(!FilterExpr::metadata_eq("author", "Bob").evaluate(&node));
        assert!(!FilterExpr::metadata_eq("missing_key", "value").evaluate(&node));
    }

    // ── Semantic axis tests ──────────────────────────────────────

    #[test]
    fn test_semantic_axis_range() {
        let node = test_node();
        assert!(FilterExpr::sem_axis_0_range(0.0, 1.0).evaluate(&node));
        assert!(!FilterExpr::sem_axis_0_range(0.6, 1.0).evaluate(&node));
        assert!(FilterExpr::sem_axis_2_range(0.7, 0.9).evaluate(&node));
    }

    // ── Lance SQL generation tests ───────────────────────────────

    #[test]
    fn test_lance_sql_simple() {
        let expr = FilterExpr::gte(FilterField::Importance, FilterValue::Float(0.5));
        assert_eq!(expr.to_lance_sql(), Some("importance >= 0.5000000000".to_string()));
    }

    #[test]
    fn test_lance_sql_and_or() {
        let expr = FilterExpr::and(vec![
            FilterExpr::gte(FilterField::Importance, FilterValue::Float(0.5)),
            FilterExpr::or(vec![
                FilterExpr::eq(FilterField::ContentType, FilterValue::String("Text".to_string())),
                FilterExpr::eq(FilterField::ContentType, FilterValue::String("Code".to_string())),
            ]),
        ]);

        let sql = expr.to_lance_sql().unwrap();
        assert!(sql.contains("importance >= "));
        assert!(sql.contains(" AND "));
        assert!(sql.contains(" OR "));
    }

    #[test]
    fn test_lance_sql_between() {
        let expr = FilterExpr::time_range(1000, 2000);
        let sql = expr.to_lance_sql().unwrap();
        assert_eq!(sql, "created_at BETWEEN 1000 AND 2000");
    }

    #[test]
    fn test_lance_sql_in() {
        let expr = FilterExpr::is_in(
            FilterField::AccessCount,
            vec![FilterValue::Int(1), FilterValue::Int(2), FilterValue::Int(3)],
        );
        let sql = expr.to_lance_sql().unwrap();
        assert_eq!(sql, "access_count IN (1, 2, 3)");
    }

    #[test]
    fn test_lance_sql_not() {
        let expr = FilterExpr::not(
            FilterExpr::eq(FilterField::Id, FilterValue::String("bad-id".to_string())),
        );
        let sql = expr.to_lance_sql().unwrap();
        assert_eq!(sql, "NOT (id = 'bad-id')");
    }

    #[test]
    fn test_lance_sql_is_null() {
        let expr = FilterExpr::IsNull(FilterField::ParentId);
        assert_eq!(expr.to_lance_sql(), Some("parent_id IS NULL".to_string()));
    }

    #[test]
    fn test_lance_sql_like() {
        let expr = FilterExpr::like(FilterField::Content, "%hello%");
        assert_eq!(expr.to_lance_sql(), Some("content LIKE '%hello%'".to_string()));
    }

    #[test]
    fn test_lance_sql_true() {
        assert_eq!(FilterExpr::True.to_lance_sql(), None);
    }

    // ── QueryFilter → FilterExpr conversion ──────────────────────

    #[test]
    fn test_query_filter_conversion() {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_millis() as i64;
        let qf = QueryFilter {
            tags: Some(vec!["rust".to_string(), "database".to_string()]),
            min_importance: Some(0.5),
            time_range: Some((now - 5000, now + 5000)),
            ..Default::default()
        };

        let expr = FilterExpr::from(&qf);
        let node = test_node();
        assert!(expr.evaluate(&node));

        // Verify Lance SQL is generated
        let sql = expr.to_lance_sql();
        assert!(sql.is_some());
    }

    // ── AND/OR flattening tests ──────────────────────────────────

    #[test]
    fn test_and_flattening() {
        // AND(AND(a, b), c) should flatten to AND(a, b, c)
        let expr = FilterExpr::and(vec![
            FilterExpr::and(vec![
                FilterExpr::tag_eq("a"),
                FilterExpr::tag_eq("b"),
            ]),
            FilterExpr::tag_eq("c"),
        ]);
        match expr {
            FilterExpr::And(children) => assert_eq!(children.len(), 3),
            _ => panic!("Expected flattened AND"),
        }
    }

    #[test]
    fn test_or_flattening() {
        let expr = FilterExpr::or(vec![
            FilterExpr::or(vec![
                FilterExpr::tag_eq("a"),
                FilterExpr::tag_eq("b"),
            ]),
            FilterExpr::tag_eq("c"),
        ]);
        match expr {
            FilterExpr::Or(children) => assert_eq!(children.len(), 3),
            _ => panic!("Expected flattened OR"),
        }
    }

    #[test]
    fn test_double_negation() {
        let expr = FilterExpr::not(FilterExpr::not(FilterExpr::tag_eq("rust")));
        assert!(matches!(expr, FilterExpr::Eq(FilterField::Tag, _)));
    }

    // ── SQL LIKE pattern matching tests ──────────────────────────

    #[test]
    fn test_sql_like_matching() {
        assert!(sql_like_match("hello world", "hello%"));
        assert!(sql_like_match("hello world", "%world"));
        assert!(sql_like_match("hello world", "%lo wo%"));
        assert!(sql_like_match("hello world", "hello_world"));
        assert!(!sql_like_match("hello world", "hello_world!"));
        assert!(sql_like_match("abc", "%"));
        assert!(sql_like_match("", "%"));
        assert!(!sql_like_match("", "_"));
        assert!(sql_like_match("a", "_"));
    }
}
