//! Wildcard query: match terms using * (any chars) and ? (single char).
//!
//! Scans the term dictionary for matching terms, then rewrites to a
//! disjunction (bool/should) of term queries at weight creation time.
//!
//! See [[elasticsearch-parity]].

use luci_core::{Result, ScoreMode};

use crate::query::{Query, Weight};
use crate::query::boolean::BoolQuery;
use crate::query::term::TermQuery;
use crate::search::searcher::Searcher;

/// Wildcard query: * matches any sequence, ? matches single character.
pub struct WildcardQuery {
    pub field: String,
    pub pattern: String,
}

impl WildcardQuery {
    /// Check if a term matches the wildcard pattern.
    pub fn matches(pattern: &str, term: &str) -> bool {
        Self::matches_recursive(pattern.as_bytes(), term.as_bytes())
    }

    fn matches_recursive(pattern: &[u8], text: &[u8]) -> bool {
        if pattern.is_empty() {
            return text.is_empty();
        }
        match pattern[0] {
            b'*' => {
                Self::matches_recursive(&pattern[1..], text)
                    || (!text.is_empty()
                        && Self::matches_recursive(pattern, &text[1..]))
            }
            b'?' => {
                !text.is_empty()
                    && Self::matches_recursive(&pattern[1..], &text[1..])
            }
            c => {
                !text.is_empty()
                    && text[0] == c
                    && Self::matches_recursive(&pattern[1..], &text[1..])
            }
        }
    }

    /// Extract a prefix before the first wildcard character.
    fn prefix(pattern: &str) -> &str {
        match pattern.find(|c| c == '*' || c == '?') {
            Some(pos) => &pattern[..pos],
            None => pattern,
        }
    }
}

impl Query for WildcardQuery {
    fn create_weight(
        &self,
        searcher: &Searcher,
        score_mode: ScoreMode,
    ) -> Result<Box<dyn Weight>> {
        // Collect matching terms from all segments
        let prefix = Self::prefix(&self.pattern);
        let mut matching_terms: Vec<String> = Vec::new();

        for segment in searcher.segments() {
            let field_id = match segment.header().fields.iter()
                .find(|f| f.field_name == self.field)
                .map(|f| f.field_id)
            {
                Some(id) => id,
                None => continue,
            };

            for (term, _) in segment.terms_with_prefix(field_id, prefix) {
                if Self::matches(&self.pattern, &term) && !matching_terms.contains(&term) {
                    matching_terms.push(term);
                }
            }
        }

        if matching_terms.is_empty() {
            // No matches — return a weight that produces no scorers
            return crate::query::convert::MatchNoneQuery.create_weight(searcher, score_mode);
        }

        // Rewrite to bool/should of term queries
        let should: Vec<Box<dyn Query>> = matching_terms.into_iter()
            .map(|term| -> Box<dyn Query> {
                Box::new(TermQuery {
                    field: self.field.clone(),
                    value: term,
                })
            })
            .collect();

        BoolQuery {
            must: vec![],
            should,
            must_not: vec![],
            filter: vec![],
        }.create_weight(searcher, score_mode)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::segment::builder::SegmentBuilder;
    use crate::segment::reader::SegmentReader;
    use luci_analysis::Token;
    use luci_core::{FieldId, SegmentId};
    use luci_mapping::{FieldType, Mapping};

    #[test]
    fn wildcard_matching() {
        assert!(WildcardQuery::matches("tech*", "technology"));
        assert!(WildcardQuery::matches("tech*", "tech"));
        assert!(!WildcardQuery::matches("tech*", "tec"));
        assert!(WildcardQuery::matches("t?ch", "tech"));
        assert!(!WildcardQuery::matches("t?ch", "touch"));
        assert!(WildcardQuery::matches("*fox*", "quick fox jumps"));
        assert!(WildcardQuery::matches("*", "anything"));
        assert!(WildcardQuery::matches("?", "x"));
        assert!(!WildcardQuery::matches("?", ""));
        assert!(WildcardQuery::matches("a*b", "ab"));
        assert!(WildcardQuery::matches("a*b", "aXYZb"));
        assert!(!WildcardQuery::matches("a*b", "aXYZc"));
    }

    #[test]
    fn prefix_extraction() {
        assert_eq!(WildcardQuery::prefix("tech*"), "tech");
        assert_eq!(WildcardQuery::prefix("t?ch"), "t");
        assert_eq!(WildcardQuery::prefix("*fox"), "");
        assert_eq!(WildcardQuery::prefix("exact"), "exact");
    }

    fn make_tokens(terms: &[&str]) -> Vec<Token> {
        terms.iter().enumerate()
            .map(|(i, t)| Token::new(*t, 0, t.len(), i as u32))
            .collect()
    }

    #[test]
    fn wildcard_query_star() {
        let schema = Mapping::builder().field("tag", FieldType::Keyword).build();
        let mut builder = SegmentBuilder::new(SegmentId::new(1), &schema);
        for tag in &["technology", "technical", "tennis", "science"] {
            builder.add_document(
                &[(FieldId::new(0), vec![Token::new(*tag, 0, tag.len(), 0)])],
                b"{}",
            );
        }
        let reader = SegmentReader::open(builder.build()).unwrap();
        let searcher = Searcher::new(vec![reader]);

        let results = searcher.search_query(&WildcardQuery {
            field: "tag".into(),
            pattern: "tech*".into(),
        }, 10, 0, &crate::search::SourceFilter::Enabled, &None, false, &[]);
        assert_eq!(results.total_hits.value, 2); // technology, technical
    }

    #[test]
    fn wildcard_query_question_mark() {
        let schema = Mapping::builder().field("tag", FieldType::Keyword).build();
        let mut builder = SegmentBuilder::new(SegmentId::new(1), &schema);
        for tag in &["cat", "cut", "cot", "cart"] {
            builder.add_document(
                &[(FieldId::new(0), vec![Token::new(*tag, 0, tag.len(), 0)])],
                b"{}",
            );
        }
        let reader = SegmentReader::open(builder.build()).unwrap();
        let searcher = Searcher::new(vec![reader]);

        let results = searcher.search_query(&WildcardQuery {
            field: "tag".into(),
            pattern: "c?t".into(),
        }, 10, 0, &crate::search::SourceFilter::Enabled, &None, false, &[]);
        assert_eq!(results.total_hits.value, 3); // cat, cut, cot (not cart — 4 chars)
    }

    #[test]
    fn wildcard_no_matches() {
        let schema = Mapping::builder().field("tag", FieldType::Keyword).build();
        let mut builder = SegmentBuilder::new(SegmentId::new(1), &schema);
        builder.add_document(
            &[(FieldId::new(0), vec![Token::new("hello", 0, 5, 0)])],
            b"{}",
        );
        let reader = SegmentReader::open(builder.build()).unwrap();
        let searcher = Searcher::new(vec![reader]);

        let results = searcher.search_query(&WildcardQuery {
            field: "tag".into(),
            pattern: "xyz*".into(),
        }, 10, 0, &crate::search::SourceFilter::Enabled, &None, false, &[]);
        assert_eq!(results.total_hits.value, 0);
    }
}
