//! SHCortex — Artificial Hippocampus for LLM context compression.
//!
//! Persistent memory layer: lnhash → 3-ring mesh → BimoH → LCC → SQLite.
//! Works in front of any LLM without modifying the model.
//!
//! ```rust,no_run
//! use shcortex::Shcortex;
//!
//! let mut sh = Shcortex::new(None);
//! sh.compress("SHCortex uses SQLite with WAL mode and FTS5");
//! let ctx = sh.retrieve("database");
//! let (system, user) = sh.build_prompt("how does it work?");
//! ```

pub mod malla_multi;
pub mod prompt_engine;
pub mod lcc;
pub mod persistent_memory;

use std::collections::HashMap;
use std::path::PathBuf;
use self::malla_multi::MallaMulti;
use self::persistent_memory::PersistentMemory;

fn default_db_path() -> PathBuf {
    let home = std::env::var("HOME")
        .or_else(|_| std::env::var("USERPROFILE"))
        .unwrap_or_else(|_| ".".to_string());
    let mut p = PathBuf::from(home);
    p.push(".shcortex");
    let _ = std::fs::create_dir_all(&p);
    p.push("memory.db");
    p
}

pub struct Shcortex {
    malla: MallaMulti,
    memcons: Vec<String>,
    complexities: Vec<f32>,
    atinks: Vec<(String, String)>,
    original_text: String,
    togen_texts: HashMap<u32, String>,
    persistent: Option<PersistentMemory>,
}

impl Shcortex {
    pub fn new(db_path: Option<String>) -> Self {
        let path = db_path
            .map(PathBuf::from)
            .unwrap_or_else(default_db_path);
        let persistent = PersistentMemory::new(path.to_str().unwrap_or("memory.db"))
            .map_err(|e| eprintln!("Warning: persistent memory: {e}"))
            .ok();
        Self {
            malla: MallaMulti::new(3, 64, 0.15, 0.015, 0.07),
            memcons: Vec::new(),
            complexities: Vec::new(),
            atinks: Vec::new(),
            original_text: String::new(),
            togen_texts: HashMap::new(),
            persistent,
        }
    }

    pub fn compress(&mut self, text: &str) {
        self.original_text = text.to_string();
        self.togen_texts.clear();
        let togens = lnhash::text_to_togens(text);
        let words: Vec<&str> = text.split_whitespace().collect();
        let clean_words: Vec<String> = words.iter()
            .map(|w| w.trim_matches(|c: char| !c.is_alphanumeric()).to_string())
            .collect();
        for (i, t) in togens.iter().enumerate() {
            self.malla.inject(t.id, t.ring);
            if i < clean_words.len() {
                self.togen_texts.entry(t.id).or_insert_with(|| clean_words[i].clone());
            }
        }
        for _ in 0..50 {
            let snap = self.malla.activity_snapshot();
            self.malla.step();
            let snap2 = self.malla.activity_snapshot();
            if MallaMulti::converged(&snap, &snap2, 0.001) {
                break;
            }
        }
        let items = self.malla.consolidate_with_activity(0.7);
        self.memcons = items.iter().map(|(id, _)| format!("{id:08x}")).collect();
        self.complexities = items.iter().map(|(_, c)| *c).collect();
        self.malla.create_atinks(5);
        self.atinks = self.malla.atinks.iter()
            .map(|a| (format!("{:08x}", a.source), format!("{:08x}", a.target)))
            .collect();
        self.store_persistent();
    }

    fn store_persistent(&self) {
        if let Some(ref pm) = self.persistent {
            if self.original_text.len() < 10 {
                return;
            }
            let _ = pm.decay(0.95);
            let _ = pm.prune(2);
            let domain = lnhash::detect_domain(&self.original_text);
            let resolved: Vec<String> = self.memcons.iter()
                .map(|hex| self.resolve_id(hex))
                .collect();
            let memcon_items: Vec<(u32, &str, u8, f32)> = resolved.iter()
                .zip(self.complexities.iter())
                .filter_map(|(text, &cplx)| {
                    let id = lnhash::hash_word(text);
                    Some((id, text.as_str(), domain, cplx))
                })
                .collect();
            let _ = pm.store_memcons(&memcon_items);
            let full_id = lnhash::hash_word(&self.original_text);
            let preview: &str = if self.original_text.len() > 150 {
                &self.original_text[..150]
            } else {
                &self.original_text
            };
            let _ = pm.store_memcons(&[(full_id, preview, domain, 0.5)]);
            if !self.atinks.is_empty() {
                let atinks: Vec<(u32, u32, f32)> = self.atinks.iter()
                    .filter_map(|(s, t)| {
                        Some((
                            u32::from_str_radix(s, 16).ok()?,
                            u32::from_str_radix(t, 16).ok()?,
                            1.0,
                        ))
                    })
                    .collect();
                let _ = pm.store_atinks(&atinks);
            }
        }
    }

