use std::borrow::Cow;
use std::sync::LazyLock;
use anyhow::Context as _;
use crate::parser;
use crate::parser::ast::{
AstValue, AttrBlock, DotGraph, EdgeStmt, NodeStmt, Statement, SubgraphStmt,
};
/// Dark mode CSS injected into SVG output (leading newline included for
/// insertion).
const DARK_MODE_STYLE: &str = r##"
"##;
/// DOT graph-level defaults injected after the graph opening `{`.
const DOT_STYLE_DEFAULTS: &str = r##"
bgcolor="transparent"
node [color="#357f9e", fontname="Helvetica", fontsize=12, fontcolor="#1a1a1a"]
edge [color="#666666", fontname="Helvetica", fontsize=10, fontcolor="#666666"]
"##;
static RANKDIR_RE: LazyLock = LazyLock::new(|| {
regex::Regex::new(r"rankdir\s*=\s*\w+").expect("hardcoded regex should compile")
});
static WHITE_BG_POLYGON_RE: LazyLock = LazyLock::new(|| {
regex::Regex::new(
r#"]*fill="white"[^>]*stroke="none"[^>]*/>|]*stroke="none"[^>]*fill="white"[^>]*/>"#,
)
.expect("hardcoded regex should compile")
});
/// Rewrite `rankdir=...` in DOT source.
#[must_use]
pub fn apply_direction<'a>(source: &'a str, direction: &str) -> std::borrow::Cow<'a, str> {
let replacement = format!("rankdir={direction}");
RANKDIR_RE.replace(source, replacement.as_str())
}
/// Inject DOT graph-level style defaults.
#[must_use]
pub fn inject_dot_style_defaults(source: &str) -> String {
let source = normalize_dot_for_graphviz(source);
inject_dot_style_defaults_raw(&source)
}
fn inject_dot_style_defaults_raw(source: &str) -> String {
let Some(pos) = source.find('{') else {
return source.to_string();
};
let (before, after) = source.split_at(pos + 1);
format!("{before}{DOT_STYLE_DEFAULTS}{after}")
}
/// Post-process raw SVG output from Graphviz.
#[must_use]
pub fn postprocess_svg(raw: Vec) -> Vec {
let mut svg = String::from_utf8(raw)
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
svg = WHITE_BG_POLYGON_RE.replace_all(&svg, "").into_owned();
if let Some(svg_close) = svg
.find("