use serde::Serialize;
use hwpforge_core::image::ImageStore;
use hwpforge_foundation::FontId;
use hwpforge_smithy_hwpx::presets::builtin_presets;
use hwpforge_smithy_hwpx::{HwpxEncoder, HwpxRegistryBridge};
use hwpforge_smithy_md::MdDecoder;
use crate::output::{read_file_string, write_output_file, ToolErrorInfo, MAX_INLINE_SIZE};
#[derive(Debug, Serialize)]
pub struct ConvertData {
pub output_path: String,
pub size_bytes: u64,
pub sections: usize,
pub paragraphs: usize,
}
pub fn run_convert(
markdown: &str,
is_file: bool,
output_path: &str,
preset: &str,
) -> Result<ConvertData, ToolErrorInfo> {
let presets = builtin_presets();
let preset_info = presets.iter().find(|p| p.name == preset).ok_or_else(|| {
ToolErrorInfo::new(
"PRESET_NOT_FOUND",
format!("Preset '{preset}' not found"),
"Use hwpforge_templates to see available presets.",
)
})?;
let preset_font = preset_info.font.clone();
if !output_path.ends_with(".hwpx") {
return Err(ToolErrorInfo::new(
"INVALID_EXTENSION",
format!("Output path must end with .hwpx: {output_path}"),
"Use a .hwpx extension for the output file.",
));
}
let md_content: String = if is_file {
read_file_string(markdown)?
} else {
if markdown.len() > MAX_INLINE_SIZE {
return Err(ToolErrorInfo::new(
"INPUT_TOO_LARGE",
format!(
"Inline content is {} MB, exceeds {} MB limit",
markdown.len() / 1024 / 1024,
MAX_INLINE_SIZE / 1024 / 1024,
),
"Write the content to a file and use is_file: true.",
));
}
markdown.to_string()
};
let mut md_doc = MdDecoder::decode_with_default(&md_content).map_err(|e| {
ToolErrorInfo::new(
"MD_DECODE_ERROR",
format!("Markdown decode failed: {e}"),
"Check Markdown syntax. Use GFM (GitHub Flavored Markdown).",
)
})?;
let sections: usize = md_doc.document.sections().len();
let paragraphs: usize = md_doc.document.sections().iter().map(|s| s.paragraphs.len()).sum();
let preset_font_id = FontId::new(&preset_font).map_err(|e| {
ToolErrorInfo::new("PRESET_ERROR", format!("Invalid preset font name: {e}"), "")
})?;
let original_base =
md_doc.style_registry.fonts.first().map(|f| f.as_str().to_string()).unwrap_or_default();
md_doc.style_registry.fonts = md_doc
.style_registry
.fonts
.iter()
.map(|f| if f.as_str() == original_base { preset_font_id.clone() } else { f.clone() })
.collect();
for cs in &mut md_doc.style_registry.char_shapes {
if cs.font == original_base {
cs.font.clone_from(&preset_font);
}
}
let bridge = HwpxRegistryBridge::from_registry(&md_doc.style_registry).map_err(|e| {
ToolErrorInfo::new(
"STYLE_STORE_ERROR",
format!("Style store construction failed: {e}"),
"Check paragraph list references in the resolved style registry.",
)
})?;
let image_store = ImageStore::new();
let rebound = bridge.rebind_draft_document(md_doc.document).map_err(|e| {
ToolErrorInfo::new(
"STYLE_REBIND_ERROR",
format!("Style rebind failed: {e}"),
"Document style indices do not match the generated HWPX style store.",
)
})?;
let validated = rebound.validate().map_err(|e| {
ToolErrorInfo::new(
"VALIDATION_ERROR",
format!("Document validation failed: {e}"),
"Check document structure.",
)
})?;
let hwpx_bytes =
HwpxEncoder::encode(&validated, bridge.style_store(), &image_store).map_err(|e| {
ToolErrorInfo::new(
"ENCODE_ERROR",
format!("HWPX encoding failed: {e}"),
"This may be a bug. Please report at https://github.com/ai-screams/HwpForge/issues",
)
})?;
write_output_file(output_path, &hwpx_bytes)?;
let size_bytes: u64 = hwpx_bytes.len() as u64;
Ok(ConvertData { output_path: output_path.to_string(), size_bytes, sections, paragraphs })
}