//! Convert [`QueryAST`] to executable [`Query`] objects.
//!
//! Bridges the JSON parser output to the execution pipeline.

use crate::query::ast::QueryAST;
use crate::query::boolean::BoolQuery;
use crate::query::constant_score::ConstantScoreQuery;
use crate::query::exists::ExistsQuery;
use crate::query::match_query::MatchQuery;
use crate::query::phrase::MatchPhraseQuery;
use crate::query::prefix::PrefixQuery;
use crate::query::range::RangeQuery;
use crate::query::term::TermQuery;
use crate::query::Query;

/// Convert a parsed [`QueryAST`] into an executable [`Query`] trait object.
pub fn ast_to_query(ast: &QueryAST) -> Box<dyn Query> {
    match ast {
        QueryAST::Term { field, value } => Box::new(TermQuery {
            field: field.clone(),
            value: value.clone(),
        }),

        QueryAST::Terms { field, values } => {
            // Rewrite to Bool { should: [TermQuery * N] }
            let should: Vec<Box<dyn Query>> = values
                .iter()
                .map(|v| -> Box<dyn Query> {
                    Box::new(TermQuery {
                        field: field.clone(),
                        value: v.clone(),
                    })
                })
                .collect();
            Box::new(BoolQuery {
                must: vec![],
                should,
                must_not: vec![],
                filter: vec![],
            })
        }

        QueryAST::Match {
            field,
            query,
            analyzer,
        } => Box::new(MatchQuery {
            field: field.clone(),
            query_text: query.clone(),
            analyzer: analyzer.clone(),
        }),

        QueryAST::MatchPhrase {
            field,
            query,
            analyzer,
        } => Box::new(MatchPhraseQuery {
            field: field.clone(),
            query_text: query.clone(),
            analyzer: analyzer.clone(),
        }),

        QueryAST::Bool {
            must,
            should,
            must_not,
            filter,
        } => Box::new(BoolQuery {
            must: must.iter().map(ast_to_query).collect(),
            should: should.iter().map(ast_to_query).collect(),
            must_not: must_not.iter().map(ast_to_query).collect(),
            filter: filter.iter().map(ast_to_query).collect(),
        }),

        QueryAST::MatchBoolPrefix { field, query, analyzer } => {
            // Rewrite: analyze text, all tokens → term queries, last → prefix
            let registry = luci_analysis::AnalyzerRegistry::new();
            let analyzer_name = analyzer.as_deref().unwrap_or("standard");
            let analyzed = registry.get(analyzer_name).analyze(query);
            let tokens: Vec<String> = analyzed.into_iter().map(|t| t.text).collect();
            if tokens.is_empty() {
                Box::new(MatchNoneQuery)
            } else {
                let mut should: Vec<Box<dyn Query>> = Vec::new();
                for (i, token) in tokens.iter().enumerate() {
                    if i == tokens.len() - 1 {
                        should.push(Box::new(PrefixQuery {
                            field: field.clone(),
                            value: token.clone(),
                        }));
                    } else {
                        should.push(Box::new(TermQuery {
                            field: field.clone(),
                            value: token.clone(),
                        }));
                    }
                }
                Box::new(BoolQuery {
                    must: vec![],
                    should,
                    must_not: vec![],
                    filter: vec![],
                })
            }
        }

        QueryAST::DisMax { queries, tie_breaker } => {
            Box::new(crate::query::dis_max::DisMaxQuery {
                queries: queries.iter().map(ast_to_query).collect(),
                tie_breaker: *tie_breaker,
            })
        }

        QueryAST::Exists { field } => Box::new(ExistsQuery {
            field: field.clone(),
        }),

        QueryAST::Prefix { field, value } => Box::new(PrefixQuery {
            field: field.clone(),
            value: value.clone(),
        }),

        QueryAST::ConstantScore { query, boost } => Box::new(ConstantScoreQuery {
            inner: ast_to_query(query),
            boost: *boost,
        }),

        QueryAST::Nested { path, query, inner_hits } => Box::new(crate::query::nested::NestedQuery {
            path: path.clone(),
            inner: ast_to_query(query),
            inner_hits: inner_hits.clone(),
        }),

        QueryAST::GeoDistance { field, lat, lon, distance } => {
            let distance_km = crate::query::parser::parse_distance_km(distance);
            Box::new(crate::spatial::query::GeoDistanceQuery {
                field: field.clone(),
                center: crate::spatial::geo::GeoPoint::new(*lat, *lon),
                distance_km,
            })
        }

        QueryAST::GeoBoundingBox {
            field,
            top_left_lat,
            top_left_lon,
            bottom_right_lat,
            bottom_right_lon,
        } => Box::new(crate::spatial::query::GeoBoundingBoxQuery {
            field: field.clone(),
            top_left: crate::spatial::geo::GeoPoint::new(*top_left_lat, *top_left_lon),
            bottom_right: crate::spatial::geo::GeoPoint::new(*bottom_right_lat, *bottom_right_lon),
        }),

        QueryAST::GeoShape { field, shape, relation } => {
            let query_geom = crate::spatial::shape::parse_geojson(&shape.json)
                .unwrap_or(::geo::Geometry::Point(::geo::Point::new(0.0, 0.0)));
            let query_bbox = crate::spatial::shape::compute_bbox(&query_geom)
                .unwrap_or((0.0, 0.0, 0.0, 0.0));
            Box::new(crate::spatial::query::GeoShapeQuery {
                field: field.clone(),
                query_shape: query_geom,
                query_bbox,
                relation: relation.clone(),
            })
        }

        QueryAST::Range { field, gte, gt, lte, lt } => Box::new(RangeQuery {
            field: field.clone(),
            gte: *gte,
            gt: *gt,
            lte: *lte,
            lt: *lt,
        }),

        QueryAST::Boost { query, boost } => {
            Box::new(crate::query::boost::BoostQuery {
                inner: ast_to_query(query),
                boost: *boost,
            })
        }

        QueryAST::ScriptScore { query, script, params } => {
            Box::new(crate::query::script_score::ScriptScoreQuery {
                query: ast_to_query(query),
                script: script.clone(),
                params: params.clone(),
            })
        }

        QueryAST::FunctionScore { query, functions, score_mode, boost_mode } => {
            Box::new(crate::query::function_score::FunctionScoreQuery {
                query: ast_to_query(query),
                functions: functions.clone(),
                score_mode: score_mode.clone(),
                boost_mode: boost_mode.clone(),
            })
        }

        QueryAST::Boosting { positive, negative, negative_boost } => {
            Box::new(crate::query::boosting::BoostingQuery {
                positive: ast_to_query(positive),
                negative: ast_to_query(negative),
                negative_boost: *negative_boost,
            })
        }

        QueryAST::Fuzzy { field, value, fuzziness } => Box::new(crate::query::fuzzy::FuzzyQuery {
            field: field.clone(),
            value: value.clone(),
            fuzziness: *fuzziness,
        }),

        QueryAST::Regexp { field, value } => Box::new(crate::query::regexp::RegexpQuery {
            field: field.clone(),
            pattern: value.clone(),
        }),

        QueryAST::Wildcard { field, value } => Box::new(crate::query::wildcard::WildcardQuery {
            field: field.clone(),
            pattern: value.clone(),
        }),

        QueryAST::MultiMatch { fields, query, analyzer } => {
            let should: Vec<Box<dyn Query>> = fields.iter()
                .map(|f| -> Box<dyn Query> {
                    Box::new(MatchQuery {
                        field: f.clone(),
                        query_text: query.clone(),
                        analyzer: analyzer.clone(),
                    })
                })
                .collect();
            Box::new(BoolQuery {
                must: vec![],
                should,
                must_not: vec![],
                filter: vec![],
            })
        }

        QueryAST::SpanTerm { field, value } => Box::new(crate::query::span::SpanTermQuery {
            field: field.clone(),
            value: value.clone(),
        }),

        QueryAST::SpanNear { field, terms, slop, in_order } => Box::new(crate::query::span::SpanNearQuery {
            field: field.clone(),
            terms: terms.clone(),
            slop: *slop,
            in_order: *in_order,
        }),

        QueryAST::SpanNot { include, exclude } => Box::new(crate::query::span::SpanNotQuery {
            include: ast_to_query(include),
            exclude: ast_to_query(exclude),
        }),

        QueryAST::SpanFirst { query, end } => Box::new(crate::query::span::SpanFirstQuery {
            inner: ast_to_query(query),
            end: *end,
        }),

        QueryAST::MatchAll => Box::new(MatchAllQuery),

        QueryAST::MatchNone => Box::new(MatchNoneQuery),
    }
}

