//! File walking and extraction logic.

use std::path::Path;

use tracing::{info, warn};

use crate::config::AphoriaConfig;
use crate::corpus::{CorpusBuilder, HardcodedCorpusBuilder};
use crate::error::AphoriaError;
use crate::extractors::ExtractorRegistry;
use crate::llm::{is_high_value_file, GeminiClient, LlmCache, LlmExtractor, OntologyVocabulary};
use crate::types::{ExtractedClaim, ScanMode};

use super::filter::ClaimProcessor;

/// Extract claims from all files using configured extractors.
///
/// In persistent mode with LLM enabled, also runs LLM extraction on high-value
/// files where regex extractors found nothing. When pattern learning is enabled,
/// LLM-extracted claims are recorded for potential promotion to declarative extractors.
///
/// The `project_root` is used to compute a stable project hash for pattern learning.
pub fn extract_claims_from_files(
    files: &[crate::walker::WalkedFile],
    config: &AphoriaConfig,
    mode: ScanMode,
    project_root: &Path,
) -> Result<Vec<ExtractedClaim>, AphoriaError> {
    let registry = ExtractorRegistry::new(config);
    let mut all_claims = Vec::new();

    // Initialize LLM extractor ONLY in persistent mode with LLM enabled
    let llm_extractor = if mode == ScanMode::Persistent && config.llm.enabled {
        match create_llm_extractor(config) {
            Ok(Some(ext)) => {
                info!("LLM extractor initialized for persistent mode");
                Some(ext)
            }
            Ok(None) => {
                info!("LLM enabled but API key not available, skipping LLM extraction");
                None
            }
            Err(e) => {
                warn!(error = %e, "Failed to initialize LLM extractor");
                None
            }
        }
    } else {
        None
    };

    // Initialize claim processor for pattern learning
    let processor = ClaimProcessor::new(mode, config, project_root)?;

    let mut llm_files_processed = 0;
    let mut llm_claims_found = 0;
    let mut patterns_recorded = 0;

    for file in files {
        let content = match std::fs::read_to_string(&file.path) {
            Ok(c) => c,
            Err(e) => {
                warn!(file = %file.relative_path, error = %e, "Failed to read file");
                continue;
            }
        };

        // Run regex extractors first
        let regex_claims =
            registry.extract_all(&file.path_segments, &content, file.language, &file.relative_path);

        // If no regex claims AND LLM available AND file is high-value, try LLM extraction
        if regex_claims.is_empty() {
            if let Some(ref llm) = llm_extractor {
                // Only call LLM if high_value_only is false OR file is high-value
                let should_try_llm =
                    !config.llm.high_value_only || is_high_value_file(&file.relative_path);

                if should_try_llm {
                    let claims = llm.extract(
                        &file.path_segments,
                        &content,
                        file.language,
                        &file.relative_path,
                    );
                    if !claims.is_empty() {
                        llm_files_processed += 1;
                        llm_claims_found += claims.len();

                        // Record patterns for LLM-extracted claims (if learning enabled)
                        let count = processor.record_patterns(&claims, file.language);
                        patterns_recorded += count;
                    }
                    all_claims.extend(claims);
                }
            }
        } else {
            all_claims.extend(regex_claims);
        }
    }

    // Log LLM usage summary
    if let Some(ref llm) = llm_extractor {
        info!(
            tokens_used = llm.tokens_used(),
            budget = config.llm.max_tokens_per_scan,
            files_processed = llm_files_processed,
            claims_found = llm_claims_found,
            "LLM extraction complete"
        );
    }

    // Log pattern learning summary
    if patterns_recorded > 0 {
        info!(
            patterns_recorded = patterns_recorded,
            total_patterns = processor.pattern_count(),
            "Pattern learning complete"
        );
    }

    Ok(all_claims)
}

/// Create LLM extractor from config with ontology vocabulary.
///
/// The vocabulary is built from the hardcoded corpus to constrain LLM output
/// to concept paths that match authority subjects, enabling proper conflict detection.
fn create_llm_extractor(config: &AphoriaConfig) -> Result<Option<LlmExtractor>, AphoriaError> {
    let client = match GeminiClient::new(&config.llm)? {
        Some(c) => c,
        None => return Ok(None),
    };

    let cache = LlmCache::new(crate::config::llm_cache_dir());

    // Build ontology vocabulary from hardcoded corpus
    // We use a temporary signing key since vocabulary only needs subject/predicate/object
    let temp_key = crate::bridge::generate_signing_key();
    let builder = HardcodedCorpusBuilder::new();
    let assertions = builder.build(&temp_key, 0, &config.corpus)?;
    let vocabulary = OntologyVocabulary::from_assertions(&assertions);

    info!(concept_count = vocabulary.concepts.len(), "Built ontology vocabulary for LLM");

    Ok(Some(LlmExtractor::with_vocabulary(client, cache, config.llm.clone(), vocabulary)))
}
