use super::processor::CssProcessor;
use crate::core::utils::ensure_directory_exists;
use crate::presentation::css_processing::{apply_minification, serialize_stylesheet};
use anyhow::Result;
use include_dir::{Dir, include_dir};
use lightningcss::bundler::{Bundler, FileProvider};
use lightningcss::stylesheet::ParserOptions;
use std::fs;
use std::path::Path;
pub static STYLES: Dir = include_dir!("$CARGO_MANIFEST_DIR/styles");
pub fn process_user_css_entry_point(
css_processor: &CssProcessor,
styles_dir: &Path,
css_dir: &Path,
entry_point: &str,
) -> Result<()> {
let entry_path = styles_dir.join(entry_point);
if entry_path.exists() {
css_processor.bundle_css_files(&entry_path, css_dir)?;
println!("Bundled CSS: {} -> dist/css/main.css", entry_point);
} else {
return Err(anyhow::anyhow!(
"CSS entry point '{}' not found in styles directory '{}'.\n\
Available files: {}\n\
Fix: either create the file or remove the 'entry_point' configuration to use defaults.",
entry_point,
styles_dir.display(),
list_available_css_files(styles_dir)?
));
}
Ok(())
}
pub fn process_embedded_css_entry_point(
css_processor: &CssProcessor,
css_dir: &Path,
entry_point: &str,
) -> Result<()> {
if STYLES.get_file(entry_point).is_some() {
let main_css_path = css_dir.join("main.css");
let temp_dir = tempfile::tempdir()?;
let temp_path = temp_dir.path();
extract_embedded_css_to_temp(temp_path)?;
let fs_provider = FileProvider::new();
let mut bundler = Bundler::new(
&fs_provider,
None, ParserOptions {
filename: entry_point.to_string(),
..ParserOptions::default()
},
);
let original_dir = std::env::current_dir()?;
std::env::set_current_dir(temp_path)?;
let mut stylesheet = bundler
.bundle(Path::new(entry_point))
.map_err(|e| anyhow::anyhow!("Failed to bundle embedded CSS: {}", e))?;
std::env::set_current_dir(original_dir)?;
apply_minification(&mut stylesheet, css_processor)?;
let result = serialize_stylesheet(&stylesheet, css_processor, entry_point)?;
fs::write(&main_css_path, &result)?;
println!(
"Bundled embedded CSS: {} -> {}",
entry_point,
main_css_path.display()
);
} else {
return Err(anyhow::anyhow!(
"Embedded CSS entry point '{}' not found.\n\
Available embedded files: {}\n\
Fix: either add the file to styles/ directory or remove 'entry_point' configuration.",
entry_point,
list_embedded_css_files()
));
}
Ok(())
}
pub fn extract_embedded_css_to_temp(temp_dir: &Path) -> Result<()> {
ensure_directory_exists(temp_dir)?;
for file in STYLES.files() {
let file_path = file.path();
if let Some(file_name) = file_path.file_name()
&& let Some(extension) = Path::new(file_name).extension()
&& extension == "css"
{
let dest_path = temp_dir.join(file_name);
if let Some(content) = file.contents_utf8() {
fs::write(&dest_path, content)?;
}
}
}
Ok(())
}
fn list_available_css_files(styles_dir: &Path) -> Result<String> {
if !styles_dir.exists() {
return Ok("(no styles directory)".to_string());
}
let mut files = Vec::new();
for entry in fs::read_dir(styles_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file()
&& path.extension().is_some_and(|ext| ext == "css")
&& let Some(name) = path.file_name()
{
files.push(name.to_string_lossy().to_string());
}
}
if files.is_empty() {
Ok("(no CSS files found)".to_string())
} else {
Ok(files.join(", "))
}
}
fn list_embedded_css_files() -> String {
let mut files = Vec::new();
for file in STYLES.files() {
if let Some(file_name) = file.path().file_name()
&& file.path().extension().is_some_and(|ext| ext == "css")
{
files.push(file_name.to_string_lossy().to_string());
}
}
if files.is_empty() {
"(no embedded CSS files found)".to_string()
} else {
files.join(", ")
}
}