/// Matches all documents with score 1.0.
pub struct MatchAllQuery;

impl Query for MatchAllQuery {
    fn create_weight(
        &self,
        _searcher: &crate::search::searcher::Searcher,
        _score_mode: luci_core::ScoreMode,
    ) -> luci_core::Result<Box<dyn crate::query::Weight>> {
        Ok(Box::new(MatchAllWeight))
    }
}

struct MatchAllWeight;

impl crate::query::Weight for MatchAllWeight {
    fn is_match_all(&self) -> bool {
        true
    }

    fn scorer_supplier(
        &self,
        reader: &crate::segment::reader::SegmentReader,
    ) -> luci_core::Result<Option<Box<dyn crate::query::ScorerSupplier>>> {
        let doc_count = reader.doc_count();
        if doc_count == 0 {
            return Ok(None);
        }
        Ok(Some(Box::new(MatchAllScorerSupplier { doc_count })))
    }
}

struct MatchAllScorerSupplier {
    doc_count: u32,
}

impl crate::query::ScorerSupplier for MatchAllScorerSupplier {
    fn cost(&self) -> u64 {
        self.doc_count as u64
    }

    fn scorer(self: Box<Self>) -> luci_core::Result<Box<dyn luci_core::Scorer>> {
        Ok(Box::new(MatchAllScorer {
            current: 0,
            doc_count: self.doc_count,
        }))
    }
}

