use ratatui::prelude::*;
use ratatui::widgets::{Block, Borders, Paragraph};
use crate::app::{App, AppState, SetupField};
use crate::engine::{patterns::PhaseStyle, PATTERNS};
pub fn draw(f: &mut Frame, app: &App) {
let AppState::Setup(setup_state) = &app.state else {
return;
};
let area = f.size();
let pattern = &PATTERNS[setup_state.pattern_idx];
let outer_block = Block::default()
.title("Session Setup")
.title_alignment(Alignment::Center)
.borders(Borders::ALL)
.border_style(Style::default().fg(Color::Rgb(90, 70, 130)));
f.render_widget(outer_block, area);
let inner = Rect {
x: area.x + 2,
y: area.y + 2,
width: area.width.saturating_sub(4),
height: area.height.saturating_sub(4),
};
f.render_widget(
Paragraph::new(pattern.display_name)
.alignment(Alignment::Center)
.style(Style::default().fg(Color::Rgb(200, 175, 255)).bold()),
Rect { x: inner.x, y: inner.y, width: inner.width, height: 1 },
);
let phase_summary = pattern.phases.iter()
.map(|p| format!("{} {}s", p.name, p.duration_secs as u32))
.collect::<Vec<_>>()
.join(" · ");
f.render_widget(
Paragraph::new(phase_summary)
.alignment(Alignment::Center)
.style(Style::default().fg(Color::Rgb(130, 120, 155))),
Rect { x: inner.x, y: inner.y + 1, width: inner.width, height: 1 },
);
let card_y = inner.y + 3;
let card_h: u16 = 7;
let card_gap: u16 = 2;
let left_w = inner.width / 2 - card_gap / 2;
let right_x = inner.x + inner.width / 2 + card_gap / 2;
let right_w = inner.width.saturating_sub(inner.width / 2 + card_gap / 2);
let dur_selected = setup_state.selected_field == SetupField::Duration;
let speed_selected = setup_state.selected_field == SetupField::Tempo;
let dur_border = if dur_selected { Color::Cyan } else { Color::Rgb(70, 60, 100) };
let dur_rect = Rect { x: inner.x, y: card_y, width: left_w, height: card_h };
f.render_widget(
Block::default()
.title(" Duration ")
.title_style(Style::default().fg(if dur_selected { Color::Cyan } else { Color::Rgb(130, 120, 155) }))
.borders(Borders::ALL)
.border_style(Style::default().fg(dur_border)),
dur_rect,
);
let dur_inner = Rect {
x: dur_rect.x + 1,
y: dur_rect.y + 2,
width: left_w.saturating_sub(2),
height: card_h.saturating_sub(4),
};
let base_cycle_secs: f64 = pattern.phases.iter().map(|p| p.duration_secs).sum();
let session_secs = setup_state.duration_units as f64 * base_cycle_secs / setup_state.tempo;
let session_mins = session_secs / 60.0;
f.render_widget(
Paragraph::new(format!("{} units (≈ {:.1} min)", setup_state.duration_units, session_mins))
.alignment(Alignment::Center)
.style(if dur_selected {
Style::default().fg(Color::Cyan).bold()
} else {
Style::default().fg(Color::White)
}),
Rect { x: dur_inner.x, y: dur_inner.y, width: dur_inner.width, height: 1 },
);
f.render_widget(
Paragraph::new("1 unit = 1 breathing cycle")
.alignment(Alignment::Center)
.style(Style::default().fg(Color::Rgb(110, 100, 135)).dim()),
Rect { x: dur_inner.x, y: dur_inner.y + 1, width: dur_inner.width, height: 1 },
);
let speed_border = if speed_selected { Color::Rgb(255, 185, 80) } else { Color::Rgb(70, 60, 100) };
let speed_rect = Rect { x: right_x, y: card_y, width: right_w, height: card_h };
f.render_widget(
Block::default()
.title(" Breathing Speed ")
.title_style(Style::default().fg(if speed_selected { Color::Rgb(255, 185, 80) } else { Color::Rgb(130, 120, 155) }))
.borders(Borders::ALL)
.border_style(Style::default().fg(speed_border)),
speed_rect,
);
let speed_inner = Rect {
x: speed_rect.x + 1,
y: speed_rect.y + 2,
width: right_w.saturating_sub(2),
height: card_h.saturating_sub(4),
};
f.render_widget(
Paragraph::new(format!("{:.1}×", setup_state.tempo))
.alignment(Alignment::Center)
.style(if speed_selected {
Style::default().fg(Color::Rgb(255, 185, 80)).bold()
} else {
Style::default().fg(Color::White)
}),
Rect { x: speed_inner.x, y: speed_inner.y, width: speed_inner.width, height: 1 },
);
f.render_widget(
Paragraph::new(tempo_description(setup_state.tempo))
.alignment(Alignment::Center)
.style(Style::default().fg(Color::Rgb(110, 100, 135)).dim()),
Rect { x: speed_inner.x, y: speed_inner.y + 1, width: speed_inner.width, height: 1 },
);
let first = &pattern.phases[0];
let adj = first.duration_secs / setup_state.tempo;
f.render_widget(
Paragraph::new(format!("{} = {:.1}s per phase", first.name, adj))
.alignment(Alignment::Center)
.style(Style::default().fg(Color::Rgb(90, 80, 115)).dim()),
Rect { x: speed_inner.x, y: speed_inner.y + 2, width: speed_inner.width, height: 1 },
);
let bar_y = card_y + card_h + 1;
f.render_widget(
Paragraph::new(format!("Phase durations at {:.1}×", setup_state.tempo))
.alignment(Alignment::Center)
.style(Style::default().fg(Color::Rgb(130, 120, 155))),
Rect { x: inner.x, y: bar_y, width: inner.width, height: 1 },
);
let total_adj: f64 = base_cycle_secs / setup_state.tempo;
let bar_w = (inner.width as f64 * 0.88) as usize;
let bar_x = inner.x + ((inner.width as f64 * 0.06) as u16);
let mut bar_spans: Vec<Span> = Vec::new();
for (i, phase) in pattern.phases.iter().enumerate() {
let adj_dur = phase.duration_secs / setup_state.tempo;
let seg_w = ((adj_dur / total_adj) * bar_w as f64).round() as usize;
if seg_w == 0 { continue; }
let color = phase_color(&phase.style);
if i > 0 {
bar_spans.push(Span::styled("│", Style::default().fg(Color::Rgb(40, 35, 55))));
}
let seg_w_adj = if i > 0 { seg_w.saturating_sub(1) } else { seg_w };
bar_spans.push(Span::styled("█".repeat(seg_w_adj), Style::default().fg(color)));
}
f.render_widget(
Paragraph::new(vec![Line::from(bar_spans)]),
Rect { x: bar_x, y: bar_y + 1, width: bar_w as u16, height: 1 },
);
let mut legend_spans: Vec<Span> = Vec::new();
for (i, phase) in pattern.phases.iter().enumerate() {
let adj_dur = phase.duration_secs / setup_state.tempo;
let color = phase_color(&phase.style);
if i > 0 {
legend_spans.push(Span::raw(" "));
}
legend_spans.push(Span::styled("■ ", Style::default().fg(color)));
legend_spans.push(Span::styled(
format!("{} {:.1}s", phase.name, adj_dur),
Style::default().fg(Color::Rgb(150, 140, 170)),
));
}
f.render_widget(
Paragraph::new(vec![Line::from(legend_spans)]).alignment(Alignment::Center),
Rect { x: inner.x, y: bar_y + 2, width: inner.width, height: 1 },
);
let info_y = bar_y + 4;
let footer_y = area.bottom().saturating_sub(2);
let available = footer_y.saturating_sub(info_y);
if available >= 3 {
let (desc, best_for) = pattern_info(pattern.display_name);
f.render_widget(
Paragraph::new(desc)
.alignment(Alignment::Center)
.style(Style::default().fg(Color::Rgb(160, 150, 185))),
Rect { x: inner.x, y: info_y, width: inner.width, height: 1 },
);
if available >= 4 {
f.render_widget(
Paragraph::new(format!("Best for: {}", best_for))
.alignment(Alignment::Center)
.style(Style::default().fg(Color::Rgb(110, 100, 135)).dim()),
Rect { x: inner.x, y: info_y + 1, width: inner.width, height: 1 },
);
}
let wave_top = info_y + 3;
let wave_h = footer_y.saturating_sub(wave_top + 1);
if wave_h >= 3 {
let lines = build_waveform(inner.width, pattern.phases, setup_state.tempo, wave_h);
f.render_widget(
Paragraph::new(lines),
Rect { x: inner.x, y: wave_top, width: inner.width, height: wave_h },
);
}
}
f.render_widget(
Paragraph::new("[Tab] Switch field [↑/↓] or [+/-] Adjust [Enter] Start [Esc] Back")
.alignment(Alignment::Center)
.style(Style::default().dim()),
Rect {
x: inner.x,
y: footer_y,
width: inner.width,
height: 1,
},
);
}
fn build_waveform(width: u16, phases: &[crate::engine::patterns::Phase], tempo: f64, wave_h: u16) -> Vec<Line<'static>> {
let cols = width as usize;
let h = wave_h as usize;
let total_adj: f64 = phases.iter().map(|p| p.duration_secs / tempo).sum();
let cycles = 2usize;
let cycle_cols = (cols / cycles).max(1);
let mut col_data: Vec<(usize, Color)> = vec![(0, Color::Reset); cols];
for cycle in 0..cycles {
for c in 0..cycle_cols {
let col = cycle * cycle_cols + c;
if col >= cols { break; }
let t = (c as f64 / cycle_cols as f64) * total_adj;
let mut elapsed = 0.0f64;
for phase in phases.iter() {
let dur = phase.duration_secs / tempo;
if t < elapsed + dur || elapsed + dur >= total_adj {
let p = ((t - elapsed) / dur).clamp(0.0, 1.0);
let h_frac = match phase.style {
PhaseStyle::Rising => p,
PhaseStyle::Falling => 1.0 - p,
PhaseStyle::Steady => 1.0,
};
let color = phase_color(&phase.style);
col_data[col] = ((h_frac * h as f64) as usize, color);
break;
}
elapsed += dur;
}
}
}
let mut lines: Vec<Line<'static>> = Vec::with_capacity(h);
for row in 0..h {
let rows_from_bottom = h - 1 - row;
let mut spans: Vec<Span<'static>> = Vec::new();
let mut run = String::new();
let mut run_color = Color::Reset;
for col in 0..cols {
let (cell_h, color) = col_data[col];
let ch = if rows_from_bottom < cell_h { '█' } else { ' ' };
let cur_color = if ch == '█' { color } else { Color::Reset };
if cur_color == run_color {
run.push(ch);
} else {
if !run.is_empty() {
spans.push(Span::styled(run.clone(), Style::default().fg(run_color)));
run.clear();
}
run.push(ch);
run_color = cur_color;
}
}
if !run.is_empty() {
spans.push(Span::styled(run, Style::default().fg(run_color)));
}
lines.push(Line::from(spans));
}
lines
}
fn pattern_info(name: &str) -> (&'static str, &'static str) {
match name {
"4-7-8 Breathing" => (
"Extended exhale activates the parasympathetic nervous system.",
"anxiety, falling asleep, acute stress",
),
"Box Breathing" => (
"Equal phases build rhythmic control. Used by Navy SEALs and athletes.",
"focus, performance pressure, emotional regulation",
),
"Diaphragmatic Breathing" => (
"Engages the diaphragm fully, maximizing oxygen exchange with minimal effort.",
"daily practice, energy, reducing shallow breathing",
),
_ => ("Breathe slowly and deliberately.", "relaxation"),
}
}
fn phase_color(style: &PhaseStyle) -> Color {
match style {
PhaseStyle::Rising => Color::Rgb(0, 200, 220),
PhaseStyle::Steady => Color::Rgb(220, 190, 0),
PhaseStyle::Falling => Color::Rgb(0, 190, 100),
}
}
fn tempo_description(tempo: f64) -> &'static str {
if tempo >= 1.85 { "very fast — phases nearly halved" }
else if tempo >= 1.4 { "fast" }
else if tempo >= 1.15 { "slightly fast" }
else if tempo >= 0.85 { "normal pace" }
else if tempo >= 0.6 { "slightly slow" }
else if tempo >= 0.4 { "slow" }
else { "very slow — phases nearly doubled" }
}