//! BoolQuery: boolean combination of sub-queries.
//!
//! - `must`: conjunction (AND) — all clauses must match, scores summed
//! - `should`: disjunction (OR) — any clause may match, scores summed
//! - `must_not`: exclusion — matching docs are removed
//! - `filter`: conjunction in filter context (AND, no scoring)
//!
//! See [[query-dsl#Compound Queries]] and [[architecture-query-execution#Step 7]].

use luci_core::{DocId, Result, ScoreMode, Scorer, TwoPhaseIterator, NO_MORE_DOCS};

use crate::query::{Query, ScorerSupplier, Weight};
use crate::search::conjunction::ConjunctionScorer;
use crate::search::disjunction::DisjunctionScorer;
use crate::search::searcher::Searcher;
use crate::segment::reader::SegmentReader;

/// Boolean combination of sub-queries.
pub struct BoolQuery {
    pub must: Vec<Box<dyn Query>>,
    pub should: Vec<Box<dyn Query>>,
    pub must_not: Vec<Box<dyn Query>>,
    pub filter: Vec<Box<dyn Query>>,
}

impl Query for BoolQuery {
    fn create_weight(
        &self,
        searcher: &Searcher,
        score_mode: ScoreMode,
    ) -> Result<Box<dyn Weight>> {
        let must_weights: Vec<Box<dyn Weight>> = self.must.iter()
            .map(|q| q.create_weight(searcher, score_mode))
            .collect::<Result<_>>()?;

        let should_weights: Vec<Box<dyn Weight>> = self.should.iter()
            .map(|q| q.create_weight(searcher, score_mode))
            .collect::<Result<_>>()?;

        let must_not_weights: Vec<Box<dyn Weight>> = self.must_not.iter()
            .map(|q| q.create_weight(searcher, ScoreMode::CompleteNoScores))
            .collect::<Result<_>>()?;

        let filter_weights: Vec<Box<dyn Weight>> = self.filter.iter()
            .map(|q| q.create_weight(searcher, ScoreMode::CompleteNoScores))
            .collect::<Result<_>>()?;

        Ok(Box::new(BoolWeight {
            must: must_weights,
            should: should_weights,
            must_not: must_not_weights,
            filter: filter_weights,
        }))
    }
}

struct BoolWeight {
    must: Vec<Box<dyn Weight>>,
    should: Vec<Box<dyn Weight>>,
    must_not: Vec<Box<dyn Weight>>,
    filter: Vec<Box<dyn Weight>>,
}

impl Weight for BoolWeight {
    fn bulk_score(
        &self,
        reader: &SegmentReader,
        collector: &mut crate::search::collector::TopDocsCollector,
        segment_id: luci_core::SegmentId,
    ) -> Result<Option<u64>> {
        // Only for pure-should with 2+ clauses
        if !self.must.is_empty() || !self.filter.is_empty() || !self.must_not.is_empty() {
            return Ok(None);
        }
        if self.should.len() < 2 {
            return Ok(None);
        }

        let mut scorers: Vec<Box<dyn luci_core::Scorer>> = Vec::new();
        for w in &self.should {
            if let Some(supplier) = w.scorer_supplier(reader)? {
                scorers.push(supplier.scorer()?);
            }
        }
        if scorers.len() < 2 {
            return Ok(None);
        }

        let max_doc = reader.doc_count();
        let mut bulk = crate::search::bulk::MaxScoreBulkScorer::new(scorers);
        let hits = bulk.score(collector, segment_id, max_doc);
        Ok(Some(hits))
    }

