/* working_copy_view/refresh.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 relm4::prelude::*;

use super::{
    ChangedFileRow, ChangedFileRowInit, ChangedFileRowInput, RowSection, ViewLayout, WorkingCopyView,
    WorkingCopyViewMsg, WorkingCopyViewOutput,
};
use crate::diff_view::DiffViewMsg;
use crate::git::{self, ChangedFile, FileStatus};

#[derive(Debug)]
pub enum RefreshAction {
    Refresh,
    StatusLoaded(Result<Vec<ChangedFile>, String>, Vec<String>),
}

impl WorkingCopyView {
    pub(super) fn handle_refresh_action(&mut self, action: RefreshAction, sender: &ComponentSender<Self>) {
        match action {
            RefreshAction::Refresh => {
                let selected_paths = self.selected_paths_all();
                self.spawn_refresh(sender, selected_paths);
            }

            RefreshAction::StatusLoaded(result, reselect_paths) => match result {
                Ok(files) => {
                    sender.output(WorkingCopyViewOutput::FileCountChanged(files.len())).ok();

                    if files != self.last_files {
                        self.last_files = files.clone();

                        self.all_staged = !files.is_empty() && files.iter().all(|f| f.is_fully_staged());
                        self.has_staged_files = files.iter().any(|f| f.index_status.is_some());

                        if self.structure_unchanged() {
                            self.update_rows_in_place();
                            self.diff_view.emit(DiffViewMsg::RefreshIfChanged);
                        } else {
                            self.repopulate_files_factory();
                            self.reselect_files(&reselect_paths);
                        }
                    } else {
                        self.diff_view.emit(DiffViewMsg::RefreshIfChanged);
                    }
                }

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

    pub(super) fn spawn_refresh(&self, sender: &ComponentSender<Self>, reselect_paths: Vec<String>) {
        let p = self.path.clone();
        sender.spawn_oneshot_command(move || {
            WorkingCopyViewMsg::Refresh(RefreshAction::StatusLoaded(git::load_status_from_path(&p), reselect_paths))
        });
    }

    pub(super) fn handle_operation_complete(
        &mut self,
        result: &Result<(), String>,
        sender: &ComponentSender<Self>,
        reselect_paths: Vec<String>,
    ) {
        if let Err(msg) = result {
            sender.output(WorkingCopyViewOutput::ShowToast(msg.clone())).ok();
        }

        self.spawn_refresh(sender, reselect_paths);
    }

    pub(super) fn collect_ops(&mut self, section: RowSection, indices: &[usize]) -> Vec<(String, Option<FileStatus>)> {
        let factory = self.factory_for_mut(section);
        let guard = factory.guard();

        let mut paths = Vec::with_capacity(indices.len());

        for idx in indices {
            if let Some(row) = guard.get(*idx) {
                paths.push(row.file().path.clone());
            }
        }

        drop(guard);

        paths
            .into_iter()
            .filter_map(|path| {
                self.last_files
                    .iter()
                    .find(|f| f.path == path)
                    .map(|f| (path.clone(), f.index_status))
            })
            .collect()
    }

    pub(super) fn repopulate_files_factory(&mut self) {
        let q = self.filter_query.to_lowercase();
        let matches = |file: &ChangedFile| q.is_empty() || file.path.to_lowercase().contains(&q);

        match self.view_layout {
            ViewLayout::Unified => {
                self.staged_factory.guard().clear();
                self.unstaged_factory.guard().clear();

                let mut guard = self.files_factory.guard();

                guard.clear();

                for file in &self.last_files {
                    if matches(file) {
                        guard.push_back(ChangedFileRowInit {
                            file: file.clone(),
                            repo_path: self.path.clone(),
                            section: RowSection::Unified,
                        });
                    }
                }
            }

            ViewLayout::Split => {
                self.files_factory.guard().clear();

                {
                    let mut guard = self.staged_factory.guard();
                    guard.clear();

                    for file in &self.last_files {
                        if file.index_status.is_some() && matches(file) {
                            guard.push_back(ChangedFileRowInit {
                                file: file.clone(),
                                repo_path: self.path.clone(),
                                section: RowSection::Staged,
                            });
                        }
                    }
                }

                {
                    let mut guard = self.unstaged_factory.guard();
                    guard.clear();

                    for file in &self.last_files {
                        if file.wt_status.is_some() && matches(file) {
                            guard.push_back(ChangedFileRowInit {
                                file: file.clone(),
                                repo_path: self.path.clone(),
                                section: RowSection::Unstaged,
                            });
                        }
                    }
                }
            }
        }
    }

    pub(super) fn structure_unchanged(&self) -> bool {
        let q = self.filter_query.to_lowercase();
        let matches = |file: &ChangedFile| q.is_empty() || file.path.to_lowercase().contains(&q);

        let same = |factory: &FactoryVecDeque<ChangedFileRow>, expected: Vec<&str>| -> bool {
            factory.len() == expected.len()
                && factory
                    .iter()
                    .zip(expected.iter())
                    .all(|(row, path)| row.file().path == *path)
        };

        match self.view_layout {
            ViewLayout::Unified => {
                let new_paths: Vec<&str> = self
                    .last_files
                    .iter()
                    .filter(|f| matches(f))
                    .map(|f| f.path.as_str())
                    .collect();

                same(&self.files_factory, new_paths)
            }

            ViewLayout::Split => {
                let new_staged: Vec<&str> = self
                    .last_files
                    .iter()
                    .filter(|f| f.index_status.is_some() && matches(f))
                    .map(|f| f.path.as_str())
                    .collect();

                let new_unstaged: Vec<&str> = self
                    .last_files
                    .iter()
                    .filter(|f| f.wt_status.is_some() && matches(f))
                    .map(|f| f.path.as_str())
                    .collect();

                same(&self.staged_factory, new_staged) && same(&self.unstaged_factory, new_unstaged)
            }
        }
    }

    pub(super) fn update_rows_in_place(&mut self) {
        let q = self.filter_query.to_lowercase();
        let matches = |file: &ChangedFile| q.is_empty() || file.path.to_lowercase().contains(&q);

        match self.view_layout {
            ViewLayout::Unified => {
                let updates: Vec<ChangedFile> = self.last_files.iter().filter(|f| matches(f)).cloned().collect();
                for (idx, file) in updates.into_iter().enumerate() {
                    self.files_factory.send(idx, ChangedFileRowInput::UpdateFile(file));
                }
            }

            ViewLayout::Split => {
                let staged: Vec<ChangedFile> = self
                    .last_files
                    .iter()
                    .filter(|f| f.index_status.is_some() && matches(f))
                    .cloned()
                    .collect();

                let unstaged: Vec<ChangedFile> = self
                    .last_files
                    .iter()
                    .filter(|f| f.wt_status.is_some() && matches(f))
                    .cloned()
                    .collect();

                for (idx, file) in staged.into_iter().enumerate() {
                    self.staged_factory.send(idx, ChangedFileRowInput::UpdateFile(file));
                }

                for (idx, file) in unstaged.into_iter().enumerate() {
                    self.unstaged_factory.send(idx, ChangedFileRowInput::UpdateFile(file));
                }
            }
        }
    }
}
