/* commit_log_view/pagination.rs
 *
 * Copyright 2026 Christian Kruse
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

use std::collections::HashMap;

use adw::prelude::*;
use relm4::prelude::*;

use super::{CommitLogView, CommitLogViewMsg, CommitLogViewOutput, PAGE_SIZE, SPINNER_DELAY_MS, build_commit_item};
use crate::git::{self, BranchRefs, CommitPage, UpstreamRef};

#[derive(Debug)]
pub enum PaginationAction {
    LoadMore,
    ShowSpinnerIfPending {
        generation: u64,
    },
    PageLoaded {
        generation: u64,
        is_first: bool,
        page: Result<CommitPage, String>,
        tag_map: Option<HashMap<String, Vec<String>>>,
        branch_map: Option<HashMap<String, BranchRefs>>,
        upstream_ref: Option<Option<UpstreamRef>>,
    },
}

impl CommitLogView {
    pub(super) fn install_infinite_scroll(scroll: &gtk::ScrolledWindow, sender: &ComponentSender<Self>) {
        // when the user scrolls within one page-height of the bottom, request the next page
        let s = sender.input_sender().clone();
        scroll.vadjustment().connect_value_changed(move |adj| {
            let remaining = adj.upper() - (adj.value() + adj.page_size());
            if adj.upper() > adj.page_size() && remaining < adj.page_size() {
                s.emit(CommitLogViewMsg::Pagination(PaginationAction::LoadMore));
            }
        });
    }

    pub(super) fn schedule_spinner(sender: &ComponentSender<Self>, generation: u64) {
        let s = sender.input_sender().clone();
        gtk::glib::timeout_add_local_once(std::time::Duration::from_millis(SPINNER_DELAY_MS), move || {
            s.emit(CommitLogViewMsg::Pagination(PaginationAction::ShowSpinnerIfPending { generation }));
        });
    }

    pub(super) fn handle_pagination_action(&mut self, action: PaginationAction, sender: &ComponentSender<Self>) {
        match action {
            PaginationAction::LoadMore => {
                if self.pending_load || !self.has_more || self.current_ref.is_none() {
                    return;
                }

                let Some(ref_name) = self.current_ref.clone() else {
                    return;
                };

                let Some(start_after) = self.last_oid.clone() else {
                    return;
                };

                self.pending_load = true;
                let generation = self.generation;
                let p = self.path.clone();

                sender.spawn_oneshot_command(move || {
                    CommitLogViewMsg::Pagination(PaginationAction::PageLoaded {
                        generation,
                        is_first: false,
                        page: git::load_commits_page(&p, &ref_name, Some(&start_after), PAGE_SIZE),
                        tag_map: None,
                        branch_map: None,
                        upstream_ref: None,
                    })
                });

                Self::schedule_spinner(sender, generation);
            }

            PaginationAction::ShowSpinnerIfPending { generation } => {
                if generation == self.generation && self.pending_load {
                    self.loading = true;
                }
            }

            PaginationAction::PageLoaded {
                generation,
                is_first,
                page,
                tag_map,
                branch_map,
                upstream_ref,
            } => {
                if generation != self.generation {
                    return;
                }

                self.pending_load = false;
                self.loading = false;

                if let Some(tm) = tag_map {
                    self.tag_map = tm;
                }

                if let Some(bm) = branch_map {
                    self.branch_map = bm;
                }

                if let Some(ur) = upstream_ref {
                    self.upstream_ref = ur;
                }

                match page {
                    Ok(page) => {
                        if let Some(last) = page.commits.last() {
                            self.last_oid = Some(last.oid.clone());
                        }

                        self.has_more = page.has_more;

                        if is_first {
                            self.tip_oid = page.commits.first().map(|c| c.oid.clone());
                        }

                        let tag_map = &self.tag_map;
                        let branch_map = &self.branch_map;
                        let upstream = self.upstream_ref.as_ref();
                        let current_ref = self.current_ref.as_deref();
                        let current_kind = self.current_kind;

                        self.list_view.extend_from_iter(page.commits.into_iter().map(|commit| {
                            build_commit_item(commit, tag_map, branch_map, upstream, current_ref, current_kind)
                        }));

                        if is_first && !self.list_view.is_empty() {
                            let total = self.list_view.len();
                            let range = self.pending_select_range.take();

                            match range {
                                Some((start, len)) if start < total && len > 0 => {
                                    let clamped_len = len.min(total - start);
                                    self.list_view.selection_model.unselect_all();
                                    self.list_view.selection_model.select_range(start, clamped_len, false);
                                }

                                _ => {
                                    self.list_view.selection_model.select_item(0, true);
                                }
                            }
                        }
                    }

                    Err(msg) => {
                        sender.output(CommitLogViewOutput::ShowToast(msg)).ok();
                    }
                }
            }
        }
    }
}
