27.3 KiB · text History 3eb5b38
use crate::Commands::Tiers;
use crate::PplError;
use chrono::NaiveDate;
use crossterm::event;
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
use dateparser::{parse, DateTimeUtc};
use enum_iterator::{all, cardinality, first, last, next, previous, reverse_all, Sequence};
use interim::{parse_date_string, Dialect};
use ratatui::{
    prelude::*,
    style::{palette::tailwind::*, Color, Modifier, Style, Stylize},
    widgets::*,
    DefaultTerminal,
};
use sea_orm::sqlx::types::chrono;
use sea_orm::sqlx::types::chrono::Local;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::string::String;
use tui_input::backend::crossterm::EventHandler;
use tui_input::Input;

#[derive(Debug, PartialEq, Sequence, PartialOrd, Ord, Eq, Copy, Clone)]
enum Selection {
    Selected,
    NotSelected,
}

#[derive(Debug, Clone)]
struct TierSelect {
    selection: Selection,
    name: String,
}

#[derive(Debug, Clone)]
struct TraitSelect {
    selection: Selection,
    symbol: String,
    color: String,
    name: String,
    is_date: bool,
    is_contact: bool,
}
#[derive(Debug, Clone)]
struct Init {
    step: Steps,
    /// Input box value
    input: Input,
    /// Name
    name: String,
    /// Nicks
    nicks: Vec<String>,
    /// bday
    bday: NaiveDate,
    /// bday-parse-error
    bday_parse: String,
    /// bplace
    birthplace: String,
    /// of-parents
    of_ppl: Vec<String>,
    /// list state for the tier
    tier_state: ListState,
    /// tier storage
    tier_list: Vec<TierSelect>,
    /// editing/not
    tier_editing: Selection,
    /// list state for the traits
    trait_state: ListState,
    /// list-of-traits
    trait_list: Vec<TraitSelect>,
    /// editing/not
    trait_editing: Selection,
}

impl Default for Init {
    fn default() -> Init {
        Init {
            step: Steps::Welcome,
            input: Input::default(),
            nicks: Vec::<String>::default(),
            name: "".to_string(),
            bday: NaiveDate::default(),
            bday_parse: "".to_string(),
            birthplace: "".to_string(),
            of_ppl: Vec::<String>::default(),
            tier_state: ListState::default(),
            tier_list: all::<DefaultTiers>()
                .map(|e| TierSelect {
                    selection: Selection::Selected,
                    name: e.to_string(),
                })
                .collect::<Vec<TierSelect>>(),
            tier_editing: Selection::NotSelected,

            trait_state: ListState::default(),
            trait_editing: Selection::NotSelected,
            trait_list: vec![
                TraitSelect {
                    selection: Selection::Selected,
                    symbol: "🏷️".to_string(),
                    color: "VIOLET".to_string(),
                    name: "Nickname".to_string(),
                    is_date: false,
                    is_contact: false,
                },
                TraitSelect {
                    selection: Selection::Selected,
                    symbol: "🎂".to_string(),
                    color: "GOLD".to_string(),
                    name: "Birthday".to_string(),
                    is_date: true,
                    is_contact: false,
                },
                TraitSelect {
                    selection: Selection::Selected,
                    symbol: "💒".to_string(),
                    color: "WHITE".to_string(),
                    name: "Wedding".to_string(),
                    is_date: true,
                    is_contact: false,
                },
                TraitSelect {
                    selection: Selection::Selected,
                    symbol: "🤝".to_string(),
                    color: "PINK".to_string(),
                    name: "Met".to_string(),
                    is_date: true,
                    is_contact: false,
                },
                TraitSelect {
                    selection: Selection::Selected,
                    symbol: "📞".to_string(),
                    color: "TEAL".to_string(),
                    name: "Phone".to_string(),
                    is_date: false,
                    is_contact: true,
                },
                TraitSelect {
                    selection: Selection::Selected,
                    symbol: "📬".to_string(),
                    color: "RED".to_string(),
                    name: "Mailing".to_string(),
                    is_date: false,
                    is_contact: true,
                },
                TraitSelect {
                    selection: Selection::Selected,
                    symbol: "📧".to_string(),
                    color: "GREEN".to_string(),
                    name: "eMail".to_string(),
                    is_date: false,
                    is_contact: true,
                },
            ],
        }
    }
}

impl Init {
    fn toggle_tier(&mut self) {
        let selection = self.tier_state.selected();
        match selection {
            Some(s) => {
                self.tier_list[s].selection = match self.tier_list[s].selection {
                    Selection::Selected => Selection::NotSelected,
                    Selection::NotSelected => Selection::Selected,
                }
            }
            None => {}
        }
    }

