use crate::model;
use std::collections::HashMap;
use super::query::{find_used_components, find_used_stores};
pub fn nav_guide(map: &model::FrontendMap) {
println!("=== Navigation Guide ===\n");
println!("1. Entry Points:");
if let Some(app) = map.components.iter().find(|c| c.name == "App") {
println!(" - App: {}", app.file.display());
}
for route in map.routes.iter().take(5) {
println!(" - Route {}: {} → {}", route.path, route.component, route.file.display());
}
if !map.stores.is_empty() {
println!("\n2. Core Stores:");
for store in &map.stores {
println!(" - {} ({} subscribers)", store.name, store.subscribers.len());
}
}
let mut usage_count: HashMap<String, usize> = HashMap::new();
for comp in &map.components {
for user in &comp.used_by {
*usage_count.entry(user.to_string_lossy().to_string()).or_insert(0) += 1;
}
}
let mut usage_vec: Vec<_> = usage_count.iter().collect();
usage_vec.sort_by(|a, b| b.1.cmp(a.1));
if !usage_vec.is_empty() {
println!("\n3. Key Components (most referenced):");
for (name, count) in usage_vec.iter().take(5) {
println!(" - {} ({} references)", name, count);
}
}
if !map.api_calls.is_empty() {
println!("\n4. API Endpoints:");
for api in map.api_calls.iter().take(5) {
println!(" - {} {} ({})", api.method, api.endpoint, api.component);
}
if map.api_calls.len() > 5 {
println!(" ... and {} more", map.api_calls.len() - 5);
}
}
}
pub fn nav_quality(map: &model::FrontendMap) {
println!("=== Quality Score ===\n");
let mut score = 100.0;
let mut issues = Vec::new();
let no_props = map.components.iter().filter(|c| c.props.is_empty()).count();
if no_props > map.components.len() / 2 {
score -= 10.0;
issues.push(format!("{} components have no props ({}%)", no_props, no_props * 100 / map.components.len()));
}
let unused = map.components.iter().filter(|c| c.used_by.is_empty()).count();
if unused > 0 {
score -= (unused as f64 * 2.0).min(20.0);
issues.push(format!("{} components appear unused", unused));
}
let empty_stores = map.stores.iter().filter(|s| s.subscribers.is_empty()).count();
if empty_stores > 0 {
score -= (empty_stores as f64 * 5.0).min(15.0);
issues.push(format!("{} stores have no subscribers", empty_stores));
}
let mut naming_issues = 0;
for comp in &map.components {
if comp.name.contains('_') && !comp.name.starts_with('_') {
naming_issues += 1;
}
}
if naming_issues > 0 {
score -= (naming_issues as f64 * 0.5).min(5.0);
issues.push(format!("{} components use snake_case (should be PascalCase)", naming_issues));
}
score = score.max(0.0);
println!("Score: {:.0}/100", score);
if !issues.is_empty() {
println!("\nIssues:");
for issue in &issues {
println!(" - {}", issue);
}
} else {
println!("\nNo issues found!");
}
}
pub fn nav_health(map: &model::FrontendMap) {
println!("=== Health Check ===\n");
let mut god_components = Vec::new();
for comp in &map.components {
let used = find_used_components(map, comp);
let stores = find_used_stores(map, comp);
let apis: Vec<_> = map.api_calls.iter().filter(|a| a.component == comp.name).collect();
let total_deps = used.len() + stores.len() + apis.len();
if total_deps > 10 {
god_components.push((comp.name.clone(), total_deps));
}
}
if !god_components.is_empty() {
println!("⚠ God Components (too many dependencies):");
for (name, deps) in &god_components {
println!(" - {} ({} dependencies)", name, deps);
}
}
let mut cycles = Vec::new();
for comp in &map.components {
let used = find_used_components(map, comp);
for u in &used {
if let Some(other) = map.components.iter().find(|c| c.name == *u) {
let other_used = find_used_components(map, other);
if other_used.contains(&comp.name) {
cycles.push((comp.name.clone(), u.clone()));
}
}
}
}
if !cycles.is_empty() {
println!("\n⚠ Circular Dependencies:");
for (a, b) in &cycles {
println!(" - {} ↔ {}", a, b);
}
}
let unused: Vec<_> = map.components.iter()
.filter(|c| c.used_by.is_empty() && !c.name.starts_with('_'))
.collect();
if !unused.is_empty() {
println!("\n⚠ Potentially Unused Components:");
for comp in unused.iter().take(10) {
println!(" - {} ({})", comp.name, comp.file.display());
}
if unused.len() > 10 {
println!(" ... and {} more", unused.len() - 10);
}
}
let empty_stores: Vec<_> = map.stores.iter()
.filter(|s| s.subscribers.is_empty())
.collect();
if !empty_stores.is_empty() {
println!("\n⚠ Empty Stores (no subscribers):");
for store in &empty_stores {
println!(" - {} ({})", store.name, store.file.display());
}
}
if god_components.is_empty() && cycles.is_empty() && unused.is_empty() && empty_stores.is_empty() {
println!("✓ No health issues found!");
}
}
pub fn nav_report(map: &model::FrontendMap, output: Option<&str>) {
let report = generate_report(map);
match output {
Some(path) => {
if let Err(e) = std::fs::write(path, &report) {
eprintln!("Failed to write report to {}: {}", path, e);
} else {
println!("✓ Report saved to {}", path);
}
}
None => println!("{}", report),
}
}
fn generate_report(map: &model::FrontendMap) -> String {
let mut report = String::new();
report.push_str(&format!("# Frontend Project Report: {}\n\n", map.project.name));
report.push_str(&format!("**Framework:** {}\n", map.project.framework.as_str()));
report.push_str(&format!("**Components:** {}\n", map.project.component_count));
report.push_str(&format!("**Files:** {}\n", map.project.file_count));
report.push_str(&format!("**Routes:** {}\n", map.routes.len()));
report.push_str(&format!("**API Calls:** {}\n", map.api_calls.len()));
report.push_str(&format!("**Stores:** {}\n\n", map.stores.len()));
report.push_str("## Entry Points\n\n");
if let Some(app) = map.components.iter().find(|c| c.name == "App") {
report.push_str(&format!("- App: {}\n", app.file.display()));
}
for route in map.routes.iter().take(5) {
report.push_str(&format!("- Route {}: {} → {}\n", route.path, route.component, route.file.display()));
}
if !map.stores.is_empty() {
report.push_str("\n## State Management\n\n");
for store in &map.stores {
report.push_str(&format!("- **{}** ({}) - {} subscribers\n",
store.name, store.kind, store.subscribers.len()));
}
}
if !map.api_calls.is_empty() {
report.push_str("\n## API Endpoints\n\n");
for api in &map.api_calls {
report.push_str(&format!("- {} {} ({})\n", api.method, api.endpoint, api.component));
}
}
report
}
pub fn nav_map(map: &model::FrontendMap, full: bool) {
println!("=== Project Map ===\n");
println!("Project: {}", map.project.name);
println!("Framework: {}", map.project.framework.as_str());
println!("Components: {}", map.project.component_count);
println!("Files: {}", map.project.file_count);
println!("Routes: {}", map.routes.len());
println!("API Calls: {}", map.api_calls.len());
println!("Stores: {}", map.stores.len());
if full {
println!("\n--- Components ---");
for comp in &map.components {
println!(" {} - {}", comp.name, comp.file.display());
}
println!("\n--- Routes ---");
for route in &map.routes {
println!(" {} → {}", route.path, route.component);
}
println!("\n--- Stores ---");
for store in &map.stores {
println!(" {} ({} subscribers)", store.name, store.subscribers.len());
}
println!("\n--- API Calls ---");
for api in &map.api_calls {
println!(" {} {} → {}", api.method, api.endpoint, api.component);
}
}
}