//! Git History Viewer — `vcs.history`.
//!
//! Layout:
//! ```text
//! +------------------------+------------------------------+
//! | [Filter commits      ] |                              |
//! |------------------------|   Diff Viewer (full height)  |
//! | Revisions list         |                              |
//! | (filterable)           |                              |
//! |------------------------|                              |
//! | Changed files          |                              |
//! +------------------------+------------------------------+
//! ```
//!
//! Focus ring (Tab/Shift+Tab):  Filter → Revisions → Files → Diff
//!
//! Keybindings:
//!   Tab / Shift+Tab           — cycle panes
//!   Up / Down / PgUp / PgDn  — navigate active pane
//!   Enter (revisions)         — load changed-files list + diff for commit
//!   Enter (files)             — load diff for selected file
//!   Up/Down (files)           — navigate + auto-load diff
//!   Printable + Backspace     — filter input (when Filter pane focused)
//!   Esc                       — return to FileSelector

use std::path::PathBuf;

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

use crate::prelude::*;
use input::{Key, KeyEvent};
use operation::{Event, GitHistoryOp, Operation};
use settings::Settings;
use views::View;
use widgets::DiffWidget;
use widgets::focus::FocusRing;
use widgets::focusable::FocusOp;
use widgets::input_field::{self, InputField};
use widgets::list::SelectableList;

use crate::vcs::{CommitInfo, FileChange};

const FOCUS_FILTER: &str = "filter";
const FOCUS_REVISIONS: &str = "revisions";
const FOCUS_FILES: &str = "files";
const FOCUS_DIFF: &str = "diff";

// ---------------------------------------------------------------------------
// GitHistoryView
// ---------------------------------------------------------------------------

#[derive(Debug)]
pub struct GitHistoryView {
    pub repo_path: PathBuf,

    filter: InputField,
    pub revisions: SelectableList<CommitInfo>,
    pub files: SelectableList<FileChange>,

    pub(crate) diff: Option<DiffWidget>,

    focus: FocusRing,

    /// Generation counter for the filter search task.
    pub search_generation: u64,

    pub loading: bool,

    /// Full OID of the commit whose files/diff are currently loaded.
    pub loaded_oid: Option<String>,

    status_msg: Option<String>,
}

impl GitHistoryView {
    pub fn new(repo_path: PathBuf) -> Self {
        Self {
            repo_path,
            filter: InputField::new("Filter commits"),
            revisions: SelectableList::new(vec![]),
            files: SelectableList::new(vec![]),
            diff: None,
            focus: FocusRing::new(vec![FOCUS_FILTER, FOCUS_REVISIONS, FOCUS_FILES, FOCUS_DIFF]),
            search_generation: 0,
            loading: true,
            loaded_oid: None,
            status_msg: None,
        }
    }

    /// Return the OID of the currently selected revision, if any.
    pub fn selected_revision_oid(&self) -> Option<&str> {
        self.revisions.selected().map(|c| c.oid.as_str())
    }

    /// Return the path of the currently selected file, if any.
    pub fn selected_file_path(&self) -> Option<&std::path::Path> {
        self.files.selected().map(|f| f.path.as_path())
    }

    pub fn filter_text(&self) -> &str {
        self.filter.text()
    }

    fn focused_pane(&self) -> &str {
        self.focus.current()
    }
}

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

impl View for GitHistoryView {
    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> {

        if key.key == Key::Tab {
            return vec![Operation::Focus(FocusOp::Next)];
        }
        if key.key == Key::BackTab {
            return vec![Operation::Focus(FocusOp::Prev)];
        }

        match self.focused_pane() {
            FOCUS_FILTER => {
                if let Some(field_op) = input_field::key_to_op(key) {
                    return vec![Operation::GitHistoryLocal(GitHistoryOp::FilterInput(
                        field_op,
                    ))];
                }
                match key.key {
                    Key::ArrowUp => vec![Operation::NavigateUp],
                    Key::ArrowDown => vec![Operation::NavigateDown],
                    Key::PageUp => vec![Operation::NavigatePageUp],
                    Key::PageDown => vec![Operation::NavigatePageDown],
                    _ => vec![],
                }
            }

            FOCUS_REVISIONS => 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::GitHistoryLocal(GitHistoryOp::SelectRevision)],
                _ => {
                    if let Some(field_op) = input_field::key_to_op(key) {
                        vec![Operation::GitHistoryLocal(GitHistoryOp::FilterInput(
                            field_op,
                        ))]
                    } else {
                        vec![]
                    }
                }
            },

            FOCUS_FILES => match key.key {
                // Navigation in files auto-loads diff: move cursor first, then
                // fire SelectFile so app.rs can spawn the diff load.
                Key::ArrowUp => vec![
                    Operation::NavigateUp,
                    Operation::GitHistoryLocal(GitHistoryOp::SelectFile),
                ],
                Key::ArrowDown => vec![
                    Operation::NavigateDown,
                    Operation::GitHistoryLocal(GitHistoryOp::SelectFile),
                ],
                Key::PageUp => vec![Operation::NavigatePageUp],
                Key::PageDown => vec![Operation::NavigatePageDown],
                Key::Enter => vec![Operation::GitHistoryLocal(GitHistoryOp::SelectFile)],
                _ => vec![],
            },

