//! Query intent inference for hybrid ranking and path-witness recall.
//!
//! Applies ordered phrase rules to derive witness goals, artifact biases, and channel preferences
//! that tune candidate supplements, path-quality multipliers, and reranker guardrails.

use std::collections::BTreeSet;

use crate::domain::{
    ArtifactBias, FrameworkHint, PlannerStrictness, PlaybookReferencePolicy, SearchGoal,
    SearchIntentRuleId, SourceClass,
};
use crate::languages::SymbolLanguage;
use context::QueryContext;
use rules::SEARCH_INTENT_RULES;

#[path = "intent/context.rs"]
mod context;
#[path = "intent/rules.rs"]
mod rules;
#[cfg(test)]
#[path = "intent/tests.rs"]
mod tests;

pub(crate) type HybridRankingIntent = SearchIntent;

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct SearchIntent {
    pub(super) wants_docs: bool,
    pub(super) wants_onboarding: bool,
    pub(super) wants_runtime: bool,
    pub(super) wants_runtime_witnesses: bool,
    pub(super) wants_runtime_config_artifacts: bool,
    pub(super) wants_examples: bool,
    pub(super) wants_laravel_ui_witnesses: bool,
    pub(super) wants_laravel_form_action_witnesses: bool,
    pub(super) wants_livewire_view_witnesses: bool,
    pub(super) wants_laravel_layout_witnesses: bool,
    pub(super) wants_blade_component_witnesses: bool,
    pub(super) wants_commands_middleware_witnesses: bool,
    pub(super) wants_jobs_listeners_witnesses: bool,
    pub(super) wants_entrypoint_build_flow: bool,
    pub(super) wants_ci_workflow_witnesses: bool,
    pub(super) wants_scripts_ops_witnesses: bool,
    pub(super) wants_navigation_fallbacks: bool,
    pub(super) wants_tests: bool,
    pub(super) wants_test_witness_recall: bool,
    pub(super) wants_fixtures: bool,
    pub(super) wants_benchmarks: bool,
    pub(super) wants_readme: bool,
    pub(super) wants_contracts: bool,
    pub(super) wants_error_taxonomy: bool,
    pub(super) wants_tool_contracts: bool,
    pub(super) wants_mcp_runtime_surface: bool,
    pub(super) penalize_playbook_self_reference: bool,
    goals: BTreeSet<SearchGoal>,
    framework_hints: BTreeSet<FrameworkHint>,
    artifact_biases: BTreeSet<ArtifactBias>,
    strictness: PlannerStrictness,
    playbook_reference_policy: PlaybookReferencePolicy,
    applied_rule_ids: Vec<SearchIntentRuleId>,
}

#[derive(Debug, Clone, Copy)]
pub(super) struct SearchIntentQuerySignals<'a> {
    intent: &'a SearchIntent,
}

impl SearchIntentQuerySignals<'_> {
    pub(super) fn wants_docs(self) -> bool {
        self.intent.wants_docs
    }

    pub(super) fn wants_runtime(self) -> bool {
        self.intent.wants_runtime
    }

    pub(super) fn wants_examples(self) -> bool {
        self.intent.wants_examples
    }

    pub(super) fn wants_entrypoint_build_flow(self) -> bool {
        self.intent.wants_entrypoint_build_flow
    }

    pub(super) fn wants_tests(self) -> bool {
        self.intent.wants_tests
    }

    pub(super) fn wants_fixtures(self) -> bool {
        self.intent.wants_fixtures
    }

    pub(super) fn wants_benchmarks(self) -> bool {
        self.intent.wants_benchmarks
    }

    pub(super) fn wants_readme(self) -> bool {
        self.intent.wants_readme
    }

    pub(super) fn wants_contracts(self) -> bool {
        self.intent.wants_contracts
    }

    pub(super) fn wants_error_taxonomy(self) -> bool {
        self.intent.wants_error_taxonomy
    }

    pub(super) fn wants_tool_contracts(self) -> bool {
        self.intent.wants_tool_contracts
    }
}

#[derive(Debug, Clone, Copy)]
pub(super) struct SearchIntentWitnessSignals<'a> {
    intent: &'a SearchIntent,
}