    fn toggle_tier_editing(&mut self) {
        self.tier_editing = match self.tier_editing {
            Selection::Selected => Selection::NotSelected,
            Selection::NotSelected => Selection::Selected,
        }
    }

    fn tier_add(&mut self, tier: String) {
        let new_ts = TierSelect {
            selection: Selection::Selected,
            name: tier,
        };
        self.tier_list.push(new_ts);
    }

    fn tier_count_string(&self) -> String {
        let total = self.tier_list.len();
        let selected = self
            .tier_list
            .iter()
            .filter(|e| e.selection == Selection::Selected)
            .count();
        format!("{}/{} Selected", selected, total)
    }

    fn toggle_trait(&mut self) {
        let selection = self.trait_state.selected();
        match selection {
            Some(s) => {
                self.trait_list[s].selection = match self.trait_list[s].selection {
                    Selection::Selected => Selection::NotSelected,
                    Selection::NotSelected => Selection::Selected,
                }
            }
            None => {}
        }
    }

    fn trait_count_string(&self) -> String {
        let total = self.trait_list.len();
        let selected = self
            .trait_list
            .iter()
            .filter(|e| e.selection == Selection::Selected)
            .count();
        format!("{}/{} Selected", selected, total)
    }
}

#[derive(Debug, PartialEq, Sequence, Clone)]
enum Steps {
    Welcome,
    Name,
    Birthday,
    Birthplace,
    Of,
    Tiers,
    Traits,
    Review,
}

#[derive(Debug, PartialEq, Sequence)]
enum DefaultTiers {
    Family,
    Bests,
    Friends,
    Acquaintances,
    CoWorkers,
}

impl fmt::Display for DefaultTiers {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

fn handle_key_event(key_event: KeyEvent) {}

pub fn run_init(mut terminal: DefaultTerminal) -> Result<(), PplError> {
    let mut app = Init::default();
    loop {
        terminal.draw(|f| render(f, &mut app))?;
        match event::read()? {
            Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
                match key_event.code {
                    // KeyCode::Char('Esc') => break Ok(()),
                    KeyCode::Esc => break Ok(()),
                    KeyCode::Enter => match &app.step {
                        Steps::Welcome => {
                            app.input.reset();
                            app.step = next(&app.step).unwrap();
                        }
                        Steps::Name => {
                            if app.input.value() != "".to_string() {
                                let all_text = app.input.value();
                                let mut split = all_text.split(",").collect::<Vec<&str>>();
                                match split.len() {
                                    1 => {
                                        app.name = all_text.parse().unwrap();
                                    }
                                    _ => {
                                        split.reverse();
                                        app.name = split.pop().unwrap().to_string();
                                        split.reverse();
                                        app.nicks = split
                                            .iter()
                                            .map(|s| s.to_string())
                                            .collect::<Vec<String>>();
                                    }
                                }

                                app.input.reset();
                                app.step = next(&app.step).unwrap();
                            }
                        }
                        Steps::Birthday => {
                            if app.input.value() != "".to_string() {
                                let datestr = app.input.value();
                                let parsed = parse_date_string(datestr, Local::now(), Dialect::Us);
                                match parsed {
                                    Ok(parsed) => {
                                        app.bday = parsed.date_naive();
                                        app.input.reset();
                                        app.bday_parse = "".to_string();
                                        app.step = next(&app.step).unwrap();
                                    }
                                    Err(e) => {
                                        app.bday_parse = e.to_string();
                                    }
                                }
                            }
                        }
                        Steps::Birthplace => {
                            if app.input.value() != "".to_string() {
                                app.birthplace = app.input.value().into();
                                app.input.reset();
                                app.step = next(&app.step).unwrap();
                            }
                        }
                        Steps::Of => {
                            if app.input.value() != "".to_string() {
                                app.of_ppl = app
                                    .input
                                    .value()
                                    .to_string()
                                    .split(",")
                                    .map(|s| s.trim().to_string())
                                    .collect::<Vec<String>>();
                                app.input.reset();
                                app.step = next(&app.step).unwrap();
                            }
                        }
                        Steps::Tiers => match app.tier_editing {
                            Selection::Selected => {
                                app.tier_add(app.input.value().to_string());
                                app.input.reset();
                            }
                            Selection::NotSelected => {
                                app.input.reset();
                                app.step = next(&app.step).unwrap();
                            }
                        },
                        Steps::Traits => {
                            app.input.reset();
                            app.step = next(&app.step).unwrap();
                        }
                        Steps::Review => {}
                    },
                    KeyCode::Backspace => {
                        if app.input.value() == "" {
                            match &app.step {
                                Steps::Welcome => {}
                                Steps::Name => {
                                    app.step = previous(&app.step).unwrap();
                                }
                                Steps::Birthday => {
                                    app.name = "".to_string();
                                    app.nicks = vec![];
                                    app.input.reset();
                                    app.step = previous(&app.step).unwrap();
                                }
                                Steps::Birthplace => {
                                    app.bday = NaiveDate::default();
                                    app.input.reset();
                                    app.step = previous(&app.step).unwrap();
                                }
                                Steps::Of => {
                                    app.birthplace = "".to_string();
                                    app.input.reset();
                                    app.step = previous(&app.step).unwrap();
                                }
                                Steps::Tiers => {
                                    app.of_ppl = vec![];
                                    app.input.reset();
                                    app.step = previous(&app.step).unwrap();
                                }
                                Steps::Traits => {
                                    app.input.reset();
                                    app.step = previous(&app.step).unwrap();
                                }
                                Steps::Review => {
                                    app.input.reset();
                                    app.step = previous(&app.step).unwrap();
                                }
                            }
                        } else {
                            app.input.handle_event(&Event::Key(key_event));
                        }
                    }
                    KeyCode::Down => match &app.step {
                        Steps::Tiers => app.tier_state.scroll_up_by(1),
                        Steps::Traits => app.trait_state.scroll_up_by(1),
                        _ => {}
                    },
                    KeyCode::Up => match &app.step {
                        Steps::Tiers => app.tier_state.scroll_down_by(1),
                        Steps::Traits => app.trait_state.scroll_down_by(1),
                        _ => {}
                    },
                    KeyCode::Char(' ') => match &app.step {
                        Steps::Tiers => app.toggle_tier(),
                        Steps::Traits => app.toggle_trait(),
                        _ => {
                            app.input.handle_event(&Event::Key(key_event));
                        }
                    },
                    KeyCode::Tab => match &app.step {
                        Steps::Tiers => app.toggle_tier_editing(),
                        _ => {}
                    },
                    // app.input.handle_event(&Event::Key(key));
                    // KeyCode::Left => self.decrement_counter(),
                    // KeyCode::Right => self.increment_counter(),
                    _ => {
                        app.input.handle_event(&Event::Key(key_event));
                    }
                }
            }
            _ => {}
        }
    }
}

