use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::Frame;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::{ListItem, ListState};
use crate::app_view::AppView;
use crossterm::event::KeyModifiers;
use nexus_core::app::AppsMode;
use super::chrome;
pub fn render(f: &mut Frame, app: &AppView) {
let area = crate::ui::centered(f.area(), 64, 60);
let dim = Style::default().fg(app.theme.fg_dim);
if app.apps_mode == AppsMode::EditFile {
let name = app
.apps_cache
.get(app.apps_selected)
.cloned()
.unwrap_or_default();
let title = chrome::input_title(
app,
format!("edit {name}/"),
&app.apps_edit,
"Enter open in $EDITOR · Esc cancel",
);
let inner = chrome::render_frame(f, area, title, &app.theme, true, chrome::Tone::Normal);
let list = chrome::standard_list(Vec::<ListItem>::new(), &app.theme);
f.render_widget(list, inner);
return;
}
let items: Vec<ListItem> = if app.apps_cache.is_empty() {
vec![ListItem::new(Line::from(Span::styled(
"no apps yet — ask the model to build one",
dim,
)))]
} else {
app.apps_cache
.iter()
.map(|name| {
let n = app.app_file_count(name);
let url = app
.app_url(name)
.unwrap_or_else(|| "server not running".to_string());
let meta = format!("{n} file{} · {url}", if n == 1 { "" } else { "s" });
let content_w = (area.width.saturating_sub(5)) as usize;
let short = chrome::truncate(name, content_w.saturating_sub(1));
let meta = chrome::truncate(&meta, content_w.saturating_sub(3));
ListItem::new(vec![
Line::from(Span::styled(short, Style::default().fg(app.theme.fg))),
Line::from(Span::styled(format!(" {meta}"), dim)),
Line::from(""),
])
})
.collect()
};
let title = match app.apps_mode {
AppsMode::EditFile => unreachable!(),
AppsMode::ConfirmDelete => {
let name = app
.apps_cache
.get(app.apps_selected)
.cloned()
.unwrap_or_default();
chrome::danger_title(
app,
format!("remove app \"{name}\" and all its files?"),
"Ctrl+D confirm · Esc cancel",
)
}
AppsMode::Browse => chrome::popup_title(app, "🧩", "apps"),
};
let hint = match app.apps_mode {
AppsMode::ConfirmDelete => "Ctrl+D confirm · Esc cancel".to_string(),
_ if app.apps_cache.is_empty() => "ask the model to build you one".to_string(),
_ => format!(
"{}↑↓ · Enter open · Ctrl+E edit file · Ctrl+D remove",
chrome::count_hint(app.apps_cache.len(), "app")
),
};
let tone = if app.apps_mode == AppsMode::ConfirmDelete {
chrome::Tone::Danger
} else {
chrome::Tone::Normal
};
let inner = chrome::render_hinted(f, area, title, &hint, app, true, tone);
let list = chrome::standard_list(items, &app.theme);
let mut state = ListState::default();
if !app.apps_cache.is_empty() {
state.select(Some(app.apps_selected.min(app.apps_cache.len() - 1)));
}
chrome::render_list(
f,
list,
&mut state,
inner,
app.apps_cache.len(),
3,
&app.theme,
);
}
pub fn handle_key(app: &mut AppView, key: KeyEvent) -> Result<()> {
use super::{
BrowseAction, ConfirmDeleteAction, EditAction, classify_browse_key,
classify_confirm_delete_key, classify_edit_key,
};
match app.apps_mode {
AppsMode::EditFile => match classify_edit_key(key) {
Some(EditAction::Cancel) => {
app.apps_mode = AppsMode::Browse;
}
Some(EditAction::Save) => app.confirm_app_edit(),
Some(EditAction::Backspace) => {
app.apps_edit.pop();
}
Some(EditAction::Push(c)) => app.apps_edit.push(c),
None => {}
},
AppsMode::ConfirmDelete => match classify_confirm_delete_key(key) {
Some(ConfirmDeleteAction::Yes) => app.confirm_app_delete()?,
Some(ConfirmDeleteAction::No) => app.apps_mode = AppsMode::Browse,
None => {}
},
AppsMode::Browse => {
if key.code == KeyCode::Enter {
app.open_selected_app();
return Ok(());
}
if key.code == KeyCode::Char('e') && key.modifiers.contains(KeyModifiers::CONTROL) {
app.start_app_edit();
return Ok(());
}
match classify_browse_key(key, false, false) {
Some(BrowseAction::Close) => app.popup = nexus_core::app::Popup::None,
Some(BrowseAction::MoveUp) => app.move_apps_selection(-1),
Some(BrowseAction::MoveDown) => app.move_apps_selection(1),
Some(
a @ (BrowseAction::PageUp
| BrowseAction::PageDown
| BrowseAction::Top
| BrowseAction::Bottom),
) => {
super::apply_page(|d| app.move_apps_selection(d), a, 10);
}
Some(BrowseAction::ConfirmDelete) if !app.apps_cache.is_empty() => {
app.apps_mode = AppsMode::ConfirmDelete;
}
_ => {}
}
}
}
Ok(())
}