/* window_content.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::path::PathBuf;

use adw::prelude::*;
use gettextrs::gettext;
use gtk::{gio, glib};
use relm4::prelude::*;

use crate::repo_view::{self, RepoView, RepoViewMsg, TagAction, WorkspaceTab};
use crate::start_screen::{StartScreen, StartScreenInit, StartScreenOutput};
use crate::ui_helpers::icon_label_widget;
use crate::{git, menus, preferences, start_screen, update_check};

pub struct WindowContentInit {
    pub folder_select_supported: bool,
}

pub struct WindowContent {
    start_screen: Controller<StartScreen>,
    repo_view: Option<Controller<RepoView>>,
    stack: gtk::Stack,
    back_button: gtk::Button,
    pull_button: adw::SplitButton,
    push_button: gtk::Button,
    stash_button: adw::SplitButton,
    sidebar_button: gtk::ToggleButton,
    nav_buttons_box: gtk::Box,
    nav_working_copy: gtk::ToggleButton,
    nav_stashes: gtk::ToggleButton,
    nav_current_branch: gtk::ToggleButton,
    stash_pop_action: gio::SimpleAction,
    fetch_action: gio::SimpleAction,
    pull_action: gio::SimpleAction,
    push_action: gio::SimpleAction,
    spinner: adw::Spinner,
    menu_button: gtk::MenuButton,
    title_widget: adw::WindowTitle,
    update_banner: adw::Banner,
    latest_version: Option<String>,
    repo_id: Option<String>,
    repo_path: Option<PathBuf>,
    start_has_repos: bool,
    on_start: bool,
    sidebar_visible: bool,
}

#[derive(Debug)]
pub enum WindowContentMsg {
    AddRepository,
    CloneRepository,
    OpenRepository { path: PathBuf, id: String, name: String },
    OpenPath(PathBuf),
    BackToStart,
    FocusSearch,
    StartHasReposChanged(bool),
    Stash,
    StashWithUntracked,
    StashPop,
    StashCountChanged(usize),
    Pull,
    Push,
    Fetch,
    RemoteOpStarted,
    RemoteOpFinished,
    HeadBranchChanged(Option<String>, git::HeadKind),
    ShowRepoSettings,
    CreateTag,
    CreateBranch,
    CreateArchive,
    FocusCommit,
    ToggleSidebar,
    SidebarVisibilityChanged(bool),
    ActiveWorkspaceTabChanged(Option<WorkspaceTab>),
    NavWorkspaceSelected(WorkspaceTab),
    DismissUpdate,
}

#[derive(Debug)]
pub enum WindowContentCmd {
    UpdateAvailable(String),
}

#[derive(Debug)]
pub enum WindowContentOutput {
    TitleChanged(String),
    SearchEnabled(bool),
    RepoPathChanged(Option<PathBuf>),
}

#[relm4::component(pub)]
impl Component for WindowContent {
    type Init = WindowContentInit;
    type Input = WindowContentMsg;
    type Output = WindowContentOutput;
    type CommandOutput = WindowContentCmd;

    view! {
        adw::ToolbarView {
            add_top_bar = &adw::HeaderBar {
                #[wrap(Some)]
                set_title_widget = &model.title_widget.clone(),

                pack_start = &model.back_button.clone() {
                    set_icon_name: "go-previous-symbolic",
                    set_tooltip_text: Some(&gettext("Back to Repositories")),
                    set_visible: false,
                    connect_clicked => WindowContentMsg::BackToStart,
                },

                pack_start = &model.sidebar_button.clone() {
                    set_icon_name: "sidebar-show-symbolic",
                    set_tooltip_text: Some(&gettext("Toggle Sidebar")),
                    set_visible: false,
                    connect_clicked => WindowContentMsg::ToggleSidebar,
                },

                pack_start = &model.nav_buttons_box.clone() {},

                pack_end = &model.menu_button.clone() {
                    set_icon_name: "open-menu-symbolic",
                    set_tooltip_text: Some(&gettext("Main Menu")),
                    set_primary: true,
                    set_menu_model: Some(&menus::start_menu()),
                },

                pack_end = &model.stash_button.clone() {
                    set_child: Some(&icon_label_widget("stash-symbolic", &gettext("Stash"))),
                    set_tooltip_text: Some(&gettext("Stash")),
                    set_menu_model: Some(&menus::stash_menu()),
                    set_visible: false,
                    connect_clicked => WindowContentMsg::Stash,
                },

                pack_end = &model.spinner.clone() {
                    set_visible: false,
                },

                pack_end = &model.push_button.clone() {
                    set_child: Some(&icon_label_widget("push-symbolic", &gettext("Push"))),
                    set_tooltip_text: Some(&gettext("Push")),
                    add_css_class: "flat",
                    set_visible: false,
                    connect_clicked => WindowContentMsg::Push,
                },

                pack_end = &model.pull_button.clone() {
                    set_child: Some(&icon_label_widget("pull-symbolic", &gettext("Pull"))),
                    set_tooltip_text: Some(&gettext("Pull")),
                    set_menu_model: Some(&menus::pull_menu()),
                    set_visible: false,
                    connect_clicked => WindowContentMsg::Pull,
                },
            },

            add_top_bar = &model.update_banner.clone() {
                connect_button_clicked => WindowContentMsg::DismissUpdate,
            },

            #[wrap(Some)]
            set_content = &model.stack.clone() {
                add_named[Some("start")] = model.start_screen.widget(),
            },
        }
    }

    fn init(init: Self::Init, root: Self::Root, sender: ComponentSender<Self>) -> ComponentParts<Self> {
        let start_screen = StartScreen::builder()
            .launch(StartScreenInit {
                folder_select_supported: init.folder_select_supported,
            })
            .forward(sender.input_sender(), |output| match output {
                StartScreenOutput::OpenRepository { path, id, name } => {
                    WindowContentMsg::OpenRepository { path, id, name }
                }
                StartScreenOutput::HasReposChanged(has) => WindowContentMsg::StartHasReposChanged(has),
            });

        let stack = gtk::Stack::new();
        stack.set_transition_type(gtk::StackTransitionType::SlideLeftRight);

        let back_button = gtk::Button::new();
        let sidebar_button = gtk::ToggleButton::new();
        let sidebar_visible = preferences::app_settings().boolean("sidebar-visible");
        sidebar_button.set_active(sidebar_visible);
        let pull_button = adw::SplitButton::new();
        let push_button = gtk::Button::new();
        let stash_button = adw::SplitButton::new();
        let menu_button = gtk::MenuButton::new();
        let spinner = adw::Spinner::new();
        let title_widget = adw::WindowTitle::new("Gitte", "");

        let update_banner = adw::Banner::new("");
        update_banner.set_button_label(Some(&gettext("Dismiss")));
        update_banner.set_revealed(false);

        let nav_working_copy = gtk::ToggleButton::new();
        nav_working_copy.set_icon_name("working-copy-symbolic");
        nav_working_copy.set_tooltip_text(Some(&gettext("Working Copy")));
        nav_working_copy.set_active(true);
        nav_working_copy.add_css_class("flat");

        let nav_stashes = gtk::ToggleButton::new();
        nav_stashes.set_icon_name("stash-symbolic");
        nav_stashes.set_tooltip_text(Some(&gettext("Stashes")));
        nav_stashes.add_css_class("flat");

        let nav_current_branch = gtk::ToggleButton::new();
        nav_current_branch.set_icon_name("branch-symbolic");
        nav_current_branch.set_tooltip_text(Some(&gettext("Current Branch")));
        nav_current_branch.add_css_class("flat");

        let nav_buttons_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
        nav_buttons_box.set_visible(false);
        nav_buttons_box.append(&nav_working_copy);
        nav_buttons_box.append(&nav_stashes);
        nav_buttons_box.append(&nav_current_branch);

        {
            let s = sender.input_sender().clone();
            nav_working_copy
                .connect_clicked(move |_| s.emit(WindowContentMsg::NavWorkspaceSelected(WorkspaceTab::WorkingCopy)));
        }
        {
            let s = sender.input_sender().clone();
            nav_stashes.connect_clicked(move |_| s.emit(WindowContentMsg::NavWorkspaceSelected(WorkspaceTab::Stashes)));
        }
        {
            let s = sender.input_sender().clone();
            nav_current_branch
                .connect_clicked(move |_| s.emit(WindowContentMsg::NavWorkspaceSelected(WorkspaceTab::CurrentBranch)));
        }

        let stash_pop_action = gio::SimpleAction::new("stash-pop", None);
        let fetch_action = gio::SimpleAction::new("fetch", None);
        let pull_action = gio::SimpleAction::new("pull", None);
        let push_action = gio::SimpleAction::new("push", None);

        let model = WindowContent {
            start_screen,
            repo_view: None,
            stack,
            back_button,
            sidebar_button,
            nav_buttons_box,
            nav_working_copy,
            nav_stashes,
            nav_current_branch,
            pull_button,
            push_button,
            stash_button,
            stash_pop_action,
            fetch_action,
            pull_action,
            push_action,
            spinner,
            menu_button,
            title_widget,
            update_banner,
            latest_version: None,
            repo_id: None,
            repo_path: None,
            start_has_repos: false,
            on_start: true,
            sidebar_visible,
        };
        let widgets = view_output!();

        let action_group = gio::SimpleActionGroup::new();
        let stash_untracked_action = gio::SimpleAction::new("stash-untracked", None);
        let s = sender.input_sender().clone();
        stash_untracked_action.connect_activate(move |_, _| s.emit(WindowContentMsg::StashWithUntracked));
        action_group.add_action(&stash_untracked_action);

        let s = sender.input_sender().clone();
        model
            .stash_pop_action
            .connect_activate(move |_, _| s.emit(WindowContentMsg::StashPop));
        model.stash_pop_action.set_enabled(false);
        action_group.add_action(&model.stash_pop_action);

        let s = sender.input_sender().clone();
        model
            .fetch_action
            .connect_activate(move |_, _| s.emit(WindowContentMsg::Fetch));
        action_group.add_action(&model.fetch_action);

        let s = sender.input_sender().clone();
        model
            .pull_action
            .connect_activate(move |_, _| s.emit(WindowContentMsg::Pull));
        action_group.add_action(&model.pull_action);

        let s = sender.input_sender().clone();
        model
            .push_action
            .connect_activate(move |_, _| s.emit(WindowContentMsg::Push));
        action_group.add_action(&model.push_action);

        let settings_action = gio::SimpleAction::new("settings", None);
        let s = sender.input_sender().clone();
        settings_action.connect_activate(move |_, _| s.emit(WindowContentMsg::ShowRepoSettings));
        action_group.add_action(&settings_action);

        let create_tag_action = gio::SimpleAction::new("create-tag", None);
        let s = sender.input_sender().clone();
        create_tag_action.connect_activate(move |_, _| s.emit(WindowContentMsg::CreateTag));
        action_group.add_action(&create_tag_action);

        let create_branch_action = gio::SimpleAction::new("create-branch", None);
        let s = sender.input_sender().clone();
        create_branch_action.connect_activate(move |_, _| s.emit(WindowContentMsg::CreateBranch));
        action_group.add_action(&create_branch_action);

        let archive_action = gio::SimpleAction::new("archive", None);
        let s = sender.input_sender().clone();
        archive_action.connect_activate(move |_, _| s.emit(WindowContentMsg::CreateArchive));
        action_group.add_action(&archive_action);

        let stash_action = gio::SimpleAction::new("stash", None);
        let s = sender.input_sender().clone();
        stash_action.connect_activate(move |_, _| s.emit(WindowContentMsg::Stash));
        action_group.add_action(&stash_action);

        let focus_commit_action = gio::SimpleAction::new("focus-commit", None);
        let s = sender.input_sender().clone();
        focus_commit_action.connect_activate(move |_, _| s.emit(WindowContentMsg::FocusCommit));
        action_group.add_action(&focus_commit_action);

        let toggle_sidebar_action = gio::SimpleAction::new("toggle-sidebar", None);
        let s = sender.input_sender().clone();
        toggle_sidebar_action.connect_activate(move |_, _| s.emit(WindowContentMsg::ToggleSidebar));
        action_group.add_action(&toggle_sidebar_action);

        root.connect_realize(move |widget| {
            if let Some(win) = widget.ancestor(adw::ApplicationWindow::static_type()) {
                win.insert_action_group("repo", Some(&action_group));
            }
        });

        if update_check::should_check() {
            sender.spawn_oneshot_command(|| {
                let version = update_check::fetch_latest_version().unwrap_or_default();
                update_check::record_check_done();
                WindowContentCmd::UpdateAvailable(version)
            });
        }

        ComponentParts { model, widgets }
    }

    fn update_cmd(&mut self, msg: Self::CommandOutput, _sender: ComponentSender<Self>, _root: &Self::Root) {
        match msg {
            WindowContentCmd::UpdateAvailable(version) => {
                if version.is_empty() {
                    return;
                }

                if !update_check::is_newer(crate::config::VERSION, &version) {
                    return;
                }

                if update_check::dismissed_version() == version {
                    return;
                }

                let url = update_url();
                self.update_banner.set_use_markup(true);

                self.update_banner.set_title(&format!(
                    "{} <a href=\"{}\">{}</a>",
                    gettext("A new version of Gitte is available:"),
                    glib::markup_escape_text(url),
                    glib::markup_escape_text(&version),
                ));

                self.update_banner.set_revealed(true);
                self.latest_version = Some(version);
            }
        }
    }

    fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>, root: &Self::Root) {
        match msg {
            WindowContentMsg::AddRepository => {
                self.start_screen.emit(start_screen::StartScreenMsg::AddRepository);
            }

            WindowContentMsg::CloneRepository => {
                self.start_screen.emit(start_screen::StartScreenMsg::CloneRepository);
            }

            WindowContentMsg::OpenRepository { path, id, name } => self.open_repository(path, id, name, &sender),

            WindowContentMsg::OpenPath(path) => {
                if !self.on_start {
                    self.back_to_start(&sender);
                }

                self.start_screen.emit(start_screen::StartScreenMsg::OpenPath(path));
            }

            WindowContentMsg::BackToStart => self.back_to_start(&sender),

            WindowContentMsg::FocusSearch => {
                if self.on_start {
                    self.start_screen.emit(start_screen::StartScreenMsg::FocusSearch);
                } else if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::ToggleSearch);
                }
            }

            WindowContentMsg::StartHasReposChanged(has) => {
                self.start_has_repos = has;
                self.sync_search_enabled(&sender);
            }

            WindowContentMsg::Stash => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::Stash);
                }
            }

            WindowContentMsg::StashWithUntracked => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::StashWithUntracked);
                }
            }

            WindowContentMsg::StashPop => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::StashPop);
                }
            }

            WindowContentMsg::StashCountChanged(count) => {
                self.stash_pop_action.set_enabled(count > 0);
            }

            WindowContentMsg::HeadBranchChanged(branch, kind) => {
                self.title_widget.set_subtitle(&branch.unwrap_or_default());

                self.nav_current_branch.set_icon_name(match kind {
                    git::HeadKind::Branch => "branch-symbolic",
                    git::HeadKind::Tag => "tag-symbolic",
                    git::HeadKind::Detached => "commit-symbolic",
                });
            }

            WindowContentMsg::Pull => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::Pull);
                }
            }

            WindowContentMsg::Push => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::Push);
                }
            }

            WindowContentMsg::Fetch => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::FetchAll);
                }
            }

            WindowContentMsg::RemoteOpStarted => {
                self.spinner.set_visible(true);
                self.push_button.set_sensitive(false);
                self.push_action.set_enabled(false);
                self.pull_button.set_sensitive(false);
                self.pull_action.set_enabled(false);
                self.fetch_action.set_enabled(false);
            }

            WindowContentMsg::RemoteOpFinished => {
                self.spinner.set_visible(false);
                self.push_button.set_sensitive(true);
                self.push_action.set_enabled(true);
                self.pull_button.set_sensitive(true);
                self.pull_action.set_enabled(true);
                self.fetch_action.set_enabled(true);
            }

            WindowContentMsg::ShowRepoSettings => {
                if let (Some(repo_id), Some(repo_path)) = (&self.repo_id, &self.repo_path) {
                    preferences::show_repo_settings_dialog(root, repo_id, repo_path);
                }
            }

            WindowContentMsg::CreateTag => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::Tag(TagAction::CreateRequested));
                }
            }

            WindowContentMsg::CreateBranch => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::CreateBranch);
                }
            }

            WindowContentMsg::CreateArchive => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::ArchiveRequested("HEAD".to_string()));
                }
            }

            WindowContentMsg::FocusCommit => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::FocusCommit);
                }
            }

            WindowContentMsg::ToggleSidebar => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::ToggleSidebar);
                }
            }

            WindowContentMsg::SidebarVisibilityChanged(visible) => {
                self.sidebar_visible = visible;
                self.sidebar_button.set_active(visible);
                self.nav_buttons_box.set_visible(!visible && !self.on_start);
            }

            WindowContentMsg::ActiveWorkspaceTabChanged(tab) => {
                self.nav_working_copy.set_active(tab == Some(WorkspaceTab::WorkingCopy));
                self.nav_stashes.set_active(tab == Some(WorkspaceTab::Stashes));

                self.nav_current_branch
                    .set_active(tab == Some(WorkspaceTab::CurrentBranch));
            }

            WindowContentMsg::NavWorkspaceSelected(tab) => {
                if let Some(rv) = &self.repo_view {
                    rv.emit(RepoViewMsg::WorkspaceSelected(tab));
                }
            }

            WindowContentMsg::DismissUpdate => {
                if let Some(version) = self.latest_version.clone() {
                    update_check::set_dismissed_version(&version);
                }

                self.update_banner.set_revealed(false);
            }
        }
    }
}

impl WindowContent {
    fn sync_search_enabled(&self, sender: &ComponentSender<Self>) {
        let enabled = if self.on_start {
            self.start_has_repos
        } else {
            self.repo_view.is_some()
        };

        let _ = sender.output(WindowContentOutput::SearchEnabled(enabled));
    }

    fn open_repository(&mut self, path: PathBuf, repo_id: String, name: String, sender: &ComponentSender<Self>) {
        if let Some(old) = self.repo_view.take() {
            self.stack.remove(old.widget());
        }

        if !repo_id.is_empty() {
            let ts = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .map(|d| d.as_secs() as i64)
                .unwrap_or(0);

            self.start_screen.emit(start_screen::StartScreenMsg::RepoOpenedLocal {
                id: repo_id.clone(),
                ts,
            });
        }

        self.repo_path = Some(path.clone());
        self.repo_id = if repo_id.is_empty() {
            None
        } else {
            Some(repo_id.clone())
        };

        let repo_view = RepoView::builder()
            .launch((path.clone(), repo_id))
            .forward(sender.input_sender(), |output| match output {
                repo_view::RepoViewOutput::StashCountChanged(count) => WindowContentMsg::StashCountChanged(count),
                repo_view::RepoViewOutput::RemoteOpStarted => WindowContentMsg::RemoteOpStarted,
                repo_view::RepoViewOutput::RemoteOpFinished => WindowContentMsg::RemoteOpFinished,
                repo_view::RepoViewOutput::SidebarVisibilityChanged(v) => WindowContentMsg::SidebarVisibilityChanged(v),

                repo_view::RepoViewOutput::HeadBranchChanged(branch, kind) => {
                    WindowContentMsg::HeadBranchChanged(branch, kind)
                }

                repo_view::RepoViewOutput::ActiveWorkspaceTabChanged(tab) => {
                    WindowContentMsg::ActiveWorkspaceTabChanged(tab)
                }
            });

        self.stack.add_named(repo_view.widget(), Some("repo"));
        self.stack.set_visible_child_name("repo");
        self.back_button.set_visible(true);
        self.sidebar_button.set_visible(true);
        self.nav_buttons_box.set_visible(!self.sidebar_visible);
        self.nav_working_copy.set_active(true);
        self.nav_stashes.set_active(false);
        self.nav_current_branch.set_active(false);
        self.pull_button.set_visible(true);
        self.push_button.set_visible(true);
        self.stash_button.set_visible(true);
        self.stash_pop_action.set_enabled(false);
        self.menu_button.set_menu_model(Some(&menus::repo_menu()));
        self.on_start = false;

        self.title_widget.set_title(&name);
        self.title_widget.set_subtitle("");
        let _ = sender.output(WindowContentOutput::TitleChanged(name));
        let _ = sender.output(WindowContentOutput::RepoPathChanged(Some(path)));

        self.repo_view = Some(repo_view);
        self.sync_search_enabled(sender);
    }

    fn back_to_start(&mut self, sender: &ComponentSender<Self>) {
        self.stack.set_visible_child_name("start");
        self.back_button.set_visible(false);
        self.sidebar_button.set_visible(false);
        self.nav_buttons_box.set_visible(false);
        self.pull_button.set_visible(false);
        self.push_button.set_visible(false);
        self.stash_button.set_visible(false);
        self.stash_pop_action.set_enabled(false);
        self.spinner.set_visible(false);
        self.menu_button.set_menu_model(Some(&menus::start_menu()));
        self.on_start = true;
        self.repo_id = None;
        self.repo_path = None;

        self.title_widget.set_title("Gitte");
        self.title_widget.set_subtitle("");
        let _ = sender.output(WindowContentOutput::TitleChanged("Gitte".to_string()));
        let _ = sender.output(WindowContentOutput::RepoPathChanged(None));

        self.sync_search_enabled(sender);

        if let Some(old) = self.repo_view.take() {
            self.stack.remove(old.widget());
        }

        /*
         * Refresh the start screen so updated last_opened timestamps and any
         * out-of-band edits to repositories.json are picked up
         */
        self.start_screen.emit(start_screen::StartScreenMsg::Reload);
    }
}

fn update_url() -> &'static str {
    if cfg!(target_os = "macos") {
        "https://gitlab.com/dehesselle/gitte_macos/-/releases/permalink/latest"
    } else {
        "https://codeberg.org/ckruse/Gitte"
    }
}