            FOCUS_DIFF => match key.key {
                Key::ArrowUp => vec![Operation::NavigateUp],
                Key::ArrowDown => vec![Operation::NavigateDown],
                Key::PageUp => vec![Operation::NavigatePageUp],
                Key::PageDown => vec![Operation::NavigatePageDown],
                _ => vec![],
            },

            _ => vec![],
        }
    }

    fn handle_operation(&mut self, op: &Operation, _settings: &Settings) -> Option<Event> {
        match op {
            Operation::Focus(focus_op) => {
                match focus_op {
                    FocusOp::Next => self.focus.focus_next(),
                    FocusOp::Prev => self.focus.focus_prev(),
                    _ => {}
                }
                Some(Event::applied("git_history", op.clone()))
            }

            Operation::GitHistoryLocal(gh_op) => {
                match gh_op {
                    GitHistoryOp::FilterInput(field_op) => {
                        self.filter.apply(field_op);
                        self.search_generation += 1;
                        self.loading = true;
                        // Clear stale results immediately so the UI doesn't show
                        // revisions that no longer match the new filter.
                        self.revisions.set_items(vec![]);
                        self.files.set_items(vec![]);
                        self.diff = None;
                        self.loaded_oid = None;
                    }

                    GitHistoryOp::CommitsLoaded { generation, commits } => {
                        if *generation == self.search_generation {
                            self.revisions.set_items(commits.clone());
                            self.loading = false;
                        }
                    }

                    // SelectRevision and SelectFile are intercepted by app.rs;
                    // the view doesn't need to mutate state here.
                    GitHistoryOp::SelectRevision | GitHistoryOp::SelectFile => {}

                    GitHistoryOp::FilesLoaded { oid, files } => {
                        self.files.set_items(files.clone());
                        self.loaded_oid = Some(oid.clone());
                        self.diff = None;
                        self.loading = false;
                    }

                    GitHistoryOp::DiffLoaded { lines } => {
                        self.diff = Some(DiffWidget::new(lines));
                        self.loading = false;
                    }

                    GitHistoryOp::LoadError(msg) => {
                        self.status_msg = Some(format!("Error: {msg}"));
                        self.loading = false;
                    }
                }
                Some(Event::applied("git_history", op.clone()))
            }

            Operation::NavigateUp => {
                match self.focused_pane() {
                    FOCUS_REVISIONS => self.revisions.move_up(),
                    FOCUS_FILES => self.files.move_up(),
                    FOCUS_DIFF => {
                        if let Some(d) = &mut self.diff {
                            d.move_up();
                        }
                    }
                    _ => {}
                }
                Some(Event::applied("git_history", op.clone()))
            }

            Operation::NavigateDown => {
                match self.focused_pane() {
                    FOCUS_REVISIONS => self.revisions.move_down(),
                    FOCUS_FILES => self.files.move_down(),
                    FOCUS_DIFF => {
                        if let Some(d) = &mut self.diff {
                            d.move_down();
                        }
                    }
                    _ => {}
                }
                Some(Event::applied("git_history", op.clone()))
            }

            Operation::NavigatePageUp => {
                match self.focused_pane() {
                    FOCUS_REVISIONS => {
                        for _ in 0..10 {
                            self.revisions.move_up();
                        }
                    }
                    FOCUS_FILES => {
                        for _ in 0..10 {
                            self.files.move_up();
                        }
                    }
                    FOCUS_DIFF => {
                        if let Some(d) = &mut self.diff {
                            d.page_up(20);
                        }
                    }
                    _ => {}
                }
                Some(Event::applied("git_history", op.clone()))
            }

            Operation::NavigatePageDown => {
                match self.focused_pane() {
                    FOCUS_REVISIONS => {
                        for _ in 0..10 {
                            self.revisions.move_down();
                        }
                    }
                    FOCUS_FILES => {
                        for _ in 0..10 {
                            self.files.move_down();
                        }
                    }
                    FOCUS_DIFF => {
                        if let Some(d) = &mut self.diff {
                            d.page_down(20);
                        }
                    }
                    _ => {}
                }
                Some(Event::applied("git_history", op.clone()))
            }

            _ => None,
        }
    }

    fn render(&self, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
        // Two-column split: left (revisions + files) | right (diff)
        let cols = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([Constraint::Percentage(38), Constraint::Min(1)])
            .split(area);

        // Left column: filter bar | revisions | files
        let left_rows = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Length(1), // filter bar
                Constraint::Min(4),    // revisions — takes remaining space
                Constraint::Length(8), // files — fixed height
            ])
            .split(cols[0]);

        render_filter(self, frame, left_rows[0], theme);
        render_revisions(self, frame, left_rows[1], theme);
        render_files(self, frame, left_rows[2], theme);
        render_diff(self, frame, cols[1], theme);
    }
}

