1use std::fmt::Write;
7use std::io::{Result, Write as IoWrite};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10use super::config::VisualizerConfig;
11use super::render::{HydroWriteConfig, render_hydro_ir_dot, render_hydro_ir_mermaid};
12use crate::compile::ir::HydroRoot;
13
14pub fn open_mermaid(roots: &[HydroRoot], config: Option<HydroWriteConfig>) -> Result<()> {
16 let mermaid_src = render_with_config(roots, config, render_hydro_ir_mermaid);
17 open_mermaid_browser(&mermaid_src)
18}
19
20pub fn open_dot(roots: &[HydroRoot], config: Option<HydroWriteConfig>) -> Result<()> {
22 let dot_src = render_with_config(roots, config, render_hydro_ir_dot);
23 open_dot_browser(&dot_src)
24}
25
26pub fn save_mermaid(
29 roots: &[HydroRoot],
30 filename: Option<&str>,
31 config: Option<HydroWriteConfig>,
32) -> Result<std::path::PathBuf> {
33 let content = render_with_config(roots, config, render_hydro_ir_mermaid);
34 save_to_file(content, filename, "hydro_graph.mermaid", "Mermaid diagram")
35}
36
37pub fn save_dot(
40 roots: &[HydroRoot],
41 filename: Option<&str>,
42 config: Option<HydroWriteConfig>,
43) -> Result<std::path::PathBuf> {
44 let content = render_with_config(roots, config, render_hydro_ir_dot);
45 save_to_file(content, filename, "hydro_graph.dot", "DOT/Graphviz file")
46}
47
48fn open_mermaid_browser(mermaid_src: &str) -> Result<()> {
49 let state = serde_json::json!({
50 "code": mermaid_src,
51 "mermaid": serde_json::json!({
52 "theme": "default"
53 }),
54 "autoSync": true,
55 "updateDiagram": true
56 });
57 let state_json = serde_json::to_vec(&state)?;
58 let state_base64 = data_encoding::BASE64URL.encode(&state_json);
59 webbrowser::open(&format!(
60 "https://mermaid.live/edit#base64:{}",
61 state_base64
62 ))
63}
64
65fn open_dot_browser(dot_src: &str) -> Result<()> {
66 let mut url = "https://dreampuf.github.io/GraphvizOnline/#".to_owned();
67 for byte in dot_src.bytes() {
68 write!(url, "%{:02x}", byte).unwrap();
70 }
71 webbrowser::open(&url)
72}
73
74fn save_to_file(
77 content: String,
78 filename: Option<&str>,
79 default_name: &str,
80 content_type: &str,
81) -> Result<std::path::PathBuf> {
82 let file_path = if let Some(filename) = filename {
83 std::path::PathBuf::from(filename)
84 } else {
85 std::env::temp_dir().join(default_name)
86 };
87
88 std::fs::write(&file_path, content)?;
89 println!("Saved {} to {}", content_type, file_path.display());
90 Ok(file_path)
91}
92
93fn render_with_config<F>(
95 roots: &[HydroRoot],
96 config: Option<HydroWriteConfig>,
97 renderer: F,
98) -> String
99where
100 F: Fn(&[HydroRoot], &HydroWriteConfig) -> String,
101{
102 let config = config.unwrap_or_default();
103 renderer(roots, &config)
104}
105
106fn compress_json(json_content: &str) -> Result<Vec<u8>> {
109 use flate2::Compression;
110 use flate2::write::GzEncoder;
111
112 let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
113 encoder.write_all(json_content.as_bytes())?;
114 encoder.finish()
115}
116
117fn encode_base64_url_safe(data: &[u8]) -> String {
120 data_encoding::BASE64URL_NOPAD.encode(data)
121}
122
123fn try_compress_and_encode(json_content: &str, config: &VisualizerConfig) -> (String, bool, f64) {
129 let original_size = json_content.len();
130
131 if !config.enable_compression || original_size < config.min_compression_size {
133 let encoded = encode_base64_url_safe(json_content.as_bytes());
134 return (encoded, false, 1.0);
135 }
136
137 match compress_json(json_content) {
138 Ok(compressed) => {
139 let compressed_size = compressed.len();
140 let ratio = original_size as f64 / compressed_size as f64;
141
142 if compressed_size < original_size {
144 let encoded = encode_base64_url_safe(&compressed);
145 (encoded, true, ratio)
146 } else {
147 let encoded = encode_base64_url_safe(json_content.as_bytes());
149 (encoded, false, 1.0)
150 }
151 }
152 Err(e) => {
153 println!("ā ļø Compression failed: {}, using uncompressed", e);
155 let encoded = encode_base64_url_safe(json_content.as_bytes());
156 (encoded, false, 1.0)
157 }
158 }
159}
160
161fn calculate_url_length(base_url: &str, param_name: &str, encoded_data: &str) -> usize {
164 base_url.len() + 1 + param_name.len() + 1 + encoded_data.len()
166}
167
168fn generate_visualizer_url(
172 json_content: &str,
173 config: &VisualizerConfig,
174) -> Option<(String, bool)> {
175 let (encoded_data, is_compressed, _ratio) = try_compress_and_encode(json_content, config);
176
177 let param_name = if is_compressed { "compressed" } else { "data" };
179
180 let url_length = calculate_url_length(&config.base_url, param_name, &encoded_data);
181
182 if url_length <= config.max_url_length {
183 let url = format!("{}?{}={}", config.base_url, param_name, encoded_data);
184 Some((url, is_compressed))
185 } else {
186 None
188 }
189}
190
191fn generate_timestamped_filename() -> String {
194 let timestamp = SystemTime::now()
195 .duration_since(UNIX_EPOCH)
196 .expect("System clock is before Unix epoch - clock may be corrupted")
197 .as_secs();
198 format!("hydro_graph_{}.json", timestamp)
199}
200
201fn save_json_to_temp_file(json_content: &str) -> Result<std::path::PathBuf> {
206 let filename = generate_timestamped_filename();
207 let temp_file = std::env::temp_dir().join(filename);
208
209 std::fs::write(&temp_file, json_content)?;
210
211 println!("š Saved graph to temporary file: {}", temp_file.display());
212
213 Ok(temp_file)
214}
215
216fn url_encode_file_path(file_path: &std::path::Path) -> String {
221 let path_str = file_path.to_string_lossy();
222 urlencoding::encode(&path_str).to_string()
223}
224
225fn generate_file_based_url(file_path: &std::path::Path, config: &VisualizerConfig) -> String {
230 let encoded_path = url_encode_file_path(file_path);
231 format!("{}?file={}", config.base_url, encoded_path)
232}
233
234fn print_fallback_instructions(file_path: &std::path::Path, url: &str) {
239 println!("\nš Graph Visualization Instructions");
240 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
241 println!("The graph is too large to embed in a URL.");
242 println!("It has been saved to a temporary file:");
243 println!(" š {}", file_path.display());
244 println!();
245 println!("Opening visualizer in browser...");
246 println!(" š {}", url);
247 println!();
248 println!("If the browser doesn't open automatically, you can:");
249 println!(" 1. Manually open: {}", url);
250 println!(
251 " 2. Or visit {} and drag-and-drop the file",
252 url.split('?').next().unwrap_or(url)
253 );
254 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n");
255}
256
257fn handle_large_graph_fallback(json_content: &str, config: &VisualizerConfig) -> Result<()> {
261 let temp_file = save_json_to_temp_file(json_content)?;
262
263 let url = generate_file_based_url(&temp_file, config);
265
266 print_fallback_instructions(&temp_file, &url);
267
268 match webbrowser::open(&url) {
269 Ok(_) => {
270 println!("ā Successfully opened visualizer in browser");
271 }
272 Err(e) => {
273 println!("ā ļø Failed to open browser automatically: {}", e);
274 println!("Please manually open the URL above or drag-and-drop the file.");
275 }
276 }
277
278 Ok(())
279}
280
281fn open_json_visualizer_with_fallback(json_content: &str, config: &VisualizerConfig) -> Result<()> {
289 match generate_visualizer_url(json_content, config) {
291 Some((url, _is_compressed)) => {
292 webbrowser::open(&url)?;
294 println!("ā Successfully opened visualizer in browser");
295 Ok(())
296 }
297 None => {
298 println!("š¦ Graph too large for URL embedding, using file-based approach...");
300 handle_large_graph_fallback(json_content, config)
301 }
302 }
303}
304
305pub fn open_json_visualizer(roots: &[HydroRoot], config: Option<HydroWriteConfig>) -> Result<()> {
312 let json_content = render_with_config(roots, config, render_hydro_ir_json);
313 let viz_config = VisualizerConfig::default();
314 open_json_visualizer_with_fallback(&json_content, &viz_config)
315}
316
317pub fn open_json_visualizer_with_config(
320 roots: &[HydroRoot],
321 config: Option<HydroWriteConfig>,
322 viz_config: VisualizerConfig,
323) -> Result<()> {
324 let json_content = render_with_config(roots, config, render_hydro_ir_json);
325 open_json_visualizer_with_fallback(&json_content, &viz_config)
326}
327
328pub fn save_json(
331 roots: &[HydroRoot],
332 filename: Option<&str>,
333 config: Option<HydroWriteConfig>,
334) -> Result<std::path::PathBuf> {
335 let content = render_with_config(roots, config, render_hydro_ir_json);
336 save_to_file(content, filename, "hydro_graph.json", "JSON file")
337}
338
339fn render_hydro_ir_json(roots: &[HydroRoot], config: &HydroWriteConfig) -> String {
341 super::render::render_hydro_ir_json(roots, config)
342}