impl SearchIntentWitnessSignals<'_> {
    pub(super) fn wants_runtime_witnesses(self) -> bool {
        self.intent.wants_runtime_witnesses
    }

    pub(super) fn wants_runtime_config_artifacts(self) -> bool {
        self.intent.wants_runtime_config_artifacts
    }

    pub(super) fn wants_laravel_ui_witnesses(self) -> bool {
        self.intent.wants_laravel_ui_witnesses
    }

    pub(super) fn wants_commands_middleware_witnesses(self) -> bool {
        self.intent.wants_commands_middleware_witnesses
    }

    pub(super) fn wants_jobs_listeners_witnesses(self) -> bool {
        self.intent.wants_jobs_listeners_witnesses
    }

    pub(super) fn wants_ci_workflow_witnesses(self) -> bool {
        self.intent.wants_ci_workflow_witnesses
    }

    pub(super) fn wants_scripts_ops_witnesses(self) -> bool {
        self.intent.wants_scripts_ops_witnesses
    }

    pub(super) fn wants_test_witness_recall(self) -> bool {
        self.intent.wants_test_witness_recall
    }
}

impl Default for SearchIntent {
    fn default() -> Self {
        Self {
            wants_docs: false,
            wants_onboarding: false,
            wants_runtime: false,
            wants_runtime_witnesses: false,
            wants_runtime_config_artifacts: false,
            wants_examples: false,
            wants_laravel_ui_witnesses: false,
            wants_laravel_form_action_witnesses: false,
            wants_livewire_view_witnesses: false,
            wants_laravel_layout_witnesses: false,
            wants_blade_component_witnesses: false,
            wants_commands_middleware_witnesses: false,
            wants_jobs_listeners_witnesses: false,
            wants_entrypoint_build_flow: false,
            wants_ci_workflow_witnesses: false,
            wants_scripts_ops_witnesses: false,
            wants_navigation_fallbacks: false,
            wants_tests: false,
            wants_test_witness_recall: false,
            wants_fixtures: false,
            wants_benchmarks: false,
            wants_readme: false,
            wants_contracts: false,
            wants_error_taxonomy: false,
            wants_tool_contracts: false,
            wants_mcp_runtime_surface: false,
            penalize_playbook_self_reference: true,
            goals: BTreeSet::new(),
            framework_hints: BTreeSet::new(),
            artifact_biases: BTreeSet::new(),
            strictness: PlannerStrictness::Broad,
            playbook_reference_policy: PlaybookReferencePolicy::PenalizeSelfReference,
            applied_rule_ids: Vec::new(),
        }
    }
}

#[allow(dead_code)]
impl SearchIntent {
    pub(crate) fn from_query(query_text: &str) -> Self {
        let context = QueryContext::new(query_text);
        let mut builder = SearchIntentBuilder::default();
        builder.insert_goal(SearchGoal::Runtime);
        builder.populate_framework_hints(&context);
        builder.playbook_reference_policy = if context.mentions_playbooks() {
            PlaybookReferencePolicy::AllowSelfReference
        } else {
            PlaybookReferencePolicy::PenalizeSelfReference
        };

        for rule in SEARCH_INTENT_RULES {
            if (rule.apply)(&context, &mut builder) {
                builder.record_rule(rule.id);
            }
        }

        builder.build()
    }

    pub(super) fn goals(&self) -> &BTreeSet<SearchGoal> {
        &self.goals
    }

