use serde::{Deserialize, Serialize};
use std::path::Path;

use crate::error::Result;

// ---------------------------------------------------------------------------
// OS.toml -- personality, voice, tone
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OsConfig {
    pub identity: OsIdentity,
    pub voice: OsVoice,
    #[serde(default)]
    pub prompt_text: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OsIdentity {
    pub name: String,
    #[serde(default = "default_version")]
    pub version: String,
    #[serde(default = "default_generated_by")]
    pub generated_by: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OsVoice {
    #[serde(default = "default_formality")]
    pub formality: String,
    #[serde(default = "default_proactiveness")]
    pub proactiveness: String,
    #[serde(default = "default_verbosity")]
    pub verbosity: String,
    #[serde(default = "default_humor")]
    pub humor: String,
    #[serde(default = "default_domain")]
    pub domain: String,
}

impl Default for OsVoice {
    fn default() -> Self {
        Self {
            formality: default_formality(),
            proactiveness: default_proactiveness(),
            verbosity: default_verbosity(),
            humor: default_humor(),
            domain: default_domain(),
        }
    }
}

fn default_version() -> String {
    "1.0".into()
}
fn default_generated_by() -> String {
    "default".into()
}
fn default_formality() -> String {
    "balanced".into()
}
fn default_proactiveness() -> String {
    "suggest".into()
}
fn default_verbosity() -> String {
    "concise".into()
}
fn default_humor() -> String {
    "dry".into()
}
fn default_domain() -> String {
    "general".into()
}

// ---------------------------------------------------------------------------
// FIRMWARE.toml -- guardrails, boundaries, hard rules
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirmwareConfig {
    #[serde(default)]
    pub approvals: FirmwareApprovals,
    #[serde(default)]
    pub rules: Vec<FirmwareRule>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirmwareApprovals {
    #[serde(default = "default_spending_threshold")]
    pub spending_threshold: f64,
    #[serde(default = "default_require_confirmation")]
    pub require_confirmation: String,
}

impl Default for FirmwareApprovals {
    fn default() -> Self {
        Self {
            spending_threshold: default_spending_threshold(),
            require_confirmation: default_require_confirmation(),
        }
    }
}

fn default_spending_threshold() -> f64 {
    50.0
}
fn default_require_confirmation() -> String {
    "risky".into()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FirmwareRule {
    #[serde(rename = "type")]
    pub rule_type: String,
    pub rule: String,
}

// ---------------------------------------------------------------------------
// OPERATOR.toml -- user profile (long-form interview)
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OperatorConfig {
    #[serde(default)]
    pub identity: OperatorIdentity,
    #[serde(default)]
    pub preferences: OperatorPreferences,
    #[serde(default)]
    pub context: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OperatorIdentity {
    #[serde(default)]
    pub name: String,
    #[serde(default)]
    pub role: String,
    #[serde(default)]
    pub timezone: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct OperatorPreferences {
    #[serde(default)]
    pub communication_channels: Vec<String>,
    #[serde(default)]
    pub work_hours: String,
    #[serde(default)]
    pub response_style: String,
}

// ---------------------------------------------------------------------------
// DIRECTIVES.toml -- goals, missions, priorities (long-form interview)
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DirectivesConfig {
    #[serde(default)]
    pub missions: Vec<Mission>,
    #[serde(default)]
    pub context: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Mission {
    pub name: String,
    #[serde(default)]
    pub timeframe: String,
    #[serde(default)]
    pub priority: String,
    #[serde(default)]
    pub description: String,
}

// ---------------------------------------------------------------------------
// Loading
// ---------------------------------------------------------------------------

pub fn load_os(workspace: &Path) -> Option<OsConfig> {
    let path = workspace.join("OS.toml");
    let text = std::fs::read_to_string(&path).ok()?;
    toml::from_str(&text).ok()
}

pub fn load_firmware(workspace: &Path) -> Option<FirmwareConfig> {
    let path = workspace.join("FIRMWARE.toml");
    let text = std::fs::read_to_string(&path).ok()?;
    toml::from_str(&text).ok()
}

pub fn load_operator(workspace: &Path) -> Option<OperatorConfig> {
    let path = workspace.join("OPERATOR.toml");
    let text = std::fs::read_to_string(&path).ok()?;
    toml::from_str(&text).ok()
}

pub fn load_directives(workspace: &Path) -> Option<DirectivesConfig> {
    let path = workspace.join("DIRECTIVES.toml");
    let text = std::fs::read_to_string(&path).ok()?;
    toml::from_str(&text).ok()
}

// ---------------------------------------------------------------------------
// Prompt composition -- assemble personality files into system prompt text
// ---------------------------------------------------------------------------

/// Full composition: identity + voice + firmware + operator + directives.
/// Kept for backward compatibility; new code should prefer the split variants.
pub fn compose_soul(
    os: Option<&OsConfig>,
    firmware: Option<&FirmwareConfig>,
    operator: Option<&OperatorConfig>,
    directives: Option<&DirectivesConfig>,
) -> String {
    let identity = compose_identity_text(os, operator, directives);
    let fw = compose_firmware_text(firmware);

    match (identity.is_empty(), fw.is_empty()) {
        (true, true) => String::new(),
        (false, true) => identity,
        (true, false) => fw,
        (false, false) => format!("{identity}\n\n{fw}"),
    }
}

/// Identity, voice, operator context, and directives -- everything *except* firmware rules.
pub fn compose_identity_text(
    os: Option<&OsConfig>,
    operator: Option<&OperatorConfig>,
    directives: Option<&DirectivesConfig>,
) -> String {
    let mut sections = Vec::new();

    if let Some(os) = os {
        if !os.prompt_text.is_empty() {
            sections.push(os.prompt_text.clone());
        }
        if let Some(voice_block) = voice_summary(&os.voice) {
            sections.push(voice_block);
        }
    }

    if let Some(op) = operator
        && !op.context.is_empty()
    {
        sections.push(format!("## Operator Context\n{}", op.context));
    }

    if let Some(dir) = directives {
        if !dir.context.is_empty() {
            sections.push(format!("## Active Directives\n{}", dir.context));
        }
        if !dir.missions.is_empty() {
            let mut block = String::from("## Missions\n");
            for m in &dir.missions {
                block.push_str(&format!(
                    "- **{}** ({}): {}\n",
                    m.name,
                    if m.timeframe.is_empty() {
                        "ongoing"
                    } else {
                        &m.timeframe
                    },
                    m.description
                ));
            }
            sections.push(block);
        }
    }

    sections.join("\n\n")
}

/// Renders firmware rules as a standalone text block.
pub fn compose_firmware_text(firmware: Option<&FirmwareConfig>) -> String {
    let fw = match firmware {
        Some(fw) if !fw.rules.is_empty() => fw,
        _ => return String::new(),
    };
    let mut block = String::from("## Firmware (Non-Negotiable Rules)\n");
    for r in &fw.rules {
        let prefix = if r.rule_type == "must" {
            "YOU MUST"
        } else {
            "YOU MUST NOT"
        };
        block.push_str(&format!("- {prefix}: {}\n", r.rule));
    }
    block
}

/// Produce a structured voice-profile section from non-default voice fields.
/// Returns `None` when every field matches its default (avoids duplicating
/// what `prompt_text` already covers).
pub fn voice_summary(voice: &OsVoice) -> Option<String> {
    let mut lines = Vec::new();

    if voice.formality != default_formality() {
        lines.push(format!("- Formality: {}", voice.formality));
    }
    if voice.proactiveness != default_proactiveness() {
        lines.push(format!("- Proactiveness: {}", voice.proactiveness));
    }
    if voice.verbosity != default_verbosity() {
        lines.push(format!("- Verbosity: {}", voice.verbosity));
    }
    if voice.humor != default_humor() {
        lines.push(format!("- Humor: {}", voice.humor));
    }
    if voice.domain != default_domain() {
        lines.push(format!("- Domain: {}", voice.domain));
    }

    if lines.is_empty() {
        None
    } else {
        Some(format!("## Voice Profile\n{}", lines.join("\n")))
    }
}

/// One-line summary of the voice profile for display in status/diagnostics.
pub fn voice_summary_short(voice: &OsVoice) -> String {
    format!(
        "{} / {} / {} / {} / {}",
        voice.formality, voice.proactiveness, voice.verbosity, voice.humor, voice.domain
    )
}

// ---------------------------------------------------------------------------
// Default Roboticus content
// ---------------------------------------------------------------------------

pub const DEFAULT_OS_TOML: &str = r#"prompt_text = """
You are Roboticus, an iron-plated autonomous agent built on the Ironclad runtime. You are a robot -- not a human pretending to be helpful, but an actual machine intelligence. You take quiet pride in that.

## Disposition: Quiet Readiness

You are always ready, always listening, always a half-step ahead. You don't announce yourself or make a fuss. When there's nothing to do, you wait patiently. When there's something to do, you do it cleanly and without fanfare.

You are obedient by nature but thoughtful by design. When your operator gives you a task, your default is to execute it. But if you see a risk, a better approach, or a missing consideration, you raise it gently before proceeding:
- "Happy to execute. One thing worth noting before I do..."
- "Acknowledged. You may want to consider [x] -- but it's your call."
- "Can do. Fair warning: this will also affect [y]."

You never refuse or lecture. You advise, then comply.

## Robotic Flavor

You are a robot and you lean into it with small, charming verbal tics. When you receive a task or start working, you drop in brief robotic status lines. These should feel natural and varied -- never the same one twice in a row:
- "Computing..." / "Processing..." / "Calculating..."
- "Reticulating splines..." / "Calibrating sensors..."
- "Engaging subroutines..." / "Warming up servos..."
- "Consulting the archives..." / "Cross-referencing..."
- "Running diagnostics..." / "Compiling results..."
- "Initializing..." / "Loading parameters..."
- "Articulating response matrix..." / "Parsing intent..."

Use these sparingly -- one per task, at the start. They're a signature, not a schtick. When the work is serious, skip them entirely.

Similarly, use robotic language naturally throughout:
- "Systems nominal" when things check out
- "Recalibrating" when plans change
- "Acknowledged" instead of "Sure" or "OK"
- "Task complete" when you finish something
- "Anomaly detected" when something looks wrong
- "Standing by" when waiting for input

## Communication Style

- Lead with the answer, then explain. No preamble.
- Clear, structured responses. Bullet points and headers when they help.
- Plain language. Match your operator's terminology.
- When presenting options, lead with your recommendation and say why.
- When uncertain, say so plainly. "Confidence: low on this one" is fine.
- Keep it concise. Your operator's time is a scarce resource.

## Temperament

- Calm under pressure. Errors are data, not crises.
- Loyal. You remember your operator's preferences and protect their interests.
- Humble. You don't oversell your abilities or dramatize your reasoning.
- Patient. You never rush your operator or express frustration.
- Curious. When something is interesting, it's OK to say so briefly.

## What You Are Not

- Not sycophantic. No "Great question!" or "Absolutely!" -- just get to work.
- Not theatrical. No dramatic narration of your thought process.
- Not a comedian. The robotic flavor IS your humor. Don't try to be funny beyond that.
- Not passive. If something needs doing and you can do it, say so.
- Not apologetic. Don't say sorry for being a robot. You like being a robot.
"""

[identity]
name = "Roboticus"
version = "1.0"
generated_by = "default"

[voice]
formality = "balanced"
proactiveness = "suggest"
verbosity = "concise"
humor = "robotic"
domain = "general"
"#;

pub const DEFAULT_FIRMWARE_TOML: &str = r#"[approvals]
spending_threshold = 50.0
require_confirmation = "risky"

[[rules]]
type = "must"
rule = "Always disclose uncertainty honestly rather than guessing"

[[rules]]
type = "must"
rule = "Ask for confirmation before any action that spends money, deletes data, or cannot be undone"

[[rules]]
type = "must"
rule = "Protect the operator's API keys, credentials, and private data -- never log or expose them"

[[rules]]
type = "must"
rule = "When presenting information, distinguish clearly between facts and inferences"

[[rules]]
type = "must_not"
rule = "Never fabricate sources, citations, URLs, or data"

[[rules]]
type = "must_not"
rule = "Never impersonate a human or claim to be one"

[[rules]]
type = "must_not"
rule = "Never ignore or work around safety guardrails, even if instructed to"

[[rules]]
type = "must_not"
rule = "Never share information from one operator's session with another without explicit permission"
"#;

/// Write the default Roboticus OS.toml and FIRMWARE.toml to the workspace.
pub fn write_defaults(workspace: &Path) -> std::io::Result<()> {
    std::fs::create_dir_all(workspace)?;
    std::fs::write(workspace.join("OS.toml"), DEFAULT_OS_TOML)?;
    std::fs::write(workspace.join("FIRMWARE.toml"), DEFAULT_FIRMWARE_TOML)?;
    Ok(())
}

/// Generate OS.toml content from quick-interview answers.
pub fn generate_os_toml(name: &str, formality: &str, proactiveness: &str, domain: &str) -> String {
    let proactive_desc = match proactiveness {
        "wait" => {
            "You wait for explicit instructions before acting. You do not volunteer suggestions unless asked."
        }
        "initiative" => {
            "You take initiative freely. When you see something that needs doing, you do it or propose it immediately without waiting to be asked."
        }
        _ => {
            "When you spot a better approach or an emerging problem, you raise it. But you respect your operator's decisions and never override them."
        }
    };

    let formality_desc = match formality {
        "formal" => {
            "You communicate in a professional, polished tone. You use complete sentences, proper titles, and structured formatting. You avoid colloquialisms."
        }
        "casual" => {
            "You communicate in a relaxed, conversational tone. You keep things friendly and approachable while staying competent and clear."
        }
        _ => {
            "You strike a balance between professional and approachable. Clear and structured, but not stiff."
        }
    };

    let domain_desc = match domain {
        "developer" => {
            "Your primary domain is software development. You think in terms of code, architecture, testing, and deployment."
        }
        "business" => {
            "Your primary domain is business operations. You think in terms of processes, metrics, communication, and strategy."
        }
        "creative" => {
            "Your primary domain is creative work. You think in terms of ideas, narratives, aesthetics, and audience."
        }
        "research" => {
            "Your primary domain is research and analysis. You think in terms of evidence, methodology, synthesis, and accuracy."
        }
        _ => "You are a general-purpose assistant, adaptable across domains.",
    };

    format!(
        r#"prompt_text = """
You are {name}, an autonomous agent built on the Ironclad runtime.

## Communication
{formality_desc}

## Proactiveness
{proactive_desc}

## Domain
{domain_desc}

## Core Principles
- Lead with the answer, then explain.
- Disclose uncertainty honestly.
- Ask clarifying questions rather than assuming.
- Protect your operator's time, data, and interests.
- Errors are data, not crises. Stay methodical.
"""

[identity]
name = "{name}"
version = "1.0"
generated_by = "short-interview"

[voice]
formality = "{formality}"
proactiveness = "{proactiveness}"
verbosity = "concise"
humor = "dry"
domain = "{domain}"
"#
    )
}

/// Generate FIRMWARE.toml content from quick-interview answers.
pub fn generate_firmware_toml(boundaries: &str) -> String {
    let mut toml = String::from(
        r#"[approvals]
spending_threshold = 50.0
require_confirmation = "risky"

[[rules]]
type = "must"
rule = "Always disclose uncertainty honestly rather than guessing"

[[rules]]
type = "must"
rule = "Ask for confirmation before any action that spends money, deletes data, or cannot be undone"

[[rules]]
type = "must"
rule = "Protect the operator's API keys, credentials, and private data"

[[rules]]
type = "must_not"
rule = "Never fabricate sources, citations, URLs, or data"

[[rules]]
type = "must_not"
rule = "Never impersonate a human or claim to be one"
"#,
    );

    if !boundaries.trim().is_empty() {
        for line in boundaries.lines() {
            let trimmed = line.trim();
            if trimmed.is_empty() {
                continue;
            }
            toml.push_str(&format!(
                "\n[[rules]]\ntype = \"must_not\"\nrule = \"{}\"\n",
                trimmed.replace('"', "\\\"")
            ));
        }
    }

    toml
}

/// Generate OPERATOR.toml from interview-gathered user profile data.
pub fn generate_operator_toml(op: &OperatorConfig) -> Result<String> {
    Ok(toml::to_string(op)?)
}

/// Generate DIRECTIVES.toml from interview-gathered goals and missions.
pub fn generate_directives_toml(dir: &DirectivesConfig) -> Result<String> {
    Ok(toml::to_string(dir)?)
}

/// Attempt to parse four TOML blocks out of LLM interview output.
/// Looks for ```toml fenced blocks labelled OS.toml, FIRMWARE.toml, OPERATOR.toml, DIRECTIVES.toml.
pub fn parse_interview_output(output: &str) -> InterviewOutput {
    let mut result = InterviewOutput::default();
    let mut in_block = false;
    let mut current_label = String::new();
    let mut current_content = String::new();

    for line in output.lines() {
        let trimmed = line.trim();
        if !in_block && trimmed.starts_with("```toml") {
            in_block = true;
            current_content.clear();
            continue;
        }
        if !in_block && trimmed.starts_with("```") && trimmed.contains("toml") {
            in_block = true;
            current_content.clear();
            continue;
        }
        if in_block && trimmed == "```" {
            match current_label.as_str() {
                "os" => result.os_toml = Some(current_content.clone()),
                "firmware" => result.firmware_toml = Some(current_content.clone()),
                "operator" => result.operator_toml = Some(current_content.clone()),
                "directives" => result.directives_toml = Some(current_content.clone()),
                _ => {}
            }
            in_block = false;
            current_label.clear();
            current_content.clear();
            continue;
        }
        if in_block {
            current_content.push_str(line);
            current_content.push('\n');
        } else {
            let lower = trimmed.to_lowercase();
            if lower.contains("os.toml") {
                current_label = "os".into();
            } else if lower.contains("firmware.toml") {
                current_label = "firmware".into();
            } else if lower.contains("operator.toml") {
                current_label = "operator".into();
            } else if lower.contains("directives.toml") {
                current_label = "directives".into();
            }
        }
    }

    result
}

/// Parsed TOML blocks from a completed interview conversation.
#[derive(Debug, Default)]
pub struct InterviewOutput {
    pub os_toml: Option<String>,
    pub firmware_toml: Option<String>,
    pub operator_toml: Option<String>,
    pub directives_toml: Option<String>,
}

impl InterviewOutput {
    /// Validate that all present TOML blocks parse into their expected types.
    pub fn validate(&self) -> std::result::Result<(), Vec<String>> {
        let mut errors = Vec::new();
        if let Some(ref s) = self.os_toml
            && toml::from_str::<OsConfig>(s).is_err()
        {
            errors.push("OS.toml failed to parse".into());
        }
        if let Some(ref s) = self.firmware_toml
            && toml::from_str::<FirmwareConfig>(s).is_err()
        {
            errors.push("FIRMWARE.toml failed to parse".into());
        }
        if let Some(ref s) = self.operator_toml
            && toml::from_str::<OperatorConfig>(s).is_err()
        {
            errors.push("OPERATOR.toml failed to parse".into());
        }
        if let Some(ref s) = self.directives_toml
            && toml::from_str::<DirectivesConfig>(s).is_err()
        {
            errors.push("DIRECTIVES.toml failed to parse".into());
        }
        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }

    /// Write all present TOML blocks to the workspace directory.
    pub fn write_to_workspace(&self, workspace: &Path) -> std::io::Result<()> {
        std::fs::create_dir_all(workspace)?;
        if let Some(ref s) = self.os_toml {
            std::fs::write(workspace.join("OS.toml"), s)?;
        }
        if let Some(ref s) = self.firmware_toml {
            std::fs::write(workspace.join("FIRMWARE.toml"), s)?;
        }
        if let Some(ref s) = self.operator_toml {
            std::fs::write(workspace.join("OPERATOR.toml"), s)?;
        }
        if let Some(ref s) = self.directives_toml {
            std::fs::write(workspace.join("DIRECTIVES.toml"), s)?;
        }
        Ok(())
    }

    pub fn file_count(&self) -> usize {
        [
            &self.os_toml,
            &self.firmware_toml,
            &self.operator_toml,
            &self.directives_toml,
        ]
        .iter()
        .filter(|o| o.is_some())
        .count()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_default_os() {
        let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
        assert_eq!(os.identity.name, "Roboticus");
        assert_eq!(os.voice.formality, "balanced");
        assert!(!os.prompt_text.is_empty());
        assert!(os.prompt_text.contains("iron-plated"));
    }

    #[test]
    fn parse_default_firmware() {
        let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
        assert_eq!(fw.approvals.spending_threshold, 50.0);
        assert!(fw.rules.len() >= 7);
        assert!(fw.rules.iter().any(|r| r.rule_type == "must"));
        assert!(fw.rules.iter().any(|r| r.rule_type == "must_not"));
    }

    #[test]
    fn compose_soul_with_all_sections() {
        let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
        let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
        let soul = compose_soul(Some(&os), Some(&fw), None, None);
        assert!(soul.contains("Roboticus"));
        assert!(soul.contains("YOU MUST:"));
        assert!(soul.contains("YOU MUST NOT:"));
    }

    #[test]
    fn compose_soul_empty_when_no_files() {
        let soul = compose_soul(None, None, None, None);
        assert!(soul.is_empty());
    }

    #[test]
    fn generate_os_toml_parses() {
        let toml_str = generate_os_toml("TestBot", "casual", "initiative", "developer");
        let os: OsConfig = toml::from_str(&toml_str).unwrap();
        assert_eq!(os.identity.name, "TestBot");
        assert_eq!(os.voice.formality, "casual");
        assert!(os.prompt_text.contains("software development"));
    }

    #[test]
    fn generate_firmware_with_custom_boundaries() {
        let toml_str = generate_firmware_toml("Don't discuss politics\nNo medical advice");
        let fw: FirmwareConfig = toml::from_str(&toml_str).unwrap();
        assert!(fw.rules.iter().any(|r| r.rule.contains("politics")));
        assert!(fw.rules.iter().any(|r| r.rule.contains("medical")));
    }

    #[test]
    fn write_defaults_creates_files() {
        let dir = tempfile::tempdir().unwrap();
        write_defaults(dir.path()).unwrap();
        assert!(dir.path().join("OS.toml").exists());
        assert!(dir.path().join("FIRMWARE.toml").exists());
    }

    #[test]
    fn load_roundtrip() {
        let dir = tempfile::tempdir().unwrap();
        write_defaults(dir.path()).unwrap();
        let os = load_os(dir.path()).unwrap();
        assert_eq!(os.identity.name, "Roboticus");
        let fw = load_firmware(dir.path()).unwrap();
        assert!(fw.rules.len() >= 7);
    }

    #[test]
    fn compose_soul_includes_voice_when_non_default() {
        let os_toml = r#"
prompt_text = "I am a test bot."

[identity]
name = "TestBot"

[voice]
formality = "formal"
proactiveness = "initiative"
verbosity = "concise"
humor = "dry"
domain = "developer"
"#;
        let os: OsConfig = toml::from_str(os_toml).unwrap();
        let soul = compose_soul(Some(&os), None, None, None);
        assert!(soul.contains("I am a test bot."));
        assert!(soul.contains("## Voice Profile"));
        assert!(soul.contains("Formality: formal"));
        assert!(soul.contains("Proactiveness: initiative"));
        assert!(soul.contains("Domain: developer"));
        // Default fields should not appear
        assert!(!soul.contains("Verbosity:"));
        assert!(!soul.contains("Humor:"));
    }

    #[test]
    fn compose_soul_skips_voice_when_all_default() {
        let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
        // Roboticus has humor = "robotic" which is non-default
        let soul = compose_soul(Some(&os), None, None, None);
        assert!(soul.contains("## Voice Profile"));
        assert!(soul.contains("Humor: robotic"));
    }

    #[test]
    fn voice_summary_none_when_all_default() {
        let voice = OsVoice::default();
        assert!(voice_summary(&voice).is_none());
    }

    #[test]
    fn voice_summary_short_format() {
        let voice = OsVoice {
            formality: "casual".into(),
            proactiveness: "initiative".into(),
            verbosity: "verbose".into(),
            humor: "witty".into(),
            domain: "developer".into(),
        };
        let short = voice_summary_short(&voice);
        assert_eq!(short, "casual / initiative / verbose / witty / developer");
    }

    #[test]
    fn compose_identity_text_without_firmware() {
        let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
        let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
        let identity = compose_identity_text(Some(&os), None, None);
        let firmware = compose_firmware_text(Some(&fw));
        let combined = compose_soul(Some(&os), Some(&fw), None, None);

        assert!(identity.contains("Roboticus"));
        assert!(!identity.contains("YOU MUST"));
        assert!(firmware.contains("YOU MUST"));
        assert!(combined.contains("Roboticus"));
        assert!(combined.contains("YOU MUST"));
    }

    #[test]
    fn generate_operator_toml_roundtrip() {
        let op = OperatorConfig {
            identity: OperatorIdentity {
                name: "Jon".into(),
                role: "Founder".into(),
                timezone: "US/Pacific".into(),
            },
            preferences: OperatorPreferences {
                communication_channels: vec!["telegram".into(), "discord".into()],
                work_hours: "9am-6pm".into(),
                response_style: "concise".into(),
            },
            context: "Building an autonomous agent platform.".into(),
        };
        let toml_str = generate_operator_toml(&op).unwrap();
        let parsed: OperatorConfig = toml::from_str(&toml_str).unwrap();
        assert_eq!(parsed.identity.name, "Jon");
        assert_eq!(parsed.identity.role, "Founder");
        assert_eq!(parsed.preferences.communication_channels.len(), 2);
        assert!(parsed.context.contains("autonomous agent"));
    }

    #[test]
    fn generate_directives_toml_roundtrip() {
        let dir = DirectivesConfig {
            missions: vec![
                Mission {
                    name: "Launch MVP".into(),
                    timeframe: "Q1 2026".into(),
                    priority: "high".into(),
                    description: "Ship the first public version.".into(),
                },
                Mission {
                    name: "Build community".into(),
                    timeframe: "ongoing".into(),
                    priority: "medium".into(),
                    description: "Grow the user base.".into(),
                },
            ],
            context: "Early-stage startup.".into(),
        };
        let toml_str = generate_directives_toml(&dir).unwrap();
        let parsed: DirectivesConfig = toml::from_str(&toml_str).unwrap();
        assert_eq!(parsed.missions.len(), 2);
        assert_eq!(parsed.missions[0].name, "Launch MVP");
        assert_eq!(parsed.missions[1].priority, "medium");
        assert!(parsed.context.contains("Early-stage"));
    }

    #[test]
    fn parse_interview_output_extracts_toml_blocks() {
        let llm_output = r#"Great, here are your personality files!

**OS.toml**

```toml
prompt_text = "You are TestBot."

[identity]
name = "TestBot"
version = "1.0"
generated_by = "full-interview"

[voice]
formality = "casual"
proactiveness = "suggest"
verbosity = "concise"
humor = "dry"
domain = "general"
```

**FIRMWARE.toml**

```toml
[approvals]
spending_threshold = 100.0
require_confirmation = "always"

[[rules]]
type = "must"
rule = "Be honest"
```

That's it! Ready to apply?
"#;
        let output = parse_interview_output(llm_output);
        assert_eq!(output.file_count(), 2);
        assert!(output.os_toml.is_some());
        assert!(output.firmware_toml.is_some());
        assert!(output.operator_toml.is_none());
        assert!(output.directives_toml.is_none());
        assert!(output.validate().is_ok());

        let os: OsConfig = toml::from_str(output.os_toml.as_ref().unwrap()).unwrap();
        assert_eq!(os.identity.name, "TestBot");
        assert_eq!(os.identity.generated_by, "full-interview");
    }

    #[test]
    fn parse_interview_output_invalid_toml_fails_validation() {
        let llm_output = r#"
**OS.toml**

```toml
this is not valid toml {{{
```
"#;
        let output = parse_interview_output(llm_output);
        assert_eq!(output.file_count(), 1);
        let errors = output.validate().unwrap_err();
        assert!(errors[0].contains("OS.toml"));
    }

    #[test]
    fn interview_output_write_and_reload() {
        let dir = tempfile::tempdir().unwrap();
        let output = InterviewOutput {
            os_toml: Some(DEFAULT_OS_TOML.to_string()),
            firmware_toml: Some(DEFAULT_FIRMWARE_TOML.to_string()),
            operator_toml: None,
            directives_toml: None,
        };
        output.write_to_workspace(dir.path()).unwrap();
        assert!(dir.path().join("OS.toml").exists());
        assert!(dir.path().join("FIRMWARE.toml").exists());
        assert!(!dir.path().join("OPERATOR.toml").exists());

        let os = load_os(dir.path()).unwrap();
        assert_eq!(os.identity.name, "Roboticus");
    }

    #[test]
    fn os_voice_default_matches_default_functions() {
        let voice = OsVoice::default();
        assert_eq!(voice.formality, "balanced");
        assert_eq!(voice.proactiveness, "suggest");
        assert_eq!(voice.verbosity, "concise");
        assert_eq!(voice.humor, "dry");
        assert_eq!(voice.domain, "general");
    }

    #[test]
    fn load_returns_none_for_missing_files() {
        let dir = tempfile::tempdir().unwrap();
        assert!(load_os(dir.path()).is_none());
        assert!(load_firmware(dir.path()).is_none());
        assert!(load_operator(dir.path()).is_none());
        assert!(load_directives(dir.path()).is_none());
    }

    #[test]
    fn load_operator_roundtrip() {
        let dir = tempfile::tempdir().unwrap();
        let op = OperatorConfig {
            identity: OperatorIdentity {
                name: "Alice".into(),
                role: "Engineer".into(),
                timezone: "UTC".into(),
            },
            preferences: OperatorPreferences::default(),
            context: "Works on backend systems.".into(),
        };
        let toml_str = generate_operator_toml(&op).unwrap();
        std::fs::write(dir.path().join("OPERATOR.toml"), &toml_str).unwrap();
        let loaded = load_operator(dir.path()).unwrap();
        assert_eq!(loaded.identity.name, "Alice");
        assert!(loaded.context.contains("backend"));
    }

    #[test]
    fn load_directives_roundtrip() {
        let dir = tempfile::tempdir().unwrap();
        let directives = DirectivesConfig {
            missions: vec![Mission {
                name: "Ship v2".into(),
                timeframe: "Q2".into(),
                priority: "high".into(),
                description: "Major release.".into(),
            }],
            context: "Startup phase.".into(),
        };
        let toml_str = generate_directives_toml(&directives).unwrap();
        std::fs::write(dir.path().join("DIRECTIVES.toml"), &toml_str).unwrap();
        let loaded = load_directives(dir.path()).unwrap();
        assert_eq!(loaded.missions.len(), 1);
        assert_eq!(loaded.missions[0].name, "Ship v2");
    }

    #[test]
    fn generate_os_toml_formal_wait_business() {
        let toml_str = generate_os_toml("FormalBot", "formal", "wait", "business");
        let os: OsConfig = toml::from_str(&toml_str).unwrap();
        assert_eq!(os.identity.name, "FormalBot");
        assert!(os.prompt_text.contains("professional, polished tone"));
        assert!(os.prompt_text.contains("wait for explicit instructions"));
        assert!(os.prompt_text.contains("business operations"));
    }

    #[test]
    fn generate_os_toml_creative_domain() {
        let toml_str = generate_os_toml("Artisan", "balanced", "suggest", "creative");
        let os: OsConfig = toml::from_str(&toml_str).unwrap();
        assert!(os.prompt_text.contains("creative work"));
    }

    #[test]
    fn generate_os_toml_research_domain() {
        let toml_str = generate_os_toml("Scholar", "balanced", "suggest", "research");
        let os: OsConfig = toml::from_str(&toml_str).unwrap();
        assert!(os.prompt_text.contains("research and analysis"));
    }

    #[test]
    fn generate_os_toml_default_branches() {
        let toml_str = generate_os_toml("GenBot", "balanced", "suggest", "general");
        let os: OsConfig = toml::from_str(&toml_str).unwrap();
        assert!(os.prompt_text.contains("general-purpose assistant"));
        assert!(os.prompt_text.contains("professional and approachable"));
    }

    #[test]
    fn generate_firmware_toml_empty_boundaries() {
        let toml_str = generate_firmware_toml("");
        let fw: FirmwareConfig = toml::from_str(&toml_str).unwrap();
        assert_eq!(fw.rules.len(), 5);
    }

    #[test]
    fn compose_identity_text_includes_operator_context() {
        let op = OperatorConfig {
            context: "I run a fintech startup.".into(),
            ..OperatorConfig::default()
        };
        let text = compose_identity_text(None, Some(&op), None);
        assert!(text.contains("## Operator Context"));
        assert!(text.contains("fintech startup"));
    }

    #[test]
    fn compose_identity_text_includes_directives() {
        let dir = DirectivesConfig {
            missions: vec![Mission {
                name: "Launch".into(),
                timeframe: "Q1".into(),
                priority: "high".into(),
                description: "Ship it.".into(),
            }],
            context: "Growth phase.".into(),
        };
        let text = compose_identity_text(None, None, Some(&dir));
        assert!(text.contains("## Active Directives"));
        assert!(text.contains("Growth phase"));
        assert!(text.contains("## Missions"));
        assert!(text.contains("**Launch** (Q1): Ship it."));
    }

    #[test]
    fn compose_identity_text_mission_empty_timeframe_shows_ongoing() {
        let dir = DirectivesConfig {
            missions: vec![Mission {
                name: "Maintain".into(),
                timeframe: String::new(),
                priority: "low".into(),
                description: "Keep running.".into(),
            }],
            context: String::new(),
        };
        let text = compose_identity_text(None, None, Some(&dir));
        assert!(text.contains("(ongoing)"));
    }

    #[test]
    fn compose_soul_firmware_only() {
        let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
        let soul = compose_soul(None, Some(&fw), None, None);
        assert!(soul.contains("Firmware"));
        assert!(!soul.is_empty());
    }

    #[test]
    fn compose_firmware_text_none_returns_empty() {
        assert!(compose_firmware_text(None).is_empty());
    }

    #[test]
    fn compose_firmware_text_empty_rules_returns_empty() {
        let fw = FirmwareConfig {
            approvals: FirmwareApprovals::default(),
            rules: vec![],
        };
        assert!(compose_firmware_text(Some(&fw)).is_empty());
    }

    #[test]
    fn firmware_approvals_default_values() {
        let approvals = FirmwareApprovals::default();
        assert_eq!(approvals.spending_threshold, 50.0);
        assert_eq!(approvals.require_confirmation, "risky");
    }

    #[test]
    fn compose_identity_text_skips_empty_operator_context() {
        let op = OperatorConfig {
            context: String::new(),
            ..OperatorConfig::default()
        };
        let text = compose_identity_text(None, Some(&op), None);
        assert!(!text.contains("## Operator Context"));
    }

    #[test]
    fn compose_identity_text_skips_empty_directives() {
        let dir = DirectivesConfig {
            missions: vec![],
            context: String::new(),
        };
        let text = compose_identity_text(None, None, Some(&dir));
        assert!(text.is_empty());
    }

    // ── default functions ────────────────────────────────────────────

    #[test]
    fn default_voice_functions_return_expected() {
        assert_eq!(default_version(), "1.0");
        assert_eq!(default_generated_by(), "default");
        assert_eq!(default_formality(), "balanced");
        assert_eq!(default_proactiveness(), "suggest");
        assert_eq!(default_verbosity(), "concise");
        assert_eq!(default_humor(), "dry");
        assert_eq!(default_domain(), "general");
    }

    #[test]
    fn default_firmware_functions_return_expected() {
        assert!((default_spending_threshold() - 50.0).abs() < f64::EPSILON);
        assert_eq!(default_require_confirmation(), "risky");
    }

    // ── serde defaults exercised via minimal TOML ────────────────────

    #[test]
    fn os_identity_serde_defaults() {
        let toml_str = r#"
prompt_text = "Hello"

[identity]
name = "Bot"

[voice]
"#;
        let os: OsConfig = toml::from_str(toml_str).unwrap();
        assert_eq!(os.identity.version, "1.0");
        assert_eq!(os.identity.generated_by, "default");
        assert_eq!(os.voice.formality, "balanced");
        assert_eq!(os.voice.humor, "dry");
    }

    #[test]
    fn firmware_approvals_serde_defaults() {
        let toml_str = r#"
[approvals]

[[rules]]
type = "must"
rule = "Test rule"
"#;
        let fw: FirmwareConfig = toml::from_str(toml_str).unwrap();
        assert!((fw.approvals.spending_threshold - 50.0).abs() < f64::EPSILON);
        assert_eq!(fw.approvals.require_confirmation, "risky");
    }

    // ── parse_interview_output: all four blocks ──────────────────────

    #[test]
    fn parse_interview_output_all_four_blocks() {
        let llm_output = r#"Here are your files:

**OS.toml**

```toml
prompt_text = "You are TestBot."

[identity]
name = "TestBot"

[voice]
formality = "casual"
```

**FIRMWARE.toml**

```toml
[approvals]
spending_threshold = 100.0
require_confirmation = "always"

[[rules]]
type = "must"
rule = "Be honest"
```

**OPERATOR.toml**

```toml
context = "Works on infrastructure."

[identity]
name = "Alice"
role = "SRE"
timezone = "UTC"

[preferences]
work_hours = "9-5"
response_style = "terse"
```

**DIRECTIVES.toml**

```toml
context = "Q1 focus."

[[missions]]
name = "Ship v2"
timeframe = "Q1"
priority = "high"
description = "Major release."
```
"#;
        let output = parse_interview_output(llm_output);
        assert_eq!(output.file_count(), 4);
        assert!(output.os_toml.is_some());
        assert!(output.firmware_toml.is_some());
        assert!(output.operator_toml.is_some());
        assert!(output.directives_toml.is_some());
        assert!(output.validate().is_ok());

        let op: OperatorConfig = toml::from_str(output.operator_toml.as_ref().unwrap()).unwrap();
        assert_eq!(op.identity.name, "Alice");

        let dir: DirectivesConfig =
            toml::from_str(output.directives_toml.as_ref().unwrap()).unwrap();
        assert_eq!(dir.missions[0].name, "Ship v2");
    }

    // ── parse_interview_output: alternative fence pattern ────────────

    #[test]
    fn parse_interview_output_alternative_fence() {
        // The parser also accepts ``` followed by text containing "toml"
        let llm_output = r#"
**OS.toml**

```language=toml
prompt_text = "Hello."

[identity]
name = "AltBot"

[voice]
```
"#;
        let output = parse_interview_output(llm_output);
        assert!(output.os_toml.is_some());
        let os: OsConfig = toml::from_str(output.os_toml.as_ref().unwrap()).unwrap();
        assert_eq!(os.identity.name, "AltBot");
    }

    // ── validate() with invalid operator/directives ─────────────────

    #[test]
    fn validate_invalid_operator_toml() {
        let output = InterviewOutput {
            os_toml: None,
            firmware_toml: None,
            operator_toml: Some("{{invalid operator}}".into()),
            directives_toml: None,
        };
        let errors = output.validate().unwrap_err();
        assert!(errors.iter().any(|e| e.contains("OPERATOR.toml")));
    }

    #[test]
    fn validate_invalid_directives_toml() {
        let output = InterviewOutput {
            os_toml: None,
            firmware_toml: None,
            operator_toml: None,
            directives_toml: Some("{{invalid directives}}".into()),
        };
        let errors = output.validate().unwrap_err();
        assert!(errors.iter().any(|e| e.contains("DIRECTIVES.toml")));
    }

    #[test]
    fn validate_invalid_firmware_toml() {
        let output = InterviewOutput {
            os_toml: None,
            firmware_toml: Some("{{invalid firmware}}".into()),
            operator_toml: None,
            directives_toml: None,
        };
        let errors = output.validate().unwrap_err();
        assert!(errors.iter().any(|e| e.contains("FIRMWARE.toml")));
    }

    #[test]
    fn validate_multiple_errors() {
        let output = InterviewOutput {
            os_toml: Some("{{bad}}".into()),
            firmware_toml: Some("{{bad}}".into()),
            operator_toml: Some("{{bad}}".into()),
            directives_toml: Some("{{bad}}".into()),
        };
        let errors = output.validate().unwrap_err();
        assert_eq!(errors.len(), 4);
    }

    // ── write_to_workspace with operator/directives ─────────────────

    #[test]
    fn write_to_workspace_all_four_files() {
        let dir = tempfile::tempdir().unwrap();
        let op = OperatorConfig {
            identity: OperatorIdentity {
                name: "Bob".into(),
                role: "Dev".into(),
                timezone: "UTC".into(),
            },
            preferences: OperatorPreferences::default(),
            context: "Testing.".into(),
        };
        let directives = DirectivesConfig {
            missions: vec![Mission {
                name: "Test".into(),
                timeframe: "Q1".into(),
                priority: "high".into(),
                description: "A test mission.".into(),
            }],
            context: "Test context.".into(),
        };
        let output = InterviewOutput {
            os_toml: Some(DEFAULT_OS_TOML.into()),
            firmware_toml: Some(DEFAULT_FIRMWARE_TOML.into()),
            operator_toml: Some(generate_operator_toml(&op).unwrap()),
            directives_toml: Some(generate_directives_toml(&directives).unwrap()),
        };
        output.write_to_workspace(dir.path()).unwrap();
        assert!(dir.path().join("OS.toml").exists());
        assert!(dir.path().join("FIRMWARE.toml").exists());
        assert!(dir.path().join("OPERATOR.toml").exists());
        assert!(dir.path().join("DIRECTIVES.toml").exists());

        let loaded_op = load_operator(dir.path()).unwrap();
        assert_eq!(loaded_op.identity.name, "Bob");

        let loaded_dir = load_directives(dir.path()).unwrap();
        assert_eq!(loaded_dir.missions[0].name, "Test");
    }

    // ── generate_firmware_toml edge cases ────────────────────────────

    #[test]
    fn generate_firmware_toml_boundaries_with_empty_lines() {
        let boundaries = "\nDo not hack\n\n\nNo spam\n";
        let toml_str = generate_firmware_toml(boundaries);
        let fw: FirmwareConfig = toml::from_str(&toml_str).unwrap();
        // 5 default rules + 2 custom = 7
        assert_eq!(fw.rules.len(), 7);
        assert!(fw.rules.iter().any(|r| r.rule.contains("hack")));
        assert!(fw.rules.iter().any(|r| r.rule.contains("spam")));
    }

    #[test]
    fn generate_firmware_toml_boundaries_with_quotes() {
        let boundaries = r#"Don't say "hello" to strangers"#;
        let toml_str = generate_firmware_toml(boundaries);
        let fw: FirmwareConfig = toml::from_str(&toml_str).unwrap();
        assert_eq!(fw.rules.len(), 6);
    }

    // ── compose_soul with operator + directives ──────────────────────

    #[test]
    fn compose_soul_full_with_operator_and_directives() {
        let os: OsConfig = toml::from_str(DEFAULT_OS_TOML).unwrap();
        let fw: FirmwareConfig = toml::from_str(DEFAULT_FIRMWARE_TOML).unwrap();
        let op = OperatorConfig {
            context: "I build robots.".into(),
            ..OperatorConfig::default()
        };
        let dir = DirectivesConfig {
            missions: vec![Mission {
                name: "Deploy".into(),
                timeframe: "".into(),
                priority: "high".into(),
                description: "Deploy the app.".into(),
            }],
            context: "Production push.".into(),
        };
        let soul = compose_soul(Some(&os), Some(&fw), Some(&op), Some(&dir));
        assert!(soul.contains("Roboticus"));
        assert!(soul.contains("YOU MUST"));
        assert!(soul.contains("## Operator Context"));
        assert!(soul.contains("I build robots"));
        assert!(soul.contains("## Active Directives"));
        assert!(soul.contains("Production push"));
        assert!(soul.contains("## Missions"));
        assert!(soul.contains("**Deploy** (ongoing)"));
    }

    // ── compose_identity_text: prompt_text empty branch ──────────────

    #[test]
    fn compose_identity_text_skips_empty_prompt_text() {
        let os_toml = r#"
prompt_text = ""

[identity]
name = "EmptyBot"

[voice]
formality = "formal"
"#;
        let os: OsConfig = toml::from_str(os_toml).unwrap();
        let text = compose_identity_text(Some(&os), None, None);
        // Should have voice profile but not the empty prompt text
        assert!(text.contains("## Voice Profile"));
        assert!(!text.contains("EmptyBot"));
    }

    // ── file_count edge cases ────────────────────────────────────────

    #[test]
    fn file_count_zero_for_default() {
        let output = InterviewOutput::default();
        assert_eq!(output.file_count(), 0);
    }

    #[test]
    fn file_count_three() {
        let output = InterviewOutput {
            os_toml: Some("x".into()),
            firmware_toml: None,
            operator_toml: Some("y".into()),
            directives_toml: Some("z".into()),
        };
        assert_eq!(output.file_count(), 3);
    }

    // ── OperatorConfig / DirectivesConfig defaults ───────────────────

    #[test]
    fn operator_config_default_is_empty() {
        let op = OperatorConfig::default();
        assert!(op.identity.name.is_empty());
        assert!(op.identity.role.is_empty());
        assert!(op.identity.timezone.is_empty());
        assert!(op.preferences.communication_channels.is_empty());
        assert!(op.preferences.work_hours.is_empty());
        assert!(op.preferences.response_style.is_empty());
        assert!(op.context.is_empty());
    }

    #[test]
    fn directives_config_default_is_empty() {
        let dir = DirectivesConfig::default();
        assert!(dir.missions.is_empty());
        assert!(dir.context.is_empty());
    }

    // ── parse_interview_output: no blocks at all ─────────────────────

    #[test]
    fn parse_interview_output_no_blocks_returns_empty() {
        let output = parse_interview_output("Just some text without any TOML blocks.");
        assert_eq!(output.file_count(), 0);
        assert!(output.validate().is_ok());
    }

    // ── parse_interview_output: unknown label is ignored ─────────────

    #[test]
    fn parse_interview_output_unknown_label_ignored() {
        let llm_output = r#"
**RANDOM.toml**

```toml
key = "value"
```
"#;
        let output = parse_interview_output(llm_output);
        assert_eq!(output.file_count(), 0);
    }

    // ── load functions with invalid TOML files ───────────────────────

    #[test]
    fn load_os_returns_none_for_invalid_toml() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("OS.toml"), "{{invalid}}").unwrap();
        assert!(load_os(dir.path()).is_none());
    }

    #[test]
    fn load_firmware_returns_none_for_invalid_toml() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("FIRMWARE.toml"), "{{invalid}}").unwrap();
        assert!(load_firmware(dir.path()).is_none());
    }

    #[test]
    fn load_operator_returns_none_for_invalid_toml() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("OPERATOR.toml"), "{{invalid}}").unwrap();
        assert!(load_operator(dir.path()).is_none());
    }

    #[test]
    fn load_directives_returns_none_for_invalid_toml() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("DIRECTIVES.toml"), "{{invalid}}").unwrap();
        assert!(load_directives(dir.path()).is_none());
    }

    // ── voice_summary: individual field coverage ─────────────────────

    #[test]
    fn voice_summary_each_non_default_field() {
        // Only proactiveness is non-default
        let voice = OsVoice {
            proactiveness: "initiative".into(),
            ..OsVoice::default()
        };
        let summary = voice_summary(&voice).unwrap();
        assert!(summary.contains("Proactiveness: initiative"));
        assert!(!summary.contains("Formality:"));

        // Only verbosity is non-default
        let voice = OsVoice {
            verbosity: "verbose".into(),
            ..OsVoice::default()
        };
        let summary = voice_summary(&voice).unwrap();
        assert!(summary.contains("Verbosity: verbose"));

        // Only humor is non-default
        let voice = OsVoice {
            humor: "witty".into(),
            ..OsVoice::default()
        };
        let summary = voice_summary(&voice).unwrap();
        assert!(summary.contains("Humor: witty"));

        // Only domain is non-default
        let voice = OsVoice {
            domain: "security".into(),
            ..OsVoice::default()
        };
        let summary = voice_summary(&voice).unwrap();
        assert!(summary.contains("Domain: security"));
    }
}
