//! Branch Management View — `vcs.branches`.
//!
//! Layout:
//! ```text
//! ┌─ Git Branches ─────────────────────────────────────────────────────┐
//! │ ─── Local Branches ─────────────────────────────────────────────── │
//! │ ● main                                                              │
//! │   feature/editor-refactor                                           │
//! │   bugfix/crash-on-start                                             │
//! │                                                                     │
//! │ ─── Remote Branches ────────────────────────────────────────────── │
//! │   origin/main                                                       │
//! │   origin/feature/editor-refactor                                    │
//! ├────────────────────────────────────────────────────────────────────┤
//! │ [Enter: checkout]  [n: new branch]  [Esc: back]                    │
//! └────────────────────────────────────────────────────────────────────┘
//! ```
//!
//! Keybindings:
//!   Up / Down / PgUp / PgDn  — navigate branch list
//!   Enter                    — checkout highlighted branch
//!   n                        — open new-branch name input
//!   Esc                      — close view / cancel input

use std::path::PathBuf;
use std::cell::Cell;

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

use crate::prelude::*;
use input::{Key, KeyEvent};
use operation::{BranchViewOp, Event, Operation};
use settings::Settings;
use views::View;
use widgets::input_field::{self, InputField};

// ---------------------------------------------------------------------------
// Row — a unified list entry (section header or branch item)
// ---------------------------------------------------------------------------

#[derive(Debug, Clone)]
enum RowKind {
    Branch {
        name: String,
        is_current: bool,
        is_remote: bool,
    },
}

// ---------------------------------------------------------------------------
// BranchView
// ---------------------------------------------------------------------------

#[derive(Debug)]
pub struct BranchView {
    pub repo_path: PathBuf,
    local: Vec<String>,
    remote: Vec<String>,
    current: String,
    /// Flattened display rows (headers + branch items).
    rows: Vec<RowKind>,
    /// Cursor index into `rows`.  Always points to a `Branch` row.
    cursor: usize,
    /// Scroll offset (first visible row index) into `rows`.
    scroll: Cell<usize>,
    /// Last rendered visible height used for scrolling decisions.
    last_height: Cell<usize>,
    /// True while the initial branch list is being loaded.
    pub loading: bool,
    /// Optional new-branch name input (shown when user presses `n`).
    pub new_branch_input: Option<InputField>,
    /// Pending delete confirmation: branch name that needs a second Delete press.
    pub pending_delete: Option<String>,
    status_msg: Option<String>,
}

impl BranchView {
    pub fn new(repo_path: PathBuf) -> Self {
        Self {
            repo_path,
            local: Vec::new(),
            remote: Vec::new(),
            current: String::new(),
            rows: Vec::new(),
            cursor: 0,
            scroll: Cell::new(0),
            last_height: Cell::new(0),
            loading: true,
            new_branch_input: None,
            pending_delete: None,
            status_msg: None,
        }
    }

    /// Rebuild the flat row list from the current local/remote/current state.
    fn rebuild_rows(&mut self) {
        let mut rows = Vec::new();

        // Combine local then remote branches into a single flat list.
        for name in &self.local {
            rows.push(RowKind::Branch {
                name: name.clone(),
                is_current: name == &self.current,
                is_remote: false,
            });
        }

        for name in &self.remote {
            rows.push(RowKind::Branch {
                name: name.clone(),
                is_current: name == &self.current,
                is_remote: true,
            });
        }

        self.rows = rows;
        self.clamp_cursor_to_branch();
    }

    /// Ensure `cursor` points to a `Branch` row; advance to the first one if needed.
    fn clamp_cursor_to_branch(&mut self) {
        if matches!(self.rows.get(self.cursor), Some(RowKind::Branch { .. })) {
            return;
        }
        for (i, row) in self.rows.iter().enumerate() {
            if matches!(row, RowKind::Branch { .. }) {
                self.cursor = i;
                self.scroll.set(self.cursor); // ensure visible when rebuilding
                return;
            }
        }
    }

    fn move_up(&mut self) {
        if self.cursor == 0 {
            return;
        }
        let mut i = self.cursor - 1;
        loop {
            if matches!(self.rows.get(i), Some(RowKind::Branch { .. })) {
                self.cursor = i;
                // If cursor is above current scroll window, adjust scroll to show it.
                if self.cursor < self.scroll.get() {
                    self.scroll.set(self.cursor);
                }
                return;
            }
            if i == 0 {
                return;
            }
            i -= 1;
        }
    }