    fn scorer_supplier(
        &self,
        reader: &SegmentReader,
    ) -> Result<Option<Box<dyn ScorerSupplier>>> {
        // Collect must suppliers
        let mut must_suppliers: Vec<Box<dyn ScorerSupplier>> = Vec::new();
        for w in &self.must {
            match w.scorer_supplier(reader)? {
                Some(s) => must_suppliers.push(s),
                None => return Ok(None), // must clause with no matches = no results
            }
        }

        // Collect filter suppliers
        let mut filter_suppliers: Vec<Box<dyn ScorerSupplier>> = Vec::new();
        for w in &self.filter {
            match w.scorer_supplier(reader)? {
                Some(s) => filter_suppliers.push(s),
                None => return Ok(None), // filter with no matches = no results
            }
        }

        // Collect should suppliers
        let mut should_suppliers: Vec<Box<dyn ScorerSupplier>> = Vec::new();
        for w in &self.should {
            if let Some(s) = w.scorer_supplier(reader)? {
                should_suppliers.push(s);
            }
        }

        // Collect must_not suppliers
        let mut must_not_suppliers: Vec<Box<dyn ScorerSupplier>> = Vec::new();
        for w in &self.must_not {
            if let Some(s) = w.scorer_supplier(reader)? {
                must_not_suppliers.push(s);
            }
        }

        // If no must and no filter and no should, nothing matches
        if must_suppliers.is_empty() && filter_suppliers.is_empty() && should_suppliers.is_empty() {
            return Ok(None);
        }

        // Estimate total cost as min of must/filter costs
        let cost = must_suppliers.iter().chain(filter_suppliers.iter())
            .map(|s| s.cost())
            .min()
            .unwrap_or_else(|| {
                should_suppliers.iter().map(|s| s.cost()).sum::<u64>()
            });

        Ok(Some(Box::new(BoolScorerSupplier {
            must: must_suppliers,
            should: should_suppliers,
            must_not: must_not_suppliers,
            filter: filter_suppliers,
            cost,
        })))
    }
}

struct BoolScorerSupplier {
    must: Vec<Box<dyn ScorerSupplier>>,
    should: Vec<Box<dyn ScorerSupplier>>,
    must_not: Vec<Box<dyn ScorerSupplier>>,
    filter: Vec<Box<dyn ScorerSupplier>>,
    cost: u64,
}

// SAFETY: All contained ScorerSuppliers are Send
unsafe impl Send for BoolScorerSupplier {}

impl ScorerSupplier for BoolScorerSupplier {
    fn cost(&self) -> u64 {
        self.cost
    }

    fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>> {
        // Build all sub-scorers
        let mut required_scorers: Vec<Box<dyn Scorer>> = Vec::new();

        // Must clauses (scored)
        // Sort by cost for conjunction efficiency
        let mut must_with_cost: Vec<_> = self.must.into_iter()
            .map(|s| { let c = s.cost(); (s, c) })
            .collect();
        must_with_cost.sort_by_key(|(_, c)| *c);
        for (supplier, _) in must_with_cost {
            required_scorers.push(supplier.scorer()?);
        }

        // Filter clauses (not scored)
        let mut filter_with_cost: Vec<_> = self.filter.into_iter()
            .map(|s| { let c = s.cost(); (s, c) })
            .collect();
        filter_with_cost.sort_by_key(|(_, c)| *c);
        for (supplier, _) in filter_with_cost {
            required_scorers.push(supplier.scorer()?);
        }

        // Must_not clauses → exclusion scorers
        let mut exclusion_scorers: Vec<Box<dyn Scorer>> = Vec::new();
        for supplier in self.must_not {
            exclusion_scorers.push(supplier.scorer()?);
        }

        // Should clauses
        let should_scorers: Vec<Box<dyn Scorer>> = self.should.into_iter()
            .map(|s| s.scorer())
            .collect::<Result<_>>()?;

        // Build the composite scorer
        let mut base_scorer: Box<dyn Scorer> = if !required_scorers.is_empty() {
            if required_scorers.len() == 1 {
                required_scorers.pop().unwrap()
            } else {
                Box::new(ConjunctionScorer::new(required_scorers))
            }
        } else if !should_scorers.is_empty() {
            // No must/filter — should becomes the primary
            if should_scorers.len() == 1 {
                return Ok(should_scorers.into_iter().next().unwrap());
            }
            return Ok(Box::new(crate::search::wand::WANDScorer::new(should_scorers)));
        } else {
            // This shouldn't happen — we checked above
            return Ok(Box::new(EmptyScorer));
        };

        // Apply must_not exclusions
        if !exclusion_scorers.is_empty() {
            base_scorer = Box::new(ExclusionScorer::new(base_scorer, exclusion_scorers));
        }

        // Add should as optional boost (if we have must/filter)
        if !should_scorers.is_empty() {
            base_scorer = Box::new(OptionalScorer::new(base_scorer, should_scorers));
        }

        Ok(base_scorer)
    }
}

/// Scorer that always returns NO_MORE_DOCS.
struct EmptyScorer;

impl Scorer for EmptyScorer {
    fn doc_id(&self) -> DocId { NO_MORE_DOCS }
    fn next(&mut self) -> DocId { NO_MORE_DOCS }
    fn advance(&mut self, _: DocId) -> DocId { NO_MORE_DOCS }
    fn score(&mut self) -> f32 { 0.0 }
    fn two_phase(&mut self) -> Option<&mut dyn TwoPhaseIterator> { None }
}