    pub(super) fn query_signals(&self) -> SearchIntentQuerySignals<'_> {
        SearchIntentQuerySignals { intent: self }
    }

    pub(super) fn witness_signals(&self) -> SearchIntentWitnessSignals<'_> {
        SearchIntentWitnessSignals { intent: self }
    }

    pub(super) fn framework_hints(&self) -> &BTreeSet<FrameworkHint> {
        &self.framework_hints
    }

    pub(super) fn artifact_biases(&self) -> &BTreeSet<ArtifactBias> {
        &self.artifact_biases
    }

    pub(super) fn strictness(&self) -> PlannerStrictness {
        self.strictness
    }

    pub(super) fn playbook_reference_policy(&self) -> PlaybookReferencePolicy {
        self.playbook_reference_policy
    }

    pub(super) fn penalizes_playbook_self_reference(&self) -> bool {
        self.penalize_playbook_self_reference
    }

    pub(super) fn penalizes_generic_runtime_docs(&self) -> bool {
        !self.wants_docs && !self.wants_onboarding && !self.wants_readme
    }

    pub(super) fn applied_rule_ids(&self) -> &[SearchIntentRuleId] {
        &self.applied_rule_ids
    }

    pub(super) fn has_goal(&self, goal: SearchGoal) -> bool {
        self.goals.contains(&goal)
    }

    pub(super) fn has_framework_hint(&self, hint: FrameworkHint) -> bool {
        self.framework_hints.contains(&hint)
    }

    pub(super) fn has_artifact_bias(&self, bias: ArtifactBias) -> bool {
        self.artifact_biases.contains(&bias)
    }

    pub(super) fn has_language_hint(&self) -> bool {
        self.framework_hints.iter().any(|hint| {
            matches!(
                hint,
                FrameworkHint::Rust
                    | FrameworkHint::Php
                    | FrameworkHint::TypeScript
                    | FrameworkHint::Python
                    | FrameworkHint::Go
                    | FrameworkHint::Kotlin
                    | FrameworkHint::Lua
                    | FrameworkHint::Roc
                    | FrameworkHint::Nim
                    | FrameworkHint::Blade
            )
        })
    }

    pub(super) fn wants_language_locality_bias(&self) -> bool {
        self.has_language_hint()
            && (self.wants_runtime_witnesses
                || self.wants_runtime_config_artifacts
                || self.wants_entrypoint_build_flow
                || self.wants_test_witness_recall)
    }

    pub(super) fn prefers_symbol_language(&self, language: SymbolLanguage) -> bool {
        match language {
            SymbolLanguage::Rust => self.has_framework_hint(FrameworkHint::Rust),
            SymbolLanguage::Php => {
                self.has_framework_hint(FrameworkHint::Php)
                    || self.has_framework_hint(FrameworkHint::Blade)
                    || self.has_framework_hint(FrameworkHint::Laravel)
            }
            SymbolLanguage::Blade => {
                self.has_framework_hint(FrameworkHint::Blade)
                    || self.has_framework_hint(FrameworkHint::Php)
                    || self.has_framework_hint(FrameworkHint::Laravel)
            }
            SymbolLanguage::TypeScript => self.has_framework_hint(FrameworkHint::TypeScript),
            SymbolLanguage::Python => self.has_framework_hint(FrameworkHint::Python),
            SymbolLanguage::Go => self.has_framework_hint(FrameworkHint::Go),
            SymbolLanguage::Kotlin => self.has_framework_hint(FrameworkHint::Kotlin),
            SymbolLanguage::Java => false,
            SymbolLanguage::Lua => self.has_framework_hint(FrameworkHint::Lua),
            SymbolLanguage::Roc => self.has_framework_hint(FrameworkHint::Roc),
            SymbolLanguage::Nim => self.has_framework_hint(FrameworkHint::Nim),
        }
    }

    pub(super) fn wants_path_witness_recall(&self) -> bool {
        let query = self.query_signals();
        let witness = self.witness_signals();

        witness.wants_runtime_witnesses()
            || witness.wants_laravel_ui_witnesses()
            || witness.wants_commands_middleware_witnesses()
            || witness.wants_jobs_listeners_witnesses()
            || query.wants_entrypoint_build_flow()
            || witness.wants_ci_workflow_witnesses()
            || witness.wants_scripts_ops_witnesses()
            || witness.wants_runtime_config_artifacts()
            || witness.wants_test_witness_recall()
            || self.wants_example_or_bench_witnesses()
    }

    pub(super) fn wants_example_or_bench_witnesses(&self) -> bool {
        self.wants_examples || self.wants_benchmarks
    }

    pub(super) fn wants_class(&self, class: SourceClass) -> bool {
        let query = self.query_signals();
        let witness = self.witness_signals();

        match class {
            SourceClass::ErrorContracts => query.wants_error_taxonomy() || query.wants_contracts(),
            SourceClass::ToolContracts => query.wants_tool_contracts() || query.wants_contracts(),
            SourceClass::BenchmarkDocs => query.wants_benchmarks(),
            SourceClass::Documentation => query.wants_docs(),
            SourceClass::Readme => query.wants_readme(),
            SourceClass::Runtime => query.wants_runtime(),
            SourceClass::Project => false,
            SourceClass::Support => {
                witness.wants_runtime_witnesses()
                    || query.wants_examples()
                    || query.wants_benchmarks()
                    || witness.wants_ci_workflow_witnesses()
                    || witness.wants_scripts_ops_witnesses()
            }
            SourceClass::Tests => {
                query.wants_tests() || witness.wants_runtime_witnesses() || query.wants_examples()
            }
            SourceClass::Fixtures => query.wants_fixtures(),
            _ => false,
        }
    }
}

