//! Heuristic-based generation of memory item candidates from scan results.
//!
//! Produces 3-5 structured candidates that a human can confirm, reject, or reformulate.
//! Rules are binary and traceable — no opaque scoring.

mod dedupe;
mod heuristics;
mod render;

#[cfg(test)]
mod tests;

use super::scan::DomainInfo;

use dedupe::{assign_ids, deduplicate, prioritize_and_limit};
use heuristics::{
    detect_business_rule_candidates, detect_decision_candidates, detect_exception_candidates,
};

// ── Types ──────────────────────────────────────────────────────────

/// A memory item candidate generated by heuristics.
#[derive(Debug, Clone)]
pub struct Candidate {
    /// Candidate ID (e.g. "billing-001").
    pub id: String,
    /// Target domain.
    pub domain: String,
    /// Proposed type.
    pub type_: CandidateType,
    /// Proposed text describing the candidate.
    pub text: String,
    /// Human-readable rationale for why this was proposed.
    pub rationale: String,
    /// Files and tests that support this candidate.
    pub provenance: Vec<ProvenanceEntry>,
    /// Target note path.
    pub target_note: String,
}

/// Proposed type for a candidate, mirroring [`crate::wiki::note::MemoryItemType`].
///
/// Kept separate because candidates are ephemeral (generated, then confirmed/rejected),
/// while `MemoryItemType` is part of the persisted schema contract.
#[derive(Debug, Clone, PartialEq)]
pub enum CandidateType {
    Exception,
    Decision,
    BusinessRule,
}

impl std::fmt::Display for CandidateType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            CandidateType::Exception => write!(f, "exception"),
            CandidateType::Decision => write!(f, "decision"),
            CandidateType::BusinessRule => write!(f, "business_rule"),
        }
    }
}

#[derive(Debug, Clone)]
pub struct ProvenanceEntry {
    pub kind: String, // "file", "test", "comment"
    pub ref_: String,
}

// ── Constants ──────────────────────────────────────────────────────

const MAX_CANDIDATES: usize = 5;

// ── Public API ─────────────────────────────────────────────────────

pub use render::write_candidates_file;

/// Generate memory item candidates from scan results.
pub fn generate(domains: &[DomainInfo]) -> Vec<Candidate> {
    let mut all_candidates: Vec<Candidate> = Vec::new();

    for domain in domains {
        let mut domain_candidates = Vec::new();

        // Heuristic 1: exception candidates from file naming
        detect_exception_candidates(domain, &mut domain_candidates);

        // Heuristic 2: decision candidates from comments
        detect_decision_candidates(domain, &mut domain_candidates);

        // Heuristic 3: business_rule candidates from test + comment combinations
        detect_business_rule_candidates(domain, &mut domain_candidates);

        all_candidates.extend(domain_candidates);
    }

    // Deduplicate: one candidate per (file, type) pair
    deduplicate(&mut all_candidates);

    // Prioritize and limit
    prioritize_and_limit(&mut all_candidates);

    // Assign sequential IDs
    assign_ids(&mut all_candidates);

    all_candidates
}