    fn move_down(&mut self) {
        let mut i = self.cursor + 1;
        while i < self.rows.len() {
            if matches!(self.rows.get(i), Some(RowKind::Branch { .. })) {
                self.cursor = i;
                // If cursor moved past bottom of visible area, adjust scroll.
                let vh = self.last_height.get();
                if vh > 0 && self.cursor >= self.scroll.get() + vh {
                    self.scroll.set(self.cursor + 1 - vh);
                }
                return;
            }
            i += 1;
        }
    }

    /// Return the name of the currently highlighted branch, if any.
    pub fn selected_branch_name(&self) -> Option<&str> {
        match self.rows.get(self.cursor) {
            Some(RowKind::Branch { name, .. }) => Some(name.as_str()),
            _ => None,
        }
    }

    /// True when the highlighted row is a remote-tracking branch.
    pub fn is_remote_selected(&self) -> bool {
        matches!(
            self.rows.get(self.cursor),
            Some(RowKind::Branch { is_remote: true, .. })
        )
    }

    /// Text currently entered in the new-branch input, if open.
    pub fn new_branch_text(&self) -> Option<&str> {
        self.new_branch_input.as_ref().map(|f| f.text())
    }
}

// ---------------------------------------------------------------------------
// View trait
// ---------------------------------------------------------------------------

impl View for BranchView {
    const KIND: crate::views::ViewKind = crate::views::ViewKind::Primary;

    fn save_state(&mut self, _app: &mut crate::app_state::AppState) {}

    fn handle_key(&self, key: KeyEvent) -> Vec<Operation> {
        // While the new-branch input is open, route all keys to it.
        if self.new_branch_input.is_some() {
            return match key.key {
                Key::Enter => vec![Operation::BranchViewLocal(BranchViewOp::NewBranchConfirm)],
                Key::Escape => vec![Operation::BranchViewLocal(BranchViewOp::NewBranchCancel)],
                _ => {
                    if let Some(fop) = input_field::key_to_op(key) {
                        vec![Operation::BranchViewLocal(BranchViewOp::NewBranchInput(fop))]
                    } else {
                        vec![]
                    }
                }
            };
        }

        match key.key {
            Key::ArrowUp => vec![Operation::NavigateUp],
            Key::ArrowDown => vec![Operation::NavigateDown],
            Key::PageUp => vec![Operation::NavigatePageUp],
            Key::PageDown => vec![Operation::NavigatePageDown],
            Key::Enter => vec![Operation::BranchViewLocal(BranchViewOp::CheckoutBranch)],
            Key::Char('n') => vec![Operation::BranchViewLocal(BranchViewOp::NewBranchOpen)],
            Key::Delete => {
                // If a pending delete exists for the currently selected branch, confirm.
                if let Some(sel) = self.selected_branch_name() {
                    if self.pending_delete.as_deref() == Some(sel) {
                        vec![Operation::BranchViewLocal(BranchViewOp::DeleteConfirmed)]
                    } else {
                        vec![Operation::BranchViewLocal(BranchViewOp::DeleteRequested)]
                    }
                } else {
                    vec![]
                }
            }
            Key::Escape => vec![Operation::Close],
            _ => vec![],
        }
    }