fn render(f: &mut Frame, app: &mut Init) {
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .margin(2)
        .constraints(
            [
                Constraint::Length(1),
                Constraint::Length(3),
                Constraint::Length(1),
                Constraint::Length(7),
                Constraint::Min(1),
            ]
            .as_ref(),
        )
        .split(f.area());

    let msgw = vec![
        Span::raw("welcome to "),
        Span::styled(
            "ppl",
            Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
        ),
        Span::raw(" your local, but everywhere lrm"),
    ];
    let textw = Text::from(Line::from(msgw)).style(Style::default());
    let welcome = Paragraph::new(textw);
    f.render_widget(welcome, chunks[0]);

    let msg = vec![
        Span::raw("press "),
        Span::styled(
            "Esc",
            Style::default().add_modifier(Modifier::BOLD).fg(RED.c500),
        ),
        Span::raw(" to cancel and quit, "),
        Span::styled(
            "Enter",
            Style::default().add_modifier(Modifier::BOLD).fg(BLUE.c500),
        ),
        Span::raw(" to submit and record your responses, "),
        Span::styled(
            "Backspace",
            Style::default()
                .add_modifier(Modifier::BOLD)
                .fg(ORANGE.c500),
        ),
        Span::raw(" previous screen"),
    ];
    let text = Text::from(Line::from(msg)).style(Style::default());
    let help_message = Paragraph::new(text);
    f.render_widget(help_message, chunks[1]);

    let width = chunks[0].width.max(3) - 3; // keep 2 for borders and 1 for cursor

    let scroll = app.input.visual_scroll(width as usize);

    let ival = app.input.value().clone();
    let input = Paragraph::new(ival)
        .style(Style::default())
        // .style(match app.input_mode {
        // InputMode::Normal => Style::default(),
        // InputMode::Editing => Style::default().fg(Color::Yellow),
        // })
        .scroll((0, scroll as u16))
        .block(Block::default().borders(Borders::ALL).title("Input"));

    let mname: String = app.name.clone();
    let mnicks: String = app.nicks.join(", ");
    let mbday = app.bday.clone();
    let mplace = app.birthplace.clone();
    let mut messages = vec![
        ("name", mname),
        ("nicks", mnicks),
        ("bday", mbday.to_string()),
        ("place", mplace),
    ];

    match app.step {
        Steps::Welcome => {}
        Steps::Name => {}
        Steps::Birthday => {}
        Steps::Birthplace => {}
        Steps::Of => {}
        Steps::Tiers => {
            let oflist = &app.of_ppl.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");;
            messages.push(("of", oflist.clone()))
        }
        Steps::Traits => {
            let oflist = &app.of_ppl.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");;
            messages.push(("of", oflist.clone()));
            let tierlist = &app
                .tier_list
                .iter()
                .filter(|t| t.selection == Selection::Selected)
                .map(|t| t.name.as_str())
                .collect::<Vec<_>>()
                .join(",");
            messages.push(("circles", tierlist.clone()))
        }
        Steps::Review => {
            let oflist = &app.of_ppl.iter().map(|x| x.to_string()).collect::<Vec<String>>().join(",");;
            messages.push(("of", oflist.clone()));
            let tierlist = &app
                .tier_list
                .iter()
                .filter(|t| t.selection == Selection::Selected)
                .map(|t| t.name.as_str())
                .collect::<Vec<_>>()
                .join(",");
            messages.push(("circles", tierlist.clone()));
            let traitlist = &app
                .trait_list
                .iter()
                .filter(|t| t.selection == Selection::Selected)
                .map(|t| t.name.as_str())
                .collect::<Vec<_>>()
                .join(",");
            messages.push(("traits", traitlist.clone()))
        }
    }

    let messages_v: Vec<ListItem> = messages
        .iter()
        .filter(|(t, i)| **i != "".to_string())
        .enumerate()
        .map(|(i, (t, m))| {
            let content = vec![Line::from(vec![
                Span::styled(*t, Style::default().add_modifier(Modifier::BOLD)),
                Span::raw(format!(": {}", m)),
            ])];
            ListItem::new(content)
        })
        .collect();
    let messages =
        List::new(messages_v).block(Block::default().borders(Borders::TOP).title("Responses"));

    // let items = all::<DefaultTiers>().map(|e| e.to_string()).collect::<Vec<_>>();
    // let items = app.tier_list.iter().collect();
    let list_tiers = List::new(&app.tier_list)
        .block(Block::bordered().title("Circles"))
        .style(Style::new().white())
        .highlight_style(Style::new().italic())
        .highlight_symbol(">>")
        .repeat_highlight_symbol(true)
        .direction(ListDirection::BottomToTop);

    let list_traits = List::new(&app.trait_list)
        .block(Block::bordered().title("Fields"))
        .style(Style::new().white())
        .highlight_style(Style::new().italic())
        .highlight_symbol(">>")
        .repeat_highlight_symbol(true)
        .direction(ListDirection::BottomToTop);

    match &app.step {
        Steps::Welcome => {
            f.render_widget(
                Span::styled(
                    "welcome",
                    Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
                ),
                chunks[2],
            );
        }
        Steps::Name => {
            f.render_widget(
                Span::styled(
                    "> your name? ",
                    Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
                ),
                chunks[2],
            );
            f.render_widget(input, chunks[3]);
            f.render_widget(messages, chunks[4]);
        }
        Steps::Birthday => {
            let mut msg = vec![Span::styled(
                "> your bday?",
                Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
            )];
            match app.bday_parse.len() {
                0 => {}
                _ => {
                    msg.push(Span::styled(
                        "> invalid",
                        Style::default().add_modifier(Modifier::BOLD).fg(RED.c500),
                    ));
                    msg.push(Span::raw(&app.bday_parse));
                }
            }
            let text = Text::from(Line::from(msg)).style(Style::default());
            f.render_widget(text, chunks[2]);
            f.render_widget(input, chunks[3]);
            f.render_widget(messages, chunks[4]);
        }
        Steps::Birthplace => {
            f.render_widget(
                Span::styled(
                    "> born where?",
                    Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
                ),
                chunks[2],
            );
            f.render_widget(input, chunks[3]);
            f.render_widget(messages, chunks[4]);
        }
        Steps::Of => {
            f.render_widget(
                Span::styled(
                    "> born of? (comma seperated)",
                    Style::default().add_modifier(Modifier::BOLD).fg(WHITE),
                ),
                chunks[2],
            );
            f.render_widget(input, chunks[3]);
            f.render_widget(messages, chunks[4]);
        }
        Steps::Tiers => {
            let tcs = app.tier_count_string();
            let msg = vec![
                Span::raw("which of these groupings do you want? "),
                Span::styled("space", Style::default().add_modifier(Modifier::BOLD)),
                Span::raw(" to select/deselect, "),
                Span::styled("tab", Style::default().add_modifier(Modifier::BOLD)),
                Span::raw(" to add new ones, "),
                Span::styled(tcs, Style::default().add_modifier(Modifier::SLOW_BLINK)),
            ];
            let text = Text::from(Line::from(msg)).style(Style::default());
            let help_message = Paragraph::new(text);
            match app.tier_editing {
                Selection::NotSelected => {
                    f.render_widget(help_message, chunks[2]);
                    f.render_stateful_widget(list_tiers, chunks[3], &mut app.tier_state);
                    f.render_widget(messages, chunks[4]);
                }
                Selection::Selected => {
                    f.render_widget(help_message, chunks[2]);
                    f.render_widget(input, chunks[3]);
                    f.render_widget(messages, chunks[4]);
                }
            }
        }
        Steps::Traits => {
            let tcs = app.trait_count_string();
            let msg = vec![
                Span::raw("which of these default fields do you want? "),
                Span::styled("space", Style::default().add_modifier(Modifier::BOLD)),
                Span::raw(" to select/deselect, "),
                Span::styled("tab", Style::default().add_modifier(Modifier::BOLD)),
                Span::raw(" to add new ones, "),
                Span::styled(tcs, Style::default().add_modifier(Modifier::SLOW_BLINK)),
            ];
            let text = Text::from(Line::from(msg)).style(Style::default());
            let help_message = Paragraph::new(text);
            match app.trait_editing {
                Selection::NotSelected => {
                    f.render_widget(help_message, chunks[2]);
                    f.render_stateful_widget(list_traits, chunks[3], &mut app.trait_state);
                    f.render_widget(messages, chunks[4]);
                }
                Selection::Selected => {
                    f.render_widget(help_message, chunks[2]);
                    f.render_widget(input, chunks[3]);
                    f.render_widget(messages, chunks[4]);
                }
            }
        }
        Steps::Review => {
            let msga = vec![
                Span::raw("review your responses below "),
                Span::styled(
                    ":)",
                    Style::default()
                        .add_modifier(Modifier::SLOW_BLINK)
                        .fg(YELLOW.c500),
                ),
            ];
            let texta = Line::from(msga).style(Style::default());
            let msgb = vec![
                Span::raw(" if these look correct, press "),
                Span::styled(
                    "^Enter",
                    Style::default()
                        .add_modifier(Modifier::SLOW_BLINK)
                        .fg(YELLOW.c500),
                ),
                Span::raw(" to complete init "),
            ];
            let textb = Line::from(msgb).style(Style::default());
            let help_message = Paragraph::new(vec![texta, textb]);
            f.render_widget(help_message, chunks[3]);
            f.render_widget(messages, chunks[4]);
        }
    }
}
const TEXT_FG_COLOR: Color = SLATE.c200;
const COMPLETED_TEXT_FG_COLOR: Color = GREEN.c500;
const BLUEBLUE: Color = BLUE.c500;

