//! Pure render function: `state` → frame. Called by the runtime on each
//! draw tick.

use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::Frame;

use crate::state::{AppState, BottomPane};
use crate::widgets;

pub fn render(state: &AppState, frame: &mut Frame) {
    let area = frame.area();

    // When a bottom pane is active (approval, etc.) it takes the editor's
    // slot — codex calls this a "BottomPaneView" and reuses the surface
    // across approvals, popups, model pickers. The editor disappears
    // because typing isn't what the user is meant to do right now.
    let middle_height = match &state.bottom_pane {
        Some(BottomPane::Approval(approval)) => widgets::approval::desired_height(approval),
        Some(BottomPane::Palette(palette)) => {
            widgets::slash_palette::desired_height(palette, &state.extension_commands)
        }
        Some(BottomPane::FilePicker(fp)) => widgets::file_picker::desired_height(fp),
        Some(BottomPane::ModelPicker(mp)) => widgets::model_picker::desired_height(mp),
        Some(BottomPane::ResumePicker(_)) => {
            widgets::resume_picker::desired_height(&state.sessions)
        }
        Some(BottomPane::ForkPicker(_)) => {
            widgets::fork_picker::desired_height(&state.fork_candidates)
        }
        Some(BottomPane::BranchTree(_)) => {
            widgets::branch_tree::desired_height(state.branch_tree.as_ref())
        }
        Some(BottomPane::Settings(settings)) => widgets::settings_pane::desired_height(settings),
        None => editor_height(state),
    };

    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Min(3), // messages
            Constraint::Length(middle_height),
            Constraint::Length(if state.nav_mode { 2 } else { 1 }), // footer
        ])
        .split(area);

    widgets::messages::render(state, frame, chunks[0]);
    match &state.bottom_pane {
        Some(BottomPane::Approval(approval)) => {
            widgets::approval::render(approval, frame, chunks[1]);
        }
        Some(BottomPane::Palette(palette)) => {
            widgets::slash_palette::render(palette, &state.extension_commands, frame, chunks[1]);
        }
        Some(BottomPane::FilePicker(fp)) => {
            widgets::file_picker::render(fp, frame, chunks[1]);
        }
        Some(BottomPane::ModelPicker(mp)) => widgets::model_picker::render(mp, frame, chunks[1]),
        Some(BottomPane::ResumePicker(rp)) => {
            widgets::resume_picker::render(rp, &state.sessions, frame, chunks[1]);
        }
        Some(BottomPane::ForkPicker(fp)) => {
            widgets::fork_picker::render(fp, &state.fork_candidates, frame, chunks[1]);
        }
        Some(BottomPane::BranchTree(bt)) => {
            widgets::branch_tree::render(bt, state.branch_tree.as_ref(), frame, chunks[1]);
        }
        Some(BottomPane::Settings(settings)) => {
            widgets::settings_pane::render(settings, frame, chunks[1]);
        }
        None => {
            widgets::editor::render(state, frame, chunks[1]);
            if state.in_flight.is_some() {
                widgets::spinner::render(state, frame, chunks[1]);
            }
        }
    }
    widgets::footer::render(state, frame, chunks[2]);
}

fn editor_height(state: &AppState) -> u16 {
    let lines = state.editor.text.matches('\n').count() as u16 + 1;
    lines.clamp(3, 10)
}

#[cfg(test)]
mod tests {
    use super::*;
    use ratatui::backend::TestBackend;
    use ratatui::Terminal;

    #[test]
    fn render_default_state_does_not_panic() {
        let state = AppState::default();
        let mut term = Terminal::new(TestBackend::new(80, 24))
            .unwrap_or_else(|e| panic!("terminal should initialize: {e}"));
        term.draw(|f| render(&state, f))
            .unwrap_or_else(|e| panic!("draw should succeed: {e}"));
    }

    #[test]
    fn editor_height_grows_with_newlines() {
        let mut s = AppState::default();
        s.editor.text = "a\nb\nc\nd".into();
        assert_eq!(editor_height(&s), 4);
        s.editor.text = "a".to_string();
        assert_eq!(editor_height(&s), 3); // clamped min
        s.editor.text = (0..20).map(|_| "x\n").collect::<String>();
        assert_eq!(editor_height(&s), 10); // clamped max
    }
}
