rproj 0.20.1

Guided bootstrap-to-game-dev CLI for Roblox: takes a fresh Windows PC to a working Roblox/Luau setup, then scaffolds projects on it
use ratatui::{
    Frame,
    layout::{Constraint, Layout},
    style::Style,
    widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap},
};

use super::{
    super::new,
    model::{Draft, Modal},
};
use crate::tui;

pub fn draw(frame: &mut Frame<'_>, draft: &Draft, destination: &str) {
    let area = frame.area();
    if tui::is_too_small(area) {
        frame.render_widget(
            Paragraph::new("Resize to at least 60x16\n? Help   Esc Back   Ctrl+C Exit"),
            area,
        );
    } else {
        let rows = Layout::vertical([
            Constraint::Length(2),
            Constraint::Min(1),
            Constraint::Length(3),
        ])
        .split(area);
        frame.render_widget(
            Paragraph::new(format!(
                "rproj {} | {}: {}",
                env!("CARGO_PKG_VERSION"),
                if draft.setup_mode {
                    "Edit Saved Setup"
                } else if draft.project_mode {
                    "Edit Project"
                } else {
                    "New Project"
                },
                draft.name
            ))
            .style(tui::title_style()),
            rows[0],
        );
        let panes = tui::responsive_panes(rows[1], 40);
        let block = Block::default()
            .borders(Borders::ALL)
            .title(format!(" {} ", draft.title()))
            .border_style(Style::default().fg(if draft.details_focus {
                tui::MUTED
            } else {
                tui::ACCENT
            }));
        let inner = block.inner(panes[0]);
        frame.render_widget(block, panes[0]);
        let list_rows = Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).split(inner);
        frame.render_widget(
            Paragraph::new(format!("Filter: {}", draft.picker.query.text())),
            list_rows[0],
        );
        let items = draft
            .picker
            .filtered()
            .iter()
            .map(|p| {
                let mark = if draft.multi() {
                    if draft.checked.contains(&p.value) {
                        "[x] "
                    } else {
                        "[ ] "
                    }
                } else {
                    ""
                };
                ListItem::new(format!("{mark}{}", p.label))
            })
            .collect::<Vec<_>>();
        let mut state = ListState::default().with_selected(if items.is_empty() {
            None
        } else {
            Some(draft.picker.selected)
        });
        frame.render_stateful_widget(
            List::new(items).highlight_style(tui::selected_style(!draft.details_focus)),
            list_rows[1],
            &mut state,
        );

        let selected = draft
            .picker
            .filtered()
            .get(draft.picker.selected)
            .map(|p| p.detail.as_str())
            .unwrap_or("");
        let plan = draft.graph.plan(&draft.apps, &draft.extensions);
        let mut details = if draft.project_mode {
            format!(
                "{selected}\n\nProject: {destination}\nExisting choices are preserved; save applies additions to this project. Stop an active Watch before saving package additions. rproj.toml may be reformatted.\n"
            )
        } else if draft.setup_mode {
            format!(
                "{selected}\n\nSaved setup: {}\nFuture reuse only; existing projects are unchanged.\n",
                draft.name
            )
        } else {
            format!("{selected}\n\nDestination: {destination}\n")
        };
        if !draft.status.is_empty() {
            details.push_str(&format!("{}\n", draft.status));
        }
        if let Some(name) = &draft.save_setup {
            details.push_str(&format!("Save setup on Create: {name}\n"));
        }
        details.push('\n');
        details.push_str(&new::summary_lines(&draft.name, &draft.graph, &plan).join("\n"));
        frame.render_widget(
            Paragraph::new(details)
                .wrap(Wrap { trim: false })
                .scroll((draft.scroll, 0))
                .block(
                    Block::default()
                        .borders(Borders::ALL)
                        .title(" Details / planned files ")
                        .border_style(Style::default().fg(if draft.details_focus {
                            tui::ACCENT
                        } else {
                            tui::MUTED
                        })),
                ),
            panes[1],
        );
        tui::render_footer(
            frame,
            rows[2],
            &draft.status,
            if draft.editing() && !draft.multi() {
                "Enter select Ctrl+S save review Tab focus Esc back ? help"
            } else if draft.multi() {
                "Space toggle  Enter continue  Tab focus  PgUp/Dn details  Esc back  ? help"
            } else {
                "Enter select  Tab focus  PgUp/Dn details  Esc back  ? help"
            },
            !draft.status.is_empty(),
        );
    }
    match &draft.modal {
        Some(Modal::Name(input)) => {
            tui::render_input(frame, area, "Project name", "One folder name", input)
        }
        Some(Modal::Setup(input)) => tui::render_input(
            frame,
            area,
            "Save named setup",
            "New setup name; leave empty to disable saving",
            input,
        ),
        Some(Modal::Discard(confirm) | Modal::Create(confirm)) => {
            tui::render_confirm(frame, area, confirm)
        }
        Some(Modal::Help) => {
            let popup = tui::centered(area, 76, 16);
            frame.render_widget(Clear, popup);
            frame.render_widget(Paragraph::new(if draft.project_mode {
                "Edit Project\n\nArrows navigate; type to filter. Space adds choices; Enter accepts a step. Existing package and capability choices stay selected.\nTab switches details; arrows or Page Up/Down scroll.\n\nReview offers Save changes, Packages, and Capabilities. Ctrl+S saves from Review.\nEsc returns to project actions. Ctrl+C cancels.\n\nSaving updates rproj-managed package entries, generated configuration, tool pins, and test wiring.\nEsc or ? closes Help."
            } else if draft.setup_mode {
                "Edit Saved Setup\n\nArrows navigate; type to filter. Space toggles choices; Enter accepts a step.\nTab switches details; arrows or Page Up/Down scroll.\n\nReview offers Save and composition revisions. Ctrl+S saves from Review without closing.\nEsc cancels a revision or returns to setup actions. Ctrl+C requests Home. Unsaved changes require confirmation.\n\nExisting projects are never modified. Changed saves may reformat TOML and remove comments.\nEsc or ? closes Help."
            } else { "New Project\n\nArrows navigate; type to filter. Space toggles multiple choices.\nEnter accepts the current step. Tab switches to details; arrows or Page Up/Down scroll.\n\nReview can revise answers, omit optional files, rename, and save a new setup. Required files cannot be removed.\n\nEsc goes back or cancels a revision. Ctrl+C exits without creating anything. Only confirmed Create writes a project.\n\nEsc or ? closes Help." })
                .wrap(Wrap { trim: true }).block(Block::default().borders(Borders::ALL).title(" Help ")), popup);
        }
        None => {}
    }
}