//! Git Pull view — fetch-first pull workflow with explicit Rebase / Merge choice.
//!
//! ## Flow
//!
//! ```text
//! [Trigger vcs.pull]
//!        │
//!        ▼
//!   Fetching…           (async git fetch)
//!        │
//!   ┌────┴─────────────────────────────────────────────┐
//!   │ fast-forward?                                    │
//!   │  yes                                         no  │
//!   ▼                                                  ▼
//! FastForwarding…                           AwaitingChoice
//!        │                                  (Rebase / Merge / Cancel)
//!   ┌────┴──┐                                          │
//!   │ok  err│                             ┌────────────┼─────────────┐
//!   ▼       ▼                             ▼            ▼             ▼
//! Done   Failed                     ApplyingRebase ApplyingMerge  (Close)
//!                                         │            │
//!                                    ┌────┴──┐    ┌────┴──┐
//!                                    │ok  err│    │ok  err│
//!                                    ▼       ▼    ▼       ▼
//!                                  Done  Failed Done   Failed
//! ```
//!
//! Key bindings (choice prompt):
//! - `↑` / `↓`   — navigate choices
//! - `Enter`      — confirm highlighted choice
//! - `Esc`        — close view

use std::path::PathBuf;

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

use crate::prelude::*;
use input::{Key, KeyEvent};
use operation::{Event, GitPullOp, Operation};
use settings::Settings;
use views::View;

// ---------------------------------------------------------------------------
// Phase — the state machine for the pull workflow
// ---------------------------------------------------------------------------

#[derive(Debug, Clone)]
pub enum PullPhase {
    /// Running `git fetch`.
    Fetching,
    /// Fetch succeeded; applying fast-forward.
    FastForwarding {
        upstream: String,
        current_branch: String,
    },
    /// Branch has diverged; waiting for user to choose Rebase, Merge, or Cancel.
    AwaitingChoice {
        current_branch: String,
        upstream: String,
    },
    /// Applying `git rebase @{u}`.
    ApplyingRebase,
    /// Applying `git merge --no-ff @{u}`.
    ApplyingMerge,
    /// Finished successfully.
    Done(String),
    /// An operation failed.
    Failed(String),
}

// ---------------------------------------------------------------------------
// Choice list items (index → command spawned by app.rs)
// ---------------------------------------------------------------------------

const CHOICES: &[&str] = &["Rebase (recommended)", "Merge", "Cancel"];

/// Index into `CHOICES` for the default selection.
pub const DEFAULT_CHOICE: usize = 0;

// ---------------------------------------------------------------------------
// GitPullView
// ---------------------------------------------------------------------------

#[derive(Debug)]
pub struct GitPullView {
    /// Path to the repository root (used by `app.rs` to spawn tasks).
    pub repo_path: PathBuf,
    /// Current workflow phase.
    pub phase: PullPhase,
    /// Cursor into `CHOICES` — only meaningful in `AwaitingChoice`.
    pub cursor: usize,
}

impl GitPullView {
    pub fn new(repo_path: PathBuf) -> Self {
        Self {
            repo_path,
            phase: PullPhase::Fetching,
            cursor: DEFAULT_CHOICE,
        }
    }

    fn move_up(&mut self) {
        if self.cursor > 0 {
            self.cursor -= 1;
        }
    }

    fn move_down(&mut self) {
        if self.cursor + 1 < CHOICES.len() {
            self.cursor += 1;
        }
    }