#[derive(Debug)]
struct SearchIntentBuilder {
    goals: BTreeSet<SearchGoal>,
    framework_hints: BTreeSet<FrameworkHint>,
    artifact_biases: BTreeSet<ArtifactBias>,
    applied_rule_ids: Vec<SearchIntentRuleId>,
    strictness: Option<PlannerStrictness>,
    playbook_reference_policy: PlaybookReferencePolicy,
}

impl SearchIntentBuilder {
    fn insert_goal(&mut self, goal: SearchGoal) {
        self.goals.insert(goal);
    }

    fn insert_framework_hint(&mut self, hint: FrameworkHint) {
        self.framework_hints.insert(hint);
    }

    fn insert_artifact_bias(&mut self, bias: ArtifactBias) {
        self.artifact_biases.insert(bias);
    }

    fn has_goal(&self, goal: SearchGoal) -> bool {
        self.goals.contains(&goal)
    }

    fn has_artifact_bias(&self, bias: ArtifactBias) -> bool {
        self.artifact_biases.contains(&bias)
    }

    fn record_rule(&mut self, rule_id: SearchIntentRuleId) {
        if !self.applied_rule_ids.contains(&rule_id) {
            self.applied_rule_ids.push(rule_id);
        }
    }

    fn populate_framework_hints(&mut self, context: &QueryContext) {
        if context.mentions_rust_family() {
            self.insert_framework_hint(FrameworkHint::Rust);
        }
        if context.mentions_php_family() {
            self.insert_framework_hint(FrameworkHint::Php);
        }
        if context.mentions_typescript_family() {
            self.insert_framework_hint(FrameworkHint::TypeScript);
        }
        if context.mentions_python_family() {
            self.insert_framework_hint(FrameworkHint::Python);
        }
        if context.mentions_go_family() {
            self.insert_framework_hint(FrameworkHint::Go);
        }
        if context.mentions_kotlin_family() {
            self.insert_framework_hint(FrameworkHint::Kotlin);
        }
        if context.mentions_lua_family() {
            self.insert_framework_hint(FrameworkHint::Lua);
        }
        if context.mentions_roc_family() {
            self.insert_framework_hint(FrameworkHint::Roc);
        }
        if context.mentions_nim_family() {
            self.insert_framework_hint(FrameworkHint::Nim);
        }
        if context.has_any(&["blade"]) {
            self.insert_framework_hint(FrameworkHint::Blade);
        }
        if context.has_any(&["laravel", "blade", "livewire", "flux", "artisan"]) {
            self.insert_framework_hint(FrameworkHint::Laravel);
        }
        if context.has_any(&["livewire"]) {
            self.insert_framework_hint(FrameworkHint::Livewire);
        }
        if context.has_any(&["flux"]) {
            self.insert_framework_hint(FrameworkHint::Flux);
        }
        if context.has_any(&["mcp"]) {
            self.insert_framework_hint(FrameworkHint::Mcp);
        }
    }

