use std::collections::HashMap;
use anyhow::{anyhow, Result};
use easy_error::Error as BundError;
use rust_dynamic::value::Value;
use rust_multistackvm::multistackvm::VM;
use super::helpers::{active_config, active_store, push};
use crate::cli::doctor_scan::{self, ScanFinding};
use crate::health::{HealthEvent, HealthFinding};
use crate::project::ProjectLayout;
use crate::store::hierarchy::Hierarchy;
pub fn register(vm: &mut VM) -> Result<()> {
let words: &[(&str, fn(&mut VM) -> std::result::Result<&mut VM, BundError>)] = &[
("ink.doctor.integrity", w_integrity),
("ink.doctor.vectors", w_vectors),
("ink.doctor.scan", w_scan),
("ink.doctor.autofix", w_autofix),
];
for (name, f) in words {
vm.register_inline(name.to_string(), *f).map_err(|e| anyhow!("register {name}: {e}"))?;
}
for (name, _) in words {
if let Some(short) = name.strip_prefix("ink.") {
let _ = vm.register_alias(short.to_string(), name.to_string());
}
}
Ok(())
}
fn to_bund_err(e: anyhow::Error) -> BundError {
easy_error::err_msg(e.to_string())
}
fn w_integrity(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_integrity(vm).map_err(to_bund_err)
}
fn do_integrity(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.doctor.integrity";
let store = active_store(tag)?;
let (meta, blobs) = store.integrity_check().map_err(|e| anyhow!("{tag}: {e}"))?;
let ok = meta == "ok" && blobs == "ok";
let mut m: HashMap<String, Value> = HashMap::new();
m.insert("meta".into(), Value::from_string(&meta));
m.insert("blobs".into(), Value::from_string(&blobs));
m.insert("ok".into(), Value::from_bool(ok));
push(vm, Value::from_dict(m));
Ok(vm)
}
fn add_finding(m: &mut HashMap<String, Value>, f: &HealthFinding) {
m.insert("detail".into(), Value::from_string(&f.detail));
m.insert("class".into(), Value::from_string(&format!("{:?}", f.class)));
m.insert("severity".into(), Value::from_string(&format!("{:?}", f.severity)));
m.insert("auto_repairable".into(), Value::from_bool(f.auto_repairable));
}
fn w_vectors(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_vectors(vm).map_err(to_bund_err)
}
fn do_vectors(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.doctor.vectors";
let store = active_store(tag)?;
let event = crate::health::check_vector_parity(store);
let rows = store.row_count().map_err(|e| anyhow!("{tag}: {e}"))?;
let vectors = store.vector_count().map_err(|e| anyhow!("{tag}: {e}"))?;
let mut m: HashMap<String, Value> = HashMap::new();
match &event {
HealthEvent::Ok => {
m.insert("status".into(), Value::from_string("ok"));
}
HealthEvent::Warning(f) => {
m.insert("status".into(), Value::from_string("warning"));
add_finding(&mut m, f);
}
HealthEvent::Error(f) => {
m.insert("status".into(), Value::from_string("error"));
add_finding(&mut m, f);
}
HealthEvent::Repaired(f, note) => {
m.insert("status".into(), Value::from_string("repaired"));
add_finding(&mut m, f);
m.insert("note".into(), Value::from_string(note));
}
}
m.insert("rows".into(), Value::from_int(rows as i64));
m.insert("vectors".into(), Value::from_int(vectors as i64));
push(vm, Value::from_dict(m));
Ok(vm)
}
fn finding_dict(f: &ScanFinding) -> Value {
let mut m: HashMap<String, Value> = HashMap::new();
m.insert("class".into(), Value::from_string(f.class.slug()));
m.insert("severity".into(), Value::from_string(f.severity.slug()));
if let Some(p) = &f.path {
m.insert("path".into(), Value::from_string(p));
}
m.insert("detail".into(), Value::from_string(&f.detail));
Value::from_dict(m)
}
fn w_scan(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_scan(vm).map_err(to_bund_err)
}
fn do_scan(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.doctor.scan";
let store = active_store(tag)?;
let cfg = active_config(tag)?;
let layout = ProjectLayout::new(store.project_root());
let h = Hierarchy::load(store).map_err(|e| anyhow!("{tag}: {e}"))?;
let report = doctor_scan::scan_with_store(store, &layout, cfg, &h, None);
let items: Vec<Value> = report.findings.iter().map(finding_dict).collect();
push(vm, Value::from_list(items));
Ok(vm)
}
fn w_autofix(vm: &mut VM) -> std::result::Result<&mut VM, BundError> {
do_autofix(vm).map_err(to_bund_err)
}
fn do_autofix(vm: &mut VM) -> Result<&mut VM> {
let tag = "ink.doctor.autofix";
let store = active_store(tag)?;
let cfg = active_config(tag)?;
let root = store.project_root().to_path_buf();
let layout = ProjectLayout::new(&root);
let mut h = Hierarchy::load(store).map_err(|e| anyhow!("{tag}: {e}"))?;
let report = doctor_scan::scan_with_store(store, &layout, cfg, &h, None);
let (mut applied, mut failed) = (0i64, 0i64);
let mut results: Vec<Value> = Vec::new();
for f in &report.findings {
let outcome = doctor_scan::apply_fix_with(store, &layout, &h, f);
doctor_scan::log_fix(&root, f, &outcome);
let mut d: HashMap<String, Value> = HashMap::new();
d.insert("class".into(), Value::from_string(f.class.slug()));
if let Some(p) = &f.path {
d.insert("path".into(), Value::from_string(p));
}
match &outcome {
Ok(note) => {
applied += 1;
d.insert("ok".into(), Value::from_bool(true));
d.insert("message".into(), Value::from_string(note));
if let Ok(nh) = Hierarchy::load(store) {
h = nh;
}
}
Err(e) => {
failed += 1;
d.insert("ok".into(), Value::from_bool(false));
d.insert("message".into(), Value::from_string(&e.to_string()));
}
}
results.push(Value::from_dict(d));
}
let mut m: HashMap<String, Value> = HashMap::new();
m.insert("applied".into(), Value::from_int(applied));
m.insert("failed".into(), Value::from_int(failed));
m.insert("results".into(), Value::from_list(results));
push(vm, Value::from_dict(m));
Ok(vm)
}