    fn handle_operation(&mut self, op: &Operation, _settings: &Settings) -> Option<Event> {
        match op {
            Operation::NavigateUp => {
                self.move_up();
                Some(Event::applied("git_branches", op.clone()))
            }
            Operation::NavigateDown => {
                self.move_down();
                Some(Event::applied("git_branches", op.clone()))
            }
            Operation::NavigatePageUp => {
                for _ in 0..10 {
                    self.move_up();
                }
                Some(Event::applied("git_branches", op.clone()))
            }
            Operation::NavigatePageDown => {
                for _ in 0..10 {
                    self.move_down();
                }
                Some(Event::applied("git_branches", op.clone()))
            }

            Operation::BranchViewLocal(bv_op) => {
                match bv_op {
                    BranchViewOp::BranchesLoaded { local, remote, current } => {
                        self.local = local.clone();
                        self.remote = remote.clone();
                        self.current = current.clone();
                        self.loading = false;
                        self.rebuild_rows();
                        // Try to position cursor on the current branch.
                        for (i, row) in self.rows.iter().enumerate() {
                            match row {
                                RowKind::Branch { name, .. } if name == current => {
                                    self.cursor = i;
                                    break;
                                }
                                _ => {}
                            }
                        }
                        self.scroll.set(self.cursor);
                    }
                    BranchViewOp::LoadError(msg) => {
                        self.status_msg = Some(format!("Error: {msg}"));
                        self.loading = false;
                    }
                    // CheckoutBranch and NewBranchConfirm are intercepted in app.rs;
                    // the view does not need to react here.
                    BranchViewOp::CheckoutBranch | BranchViewOp::NewBranchConfirm => {}
                    BranchViewOp::CheckoutCompleted(branch) => {
                        self.current = branch.clone();
                        self.status_msg = Some(format!("Switched to '{branch}'"));
                        self.loading = false;
                        self.rebuild_rows();
                    }
                    BranchViewOp::CheckoutError(msg) => {
                        self.status_msg = Some(format!("Checkout failed: {msg}"));
                        self.loading = false;
                    }
                    BranchViewOp::NewBranchOpen => {
                        self.new_branch_input = Some(InputField::new("Branch name"));
                        self.status_msg = None;
                    }
                    BranchViewOp::NewBranchInput(fop) => {
                        if let Some(f) = &mut self.new_branch_input {
                            f.apply(fop);
                        }
                    }
                    BranchViewOp::NewBranchCancel => {
                        self.new_branch_input = None;
                    }
                    BranchViewOp::CreateCompleted(branch) => {
                        self.new_branch_input = None;
                        self.current = branch.clone();
                        self.status_msg = Some(format!("Created and switched to '{branch}'"));
                        self.loading = false;
                        self.rebuild_rows();
                    }
                    BranchViewOp::CreateError(msg) => {
                        self.new_branch_input = None;
                        self.status_msg = Some(format!("Create failed: {msg}"));
                        self.loading = false;
                    }
                    BranchViewOp::DeleteConfirmNeeded { branch, merged } => {
                        // Ask the user to press Delete again to confirm.
                        self.pending_delete = Some(branch.clone());
                        if *merged {
                            self.status_msg = Some(format!("Press Delete again to delete '{branch}' (merged)"));
                        } else {
                            self.status_msg = Some(format!("Press Delete again to delete '{branch}' (not merged)"));
                        }
                    }
                    BranchViewOp::DeleteCompleted(branch) => {
                        self.pending_delete = None;
                        // Remove branch from lists if present.
                        self.local.retain(|b| b != branch);
                        self.remote.retain(|b| b != branch);
                        self.status_msg = Some(format!("Deleted '{branch}'"));
                        self.rebuild_rows();
                    }
                    BranchViewOp::DeleteError(msg) => {
                        self.pending_delete = None;
                        self.status_msg = Some(format!("Delete failed: {msg}"));
                    }
                    BranchViewOp::DeleteRequested | BranchViewOp::DeleteConfirmed => {
                        // Intercepted/handled by app.rs — no view action here.
                    }
                }
                Some(Event::applied("git_branches", op.clone()))
            }

            _ => None,
        }
    }

    fn render(&self, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
        render_view(self, frame, area, theme);
    }

    fn status_bar(
        &self,
        _state: &crate::app_state::AppState,
        bar: &mut crate::widgets::status_bar::StatusBarBuilder,
    ) {
        bar.label("Git Branches");
        if !self.current.is_empty() {
            bar.label(format!("  {}", self.current));
        }
    }
}

// ---------------------------------------------------------------------------
// Render
// ---------------------------------------------------------------------------

fn render_view(view: &BranchView, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
    let _bg = theme.bg();

    // Borderless layout: render directly into the provided area (no outer block).
    let inner = area;

    // Vertical split: branch list | optional input area (3 rows)
    let has_input = view.new_branch_input.is_some();
    let input_height = if has_input { 3 } else { 0 };

    let rows = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Min(1), Constraint::Length(input_height)])
        .split(inner);

    let list_area = rows[0];
    let input_area = rows[1];

    render_branch_list(view, frame, list_area, theme);

    if has_input {
        render_new_branch_input(view, frame, input_area, theme);
    }
}