    fn build(self) -> SearchIntent {
        let strictness = self.strictness.unwrap_or_else(|| {
            if self.goals.contains(&SearchGoal::RuntimeWitnesses)
                || self.artifact_biases.contains(&ArtifactBias::LaravelUi)
                || self
                    .artifact_biases
                    .contains(&ArtifactBias::RuntimeConfigArtifact)
                || self.artifact_biases.contains(&ArtifactBias::TestWitness)
            {
                PlannerStrictness::WitnessFocused
            } else {
                PlannerStrictness::Broad
            }
        });

        let wants_docs = self.goals.contains(&SearchGoal::Documentation);
        let wants_onboarding = self.goals.contains(&SearchGoal::Onboarding);
        let wants_runtime = self.goals.contains(&SearchGoal::Runtime);
        let wants_runtime_witnesses = self.goals.contains(&SearchGoal::RuntimeWitnesses);
        let wants_runtime_config_artifacts = self
            .artifact_biases
            .contains(&ArtifactBias::RuntimeConfigArtifact);
        let wants_examples = self.goals.contains(&SearchGoal::Examples);
        let wants_laravel_ui_witnesses = self.artifact_biases.contains(&ArtifactBias::LaravelUi);
        let wants_laravel_form_action_witnesses = self
            .artifact_biases
            .contains(&ArtifactBias::LaravelFormAction);
        let wants_livewire_view_witnesses =
            self.artifact_biases.contains(&ArtifactBias::LivewireView);
        let wants_laravel_layout_witnesses =
            self.artifact_biases.contains(&ArtifactBias::LaravelLayout);
        let wants_blade_component_witnesses =
            self.artifact_biases.contains(&ArtifactBias::BladeComponent);
        let wants_commands_middleware_witnesses = self
            .artifact_biases
            .contains(&ArtifactBias::CommandsMiddleware);
        let wants_jobs_listeners_witnesses =
            self.artifact_biases.contains(&ArtifactBias::JobsListeners);
        let wants_entrypoint_build_flow = self.goals.contains(&SearchGoal::EntryPointBuildFlow);
        let wants_ci_workflow_witnesses = self.artifact_biases.contains(&ArtifactBias::CiWorkflow);
        let wants_scripts_ops_witnesses = self.artifact_biases.contains(&ArtifactBias::ScriptsOps);
        let wants_navigation_fallbacks = self.goals.contains(&SearchGoal::NavigationFallbacks);
        let wants_tests = self.goals.contains(&SearchGoal::Tests);
        let wants_test_witness_recall = self.artifact_biases.contains(&ArtifactBias::TestWitness);
        let wants_fixtures = self.goals.contains(&SearchGoal::Fixtures);
        let wants_benchmarks = self.goals.contains(&SearchGoal::Benchmarks);
        let wants_readme = self.goals.contains(&SearchGoal::Readme);
        let wants_contracts = self.goals.contains(&SearchGoal::Contracts);
        let wants_error_taxonomy = self.goals.contains(&SearchGoal::ErrorTaxonomy);
        let wants_tool_contracts = self.goals.contains(&SearchGoal::ToolContracts);
        let wants_mcp_runtime_surface = self.goals.contains(&SearchGoal::McpRuntimeSurface);
        let penalize_playbook_self_reference =
            self.playbook_reference_policy == PlaybookReferencePolicy::PenalizeSelfReference;

        SearchIntent {
            wants_docs,
            wants_onboarding,
            wants_runtime,
            wants_runtime_witnesses,
            wants_runtime_config_artifacts,
            wants_examples,
            wants_laravel_ui_witnesses,
            wants_laravel_form_action_witnesses,
            wants_livewire_view_witnesses,
            wants_laravel_layout_witnesses,
            wants_blade_component_witnesses,
            wants_commands_middleware_witnesses,
            wants_jobs_listeners_witnesses,
            wants_entrypoint_build_flow,
            wants_ci_workflow_witnesses,
            wants_scripts_ops_witnesses,
            wants_navigation_fallbacks,
            wants_tests,
            wants_test_witness_recall,
            wants_fixtures,
            wants_benchmarks,
            wants_readme,
            wants_contracts,
            wants_error_taxonomy,
            wants_tool_contracts,
            wants_mcp_runtime_surface,
            penalize_playbook_self_reference,
            goals: self.goals,
            framework_hints: self.framework_hints,
            artifact_biases: self.artifact_biases,
            strictness,
            playbook_reference_policy: self.playbook_reference_policy,
            applied_rule_ids: self.applied_rule_ids,
        }
    }
}

impl Default for SearchIntentBuilder {
    fn default() -> Self {
        Self {
            goals: BTreeSet::new(),
            framework_hints: BTreeSet::new(),
            artifact_biases: BTreeSet::new(),
            applied_rule_ids: Vec::new(),
            strictness: None,
            playbook_reference_policy: PlaybookReferencePolicy::PenalizeSelfReference,
        }
    }
}