    /// Returns `true` when the view has reached a terminal state and the user
    /// can dismiss it by pressing Escape.
    pub fn is_terminal(&self) -> bool {
        matches!(self.phase, PullPhase::Done(_) | PullPhase::Failed(_))
    }
}

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

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

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

    fn handle_key(&self, key: KeyEvent) -> Vec<Operation> {
        match &self.phase {
            PullPhase::AwaitingChoice { .. } => match key.key {
                Key::ArrowUp => vec![Operation::GitPullLocal(GitPullOp::ChoiceUp)],
                Key::ArrowDown => vec![Operation::GitPullLocal(GitPullOp::ChoiceDown)],
                Key::Enter => vec![Operation::GitPullLocal(GitPullOp::ChoiceConfirm)],
                Key::Escape => vec![Operation::Close],
                _ => vec![],
            },
            // In terminal or in-progress phases Esc closes, nothing else is
            // actionable from the keyboard.
            _ => match key.key {
                Key::Escape | Key::Enter => vec![Operation::Close],
                _ => vec![],
            },
        }
    }

    fn handle_operation(&mut self, op: &Operation, _settings: &Settings) -> Option<Event> {
        match op {
            Operation::GitPullLocal(gp_op) => {
                match gp_op {
                    // ── Async fetch result ───────────────────────────────
                    GitPullOp::FetchCompleted {
                        can_fast_forward,
                        upstream,
                        current_branch,
                    } => {
                        if *can_fast_forward {
                            self.phase = PullPhase::FastForwarding {
                                upstream: upstream.clone(),
                                current_branch: current_branch.clone(),
                            };
                        } else {
                            self.phase = PullPhase::AwaitingChoice {
                                current_branch: current_branch.clone(),
                                upstream: upstream.clone(),
                            };
                            self.cursor = DEFAULT_CHOICE;
                        }
                    }
                    GitPullOp::FetchError(msg) => {
                        self.phase = PullPhase::Failed(format!("Fetch failed: {msg}"));
                    }

                    // ── Async fast-forward result ────────────────────────
                    GitPullOp::FastForwardCompleted => {
                        let upstream = match &self.phase {
                            PullPhase::FastForwarding { upstream, .. } => upstream.clone(),
                            _ => "upstream".to_owned(),
                        };
                        self.phase = PullPhase::Done(format!("Fast-forwarded to {upstream}"));
                    }
                    GitPullOp::FastForwardError(msg) => {
                        self.phase = PullPhase::Failed(format!("Fast-forward failed: {msg}"));
                    }

                    // ── Async rebase result ──────────────────────────────
                    GitPullOp::RebaseCompleted => {
                        self.phase = PullPhase::Done("Rebase complete".to_owned());
                    }
                    GitPullOp::RebaseError(msg) => {
                        self.phase = PullPhase::Failed(format!("Rebase failed: {msg}"));
                    }

                    // ── Async merge result ───────────────────────────────
                    GitPullOp::MergeCompleted => {
                        self.phase = PullPhase::Done("Merge complete".to_owned());
                    }
                    GitPullOp::MergeError(msg) => {
                        self.phase = PullPhase::Failed(format!("Merge failed: {msg}"));
                    }

                    // ── User navigation (intercepted here, not in app.rs) ─
                    GitPullOp::ChoiceUp => {
                        self.move_up();
                    }
                    GitPullOp::ChoiceDown => {
                        self.move_down();
                    }

                    // ChoiceConfirm is intercepted by app.rs before reaching
                    // the view; no view-side handling needed.
                    GitPullOp::ChoiceConfirm => {}
                }
                Some(Event::applied("git_pull", op.clone()))
            }

            // NavigateUp / NavigateDown work in the choice prompt too.
            Operation::NavigateUp => {
                if matches!(self.phase, PullPhase::AwaitingChoice { .. }) {
                    self.move_up();
                    Some(Event::applied("git_pull", op.clone()))
                } else {
                    None
                }
            }
            Operation::NavigateDown => {
                if matches!(self.phase, PullPhase::AwaitingChoice { .. }) {
                    self.move_down();
                    Some(Event::applied("git_pull", op.clone()))
                } else {
                    None
                }
            }

            _ => 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: Pull");
    }
}

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

fn render_view(view: &GitPullView, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
    let bg = theme.bg();
    frame.render_widget(
        Block::default().style(Style::default().bg(bg)),
        area,
    );

    // Centre the content vertically with a max content height.
    let content_height: u16 = match &view.phase {
        PullPhase::AwaitingChoice { .. } => 12,
        _ => 6,
    };
    let content_height = content_height.min(area.height);

    let vert_pad = area.height.saturating_sub(content_height) / 2;
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(vert_pad),
            Constraint::Length(content_height),
            Constraint::Min(0),
        ])
        .split(area);
    let content_area = chunks[1];

    // Horizontal padding.
    let horiz_pad = (area.width / 6).min(8);
    let inner_chunks = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Length(horiz_pad),
            Constraint::Min(1),
            Constraint::Length(horiz_pad),
        ])
        .split(content_area);
    let inner = inner_chunks[1];

    match &view.phase {
        PullPhase::Fetching => render_status(
            "Fetching from remote\u{2026}",
            None,
            "[Esc: cancel]",
            inner,
            frame,
            theme,
        ),
        PullPhase::FastForwarding { upstream, .. } => render_status(
            &format!("Fast-forwarding to {upstream}\u{2026}"),
            None,
            "",
            inner,
            frame,
            theme,
        ),
        PullPhase::ApplyingRebase => render_status(
            "Rebasing onto upstream\u{2026}",
            None,
            "",
            inner,
            frame,
            theme,
        ),
        PullPhase::ApplyingMerge => render_status(
            "Merging upstream\u{2026}",
            None,
            "",
            inner,
            frame,
            theme,
        ),
        PullPhase::Done(msg) => render_status(
            &format!("\u{2713} {msg}"),
            None,
            "[Esc: close]",
            inner,
            frame,
            theme,
        ),
        PullPhase::Failed(msg) => render_status(
            &format!("\u{2717} {msg}"),
            None,
            "[Esc: close]",
            inner,
            frame,
            theme,
        ),
        PullPhase::AwaitingChoice {
            current_branch,
            upstream,
        } => render_choice(view, current_branch, upstream, inner, frame, theme),
    }
}