    fn resolve_id(&self, hex_id: &str) -> String {
        u32::from_str_radix(hex_id, 16)
            .ok()
            .and_then(|id| self.togen_texts.get(&id).cloned())
            .unwrap_or_else(|| hex_id.to_string())
    }

    fn resolve_memcons(&self) -> Vec<String> {
        self.memcons.iter().map(|m| self.resolve_id(m)).collect()
    }

    fn resolve_atinks(&self) -> Vec<(String, String)> {
        self.atinks.iter()
            .map(|(s, t)| (self.resolve_id(s), self.resolve_id(t)))
            .collect()
    }

    pub fn retrieve(&self, question: &str) -> Vec<String> {
        if let Some(ref pm) = self.persistent {
            pm.retrieve_context(question, 255).unwrap_or_default()
        } else {
            Vec::new()
        }
    }

    pub fn build_prompt(&self, question: &str) -> (Option<String>, String) {
        let ctx = self.retrieve(question);
        let system_msg = prompt_engine::PromptEngine::build_system_context(&ctx);
        let tokens = self.original_text.split_whitespace().count();
        if tokens < 500 {
            let engine = prompt_engine::PromptEngine::new(500);
            let text_labels = self.resolve_memcons();
            let atink_labels = self.resolve_atinks();
            let user_msg = engine.build_user_prompt(
                &self.original_text, &text_labels, &atink_labels, question,
            );
            return (system_msg, user_msg);
        }
        let sparsity = lcc::adaptive_sparsity(tokens, &self.complexities);
        let position_map = lcc::build_position_map(&self.original_text);
        let memcons = lcc::memcons_from_hex(&self.memcons, &self.complexities);
        let sparse = lcc::lcc_slice(&self.original_text, &memcons, &position_map, sparsity);
        let user_msg = prompt_engine::PromptEngine::build_user_prompt_sparse(
            &sparse.compact_text, sparse.tokens_selected, sparse.tokens_total, question,
        );
        (system_msg, user_msg)
    }

    pub fn status(&self) -> MemStatus {
        let (total_memcons, total_atinks, total_hectores, db_size_kb) =
            if let Some(ref pm) = self.persistent {
                pm.stats()
                    .map(|s| (s.total_memcons, s.total_atinks, s.total_hectores, s.db_size_kb))
                    .unwrap_or((0, 0, 0, 0))
            } else {
                (0, 0, 0, 0)
            };
        MemStatus {
            text_tokens: self.original_text.split_whitespace().count(),
            ram_memcons: self.memcons.len(),
            ram_atinks: self.atinks.len(),
            db_memcons: total_memcons,
            db_atinks: total_atinks,
            db_hectores: total_hectores,
            db_size_kb,
        }
    }

    pub fn malla_mut(&mut self) -> &mut MallaMulti { &mut self.malla }
    pub fn memcons(&self) -> &[String] { &self.memcons }
    pub fn complexities(&self) -> &[f32] { &self.complexities }
    pub fn atinks(&self) -> &[(String, String)] { &self.atinks }
    pub fn set_memcons(&mut self, memcons: Vec<String>) { self.memcons = memcons; }
    pub fn set_complexities(&mut self, complexities: Vec<f32>) { self.complexities = complexities; }
    pub fn set_atinks(&mut self, atinks: Vec<(String, String)>) { self.atinks = atinks; }
    pub fn original_text(&self) -> &str { &self.original_text }
}

pub struct MemStatus {
    pub text_tokens: usize,
    pub ram_memcons: usize,
    pub ram_atinks: usize,
    pub db_memcons: u64,
    pub db_atinks: u64,
    pub db_hectores: u64,
    pub db_size_kb: u64,
}
