use ratatui::crossterm::event::KeyEvent;
use std::sync::mpsc::TryRecvError;
use super::ai_state::{AiResponse, AiState};
use super::context::{ContextParams, QueryContext};
use super::prompt::build_prompt;
use super::selection::{apply::apply_suggestion, keybindings};
use crate::autocomplete::AutocompleteState;
use crate::input::InputState;
use crate::query::QueryState;
pub fn apply_clicked_suggestion(
suggestion: &super::suggestion::Suggestion,
input_state: &mut InputState,
query_state: &mut QueryState,
autocomplete_state: &mut AutocompleteState,
) {
apply_suggestion(suggestion, input_state, query_state, autocomplete_state);
}
pub fn handle_suggestion_selection(
key: KeyEvent,
ai_state: &mut AiState,
input_state: &mut InputState,
query_state: &mut QueryState,
autocomplete_state: &mut AutocompleteState,
) -> bool {
if !ai_state.visible || ai_state.suggestions.is_empty() {
return false;
}
let suggestion_count = ai_state.suggestions.len();
if let Some(index) = keybindings::handle_direct_selection(key, suggestion_count)
&& let Some(suggestion) = ai_state.suggestions.get(index)
{
apply_suggestion(suggestion, input_state, query_state, autocomplete_state);
ai_state.selection.clear_selection();
return true;
}
if keybindings::handle_navigation(key, &mut ai_state.selection, suggestion_count) {
return true;
}
if let Some(index) = keybindings::handle_apply_selection(key, &ai_state.selection)
&& let Some(suggestion) = ai_state.suggestions.get(index)
{
apply_suggestion(suggestion, input_state, query_state, autocomplete_state);
ai_state.selection.clear_selection();
return true;
}
false
}
pub fn poll_response_channel(ai_state: &mut AiState) -> bool {
if ai_state.response_rx.is_none() {
return false;
}
let mut responses = Vec::new();
let mut disconnected = false;
if let Some(ref rx) = ai_state.response_rx {
loop {
match rx.try_recv() {
Ok(response) => {
responses.push(response);
}
Err(TryRecvError::Empty) => {
break;
}
Err(TryRecvError::Disconnected) => {
disconnected = true;
break;
}
}
}
}
let had_responses = !responses.is_empty();
for response in responses {
process_response(ai_state, response);
}
if disconnected && ai_state.loading {
ai_state.set_error("AI worker disconnected unexpectedly".to_string());
}
had_responses || disconnected
}
pub fn handle_execution_result(
ai_state: &mut AiState,
query_result: &Result<String, String>,
query: &str,
cursor_pos: usize,
params: ContextParams,
) {
let query_changed = ai_state.is_query_changed(query);
if !query_changed {
return;
}
ai_state.cancel_in_flight_request();
ai_state.clear_stale_response();
ai_state.set_last_query_hash(query);
match query_result {
Err(error) => {
if ai_state.visible {
let context = QueryContext::new(
query.to_string(),
cursor_pos,
None,
Some(error.to_string()),
params,
ai_state.max_context_length,
);
let prompt = build_prompt(&context);
ai_state.send_request(prompt);
}
}
Ok(output) => {
if ai_state.visible {
let context = QueryContext::new(
query.to_string(),
cursor_pos,
Some(output.clone()),
None,
params,
ai_state.max_context_length,
);
let prompt = build_prompt(&context);
ai_state.send_request(prompt);
}
}
}
}
pub fn handle_query_result<T: ToString>(
ai_state: &mut AiState,
query_result: &Result<T, String>,
query: &str,
cursor_pos: usize,
params: ContextParams,
) {
let result: Result<String, String> = match query_result {
Ok(output) => Ok(output.to_string()),
Err(e) => Err(e.clone()),
};
handle_execution_result(ai_state, &result, query, cursor_pos, params);
}
fn process_response(ai_state: &mut AiState, response: AiResponse) {
let current_request_id = ai_state.current_request_id();
match response {
AiResponse::Chunk { text, request_id } => {
if request_id < current_request_id {
return;
}
ai_state.append_chunk(&text);
}
AiResponse::Complete { request_id } => {
if request_id < current_request_id {
return;
}
ai_state.complete_request();
}
AiResponse::Error(error_msg) => {
ai_state.set_error(error_msg);
}
AiResponse::Cancelled { request_id: _ } => {
ai_state.loading = false;
ai_state.in_flight_request_id = None;
}
}
}