//! Helper functions extracted from the SEARCH executor to keep file size
//! under the 600-line limit (`CODING_GUIDELINES` §9).
//!
//! - User-state filter extraction helpers
//! - `build_user_context` free function (was a private method on `SearchExecutor`)
//! - Creator metadata filter predicates

use std::collections::HashMap;

use crate::entities::{CreatorItemsBitmap, InteractionLedger};
use crate::ranking::executor::UserContext;
use crate::schema::Timestamp;
use crate::storage::indexes::filter::FilterExpr;

// ── User-state filter extraction ─────────────────────────────────────────────

fn collect_user_state_filters<'a>(expr: &'a FilterExpr, out: &mut Vec<&'a FilterExpr>) {
    match expr {
        FilterExpr::Saved(_) | FilterExpr::Liked(_) | FilterExpr::InProgress { .. } => {
            out.push(expr);
        }
        FilterExpr::And(children) | FilterExpr::Or(children) => {
            for child in children {
                collect_user_state_filters(child, out);
            }
        }
        FilterExpr::Not(inner) => {
            collect_user_state_filters(inner, out);
        }
        _ => {}
    }
}

pub(super) fn extract_user_state_filters(expr: &FilterExpr) -> Vec<&FilterExpr> {
    let mut result = Vec::new();
    collect_user_state_filters(expr, &mut result);
    result
}

// ── UserContext builder ───────────────────────────────────────────────────────

/// Build a `UserContext` for personalized scoring.
pub(super) fn build_user_context(
    interaction_ledger: Option<&InteractionLedger>,
    creator_items: Option<&CreatorItemsBitmap>,
    user_id: u64,
    now: Timestamp,
) -> UserContext {
    let now_ns = now.as_nanos();

    let top_creators = interaction_ledger
        .map(|il| il.top_creators(user_id, 50, now_ns))
        .unwrap_or_default();

    let mut creator_interaction_boosts: HashMap<u32, f64> = HashMap::new();
    if let Some(creator_items) = creator_items {
        for (creator_id, weight) in &top_creators {
            if let Some(bitmap) = creator_items.get(*creator_id) {
                for item_id in &bitmap {
                    creator_interaction_boosts.insert(item_id, *weight);
                }
            }
        }
    }

    if !creator_interaction_boosts.is_empty() {
        let max_weight = creator_interaction_boosts
            .values()
            .copied()
            .fold(0.0_f64, f64::max);
        if max_weight > f64::EPSILON {
            for val in creator_interaction_boosts.values_mut() {
                *val /= max_weight;
            }
        }
    }

    UserContext {
        user_id,
        creator_interaction_boosts,
    }
}

// ── Creator metadata filter helpers ──────────────────────────────────────────

/// Check if a filter expression contains metadata-based predicates
/// (`CategoryEq`, `FormatEq`) that need evaluating against entity metadata.
///
/// For creator searches, bitmap indexes are not populated with per-creator
/// metadata like `verified` or `language`. Instead, `FilterExpr::eq("language", "en")`
/// routes to `CategoryEq` and needs to be evaluated against the entity store.
pub(super) fn has_creator_metadata_filter(expr: &FilterExpr) -> bool {
    match expr {
        FilterExpr::CategoryEq(_) | FilterExpr::FormatEq(_) => true,
        FilterExpr::And(children) | FilterExpr::Or(children) => {
            children.iter().any(has_creator_metadata_filter)
        }
        FilterExpr::Not(inner) => has_creator_metadata_filter(inner),
        _ => false,
    }
}

/// Evaluate a filter expression against entity metadata key-value map.
///
/// Returns `true` if the entity passes the filter. Non-metadata filter
/// variants (duration, range, user-state) pass through -- they are handled
/// by earlier stages.
pub(super) fn evaluate_metadata_filter(expr: &FilterExpr, meta: &HashMap<String, String>) -> bool {
    match expr {
        // CategoryEq and FormatEq are the "generic eq" bucket in the current
        // filter expr design. FilterExpr::eq("language", "en") produces
        // CategoryEq("en"), so we check every metadata value for a match.
        FilterExpr::CategoryEq(value) => meta.values().any(|v| v == value),
        FilterExpr::FormatEq(value) => meta.values().any(|v| v == value),
        FilterExpr::And(children) => children.iter().all(|c| evaluate_metadata_filter(c, meta)),
        FilterExpr::Or(children) => children.iter().any(|c| evaluate_metadata_filter(c, meta)),
        FilterExpr::Not(inner) => !evaluate_metadata_filter(inner, meta),
        // Non-metadata filters pass through (already handled by bitmap/range evaluator).
        _ => true,
    }
}
