use serde::{Deserialize, Serialize};
use std::path::Path;
use crate::types::*;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogEntry {
pub seq: u32,
pub chapter: String,
pub beat: String,
pub event: LogEvent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LogEvent {
SceneEntered { scene_id: String, location: String },
ChoiceMade { scene_id: String, choice_label: String, choice_index: usize },
FlagSet { id: String, value: FlagValue },
ReputationChanged { axis: String, delta: i32, new_value: i32 },
SkillUnlocked { character: String, skill: String },
CombatStarted { encounter_id: String },
StandoffChosen { posture: String, focus_target: Option<String> },
CombatEnded { result: String, rounds: u32 },
RelayBranchChosen { branch: String },
MemoryEvent { object_id: String, action: String },
GameSaved { slot: String, path: String },
Note(String),
}
#[derive(Debug, Default)]
pub struct EventLog {
entries: Vec<LogEntry>,
next_seq: u32,
current_chapter: String,
current_beat: String,
}
impl EventLog {
pub fn new() -> Self {
Self::default()
}
pub fn set_context(&mut self, chapter: &str, beat: &str) {
self.current_chapter = chapter.to_string();
self.current_beat = beat.to_string();
}
pub fn log(&mut self, event: LogEvent) {
self.entries.push(LogEntry {
seq: self.next_seq,
chapter: self.current_chapter.clone(),
beat: self.current_beat.clone(),
event,
});
self.next_seq += 1;
}
pub fn scene_entered(&mut self, scene_id: &str, location: &str) {
self.log(LogEvent::SceneEntered {
scene_id: scene_id.to_string(),
location: location.to_string(),
});
}
pub fn choice_made(&mut self, scene_id: &str, label: &str, index: usize) {
self.log(LogEvent::ChoiceMade {
scene_id: scene_id.to_string(),
choice_label: label.to_string(),
choice_index: index,
});
}
pub fn flag_set(&mut self, id: &str, value: &FlagValue) {
self.log(LogEvent::FlagSet {
id: id.to_string(),
value: value.clone(),
});
}
pub fn relay_branch(&mut self, branch: &str) {
self.log(LogEvent::RelayBranchChosen { branch: branch.to_string() });
}
pub fn entries(&self) -> &[LogEntry] {
&self.entries
}
pub fn export_text(&self) -> String {
let mut output = String::new();
output.push_str("=== Saint's Mile — Event Log ===\n\n");
for entry in &self.entries {
let line = match &entry.event {
LogEvent::SceneEntered { scene_id, location } =>
format!("[{}] Scene: {} @ {}", entry.seq, scene_id, location),
LogEvent::ChoiceMade { scene_id, choice_label, .. } =>
format!("[{}] Choice: \"{}\" in {}", entry.seq, choice_label, scene_id),
LogEvent::FlagSet { id, value } =>
format!("[{}] Flag: {} = {:?}", entry.seq, id, value),
LogEvent::ReputationChanged { axis, delta, new_value } =>
format!("[{}] Rep: {} {:+} → {}", entry.seq, axis, delta, new_value),
LogEvent::SkillUnlocked { character, skill } =>
format!("[{}] Skill: {} unlocked {} ", entry.seq, character, skill),
LogEvent::CombatStarted { encounter_id } =>
format!("[{}] Combat: {} started", entry.seq, encounter_id),
LogEvent::StandoffChosen { posture, focus_target } =>
format!("[{}] Standoff: {} (focus: {:?})", entry.seq, posture, focus_target),
LogEvent::CombatEnded { result, rounds } =>
format!("[{}] Combat: {} in {} rounds", entry.seq, result, rounds),
LogEvent::RelayBranchChosen { branch } =>
format!("[{}] RELAY BRANCH: {}", entry.seq, branch),
LogEvent::MemoryEvent { object_id, action } =>
format!("[{}] Memory: {} — {}", entry.seq, object_id, action),
LogEvent::GameSaved { slot, .. } =>
format!("[{}] Saved: {}", entry.seq, slot),
LogEvent::Note(text) =>
format!("[{}] Note: {}", entry.seq, text),
};
output.push_str(&format!(" {}/{} {}\n", entry.chapter, entry.beat, line));
}
output.push_str("\n=== Summary ===\n");
let choices: Vec<_> = self.entries.iter()
.filter_map(|e| match &e.event {
LogEvent::ChoiceMade { choice_label, .. } => Some(choice_label.as_str()),
_ => None,
})
.collect();
output.push_str(&format!("Choices made: {}\n", choices.len()));
for c in &choices {
output.push_str(&format!(" - {}\n", c));
}
let relay = self.entries.iter()
.find_map(|e| match &e.event {
LogEvent::RelayBranchChosen { branch } => Some(branch.as_str()),
_ => None,
});
if let Some(branch) = relay {
output.push_str(&format!("Relay branch: {}\n", branch));
}
output
}
pub fn export_ron(&self, path: &Path) -> anyhow::Result<()> {
let serialized = ron::ser::to_string_pretty(&self.entries, ron::ser::PrettyConfig::default())?;
std::fs::write(path, serialized)?;
Ok(())
}
}