struct MatchAllScorer {
    current: u32,
    doc_count: u32,
}

impl luci_core::Scorer for MatchAllScorer {
    fn doc_id(&self) -> luci_core::DocId {
        if self.current < self.doc_count {
            luci_core::DocId::new(self.current)
        } else {
            luci_core::NO_MORE_DOCS
        }
    }
    fn next(&mut self) -> luci_core::DocId {
        self.current += 1;
        self.doc_id()
    }
    fn advance(&mut self, target: luci_core::DocId) -> luci_core::DocId {
        self.current = target.as_u32();
        self.doc_id()
    }
    fn score(&mut self) -> f32 {
        1.0
    }
    fn two_phase(&mut self) -> Option<&mut dyn luci_core::TwoPhaseIterator> {
        None
    }
}

pub struct MatchNoneQuery;

impl Query for MatchNoneQuery {
    fn create_weight(
        &self,
        _searcher: &crate::search::searcher::Searcher,
        _score_mode: luci_core::ScoreMode,
    ) -> luci_core::Result<Box<dyn crate::query::Weight>> {
        Ok(Box::new(MatchNoneWeight))
    }
}

struct MatchNoneWeight;

impl crate::query::Weight for MatchNoneWeight {
    fn scorer_supplier(
        &self,
        _reader: &crate::segment::reader::SegmentReader,
    ) -> luci_core::Result<Option<Box<dyn crate::query::ScorerSupplier>>> {
        Ok(None)
    }
}

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

    #[test]
    fn convert_term() {
        let ast = QueryAST::Term {
            field: "f".into(),
            value: "v".into(),
        };
        let _query = ast_to_query(&ast); // should not panic
    }

    #[test]
    fn convert_bool_nested() {
        let ast = QueryAST::Bool {
            must: vec![QueryAST::Term {
                field: "a".into(),
                value: "1".into(),
            }],
            should: vec![QueryAST::Match {
                field: "b".into(),
                query: "hello".into(),
                analyzer: None,
            }],
            must_not: vec![],
            filter: vec![QueryAST::Exists {
                field: "c".into(),
            }],
        };
        let _query = ast_to_query(&ast);
    }

    #[test]
    fn convert_all_types() {
        let types = vec![
            QueryAST::Term { field: "f".into(), value: "v".into() },
            QueryAST::Terms { field: "f".into(), values: vec!["a".into(), "b".into()] },
            QueryAST::Match { field: "f".into(), query: "q".into(), analyzer: None },
            QueryAST::MatchPhrase { field: "f".into(), query: "q".into(), analyzer: None },
            QueryAST::Exists { field: "f".into() },
            QueryAST::Prefix { field: "f".into(), value: "p".into() },
            QueryAST::ConstantScore { query: Box::new(QueryAST::MatchAll), boost: 1.0 },
            QueryAST::MatchAll,
            QueryAST::MatchNone,
        ];
        for ast in &types {
            let _q = ast_to_query(ast);
        }
    }
}