/// Wraps a base scorer and skips docs that appear in exclusion scorers (must_not).
struct ExclusionScorer {
    base: Box<dyn Scorer>,
    exclusions: Vec<Box<dyn Scorer>>,
}

impl ExclusionScorer {
    fn new(base: Box<dyn Scorer>, exclusions: Vec<Box<dyn Scorer>>) -> Self {
        let mut s = Self { base, exclusions };
        s.skip_excluded();
        s
    }

    fn is_excluded(&mut self) -> bool {
        let target = self.base.doc_id();
        for exc in &mut self.exclusions {
            let doc = exc.advance(target);
            if doc == target {
                return true;
            }
        }
        false
    }

    fn skip_excluded(&mut self) {
        while self.base.doc_id() != NO_MORE_DOCS && self.is_excluded() {
            self.base.next();
        }
    }
}

impl Scorer for ExclusionScorer {
    fn doc_id(&self) -> DocId { self.base.doc_id() }
    fn next(&mut self) -> DocId {
        self.base.next();
        self.skip_excluded();
        self.base.doc_id()
    }
    fn advance(&mut self, target: DocId) -> DocId {
        self.base.advance(target);
        self.skip_excluded();
        self.base.doc_id()
    }
    fn score(&mut self) -> f32 { self.base.score() }
    fn two_phase(&mut self) -> Option<&mut dyn TwoPhaseIterator> { None }
}

/// Wraps a required base scorer with optional should scorers that add to the score.
struct OptionalScorer {
    base: Box<dyn Scorer>,
    optionals: Vec<Box<dyn Scorer>>,
}

impl OptionalScorer {
    fn new(base: Box<dyn Scorer>, optionals: Vec<Box<dyn Scorer>>) -> Self {
        Self { base, optionals }
    }
}

impl Scorer for OptionalScorer {
    fn doc_id(&self) -> DocId { self.base.doc_id() }
    fn next(&mut self) -> DocId { self.base.next() }
    fn advance(&mut self, target: DocId) -> DocId { self.base.advance(target) }
    fn score(&mut self) -> f32 {
        let mut score = self.base.score();
        let target = self.base.doc_id();
        for opt in &mut self.optionals {
            if opt.advance(target) == target {
                score += opt.score();
            }
        }
        score
    }
    fn two_phase(&mut self) -> Option<&mut dyn TwoPhaseIterator> { None }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::query::term::TermQuery;

    use crate::segment::builder::SegmentBuilder;
    use luci_analysis::Token;
    use luci_core::{FieldId, SegmentId};
    use luci_mapping::{FieldType,Mapping};

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

    fn build_test_searcher() -> Searcher {
        let schema = Mapping::builder()
            .field("body", FieldType::Text)
            .field("tag", FieldType::Keyword)
            .build();
        let mut builder = SegmentBuilder::new(SegmentId::new(1), &schema);

        // Doc 0: body="hello world", tag="a"
        builder.add_document(&[
            (FieldId::new(0), make_tokens(&["hello", "world"])),
            (FieldId::new(1), make_tokens(&["a"])),
        ], b"{}");
        // Doc 1: body="hello luci", tag="b"
        builder.add_document(&[
            (FieldId::new(0), make_tokens(&["hello", "luci"])),
            (FieldId::new(1), make_tokens(&["b"])),
        ], b"{}");
        // Doc 2: body="goodbye world", tag="a"
        builder.add_document(&[
            (FieldId::new(0), make_tokens(&["goodbye", "world"])),
            (FieldId::new(1), make_tokens(&["a"])),
        ], b"{}");
        // Doc 3: body="luci search", tag="c"
        builder.add_document(&[
            (FieldId::new(0), make_tokens(&["luci", "search"])),
            (FieldId::new(1), make_tokens(&["c"])),
        ], b"{}");

        let reader = SegmentReader::open(builder.build()).unwrap();
        Searcher::new(vec![reader])
    }

    fn collect_doc_ids(scorer: &mut dyn Scorer) -> Vec<u32> {
        let mut ids = Vec::new();
        while scorer.doc_id() != NO_MORE_DOCS {
            ids.push(scorer.doc_id().as_u32());
            scorer.next();
        }
        ids
    }

