//! ExistsQuery: match documents where a field has a value.
//!
//! Checks field norms — any doc with norm > 0 has the field indexed.
//!
//! See [[query-dsl#Term-Level Queries]] and [[architecture-query-execution#Step 6]].

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

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

pub struct ExistsQuery {
    pub field: String,
}

impl Query for ExistsQuery {
    fn create_weight(
        &self,
        _searcher: &Searcher,
        _score_mode: ScoreMode,
    ) -> Result<Box<dyn Weight>> {
        Ok(Box::new(ExistsWeight {
            field: self.field.clone(),
        }))
    }
}

struct ExistsWeight {
    field: String,
}

impl Weight for ExistsWeight {
    fn scorer_supplier(
        &self,
        reader: &SegmentReader,
    ) -> Result<Option<Box<dyn ScorerSupplier>>> {
        let field_id = match reader.header().fields.iter()
            .find(|f| f.field_name == self.field)
            .map(|f| f.field_id)
        {
            Some(id) => id,
            None => return Ok(None),
        };

        // Check if norms exist for this field
        if reader.norms(field_id).is_none() {
            return Ok(None);
        }

        Ok(Some(Box::new(ExistsScorerSupplier {
            field_id,
            doc_count: reader.doc_count(),
            segment_data: reader as *const SegmentReader,
        })))
    }
}

struct ExistsScorerSupplier {
    field_id: FieldId,
    doc_count: u32,
    segment_data: *const SegmentReader,
}

unsafe impl Send for ExistsScorerSupplier {}

impl ScorerSupplier for ExistsScorerSupplier {
    fn cost(&self) -> u64 {
        self.doc_count as u64
    }

    fn scorer(self: Box<Self>) -> Result<Box<dyn Scorer>> {
        let reader = unsafe { &*self.segment_data };
        let norms = reader.norms(self.field_id).unwrap();
        Ok(Box::new(ExistsScorer {
            doc_count: self.doc_count,
            current: 0,
            norms_fn: Box::new(move |doc_id: DocId| norms.norm(doc_id) > 0.0),
        }))
    }
}

struct ExistsScorer {
    doc_count: u32,
    current: u32,
    norms_fn: Box<dyn Fn(DocId) -> bool + Send>,
}

impl ExistsScorer {
    fn advance_to_existing(&mut self) {
        while self.current < self.doc_count {
            if (self.norms_fn)(DocId::new(self.current)) {
                return;
            }
            self.current += 1;
        }
    }
}

impl Scorer for ExistsScorer {
    fn doc_id(&self) -> DocId {
        if self.current < self.doc_count {
            DocId::new(self.current)
        } else {
            NO_MORE_DOCS
        }
    }

    fn next(&mut self) -> DocId {
        self.current += 1;
        self.advance_to_existing();
        self.doc_id()
    }

    fn advance(&mut self, target: DocId) -> DocId {
        self.current = target.as_u32();
        self.advance_to_existing();
        self.doc_id()
    }

    fn score(&mut self) -> f32 { 1.0 }
    fn two_phase(&mut self) -> Option<&mut dyn TwoPhaseIterator> { None }
}
