//! Bottom-pane approval widget — codex-style permission prompt.
//!
//! Replaces the M2 centered `Modal::PermissionPrompt`. Takes the editor's
//! layout slot when `state.bottom_pane = Some(BottomPane::Approval(...))`.
//! Renders a bolded question header, optional args preview, a navigable
//! list of options with selection marker + per-option shortcut hint, and a
//! footer cue spelling out navigation keys.
//!
//! Selection lives in `ApprovalState::selected`; this widget is pure
//! rendering. Keyboard handling for ↑/↓/Enter/Esc/letter-shortcuts is in
//! `update.rs::handle_bottom_pane_key`.

use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
use ratatui::Frame;

use crate::state::ApprovalState;
use crate::theme;

/// Compute the height the approval pane wants. Caller (render::render)
/// hands this to the layout `Constraint::Length(...)`.
///
/// Layout: top border (1) + question (1+) + blank (1) + args lines (0–4) +
/// blank (1, only if args shown) + one row per option + blank (1) +
/// footer hint (1) + bottom border (1).
pub fn desired_height(state: &ApprovalState) -> u16 {
    let args_rows = if state.args_preview.trim().is_empty() {
        0
    } else {
        // Clamp args preview to at most 4 lines so a verbose tool call
        // can't squeeze out the option list. Anything longer remains
        // visible in the transcript's ToolCallStarted record.
        state.args_preview.lines().count().min(4) as u16 + 1
    };
    let options = state.options.len() as u16;
    // Borders(2) + question(1) + blank(1) + args + options + blank(1) + hint(1)
    2 + 1 + 1 + args_rows + options + 1 + 1
}

pub fn render(state: &ApprovalState, frame: &mut Frame, area: Rect) {
    let block = Block::default()
        .title(" Permission ")
        .borders(Borders::ALL)
        .border_style(theme::modal_border());
    let inner = block.inner(area);
    frame.render_widget(block, area);

    // Reserve the footer hint row at the very bottom — same trick the
    // M2 modal needed: the hint must always be visible regardless of
    // option count / args length / terminal size.
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Min(0), Constraint::Length(1)])
        .split(inner);
    let body_area = chunks[0];
    let hint_area = chunks[1];

    let mut body: Vec<Line> = Vec::new();
    body.push(Line::from(Span::styled(
        state.question.clone(),
        Style::default()
            .add_modifier(Modifier::BOLD)
            .fg(theme::primary().fg.unwrap_or(ratatui::style::Color::White)),
    )));
    body.push(Line::from(""));

    if !state.args_preview.trim().is_empty() {
        for line in state.args_preview.lines().take(4) {
            body.push(Line::from(Span::styled(line.to_string(), theme::dim())));
        }
        body.push(Line::from(""));
    }

    for (idx, opt) in state.options.iter().enumerate() {
        let selected = idx == state.selected;
        let marker = if selected { "› " } else { "  " };
        let marker_style = if selected {
            theme::assistant()
        } else {
            theme::dim()
        };
        let label_style = if selected {
            theme::primary().add_modifier(Modifier::BOLD)
        } else {
            theme::primary()
        };
        let mut spans = vec![
            Span::styled(marker, marker_style),
            Span::styled(opt.label.clone(), label_style),
        ];
        if let Some(c) = opt.shortcut {
            spans.push(Span::raw("  "));
            spans.push(Span::styled(format!("({c})"), theme::dim()));
        }
        body.push(Line::from(spans));
    }

    frame.render_widget(Paragraph::new(body).wrap(Wrap { trim: false }), body_area);

    let hint = Line::from(vec![
        Span::styled("↑↓", theme::assistant()),
        Span::raw(" navigate  "),
        Span::styled("Enter", theme::assistant()),
        Span::raw(" select  "),
        Span::styled("Esc", theme::error()),
        Span::raw(" cancel"),
    ]);
    frame.render_widget(Paragraph::new(hint), hint_area);
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::state::{ApprovalOption, ApprovalState};
    use capo_agent::{Decision, PermissionChoice};
    use ratatui::backend::TestBackend;
    use ratatui::Terminal;
    use serde_json::Value;

    fn sample_state(selected: usize) -> ApprovalState {
        ApprovalState {
            question: "Allow capo to run this `bash` call?".into(),
            args_preview: "{\n  \"command\": \"ls -la\"\n}".into(),
            options: vec![
                ApprovalOption {
                    label: "Yes, just this once".into(),
                    choice: PermissionChoice::AllowOnce,
                    decision: Decision::Allowed,
                    shortcut: Some('a'),
                },
                ApprovalOption {
                    label: "Yes, and don't ask again this session".into(),
                    choice: PermissionChoice::AllowSession,
                    decision: Decision::Allowed,
                    shortcut: Some('s'),
                },
                ApprovalOption {
                    label: "No, deny this call".into(),
                    choice: PermissionChoice::Deny,
                    decision: Decision::Denied("user denied".into()),
                    shortcut: Some('d'),
                },
            ],
            selected,
            tool: "bash".into(),
            args: Value::Null,
            resolver: None,
        }
    }

    #[test]
    fn renders_first_option_selected() -> Result<(), Box<dyn std::error::Error>> {
        let state = sample_state(0);
        let mut term = Terminal::new(TestBackend::new(70, 14))?;
        term.draw(|f| render(&state, f, f.area()))?;
        insta::assert_snapshot!(term.backend());
        Ok(())
    }

    #[test]
    fn renders_middle_option_selected() -> Result<(), Box<dyn std::error::Error>> {
        let state = sample_state(1);
        let mut term = Terminal::new(TestBackend::new(70, 14))?;
        term.draw(|f| render(&state, f, f.area()))?;
        insta::assert_snapshot!(term.backend());
        Ok(())
    }

    #[test]
    fn desired_height_accounts_for_options_and_args() {
        let state = sample_state(0);
        // borders(2) + question(1) + blank(1) + args_rows(3 lines + 1 blank = 4)
        // + options(3) + blank(1) + hint(1) = 13
        assert_eq!(desired_height(&state), 13);
    }

    #[test]
    fn desired_height_no_args() {
        let mut state = sample_state(0);
        state.args_preview = "".into();
        // 2 + 1 + 1 + 0 + 3 + 1 + 1 = 9
        assert_eq!(desired_height(&state), 9);
    }
}