    #[test]
    fn bool_must_two_clauses() {
        let searcher = build_test_searcher();
        let query = BoolQuery {
            must: vec![
                Box::new(TermQuery { field: "body".into(), value: "hello".into() }),
                Box::new(TermQuery { field: "body".into(), value: "world".into() }),
            ],
            should: vec![],
            must_not: vec![],
            filter: vec![],
        };

        let weight = query.create_weight(&searcher, ScoreMode::Complete).unwrap();
        let supplier = weight.scorer_supplier(&searcher.segments()[0]).unwrap().unwrap();
        let mut scorer = supplier.scorer().unwrap();

        // Only doc 0 has both "hello" AND "world"
        let ids = collect_doc_ids(scorer.as_mut());
        assert_eq!(ids, vec![0]);
    }

    #[test]
    fn bool_should_two_clauses() {
        let searcher = build_test_searcher();
        let query = BoolQuery {
            must: vec![],
            should: vec![
                Box::new(TermQuery { field: "body".into(), value: "hello".into() }),
                Box::new(TermQuery { field: "body".into(), value: "goodbye".into() }),
            ],
            must_not: vec![],
            filter: vec![],
        };

        let weight = query.create_weight(&searcher, ScoreMode::Complete).unwrap();
        let supplier = weight.scorer_supplier(&searcher.segments()[0]).unwrap().unwrap();
        let mut scorer = supplier.scorer().unwrap();

        // Docs 0, 1, 2: "hello" in 0,1 and "goodbye" in 2
        let ids = collect_doc_ids(scorer.as_mut());
        assert_eq!(ids, vec![0, 1, 2]);
    }

    #[test]
    fn bool_must_not() {
        let searcher = build_test_searcher();
        let query = BoolQuery {
            must: vec![
                Box::new(TermQuery { field: "body".into(), value: "hello".into() }),
            ],
            should: vec![],
            must_not: vec![
                Box::new(TermQuery { field: "body".into(), value: "world".into() }),
            ],
            filter: vec![],
        };

        let weight = query.create_weight(&searcher, ScoreMode::Complete).unwrap();
        let supplier = weight.scorer_supplier(&searcher.segments()[0]).unwrap().unwrap();
        let mut scorer = supplier.scorer().unwrap();

        // "hello" in docs 0,1. "world" in docs 0,2. Must_not removes doc 0.
        let ids = collect_doc_ids(scorer.as_mut());
        assert_eq!(ids, vec![1]);
    }

    #[test]
    fn bool_filter_no_scores() {
        let searcher = build_test_searcher();
        let query = BoolQuery {
            must: vec![],
            should: vec![],
            must_not: vec![],
            filter: vec![
                Box::new(TermQuery { field: "tag".into(), value: "a".into() }),
            ],
        };

        let weight = query.create_weight(&searcher, ScoreMode::Complete).unwrap();
        let supplier = weight.scorer_supplier(&searcher.segments()[0]).unwrap().unwrap();
        let mut scorer = supplier.scorer().unwrap();

        // tag="a" in docs 0, 2
        let ids = collect_doc_ids(scorer.as_mut());
        assert_eq!(ids, vec![0, 2]);
    }

    #[test]
    fn bool_must_plus_filter() {
        let searcher = build_test_searcher();
        let query = BoolQuery {
            must: vec![
                Box::new(TermQuery { field: "body".into(), value: "hello".into() }),
            ],
            should: vec![],
            must_not: vec![],
            filter: vec![
                Box::new(TermQuery { field: "tag".into(), value: "a".into() }),
            ],
        };

        let weight = query.create_weight(&searcher, ScoreMode::Complete).unwrap();
        let supplier = weight.scorer_supplier(&searcher.segments()[0]).unwrap().unwrap();
        let mut scorer = supplier.scorer().unwrap();

        // "hello" in 0,1 AND tag="a" in 0,2 → only doc 0
        let ids = collect_doc_ids(scorer.as_mut());
        assert_eq!(ids, vec![0]);
    }

    #[test]
    fn bool_empty_must_returns_none() {
        let searcher = build_test_searcher();
        let query = BoolQuery {
            must: vec![
                Box::new(TermQuery { field: "body".into(), value: "nonexistent".into() }),
            ],
            should: vec![],
            must_not: vec![],
            filter: vec![],
        };

        let weight = query.create_weight(&searcher, ScoreMode::Complete).unwrap();
        let supplier = weight.scorer_supplier(&searcher.segments()[0]).unwrap();
        assert!(supplier.is_none());
    }
}