fn render_branch_list(
    view: &BranchView,
    frame: &mut Frame,
    area: Rect,
    theme: &crate::theme::Theme,
) {
    let bg = theme.bg();
    let fg = theme.fg();
    let fg_dim = theme.fg_dim();
    let sel_bg = theme.selection_bg();
    let sel_fg = theme.selection_fg();
    let accent = theme.accent();

    if view.loading && view.rows.is_empty() {
        frame.render_widget(
            Paragraph::new(Span::styled("Loading…", Style::default().fg(fg_dim))),
            area,
        );
        return;
    }

    // Compute visible window (scroll) but preserve the view's scroll so that
    // upward movement only scrolls when the cursor reaches the top of the window.
    let height = area.height as usize;
    view.last_height.set(height);
    let total = view.rows.len();

    // Start from the persisted scroll offset.
    let mut scroll = if height == 0 { 0 } else { view.scroll.get() };

    // Clamp scroll into a valid range.
    let max_scroll = total.saturating_sub(height);
    if scroll > max_scroll { scroll = max_scroll; }

    if height > 0 {
        if view.cursor < scroll {
            // Cursor moved above the visible window — scroll up to show it at top.
            scroll = view.cursor;
        } else if view.cursor >= scroll + height {
            // Cursor moved below visible window — scroll down so cursor is visible at bottom.
            scroll = view.cursor + 1 - height;
            if scroll > max_scroll { scroll = max_scroll; }
        }
    } else {
        scroll = 0;
    }

    let items: Vec<ListItem> = view
        .rows
        .iter()
        .enumerate()
        .skip(scroll)
        .take(if height == 0 { 0 } else { height })
        .map(|(i, row)| {
            let RowKind::Branch { name, is_current, is_remote } = row;
            let selected = i == view.cursor;
            let item_bg = if selected { sel_bg } else { bg };
            let item_fg = if selected { sel_fg } else { fg };

            let indicator = if *is_current { "● " } else { "  " };
            let indicator_style = Style::default()
                .fg(if *is_current { accent } else { fg_dim })
                .bg(item_bg);

            let name_style = Style::default()
                .fg(item_fg)
                .bg(item_bg)
                .add_modifier(if selected { Modifier::BOLD } else { Modifier::empty() });

            let remote_label_style = Style::default().fg(fg_dim).bg(item_bg);

            let mut spans = vec![
                Span::styled(indicator, indicator_style),
                Span::styled(name.clone(), name_style),
            ];

            if *is_remote {
                spans.push(Span::styled("  (remote)", remote_label_style));
            }

            if *is_current {
                spans.push(Span::styled(
                    "  (current)",
                    Style::default().fg(accent).bg(item_bg),
                ));
            }

            ListItem::new(Line::from(spans))
        })
        .collect();

    // If there are no items visible, show empty placeholder.
    if items.is_empty() {
        frame.render_widget(
            Paragraph::new(Span::styled("  (no branches)", Style::default().fg(fg_dim))),
            area,
        );
        // persist scroll
        view.scroll.set(0);
        return;
    }

    frame.render_widget(List::new(items).style(Style::default().bg(bg)), area);

    // persist computed scroll so subsequent moves use it.
    view.scroll.set(scroll);

    // Status message overlay at bottom-right of list.
    if let Some(msg) = &view.status_msg {
        let max_w = area.width.saturating_sub(2) as usize;
        let truncated: String = msg.chars().take(max_w).collect();
        let msg_len = truncated.chars().count() as u16;
        let msg_area = Rect {
            x: area.x + area.width.saturating_sub(msg_len + 1),
            y: area.y + area.height.saturating_sub(1),
            width: msg_len + 1,
            height: 1,
        };
        frame.render_widget(
            Paragraph::new(Span::styled(
                truncated,
                Style::default()
                    .fg(theme.fg_dim())
                    .add_modifier(Modifier::ITALIC),
            )),
            msg_area,
        );
    }
}


fn render_new_branch_input(
    view: &BranchView,
    frame: &mut Frame,
    area: Rect,
    theme: &crate::theme::Theme,
) {
    if let Some(input) = &view.new_branch_input {
        input.render(frame, area, true, theme);
    }
}
