use std::sync::mpsc::Sender;
use rag_rat_core::index::oracle::{OracleTool, ToolAvailability, ToolManifest, probe_oracle_tool};
use rag_rat_core::language::Language;
use ratatui::Frame;
use ratatui::crossterm::event::{KeyCode, KeyEvent};
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Paragraph, Wrap};
use super::super::probe::{ProbeKind, ProbeStatus};
use super::super::state::WizardState;
use super::super::theme;
use super::types::{CheckResult, Outcome, Sev, StepId};
pub(super) fn render_oracle(f: &mut Frame, area: Rect, state: &WizardState) {
let chunks = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]).split(area);
let on = state.draft.oracle_auto_run;
let label = if on { "[ ON ]" } else { "[ OFF ]" };
f.render_widget(
Paragraph::new(Span::styled(label, theme::focus()))
.style(theme::base())
.block(theme::block("space: toggle t: test")),
chunks[0],
);
let detected: Vec<&str> = Language::all()
.iter()
.filter(|&&l| state.scan.language_counts().get(&l).copied().unwrap_or(0) > 0)
.map(|&l| l.as_str())
.collect();
let mut lines = vec![
Line::from(Span::styled("SCIP tools for your languages:", theme::base())),
Line::from(""),
];
match state.probes.status(StepId::Oracle) {
ProbeStatus::Running(ProbeKind::OracleTool) => {
lines.push(Line::from(Span::styled(" Tool test running...", theme::warning())));
lines.push(Line::from(""));
},
ProbeStatus::Done { result, .. } => {
let style = match result.severity {
Sev::Ok => theme::success(),
Sev::Warn => theme::warning(),
Sev::Block => theme::error(),
};
let msg = result.message.as_deref().unwrap_or("Oracle tool test passed.");
lines.push(Line::from(Span::styled(format!(" Tool test: {msg}"), style)));
lines.push(Line::from(""));
},
_ => {},
}
for tool in OracleTool::ALL {
let m = ToolManifest::for_tool(*tool);
let relevant = m.languages.iter().any(|l| detected.contains(l));
let style = if relevant { theme::accent() } else { theme::muted() };
let name = format!("{:?}", tool)
.replace("RustAnalyzer", "rust-analyzer")
.replace("ScipClang", "scip-clang")
.replace("ScipPython", "scip-python")
.replace("ScipTypescript", "scip-typescript")
.replace("ScipJava", "scip-java");
lines.push(Line::from(vec![
Span::styled(format!(" {} {} ", if relevant { "▶" } else { " " }, name), style),
Span::styled(format!("— {}", m.languages.join(", ")), theme::muted()),
]));
}
f.render_widget(
Paragraph::new(lines)
.style(theme::base())
.wrap(Wrap { trim: false })
.block(theme::block("Tool availability")),
chunks[1],
);
}
pub(super) fn handle_oracle(key: KeyEvent, state: &mut WizardState) -> Outcome {
match key.code {
KeyCode::Char(' ') => {
state.draft.oracle_auto_run = !state.draft.oracle_auto_run;
Outcome::Consumed
},
KeyCode::Char('t') | KeyCode::Char('T') => {
let tools = tools_for_scan(state);
let (log_tx, log_rx) = std::sync::mpsc::channel();
state.start_oracle_log(log_rx);
state.probes.spawn(StepId::Oracle, ProbeKind::OracleTool, move || {
probe_oracle_tools(tools, log_tx)
});
Outcome::Consumed
},
KeyCode::Enter => Outcome::Advance,
KeyCode::Esc => Outcome::Back,
_ => Outcome::Pass,
}
}
fn tools_for_scan(state: &WizardState) -> Vec<OracleTool> {
let detected: Vec<&str> = Language::all()
.iter()
.filter(|&&l| state.scan.language_counts().get(&l).copied().unwrap_or(0) > 0)
.map(|&l| l.as_str())
.collect();
OracleTool::ALL
.iter()
.copied()
.filter(|&t| ToolManifest::for_tool(t).languages.iter().any(|l| detected.contains(l)))
.collect()
}
fn probe_oracle_tools(tools: Vec<OracleTool>, log_tx: Sender<String>) -> CheckResult {
if tools.is_empty() {
send_log(&log_tx, "No detected languages need Oracle tools.");
return CheckResult::ok();
}
send_log(&log_tx, format!("Checking {} Oracle tool(s)...", tools.len()));
for tool in tools {
let manifest = ToolManifest::for_tool(tool);
send_log(&log_tx, format!("Checking {} ({})...", tool.as_db_str(), manifest.program));
match probe_oracle_tool(tool) {
ToolAvailability::Available { program, version, .. } => {
send_log(&log_tx, format!("available: {program} {version}"));
},
ToolAvailability::Blocked { program, hint, .. } => {
send_log(&log_tx, format!("blocked: {program}"));
send_log(&log_tx, hint.clone());
return CheckResult::warn(hint);
},
}
}
send_log(&log_tx, "All relevant Oracle tools are available.");
CheckResult::ok()
}
pub(super) fn send_log(log_tx: &Sender<String>, line: impl Into<String>) {
let _ = log_tx.send(line.into());
}