smart-tree 8.0.0

Smart Tree - An intelligent, AI-friendly directory visualization tool
Documentation
//! Relations formatter that works with the standard formatter interface
//! "Making relations a first-class mode!" - Omni

use crate::formatters::Formatter;
use crate::relations::RelationAnalyzer;
use crate::scanner::{FileNode, TreeStats};
use anyhow::Result;
use std::io::Write;
use std::path::Path;

/// Main relations formatter - delegates to text formatter by default
pub struct RelationsFormatter {
    filter: Option<String>,
    focus: Option<std::path::PathBuf>,
}

impl RelationsFormatter {
    pub fn new(filter: Option<String>, focus: Option<std::path::PathBuf>) -> Self {
        Self { filter, focus }
    }
}

impl Formatter for RelationsFormatter {
    fn format(
        &self,
        writer: &mut dyn Write,
        _nodes: &[FileNode],
        _stats: &TreeStats,
        root_path: &Path,
    ) -> Result<()> {
        // Create relation analyzer
        let mut analyzer = RelationAnalyzer::new();

        // Analyze the directory
        eprintln!("๐Ÿ” Analyzing code relationships...");
        analyzer.analyze_directory(root_path)?;

        // Apply filters if specified
        if let Some(filter_type) = &self.filter {
            // In a real implementation, we'd filter the relations
            eprintln!("๐Ÿ“‹ Filtering by: {}", filter_type);
        }

        // Get relations based on focus or all
        let relations: Vec<&crate::relations::FileRelation> = if let Some(focus_file) = &self.focus
        {
            // Convert relative path to absolute for matching
            let abs_focus = if focus_file.is_relative() {
                root_path.join(focus_file)
            } else {
                focus_file.clone()
            };

            let file_relations = analyzer.get_file_relations(&abs_focus);
            eprintln!(
                "๐Ÿ“„ Found {} relationships for {}",
                file_relations.len(),
                focus_file.display()
            );
            file_relations
        } else {
            analyzer.get_relations().iter().collect()
        };

        // Write header
        writeln!(writer, "๐Ÿ”— Code Relationship Analysis")?;
        writeln!(writer, "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”")?;
        writeln!(writer)?;

        // If no relationships found
        if relations.is_empty() {
            if let Some(focus_file) = &self.focus {
                writeln!(
                    writer,
                    "No relationships found for: {}",
                    focus_file.display()
                )?;
            } else {
                writeln!(writer, "No relationships found in the codebase.")?;
            }
            return Ok(());
        }

        // Group relations by type
        use crate::relations::RelationType;
        let mut imports = Vec::new();
        let mut calls = Vec::new();
        let mut types = Vec::new();
        let mut tests = Vec::new();
        let mut coupled = Vec::new();

        for relation in &relations {
            match &relation.relation_type {
                RelationType::Imports => imports.push(relation),
                RelationType::FunctionCall => calls.push(relation),
                RelationType::TypeUsage => types.push(relation),
                RelationType::TestedBy => tests.push(relation),
                RelationType::Coupled => coupled.push(relation),
                RelationType::Exports => {} // Skip exports for now
            }
        }

        // Display relationships by type
        if !imports.is_empty() {
            writeln!(writer, "๐Ÿ“ฆ Imports ({}):", imports.len())?;
            for rel in imports {
                writeln!(
                    writer,
                    "  {} โ†’ {}",
                    rel.source.display(),
                    rel.target.display()
                )?;
            }
            writeln!(writer)?;
        }

        if !calls.is_empty() {
            writeln!(writer, "๐Ÿ“ž Function Calls ({}):", calls.len())?;
            for rel in calls {
                writeln!(
                    writer,
                    "  {} โ†’ {}",
                    rel.source.display(),
                    rel.target.display()
                )?;
            }
            writeln!(writer)?;
        }

        if !types.is_empty() {
            writeln!(writer, "๐Ÿท๏ธ  Type Usage ({}):", types.len())?;
            for rel in types {
                writeln!(
                    writer,
                    "  {} โ†’ {}",
                    rel.source.display(),
                    rel.target.display()
                )?;
            }
            writeln!(writer)?;
        }

        if !tests.is_empty() {
            writeln!(writer, "๐Ÿงช Tests ({}):", tests.len())?;
            for rel in tests {
                writeln!(
                    writer,
                    "  {} โ†’ {}",
                    rel.source.display(),
                    rel.target.display()
                )?;
            }
            writeln!(writer)?;
        }

        if !coupled.is_empty() {
            writeln!(writer, "๐Ÿ”— Coupled Changes ({}):", coupled.len())?;
            for rel in coupled {
                writeln!(
                    writer,
                    "  {} โ†” {}",
                    rel.source.display(),
                    rel.target.display()
                )?;
            }
            writeln!(writer)?;
        }

        // Summary
        writeln!(writer, "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”")?;
        writeln!(writer, "Total relationships: {}", relations.len())?;
        if let Some(focus_file) = &self.focus {
            writeln!(writer, "Focused on: {}", focus_file.display())?;
        } else {
            writeln!(writer, "Files analyzed: {}", root_path.display())?;
        }

        Ok(())
    }
}