/* commit_log_view/reference.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, build_commit_item};
use crate::branch_info_box::BranchInfoBoxMsg;
use crate::commit_search_bar::CommitSearchBarMsg;
use crate::git::{self, BranchInfo, BranchRefs, TagInfo, UpstreamRef};
use crate::tag_info_box::TagInfoBoxMsg;

#[derive(Debug)]
pub enum ReferenceAction {
    ShowBranch {
        name: String,
        kind: git::BranchKind,
        is_head: bool,
    },
    ShowTag(String),
    ShowDetachedHead(String),
    Refresh,
    BranchInfoLoaded {
        generation: u64,
        result: Result<BranchInfo, String>,
    },
    TagInfoLoaded {
        generation: u64,
        result: Result<TagInfo, String>,
    },
    BadgesUpdated {
        generation: u64,
        tag_map: HashMap<String, Vec<String>>,
        branch_map: HashMap<String, BranchRefs>,
        upstream_ref: Option<UpstreamRef>,
    },
}

impl CommitLogView {
    pub(super) fn handle_reference_action(
        &mut self,
        action: ReferenceAction,
        widgets: &mut <Self as Component>::Widgets,
        sender: &ComponentSender<Self>,
    ) {
        match action {
            ReferenceAction::ShowBranch { name, kind, is_head } => {
                self.search_bar.emit(CommitSearchBarMsg::Hide);
                self.search_bar.emit(CommitSearchBarMsg::SetRef(Some(name.clone())));

                self.current_kind = Some(kind);
                self.current_is_head = is_head;
                self.current_is_detached = false;
                self.update_history_actions_enabled();
                self.branch_info_box.emit(BranchInfoBoxMsg::Clear);
                self.tag_info_box.emit(TagInfoBoxMsg::Clear);

                self.start_load(widgets, &name, sender);

                let generation = self.generation;
                let p = self.path.clone();
                sender.spawn_oneshot_command(move || {
                    CommitLogViewMsg::Reference(ReferenceAction::BranchInfoLoaded {
                        generation,
                        result: git::load_branch_info(&p, &name, kind, is_head),
                    })
                });
            }

            ReferenceAction::ShowTag(ref_name) => {
                self.search_bar.emit(CommitSearchBarMsg::Hide);
                self.search_bar.emit(CommitSearchBarMsg::SetRef(Some(ref_name.clone())));
                self.current_kind = None;
                self.current_is_head = false;
                self.current_is_detached = false;
                self.update_history_actions_enabled();
                self.branch_info_box.emit(BranchInfoBoxMsg::Clear);
                self.tag_info_box.emit(TagInfoBoxMsg::Clear);

                self.start_load(widgets, &ref_name, sender);

                let generation = self.generation;
                let p = self.path.clone();
                sender.spawn_oneshot_command(move || {
                    CommitLogViewMsg::Reference(ReferenceAction::TagInfoLoaded {
                        generation,
                        result: git::load_tag_info(&p, &ref_name),
                    })
                });
            }

            ReferenceAction::ShowDetachedHead(ref_name) => {
                self.search_bar.emit(CommitSearchBarMsg::Hide);
                self.search_bar.emit(CommitSearchBarMsg::SetRef(Some(ref_name.clone())));
                self.current_kind = None;
                self.current_is_head = false;
                self.current_is_detached = true;
                self.update_history_actions_enabled();
                self.branch_info_box.emit(BranchInfoBoxMsg::Clear);
                self.tag_info_box.emit(TagInfoBoxMsg::Clear);

                self.start_load(widgets, &ref_name, sender);
            }

            ReferenceAction::Refresh => {
                let Some(ref_name) = self.current_ref.clone() else {
                    return;
                };

                // If we're viewing a branch that has been deleted (e.g. pruned by auto-fetch or user deleted
                // it in the shell), clear the view instead of showing an error toast
                if let Some(kind) = self.current_kind
                    && !git::branch_exists(&self.path, &ref_name, kind)
                {
                    self.current_ref = None;
                    self.current_kind = None;
                    self.branch_info_box.emit(BranchInfoBoxMsg::Clear);
                    self.tag_info_box.emit(TagInfoBoxMsg::Clear);
                    self.list_view.clear();
                    self.diff_factory.guard().clear();
                    widgets.detail_stack.set_visible_child_name("empty");

                    return;
                }

                // Performance optimization: only do a full reload if the tip changed.
                // If only tags or upstream moved, do a lightweight badge-only refresh.
                let new_tip = git::resolve_tip_oid(&self.path, &ref_name);
                if new_tip.is_some() && new_tip == self.tip_oid {
                    let generation = self.generation;
                    let p = self.path.clone();
                    let kind = self.current_kind;
                    let ref_name_for_upstream = ref_name.clone();

                    sender.spawn_oneshot_command(move || {
                        let tag_map = git::build_tag_oid_map(&p).unwrap_or_default();
                        let branch_map = git::build_branch_oid_map(&p).unwrap_or_default();
                        let upstream_ref = kind.and_then(|k| git::resolve_upstream_oid(&p, &ref_name_for_upstream, k));
                        CommitLogViewMsg::Reference(ReferenceAction::BadgesUpdated {
                            generation,
                            tag_map,
                            branch_map,
                            upstream_ref,
                        })
                    });

                    if let Some(kind) = self.current_kind {
                        let generation = self.generation;
                        let p = self.path.clone();
                        let is_head = self.current_is_head;

                        sender.spawn_oneshot_command(move || {
                            CommitLogViewMsg::Reference(ReferenceAction::BranchInfoLoaded {
                                generation,
                                result: git::load_branch_info(&p, &ref_name, kind, is_head),
                            })
                        });
                    } else if !self.current_is_detached {
                        let generation = self.generation;
                        let p = self.path.clone();

                        sender.spawn_oneshot_command(move || {
                            CommitLogViewMsg::Reference(ReferenceAction::TagInfoLoaded {
                                generation,
                                result: git::load_tag_info(&p, &ref_name),
                            })
                        });
                    }

                    return;
                }

                self.branch_info_box.emit(BranchInfoBoxMsg::Clear);
                self.tag_info_box.emit(TagInfoBoxMsg::Clear);
                self.start_load(widgets, &ref_name, sender);

                if let Some(kind) = self.current_kind {
                    let generation = self.generation;
                    let p = self.path.clone();
                    let is_head = self.current_is_head;

                    sender.spawn_oneshot_command(move || {
                        CommitLogViewMsg::Reference(ReferenceAction::BranchInfoLoaded {
                            generation,
                            result: git::load_branch_info(&p, &ref_name, kind, is_head),
                        })
                    });
                } else if !self.current_is_detached {
                    let generation = self.generation;
                    let p = self.path.clone();

                    sender.spawn_oneshot_command(move || {
                        CommitLogViewMsg::Reference(ReferenceAction::TagInfoLoaded {
                            generation,
                            result: git::load_tag_info(&p, &ref_name),
                        })
                    });
                }
            }

            ReferenceAction::BranchInfoLoaded { generation, result } => {
                if generation != self.generation {
                    return;
                }

                match result {
                    Ok(info) => {
                        self.branch_info_box.emit(BranchInfoBoxMsg::ShowBranchInfo(info));
                    }

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

            ReferenceAction::TagInfoLoaded { generation, result } => {
                if generation != self.generation {
                    return;
                }

                match result {
                    Ok(info) => {
                        self.tag_info_box.emit(TagInfoBoxMsg::ShowTagInfo(info));
                    }

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

            ReferenceAction::BadgesUpdated {
                generation,
                tag_map,
                branch_map,
                upstream_ref,
            } => {
                if generation != self.generation {
                    return;
                }

                let commits: Vec<git::CommitInfo> = self
                    .list_view
                    .iter()
                    .map(|item| item.borrow::<super::CommitItem>().commit.clone())
                    .collect();

                if commits.is_empty() {
                    return;
                }

                self.tag_map = tag_map;
                self.branch_map = branch_map;
                self.upstream_ref = upstream_ref;

                let selected_positions = self.selected_positions();

                self.list_view.clear();
                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(
                    commits.into_iter().map(|commit| {
                        build_commit_item(commit, tag_map, branch_map, upstream, current_ref, current_kind)
                    }),
                );

                let len = self.list_view.len();
                self.list_view.selection_model.unselect_all();

                for pos in selected_positions {
                    if pos < len {
                        self.list_view.selection_model.select_item(pos, false);
                    }
                }
            }
        }
    }
}
