1use std::time::Instant;
4
5use ratatui::layout::Rect;
6use ratatui::style::{Modifier, Style};
7use ratatui::text::{Line, Span};
8use ratatui::widgets::{Block, BorderType, Borders, Paragraph, Wrap};
9use ratatui::Frame;
10
11use crate::grex_integration::GrexOptions;
12use crate::input::editor::Editor;
13use crate::ui::{centered_overlay, theme};
14
15#[derive(Default)]
16pub struct GrexOverlayState {
17 pub editor: Editor,
18 pub options: GrexOptions,
19 pub generated_pattern: Option<String>,
20 pub generation_counter: u64,
21 pub debounce_deadline: Option<Instant>,
22}
23
24pub fn render(frame: &mut Frame, area: Rect, state: &GrexOverlayState) {
25 render_with_border(frame, area, state, BorderType::Plain);
26}
27
28pub fn render_with_border(frame: &mut Frame, area: Rect, state: &GrexOverlayState, bt: BorderType) {
29 let width = area.width.clamp(60, 80);
30 let height = area.height.saturating_sub(4).clamp(12, 18);
31 let overlay_area = centered_overlay(frame, area, width, height);
32
33 let dim = Style::default().fg(theme::SUBTEXT);
34 let title_style = Style::default()
35 .fg(theme::BLUE)
36 .add_modifier(Modifier::BOLD);
37 let label_style = Style::default().fg(theme::MAUVE);
38
39 let mut lines: Vec<Line<'static>> = Vec::with_capacity(16);
40 lines.push(Line::from(Span::styled(
41 "Generate Regex from Examples",
42 title_style,
43 )));
44 lines.push(Line::from(""));
45 lines.push(build_flag_row(&state.options));
46 lines.push(Line::from(""));
47 lines.push(Line::from(Span::styled(
48 "Examples (one per line):",
49 label_style,
50 )));
51
52 let content = state.editor.content();
53 if content.is_empty() {
54 lines.push(Line::from(Span::styled(
55 " Enter one example per line. Tab to accept.",
56 dim,
57 )));
58 } else {
59 for line in content.lines() {
60 lines.push(Line::from(format!(" {line}")));
61 }
62 }
63
64 lines.push(Line::from(""));
65 lines.push(Line::from(Span::styled("Generated pattern:", label_style)));
66 match state.generated_pattern.as_deref() {
67 Some(p) if !p.is_empty() => {
68 lines.push(Line::from(Span::styled(
69 format!(" {p}"),
70 Style::default().fg(theme::GREEN),
71 )));
72 }
73 _ => {
74 lines.push(Line::from(Span::styled(" (none yet)", dim)));
75 }
76 }
77
78 lines.push(Line::from(""));
79 lines.push(Line::from(Span::styled(
80 "Alt+d/a/c: toggle flags Tab: accept Esc: cancel",
81 dim,
82 )));
83
84 let block = Block::default()
85 .borders(Borders::ALL)
86 .border_type(bt)
87 .border_style(Style::default().fg(theme::BLUE))
88 .title(Span::styled(
89 " Generate Regex from Examples (Ctrl+X) ",
90 Style::default().fg(theme::TEXT),
91 ))
92 .style(Style::default().bg(theme::BASE));
93
94 let paragraph = Paragraph::new(lines)
95 .block(block)
96 .wrap(Wrap { trim: false });
97
98 frame.render_widget(paragraph, overlay_area);
99}
100
101fn build_flag_row(options: &GrexOptions) -> Line<'static> {
102 fn flag_span(label: &'static str, on: bool) -> Vec<Span<'static>> {
103 let marker = if on { "●" } else { "○" };
104 let style = if on {
105 Style::default().fg(theme::GREEN)
106 } else {
107 Style::default().fg(theme::SUBTEXT)
108 };
109 vec![
110 Span::raw(label),
111 Span::raw(" "),
112 Span::styled(marker, style),
113 ]
114 }
115 let mut spans: Vec<Span<'static>> =
116 vec![Span::styled("Flags: ", Style::default().fg(theme::MAUVE))];
117 spans.extend(flag_span("[D]igit", options.digit));
118 spans.push(Span::raw(" "));
119 spans.extend(flag_span("[A]nchors", options.anchors));
120 spans.push(Span::raw(" "));
121 spans.extend(flag_span("[C]ase-insensitive", options.case_insensitive));
122 Line::from(spans)
123}