use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct FileTreeNode {
    pub name: String,
    pub path: String,
    pub is_dir: bool,
    pub node_count: usize,
    pub children: Vec<FileTreeNode>,
}

/// Build a file tree from a list of graph nodes (serde_json::Value with file_path field).
pub fn build_file_tree(nodes: &[serde_json::Value]) -> Vec<FileTreeNode> {
    // Count nodes per file
    let mut file_counts: BTreeMap<String, usize> = BTreeMap::new();
    for node in nodes {
        if let Some(fp) = node.get("file_path").and_then(|v| v.as_str()) {
            *file_counts.entry(fp.to_string()).or_default() += 1;
        }
    }

    // Build tree structure
    let mut root_children: BTreeMap<String, FileTreeNode> = BTreeMap::new();

    for (file_path, count) in &file_counts {
        let parts: Vec<&str> = file_path.split('/').collect();
        insert_path(&mut root_children, &parts, file_path, *count);
    }

    let mut result: Vec<FileTreeNode> = root_children.into_values().collect();
    sort_tree(&mut result);
    result
}

fn insert_path(
    children: &mut BTreeMap<String, FileTreeNode>,
    parts: &[&str],
    full_path: &str,
    node_count: usize,
) {
    if parts.is_empty() {
        return;
    }

    let name = parts[0].to_string();
    let is_leaf = parts.len() == 1;

    let entry = children
        .entry(name.clone())
        .or_insert_with(|| FileTreeNode {
            name: name.clone(),
            path: if is_leaf {
                full_path.to_string()
            } else {
                String::new()
            },
            is_dir: !is_leaf,
            node_count: 0,
            children: Vec::new(),
        });

    if is_leaf {
        entry.node_count = node_count;
        entry.is_dir = false;
        entry.path = full_path.to_string();
    } else {
        entry.is_dir = true;
        entry.node_count += node_count;
        let mut child_map: BTreeMap<String, FileTreeNode> = entry
            .children
            .drain(..)
            .map(|c| (c.name.clone(), c))
            .collect();
        insert_path(&mut child_map, &parts[1..], full_path, node_count);
        entry.children = child_map.into_values().collect();
    }
}

fn sort_tree(nodes: &mut [FileTreeNode]) {
    nodes.sort_by(|a, b| {
        // Directories first, then alphabetical
        b.is_dir.cmp(&a.is_dir).then(a.name.cmp(&b.name))
    });
    for node in nodes.iter_mut() {
        sort_tree(&mut node.children);
    }
}

#[component]
pub fn FileTree(
    tree: Vec<FileTreeNode>,
    filter: String,
    on_file_click: EventHandler<String>,
) -> Element {
    rsx! {
        div { class: "file-tree",
            for node in tree.iter() {
                FileTreeItem {
                    node: node.clone(),
                    depth: 0,
                    filter: filter.clone(),
                    on_file_click: move |path: String| on_file_click.call(path),
                }
            }
        }
    }
}

#[component]
fn FileTreeItem(
    node: FileTreeNode,
    depth: usize,
    filter: String,
    on_file_click: EventHandler<String>,
) -> Element {
    let mut expanded = use_signal(|| depth == 0);

    // Filter: hide nodes that don't match
    let matches_filter = filter.is_empty()
        || node.name.to_lowercase().contains(&filter.to_lowercase())
        || node
            .children
            .iter()
            .any(|c| c.name.to_lowercase().contains(&filter.to_lowercase()));

    if !matches_filter {
        return rsx! {};
    }

    let padding_left = format!("{}px", 8 + depth * 16);

    rsx! {
        div {
            div {
                class: if node.is_dir { "file-tree-item file-tree-dir" } else { "file-tree-item file-tree-file" },
                style: "padding-left: {padding_left};",
                onclick: {
                    let path = node.path.clone();
                    let is_dir = node.is_dir;
                    move |_| {
                        if is_dir {
                            expanded.set(!expanded());
                        } else {
                            on_file_click.call(path.clone());
                        }
                    }
                },

                span { class: "file-tree-icon",
                    if node.is_dir {
                        if expanded() { "\u{25BE}" } else { "\u{25B8}" }
                    } else {
                        "\u{25CB}"
                    }
                }

                span { class: "file-tree-name", "{node.name}" }

                if node.node_count > 0 {
                    span { class: "file-tree-badge", "{node.node_count}" }
                }
            }

            if node.is_dir && expanded() {
                for child in node.children.iter() {
                    FileTreeItem {
                        node: child.clone(),
                        depth: depth + 1,
                        filter: filter.clone(),
                        on_file_click: move |path: String| on_file_click.call(path),
                    }
                }
            }
        }
    }
}
