//! Helpers shared by the experiment binaries.
//!
//! Sandbox tooling for iterating on Whisper / LLM prompts against a real case
//! without modifying production state. All outputs land under `case_<id>/runs/`
//! and `case_<id>/compare/` — never in `tmpdata/`.
//!
//! **Security note**: The Ionos bearer key flows through this code as a
//! `String` inside `Settings.llm.api_key`. Helpers here MUST NOT persist or
//! print the key. `RunMeta` deliberately stores only the URL/model/timestamp —
//! never the key — so on-disk audit logs are safe to share.

use std::path::Path;

use anyhow::Context;
use serde::Serialize;
use time::OffsetDateTime;
use time::format_description::well_known::Iso8601;

/// Generate a run identifier in the form
/// `<UTC-ISO>_l-<lid>_run<n>`. Used as the directory name under
/// `case_<id>/runs/`.
pub fn run_id(llm_variant: &str, run_index: u32) -> String {
    let now = OffsetDateTime::now_utc();
    let stamp = now
        .format(&Iso8601::DEFAULT)
        .unwrap_or_else(|_| "unknown".into());
    let safe_stamp = stamp.replace(':', "-").replace('.', "-");
    format!("{safe_stamp}_l-{llm_variant}_run{run_index}")
}

/// Read a prompt file. Returns empty string for an empty file (used as
/// "no prompt" signal by `run_full_case`'s settings override).
pub fn load_prompt(path: &Path) -> anyhow::Result<String> {
    let content = std::fs::read_to_string(path)
        .with_context(|| format!("reading prompt file {}", path.display()))?;
    Ok(content.trim_end_matches('\n').to_string())
}

/// Atomically write content to `path` via a sibling tempfile + rename.
/// Same pattern the server uses for `document.md` — ensures readers never
/// observe a partial file.
pub fn atomic_write(path: &Path, content: &str) -> anyhow::Result<()> {
    let parent = path
        .parent()
        .with_context(|| format!("path {} has no parent", path.display()))?;
    std::fs::create_dir_all(parent)?;
    let mut tmp = tempfile::Builder::new()
        .prefix(".tmp-")
        .tempfile_in(parent)?;
    use std::io::Write;
    tmp.write_all(content.as_bytes())?;
    tmp.flush()?;
    tmp.persist(path)
        .map_err(|e| anyhow::anyhow!("persist failed: {}", e.error))?;
    Ok(())
}

/// Snapshot of variant identities + provenance for one run. Serialized
/// as `meta.json` next to the produced `document.md`. **Never contains
/// the API key** — only the LLM endpoint URL and model name.
#[derive(Debug, Serialize)]
pub struct RunMeta {
    pub run_id: String,
    pub created_at_utc: String,
    pub case: String,
    pub llm_variant: String,
    pub whisper_url: String,
    pub llm_url: String,
    pub llm_model: String,
    pub llm_temperature: f32,
    pub vocab_dir: String,
    pub vocab_loaded: usize,
    pub gazetteer_dict: GazetteerDictMode,
    pub timings_ms: RunTimings,
    pub recordings: Vec<RecordingMeta>,
}

#[derive(Debug, Serialize)]
pub enum GazetteerDictMode {
    NoDict,
    SpellbookHunspell { stem: String },
}

#[derive(Debug, Default, Serialize)]
pub struct RunTimings {
    pub whisper_total_ms: u64,
    pub gazetteer_total_ms: u64,
    pub llm_ms: u64,
    pub wallclock_ms: u64,
}

#[derive(Debug, Serialize)]
pub struct RecordingMeta {
    pub recorded_at: String,
    pub audio_path: String,
    pub whisper_chars_raw: usize,
    pub whisper_chars_after_gazetteer: usize,
}
