use std::borrow::{BorrowMut, Cow};
use std::sync::{Arc, Mutex};
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::text::Line;
use ratatui::widgets::{Paragraph, Scrollbar, ScrollbarOrientation};
use tracing::Level;
use crate::global_state::GlobalState;
use crate::logging::SharedLogBuffer;
use crate::types::tui_state::TuiState;
use super::{wrap_string, Theme};
pub fn draw(
f: &mut ratatui::Frame,
_global_state: Arc<GlobalState>,
tui_state: &mut TuiState,
log_buffer: &Arc<Mutex<SharedLogBuffer>>,
area: Rect,
theme: &Theme
) {
let size = area.as_size();
if size.height < 10 || size.width < 10 {
return
}
let mut buffer = log_buffer.lock().expect("locking shared buffer mutex should always work");
if tui_state.log_tab_stage.scroll_state.vertical_scroll.is_none() && buffer.limit.is_none() {
let l = buffer.limit.borrow_mut();
*l = Some(500);
} else if tui_state.log_tab_stage.scroll_state.vertical_scroll.is_some() && buffer.limit.is_some() {
let l = buffer.limit.borrow_mut();
*l = None;
}
let max_msg_width = area.width;
let item_count_len = buffer.logs.len().to_string().len().max(6);
let is_dark_theme = matches!(&theme,Theme::Dark(_));
let s = |level|if is_dark_theme {
match level {
Level::ERROR => Style::default().fg(Color::LightRed),
Level::TRACE => Style::default().fg(Color::LightGreen),
Level::DEBUG => Style::default().fg(Color::LightBlue),
Level::WARN => Style::default().fg(Color::Yellow),
Level::INFO => Style::default().fg(Color::Gray),
}
} else {
match level {
Level::ERROR => Style::default().fg(Color::Red),
Level::TRACE => Style::default().fg(Color::Blue),
Level::DEBUG => Style::default().fg(Color::Green),
Level::WARN => Style::default().fg(Color::Magenta),
Level::INFO => Style::default().fg(Color::Black),
}
};
let thread_name_style = if is_dark_theme { Style::default().fg(Color::DarkGray) } else { Style::default().fg(Color::Black) };
let fg_s = if is_dark_theme { Style::default().fg(Color::White) } else { Style::default().fg(Color::Black) };
let max_site_len = tui_state.site_rects.iter().map(|x|x.1.len()).max().unwrap_or(0);
let items: Vec<Line> =
buffer.logs.iter_mut().enumerate().flat_map(|(i, x)| {
let level = x.lvl;
let nr_str = format!("{:1$} | ", i + 1, item_count_len);
let system_message = x.thread.clone().unwrap_or_default().starts_with("odd_box");
let lvl_str = format!("{:>1$} ", match x.lvl {
Level::INFO if system_message => "INF 🧊",
Level::TRACE if system_message => "TRC 🧊",
Level::DEBUG if system_message => "DBG 🧊",
Level::ERROR if system_message => "ERR 🧊",
Level::WARN if system_message => "WRN 🧊",
Level::INFO => "INF 🥝",
Level::TRACE => "TRC 🥸",
Level::DEBUG => "DBG 👀",
Level::ERROR => "ERR 👺",
Level::WARN => "WRN 🥦",
}, 5);
let thread_str = if let Some(n) = &x.thread {
format!("{n}{} ", " ".repeat(max_site_len.saturating_sub(n.len())))
} else {
(" -").into()
};
x.msg = x.msg.trim().to_string();
let max_width = (max_msg_width as usize)
.saturating_sub(8)
.saturating_sub(nr_str.len() + lvl_str.len() + thread_str.len());
if x.msg.len() > max_width {
wrap_string(x.msg.as_str(), max_width)
.into_iter().enumerate().map(|(i, m)| {
let level_span = if i == 0 {
ratatui::text::Span::styled(lvl_str.clone(), s(level))
} else {
ratatui::text::Span::styled(
Cow::from(" ".repeat(lvl_str.len() - 1)),
Style::default(),
)
};
if i == 0 {
Line::from(vec![
ratatui::text::Span::styled(nr_str.to_string(), fg_s),
level_span,
ratatui::text::Span::styled(thread_str.to_string(), thread_name_style),
ratatui::text::Span::styled(m.clone(), s(level)),
])
} else {
let padding = " ".repeat(lvl_str.len() + thread_str.len() - 2);
Line::from(vec![
ratatui::text::Span::styled(nr_str.to_string(), fg_s),
ratatui::text::Span::styled(padding, Style::default()),
ratatui::text::Span::styled("", Style::default()),
ratatui::text::Span::styled("", Style::default()),
ratatui::text::Span::styled(m.clone(), s(level)),
])
}
}).collect::<Vec<Line>>()
} else {
let padding = if x.src.len() > 0 && !x.src.ends_with(' ') {
" "
} else {
""
};
let message = ratatui::text::Span::styled(
format!("{}{padding}{}", &x.src, &x.msg),
s(level),
);
vec![Line::from(vec![
ratatui::text::Span::styled(nr_str, fg_s),
ratatui::text::Span::styled(lvl_str, s(level)),
ratatui::text::Span::styled(thread_str, thread_name_style),
message,
])]
}
}).collect();
let wrapped_line_count = items.len();
tui_state.log_tab_stage.scroll_state.total_rows = wrapped_line_count;
let height_of_logs_area = area.height.saturating_sub(0); tui_state.log_tab_stage.scroll_state.area_height = height_of_logs_area as usize;
tui_state.log_tab_stage.scroll_state.area_width = area.width as usize;
let scroll_pos = { tui_state.log_tab_stage.scroll_state.vertical_scroll };
let scrollbar_hovered = tui_state.log_tab_stage.scroll_state.scroll_bar_hovered;
let max_scroll_pos = items.len().saturating_sub(height_of_logs_area as usize);
let visible_rows = area.height as usize;
let start = scroll_pos.unwrap_or(max_scroll_pos);
let end = std::cmp::min(start + visible_rows, items.len());
if start > items.len() || end > items.len() || start >= end {
return
}
let display_rows = &items[start..end];
let clamped_items : Vec<Line> = display_rows.iter().map(|x| {
x.clone()
}).collect();
let paragraph = Paragraph::new(clamped_items);
let mut scrollbar = Scrollbar::default()
.style( Style::default())
.orientation(ScrollbarOrientation::VerticalRight)
.begin_symbol(Some("↑"))
.end_symbol(Some("↓")).thumb_style(Style::new().fg(Color::LightBlue));
if scrollbar_hovered {
scrollbar = scrollbar.thumb_style(Style::default().fg(Color::Yellow).bg(Color::LightRed));
}
let mut scrollbar_state = tui_state.log_tab_stage.scroll_state.vertical_scroll_state.borrow_mut();
*scrollbar_state = scrollbar_state.content_length(items.len().saturating_sub(height_of_logs_area as usize));
if scroll_pos.is_none() {
*scrollbar_state = scrollbar_state.position(items.len().saturating_sub(height_of_logs_area as usize));
}
f.render_widget(paragraph, area);
f.render_stateful_widget(scrollbar,area, &mut scrollbar_state);
}