/* tree_node.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::cell::{Cell, RefCell};
use std::collections::BTreeMap;

use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{gio, glib};

use crate::git::BranchEntry;

mod imp {
    use super::*;

    #[derive(Default)]
    pub struct TreeNode {
        pub name: RefCell<String>,
        pub full_ref: RefCell<String>,
        pub icon: RefCell<String>,
        pub is_group: Cell<bool>,
        pub is_head: Cell<bool>,
        pub has_upstream: Cell<bool>,
        pub children: RefCell<Vec<BranchEntry>>,
        pub group_context: RefCell<Option<String>>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for TreeNode {
        const NAME: &'static str = "GitteTreeNode";
        type Type = super::TreeNode;
    }

    impl ObjectImpl for TreeNode {}
}

glib::wrapper! {
    pub struct TreeNode(ObjectSubclass<imp::TreeNode>);
}

impl TreeNode {
    fn new_leaf(name: &str, full_ref: &str, icon: &str, is_head: bool, has_upstream: bool) -> Self {
        let obj: Self = glib::Object::new();

        obj.imp().name.replace(name.to_string());
        obj.imp().full_ref.replace(full_ref.to_string());
        obj.imp().icon.replace(icon.to_string());
        obj.imp().is_group.set(false);
        obj.imp().is_head.set(is_head);
        obj.imp().has_upstream.set(has_upstream);

        obj
    }

    fn new_group(
        name: &str,
        full_ref: &str,
        icon: &str,
        children: Vec<BranchEntry>,
        group_context: Option<String>,
    ) -> Self {
        let obj: Self = glib::Object::new();

        obj.imp().name.replace(name.to_string());
        obj.imp().full_ref.replace(full_ref.to_string());
        obj.imp().icon.replace(icon.to_string());
        obj.imp().is_group.set(true);
        obj.imp().children.replace(children);
        obj.imp().group_context.replace(group_context);

        obj
    }

    pub fn name(&self) -> String {
        self.imp().name.borrow().clone()
    }

    pub fn full_ref(&self) -> String {
        self.imp().full_ref.borrow().clone()
    }

    pub fn icon(&self) -> String {
        self.imp().icon.borrow().clone()
    }

    pub fn is_group(&self) -> bool {
        self.imp().is_group.get()
    }

    pub fn is_head(&self) -> bool {
        self.imp().is_head.get()
    }

    pub fn has_upstream(&self) -> bool {
        self.imp().has_upstream.get()
    }

    pub fn children(&self) -> Vec<BranchEntry> {
        self.imp().children.borrow().clone()
    }
}

fn group_icon_name(prefix: &str) -> &'static str {
    match prefix {
        "feature" | "features" | "feat" => "sparkle-regular-symbolic",
        "fix" | "bugfix" | "hotfix" => "bug-symbolic",
        "refactor" | "refactoring" => "arrow-counterclockwise-regular-symbolic",
        "release" | "releases" => "rocket-regular-symbolic",
        "chore" | "maintenance" => "wrench-symbolic",
        "docs" | "documentation" => "document-text-symbolic",
        "test" | "tests" => "beaker-symbolic",
        "ci" | "cd" => "build-symbolic",
        _ => "folder-symbolic",
    }
}

fn build_grouped_store(items: Vec<BranchEntry>, path_prefix: &str, head_branch: Option<&str>) -> gio::ListStore {
    let store = gio::ListStore::new::<TreeNode>();

    let mut groups: BTreeMap<String, Vec<BranchEntry>> = BTreeMap::new();
    let mut leaves: Vec<BranchEntry> = Vec::new();

    for entry in items {
        if let Some((prefix, suffix)) = entry.name.split_once('/') {
            groups.entry(prefix.to_string()).or_default().push(BranchEntry {
                name: suffix.to_string(),
                has_upstream: entry.has_upstream,
            });
        } else {
            leaves.push(entry);
        }
    }

    let join = |name: &str| -> String {
        if path_prefix.is_empty() {
            name.to_string()
        } else {
            format!("{path_prefix}/{name}")
        }
    };

    for leaf in &leaves {
        let full_ref = join(&leaf.name);
        let is_head = head_branch == Some(full_ref.as_str());

        store.append(&TreeNode::new_leaf(&leaf.name, &full_ref, "branch-symbolic", is_head, leaf.has_upstream));
    }

    for (prefix, children) in groups {
        let full_ref = join(&prefix);

        store.append(&TreeNode::new_group(
            &prefix,
            &full_ref,
            group_icon_name(&prefix),
            children,
            head_branch.map(str::to_string),
        ));
    }

    store
}

pub fn build_branch_store(branches: Vec<BranchEntry>, head_branch: Option<&str>) -> gio::ListStore {
    build_grouped_store(branches, "", head_branch)
}

pub fn build_remote_store(remotes: Vec<(String, Vec<String>)>) -> gio::ListStore {
    let store = gio::ListStore::new::<TreeNode>();

    for (remote_name, branches) in remotes {
        let entries: Vec<BranchEntry> = branches
            .into_iter()
            .map(|name| BranchEntry {
                name,
                has_upstream: false,
            })
            .collect();

        store.append(&TreeNode::new_group(&remote_name, &remote_name, "network-server-symbolic", entries, None));
    }

    store
}

pub fn create_children_model(item: &glib::Object) -> Option<gio::ListModel> {
    let node = item.downcast_ref::<TreeNode>()?;

    if !node.is_group() {
        return None;
    }

    let head_branch = node.imp().group_context.borrow().clone();
    let store = build_grouped_store(node.children(), &node.full_ref(), head_branch.as_deref());

    Some(store.upcast())
}