/// Render a simple single-message status screen.
fn render_status(
    message: &str,
    _detail: Option<&str>,
    hint: &str,
    area: Rect,
    frame: &mut Frame,
    theme: &crate::theme::Theme,
) {
    let fg = theme.fg();
    let fg_dim = theme.fg_dim();
    let bg = theme.bg();
    let base = Style::default().bg(bg);

    // Message.
    frame.render_widget(
        Paragraph::new(Span::styled(
            message,
            Style::default().fg(fg).bg(bg).add_modifier(Modifier::BOLD),
        ))
        .alignment(Alignment::Center),
        area,
    );

    // Hint at the bottom of the allocated area.
    if !hint.is_empty() && area.height >= 2 {
        let hint_area = Rect {
            y: area.y + area.height - 1,
            height: 1,
            ..area
        };
        frame.render_widget(
            Paragraph::new(Span::styled(hint, Style::default().fg(fg_dim).bg(bg)))
                .style(base)
                .alignment(Alignment::Center),
            hint_area,
        );
    }
}

/// Render the diverged-branch choice prompt.
fn render_choice(
    view: &GitPullView,
    current_branch: &str,
    upstream: &str,
    area: Rect,
    frame: &mut Frame,
    theme: &crate::theme::Theme,
) {
    let fg = theme.fg();
    let fg_dim = theme.fg_dim();
    let accent = theme.accent();
    let sel_bg = theme.selection_bg();
    let sel_fg = theme.selection_fg();
    let bg = theme.bg();

    // Split: title (2) + gap (1) + sub-title (1) + gap (1) + list (3) + gap (1) + hint (1)
    let rows = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(1), // title
            Constraint::Length(1), // subtitle
            Constraint::Length(1), // spacer
            Constraint::Length(CHOICES.len() as u16), // choice list
            Constraint::Min(0),    // spacer
            Constraint::Length(1), // hint
        ])
        .split(area);

    // Title.
    frame.render_widget(
        Paragraph::new(Line::from(vec![
            Span::styled("Branch ", Style::default().fg(fg_dim).bg(bg)),
            Span::styled(
                current_branch,
                Style::default()
                    .fg(accent)
                    .bg(bg)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::styled(
                " has diverged from ",
                Style::default().fg(fg_dim).bg(bg),
            ),
            Span::styled(
                upstream,
                Style::default()
                    .fg(accent)
                    .bg(bg)
                    .add_modifier(Modifier::BOLD),
            ),
        ])),
        rows[0],
    );

    // Subtitle.
    frame.render_widget(
        Paragraph::new(Span::styled(
            "Choose how to integrate the upstream changes:",
            Style::default().fg(fg).bg(bg),
        )),
        rows[1],
    );

    // Choices.
    let items: Vec<ListItem> = CHOICES
        .iter()
        .enumerate()
        .map(|(i, label)| {
            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 selected { "\u{25b6} " } else { "  " };

            ListItem::new(Line::from(vec![
                Span::styled(
                    indicator,
                    Style::default()
                        .fg(if selected { accent } else { fg_dim })
                        .bg(item_bg),
                ),
                Span::styled(
                    *label,
                    Style::default()
                        .fg(item_fg)
                        .bg(item_bg)
                        .add_modifier(if selected {
                            Modifier::BOLD
                        } else {
                            Modifier::empty()
                        }),
                ),
            ]))
        })
        .collect();

    frame.render_widget(
        List::new(items)
            .block(Block::default().borders(Borders::NONE))
            .style(Style::default().bg(bg)),
        rows[3],
    );

    // Hint.
    frame.render_widget(
        Paragraph::new(Span::styled(
            "[\u{2191}\u{2193}: navigate]  [Enter: confirm]  [Esc: cancel]",
            Style::default().fg(fg_dim).bg(bg),
        ))
        .alignment(Alignment::Center),
        rows[5],
    );
}