fn lcolor(input: &String) -> Color {
    match input.to_lowercase().as_str() {
        "red" => RED.c500,
        "gold" => AMBER.c500,
        "amber" => AMBER.c500,
        "green" => GREEN.c500,
        "teal" => TEAL.c500,
        "cyan" => CYAN.c500,
        "blue" => BLUE.c500,
        "violet" => VIOLET.c500,
        "pink" => PINK.c500,
        "slate" => SLATE.c500,
        _ => TEXT_FG_COLOR,
    }
}

impl From<&TierSelect> for ListItem<'_> {
    fn from(value: &TierSelect) -> Self {
        let line = match value.selection {
            Selection::NotSelected => Line::styled(format!(" ☐ {}", value.name), TEXT_FG_COLOR),
            Selection::Selected => {
                Line::styled(format!(" ✓ {}", value.name), COMPLETED_TEXT_FG_COLOR)
            }
        };
        ListItem::new(line)
    }
}

impl From<&TraitSelect> for ListItem<'_> {
    fn from(value: &TraitSelect) -> Self {
        let line = match value.selection {
            Selection::NotSelected => {
                Line::styled(format!(" ☐ {} {}", value.symbol, value.name), SLATE.c500)
            }
            Selection::Selected => Line::styled(
                format!(" ✓ {} {}", value.symbol, value.name),
                lcolor(&value.color),
            ),
        };
        ListItem::new(line)
    }
}