//! Query DSL: AST types, JSON parser, and execution framework.
//!
//! Implements the `Query → Weight → ScorerSupplier → Scorer` pipeline
//! from [[architecture-query-execution]]. Queries are parsed from JSON, converted to
//! weights with index-level statistics, then per-segment scorers are built.
//!
//! See [[query-dsl]] and [[architecture-query-execution|milestone-2]].

pub mod ast;
pub mod parser;
pub mod convert;
pub mod term;
pub mod boolean;
pub mod match_query;
pub mod phrase;
pub mod exists;
pub mod prefix;
pub mod constant_score;
pub mod range;
pub mod wildcard;
pub mod regexp;
pub mod fuzzy;
pub mod boost;
pub mod boosting;
pub mod function_score;
pub mod script_score;
pub mod span;
pub mod nested;
pub mod dis_max;

use luci_core::{Result, ScoreMode, Scorer};

use crate::search::searcher::Searcher;
use crate::segment::reader::SegmentReader;

/// A parsed query that can create weights for execution.
///
/// Corresponds to `Query` in [[architecture-query-execution#Core Traits]].
/// Object-safe — used as `Box<dyn Query>` in the execution engine.
pub trait Query: Send + Sync {
    /// Create a weight from this query using index-level statistics.
    fn create_weight(
        &self,
        searcher: &Searcher,
        score_mode: ScoreMode,
    ) -> Result<Box<dyn Weight>>;
}

/// Segment-independent query state. Created once per query, then asked
/// for per-segment scorer suppliers.
///
/// Corresponds to `Weight` in [[architecture-query-execution#Core Traits]].
pub trait Weight: Send + Sync {
    /// Create a scorer supplier for a segment, or `None` if this weight
    /// cannot match any documents in the segment.
    fn scorer_supplier(
        &self,
        reader: &SegmentReader,
    ) -> Result<Option<Box<dyn ScorerSupplier>>>;

    /// Returns true if this weight matches all documents in every segment.
    /// Used to bypass scorer iteration in agg-only paths.
    fn is_match_all(&self) -> bool {
        false
    }

    /// Optional: score all matching documents directly into a collector,
    /// bypassing the doc-at-a-time Scorer interface. Returns total hits
    /// if supported, None to fall back to doc-at-a-time.
    fn bulk_score(
        &self,
        _reader: &SegmentReader,
        _collector: &mut crate::search::collector::TopDocsCollector,
        _segment_id: luci_core::SegmentId,
    ) -> Result<Option<u64>> {
        Ok(None)
    }

    /// Explain the score for a specific document.
    ///
    /// Default: builds a scorer, advances to the doc, returns a generic
    /// explanation. Override for detailed BM25/boolean breakdowns.
    /// See [[feature-search-explain]].
    fn explain(
        &self,
        reader: &SegmentReader,
        doc: luci_core::DocId,
    ) -> Result<crate::search::Explanation> {
        use luci_core::{Scorer as _, NO_MORE_DOCS};
        let supplier = match self.scorer_supplier(reader)? {
            Some(s) => s,
            None => return Ok(crate::search::Explanation::no_match(
                "no matching docs in segment".into()
            )),
        };
        let mut scorer = supplier.scorer()?;
        let found = scorer.advance(doc);
        if found != doc {
            return Ok(crate::search::Explanation::no_match(
                format!("doc {} not matched", doc.as_u32())
            ));
        }
        let score = scorer.score();
        Ok(crate::search::Explanation::leaf(score, format!("score(doc={})", doc.as_u32())))
    }

}

/// Knows the estimated cost of scoring before a scorer is built.
///
/// This enables cost-based clause ordering in boolean conjunctions:
/// the cheapest supplier becomes the lead iterator.
///
/// Corresponds to `ScorerSupplier` in [[architecture-query-execution#Core Traits]].
pub trait ScorerSupplier: Send {
    /// Estimated number of matching documents in this segment.
    fn cost(&self) -> u64;

    /// Build the actual scorer. `lead_cost` is the cost of the lead
    /// iterator in a conjunction — implementations may use it to choose
    /// between algorithms.
    fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>>;
}

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

    // Verify trait object safety
    #[test]
    fn query_is_object_safe() {
        fn _takes_query(_q: &dyn Query) {}
    }

    #[test]
    fn weight_is_object_safe() {
        fn _takes_weight(_w: &dyn Weight) {}
    }

    #[test]
    fn scorer_supplier_is_object_safe() {
        fn _takes_ss(_ss: Box<dyn ScorerSupplier>) {}
    }
}