// ---------------------------------------------------------------------------
// Render helpers
// ---------------------------------------------------------------------------

fn render_filter(view: &GitHistoryView, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
    let focused = view.focus.is_focused(FOCUS_FILTER);
    view.filter.render(frame, area, focused, theme);

    // Loading indicator overlaid at right edge of filter bar.
    if view.loading {
        let indicator_area = Rect {
            x: area.x + area.width.saturating_sub(10),
            y: area.y,
            width: 10.min(area.width),
            height: 1,
        };
        frame.render_widget(
            Paragraph::new(Span::styled(
                " loading…",
                Style::default().fg(theme.fg_dim()),
            )),
            indicator_area,
        );
    }
}

fn render_revisions(
    view: &GitHistoryView,
    frame: &mut Frame,
    area: Rect,
    theme: &crate::theme::Theme,
) {
    let focused = view.focus.is_focused(FOCUS_REVISIONS);
    let bg = if focused {
        theme.bg_active()
    } else {
        theme.bg_inactive()
    };

    let block = Block::default()
        .title(Span::styled(
            " Revisions ",
            Style::default()
                .fg(theme.accent())
                .add_modifier(Modifier::BOLD),
        ))
        .borders(Borders::TOP)
        .border_type(BorderType::Plain)
        .style(Style::default().bg(bg));

    let inner = block.inner(area);
    frame.render_widget(block, area);

    frame.render_widget(
        view.revisions.widget(|commit, selected| {
            let short_oid: String = commit.oid.chars().take(8).collect();
            let sel_bg = if selected { theme.selection_bg() } else { bg };
            let sel_fg = if selected {
                theme.selection_fg()
            } else {
                theme.fg()
            };

            let spans = vec![
                Span::styled(
                    format!("{short_oid} "),
                    Style::default().fg(Color::DarkGray).bg(sel_bg),
                ),
                Span::styled(
                    truncate(&commit.summary, 32),
                    Style::default()
                        .fg(sel_fg)
                        .bg(sel_bg)
                        .add_modifier(if selected { Modifier::BOLD } else { Modifier::empty() }),
                ),
                Span::styled(
                    format!(" {}", commit.date_relative),
                    Style::default().fg(theme.fg_dim()).bg(sel_bg),
                ),
            ];
            ListItem::new(Line::from(spans))
        }),
        inner,
    );
}

fn render_files(
    view: &GitHistoryView,
    frame: &mut Frame,
    area: Rect,
    theme: &crate::theme::Theme,
) {
    let focused = view.focus.is_focused(FOCUS_FILES);
    let bg = if focused {
        theme.bg_active()
    } else {
        theme.bg_inactive()
    };

    let block = Block::default()
        .title(Span::styled(
            " Changed Files ",
            Style::default()
                .fg(theme.accent())
                .add_modifier(Modifier::BOLD),
        ))
        .borders(Borders::TOP)
        .border_type(BorderType::Plain)
        .style(Style::default().bg(bg));

    let inner = block.inner(area);
    frame.render_widget(block, area);

    let file_items = crate::widgets::file_tree_list::build_tree_list_items_from_filechanges(
        view.files.items(),
        view.files.cursor(),
        view.focus.is_focused(FOCUS_FILES),
    );
    frame.render_widget(List::new(file_items), inner);
}

fn render_diff(
    view: &GitHistoryView,
    frame: &mut Frame,
    area: Rect,
    theme: &crate::theme::Theme,
) {
    let focused = view.focus.is_focused(FOCUS_DIFF);
    let bg = if focused {
        theme.bg_active()
    } else {
        theme.bg_inactive()
    };

    let block = Block::default()
        .title(Span::styled(
            " Diff ",
            Style::default()
                .fg(theme.accent())
                .add_modifier(Modifier::BOLD),
        ))
        .borders(Borders::LEFT)
        .border_type(BorderType::Plain)
        .style(Style::default().bg(bg));

    let inner = block.inner(area);
    frame.render_widget(block, area);

    // Status message overrides diff content if present.
    if let Some(ref msg) = view.status_msg {
        frame.render_widget(
            Paragraph::new(Span::styled(msg.as_str(), Style::default().fg(theme.level_warn()))),
            inner,
        );
        return;
    }

    match &view.diff {
        None => {
            let hint = if view.loaded_oid.is_none() {
                "Select a revision and press Enter to view diff"
            } else if view.files.is_empty() {
                "(no files changed)"
            } else {
                "Navigate to a file and press Enter to view diff"
            };
            frame.render_widget(
                Paragraph::new(Span::styled(hint, Style::default().fg(theme.fg_dim()))),
                inner,
            );
        }
        Some(w) => {
            w.render_into(inner, frame.buffer_mut(), focused);
        }
    }
}

fn truncate(s: &str, max_chars: usize) -> String {
    let chars: Vec<char> = s.chars().collect();
    if chars.len() <= max_chars {
        s.to_owned()
    } else {
        let mut t: String = chars[..max_chars - 1].iter().collect();
        t.push('…');
        t
    }
}
