//! Validator module: Validate specs against JSON Schema and perform semantic checks
//!
//! Two-stage validation:
//! 1. JSON Schema validation (using embedded unit.spec.json)
//! 2. Semantic validation (Rust keywords, deps, etc.)

use crate::syntax::{token_stream_contains_unsafe_keyword, validate_expect_expr};
use crate::types::{
    DepRef, DepRefParseError, LoadedMoleculeTest, LoadedSpec, QualifiedUnitRef, callable_name,
    has_callable_collision,
};
use crate::{AUTHORED_SPEC_VERSION, Result, SpecError, SpecWarning};
use serde_json::Value;
use serde_yaml_bw::Value as YamlValue;
use std::collections::{HashMap, HashSet};
use std::sync::OnceLock;

/// JSON Schema for unit.spec validation (embedded at compile time)
const SCHEMA_JSON: &str = include_str!("schema/unit.spec.json");

/// JSON Schema for test.spec validation (embedded at compile time)
const TEST_SCHEMA_JSON: &str = include_str!("schema/test.spec.json");

static COMPILED_SCHEMA: OnceLock<jsonschema::Validator> = OnceLock::new();
static COMPILED_TEST_SCHEMA: OnceLock<jsonschema::Validator> = OnceLock::new();

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ValidationOptions {
    pub strict_deps: bool,
    pub allow_unsafe_local_test_expect: bool,
}

impl ValidationOptions {
    pub fn strict() -> Self {
        Self {
            strict_deps: true,
            allow_unsafe_local_test_expect: false,
        }
    }
}

/// A library-aware validation snapshot used by later M9 slices.
#[derive(Debug, Clone)]
pub struct QualifiedLoadedSpec<'a> {
    pub loaded: &'a LoadedSpec,
    pub qualified_id: QualifiedUnitRef,
    pub qualified_deps: Vec<QualifiedUnitRef>,
}

impl<'a> QualifiedLoadedSpec<'a> {
    pub fn local_identity(loaded: &'a LoadedSpec) -> Self {
        Self {
            loaded,
            qualified_id: QualifiedUnitRef::local(loaded.spec.id.clone()),
            qualified_deps: Vec::new(),
        }
    }

    pub fn local(loaded: &'a LoadedSpec) -> std::result::Result<Self, DepRefParseError> {
        let dep_refs = parse_dep_refs(&loaded.spec.deps)?;
        Ok(Self {
            loaded,
            qualified_id: QualifiedUnitRef::local(loaded.spec.id.clone()),
            qualified_deps: dep_refs
                .into_iter()
                .map(|dep| dep.to_qualified(None))
                .collect(),
        })
    }
}

/// Validate a single spec against the JSON Schema
pub fn validate_schema(spec: &LoadedSpec) -> Result<()> {
    validate_json_value(
        &serde_json::to_value(&spec.spec).map_err(SpecError::Json)?,
        &spec.source.file_path,
    )
}

/// Validate a raw YAML-authored value against the JSON Schema before deserialization.
///
/// This is the validation path used by the loader so that unknown fields and other
/// authoring-time mistakes are rejected before serde can apply defaults or drop data.
pub fn validate_raw_yaml(yaml_value: &YamlValue, file_path: &str) -> Result<()> {
    let spec_json = serde_json::to_value(yaml_value).map_err(SpecError::Json)?;
    validate_json_value(&spec_json, file_path)
}

fn compiled_schema() -> Result<&'static jsonschema::Validator> {
    if let Some(schema) = COMPILED_SCHEMA.get() {
        return Ok(schema);
    }

    let schema_json: Value = serde_json::from_str(SCHEMA_JSON).map_err(SpecError::Json)?;
    let schema =
        jsonschema::draft7::new(&schema_json).map_err(|e| SpecError::SchemaValidation {
            message: format!("Schema compilation failed: {e}"),
            path: "<schema>".to_string(),
        })?;

    let _ = COMPILED_SCHEMA.set(schema);

    Ok(COMPILED_SCHEMA
        .get()
        .expect("COMPILED_SCHEMA must be set after successful compilation"))
}

fn humanize_validation_error(error: &jsonschema::ValidationError<'_>) -> String {
    use jsonschema::error::ValidationErrorKind;

    let field_path = error.instance_path.to_string();
    let field_label = if field_path.is_empty() || field_path == "/" {
        String::new()
    } else {
        format!(" at {field_path}")
    };

    match &error.kind {
        ValidationErrorKind::Required { property } => {
            format!("missing required field: {}{}", property, field_label)
        }
        ValidationErrorKind::AdditionalProperties { unexpected } => {
            let fields = unexpected.to_vec().join(", ");
            format!("unknown field{}: {}", field_label, fields)
        }
        ValidationErrorKind::Enum { .. } => {
            format!(
                "invalid value{}: {} — check allowed values",
                field_label, error
            )
        }
        ValidationErrorKind::Pattern { .. } => {
            if field_path == "/id" || field_path.ends_with("/id") {
                format!(
                    "invalid id format{}: use \"module/name\" (e.g., \"pricing/apply_tax\")",
                    field_label
                )
            } else {
                format!("invalid format{}: {}", field_label, error)
            }
        }
        _ => {
            if field_path.is_empty() {
                error.to_string()
            } else {
                format!("{} (at {})", error, field_path)
            }
        }
    }
}

fn validate_json_value(spec_json: &Value, file_path: &str) -> Result<()> {
    let schema = compiled_schema()?;

    // Validate against schema
    let validation_result = schema.validate(spec_json);

    match validation_result {
        Ok(()) => Ok(()),
        Err(error) => Err(SpecError::SchemaValidation {
            message: humanize_validation_error(&error),
            path: file_path.to_string(),
        }),
    }
}

/// Perform semantic validation (Rust keywords, deps, etc.)
pub fn validate_semantic(spec: &LoadedSpec) -> Result<()> {
    validate_semantic_with_options(spec, &ValidationOptions::strict())
}

/// Perform semantic validation with explicit options.
pub fn validate_semantic_with_options(
    spec: &LoadedSpec,
    options: &ValidationOptions,
) -> Result<()> {
    // Check if ID contains Rust reserved keywords
    validate_rust_keywords(&spec.spec.id, &spec.source.file_path)?;

    // Reject IDs containing reserved namespace segments. "molecule_tests" is emitted as a
    // generated module/file name by build_namespaces + molecule test generation, so allowing it
    // anywhere in a unit ID can create molecule_tests.rs vs molecule_tests/mod.rs collisions.
    validate_reserved_spec_id_segments(&spec.spec.id, &spec.source.file_path)?;

    let dep_refs = parse_dep_refs(&spec.spec.deps).map_err(|err| invalid_dep_error(err, spec))?;

    // Check dep IDs for Rust reserved keywords (would generate invalid use paths)
    for dep in &dep_refs {
        validate_dep_ref_keywords(dep, &spec.source.file_path)?;
    }

    let owner_callable_name = callable_name(&spec.spec.id);
    if let Some(authored_dep) = spec
        .spec
        .deps
        .iter()
        .zip(dep_refs.iter())
        .find(|(_, dep)| dep.callable_name() == owner_callable_name)
        .map(|(authored_dep, _)| authored_dep)
    {
        return Err(SpecError::DepCollision {
            dep1: authored_dep.clone(),
            dep2: spec.spec.id.clone(),
            fn_name: owner_callable_name.to_string(),
            path: spec.source.file_path.clone(),
        });
    }

    // Check for dep fn_name collisions
    if let Some((dep1, dep2)) = has_dep_ref_collision(&dep_refs) {
        return Err(SpecError::DepCollision {
            dep1: dep1.to_string(),
            dep2: dep2.to_string(),
            fn_name: dep1.callable_name().to_string(),
            path: spec.source.file_path.clone(),
        });
    }

    // Check for use statements in body.rust (line-start to avoid false positives in comments).
    // Also catches visibility-prefixed forms: `pub use`, `pub(crate) use`, etc.
    let has_use_stmt = spec.spec.body.rust.lines().any(|line| {
        let trimmed = line.trim_start();
        // Plain `use` or `use<TAB>`
        if trimmed.starts_with("use ") || trimmed.starts_with("use\t") {
            return true;
        }
        // `pub use ...` / `pub(crate) use ...` / `pub(super) use ...`
        if let Some(rest) = trimmed.strip_prefix("pub") {
            let rest = rest
                .trim_start_matches(|c: char| {
                    c == '(' || c == ')' || c.is_alphanumeric() || c == '_'
                })
                .trim_start();
            if rest.starts_with("use ") || rest.starts_with("use\t") {
                return true;
            }
        }
        false
    });
    if has_use_stmt {
        return Err(SpecError::UseStatementInBody {
            path: spec.source.file_path.clone(),
        });
    }

    validate_body_rust_block(spec)?;
    validate_local_test_expects(spec, options)?;
    validate_contract_input_types(spec)?;

    Ok(())
}

fn validate_reserved_spec_id_segments(id: &str, file_path: &str) -> Result<()> {
    for segment in id.split('/') {
        if segment == "molecule_tests" {
            return Err(SpecError::ReservedUnitName {
                segment: segment.to_string(),
                path: file_path.to_string(),
            });
        }
    }

    Ok(())
}

fn parse_dep_refs(dep_ids: &[String]) -> std::result::Result<Vec<DepRef>, DepRefParseError> {
    dep_ids.iter().map(|dep| DepRef::parse(dep)).collect()
}

fn invalid_dep_error(err: DepRefParseError, spec: &LoadedSpec) -> SpecError {
    SpecError::SemanticValidation {
        message: err.to_string(),
        path: spec.source.file_path.clone(),
    }
}

fn validate_dep_ref_keywords(dep: &DepRef, file_path: &str) -> Result<()> {
    if let Some(alias) = dep.library_alias() {
        validate_keyword_segment(alias, &dep.to_string(), file_path)?;
    }

    for segment in dep.unit_id().split('/') {
        validate_keyword_segment(segment, &dep.to_string(), file_path)?;
    }

    Ok(())
}

fn validate_keyword_segment(segment: &str, authored: &str, file_path: &str) -> Result<()> {
    if crate::types::is_rust_keyword(segment) {
        return Err(SpecError::RustKeyword {
            segment: segment.to_string(),
            id: authored.to_string(),
            path: file_path.to_string(),
        });
    }

    Ok(())
}

fn has_dep_ref_collision(deps: &[DepRef]) -> Option<(&DepRef, &DepRef)> {
    for (i, first) in deps.iter().enumerate() {
        for second in &deps[i + 1..] {
            if first.callable_name() == second.callable_name() {
                return Some((first, second));
            }
        }
    }

    None
}

fn validate_local_test_expects(spec: &LoadedSpec, options: &ValidationOptions) -> Result<()> {
    let path = spec.source.file_path.clone();
    let mut seen_ids = HashSet::new();
    for test in &spec.spec.local_tests {
        if !seen_ids.insert(test.id.as_str()) {
            return Err(SpecError::DuplicateLocalTestId {
                id: test.id.clone(),
                path: path.clone(),
            });
        }

        validate_expect_expr(test.expect.trim(), options.allow_unsafe_local_test_expect).map_err(
            |err| SpecError::LocalTestExpectNotExpr {
                id: test.id.clone(),
                message: err.message(),
                path: path.clone(),
            },
        )?;
    }
    Ok(())
}

fn validate_body_rust_block(spec: &LoadedSpec) -> Result<()> {
    let path = spec.source.file_path.clone();
    syn::parse_str::<syn::Block>(&spec.spec.body.rust).map_err(|_| {
        if syn::parse_str::<syn::ItemFn>(&spec.spec.body.rust).is_ok() {
            SpecError::BodyRustLooksLikeFnDeclaration { path }
        } else {
            SpecError::BodyRustMustBeBlock {
                message: "body.rust must be a Rust block expression starting with `{`".to_string(),
                path,
            }
        }
    })?;
    Ok(())
}

fn validate_contract_input_types(spec: &LoadedSpec) -> Result<()> {
    let path = &spec.source.file_path;
    if let Some(contract) = &spec.spec.contract {
        if let Some(inputs) = &contract.inputs {
            for (name, type_str) in inputs {
                syn::parse_str::<syn::Ident>(name).map_err(|_| {
                    SpecError::ContractInputNameInvalid {
                        name: name.clone(),
                        message: "use a snake_case identifier (e.g. my_param)".to_string(),
                        path: path.clone(),
                    }
                })?;
                syn::parse_str::<syn::Type>(type_str).map_err(|err| {
                    SpecError::ContractTypeInvalid {
                        field: format!("inputs.{name}"),
                        type_str: type_str.clone(),
                        message: err.to_string(),
                        path: path.clone(),
                    }
                })?;
            }
        }
        if let Some(returns) = &contract.returns {
            syn::parse_str::<syn::Type>(returns).map_err(|err| SpecError::ContractTypeInvalid {
                field: "returns".to_string(),
                type_str: returns.clone(),
                message: err.to_string(),
                path: path.clone(),
            })?;
        }
    }
    Ok(())
}

/// Check if any segment of an ID is a Rust reserved keyword
pub fn validate_rust_keywords(id: &str, file_path: &str) -> Result<()> {
    for segment in id.split('/') {
        if crate::types::is_rust_keyword(segment) {
            return Err(SpecError::RustKeyword {
                segment: segment.to_string(),
                id: id.to_string(),
                path: file_path.to_string(),
            });
        }
    }
    Ok(())
}

/// Check for duplicate IDs across all loaded specs.
///
/// Returns all duplicate pairs, not just the first. Each additional file that
/// shares an ID produces a separate error citing the original file as file1.
pub fn validate_no_duplicate_ids(specs: &[LoadedSpec]) -> Vec<SpecError> {
    let scoped_specs: Vec<_> = specs
        .iter()
        .map(QualifiedLoadedSpec::local_identity)
        .collect();
    validate_no_duplicate_qualified_ids(&scoped_specs)
}

pub fn validate_no_duplicate_qualified_ids(specs: &[QualifiedLoadedSpec<'_>]) -> Vec<SpecError> {
    let mut seen: HashMap<QualifiedUnitRef, String> = HashMap::new();
    let mut errors = Vec::new();

    for spec in specs {
        if let Some(existing_file) = seen.get(&spec.qualified_id) {
            errors.push(SpecError::DuplicateId {
                id: spec.qualified_id.to_string(),
                file1: existing_file.clone(),
                file2: spec.loaded.source.file_path.clone(),
            });
        } else {
            seen.insert(
                spec.qualified_id.clone(),
                spec.loaded.source.file_path.clone(),
            );
        }
    }

    errors
}

/// Emit a warning for each spec that lacks a spec_version field.
///
/// Called after per-spec semantic validation; warnings are non-fatal.
pub fn check_spec_versions(specs: &[LoadedSpec]) -> Vec<SpecWarning> {
    specs
        .iter()
        .filter(|s| s.spec.spec_version.is_none())
        .map(|s| SpecWarning::MissingSpecVersion {
            path: s.source.file_path.clone(),
            version: AUTHORED_SPEC_VERSION,
        })
        .collect()
}

/// Validate that all internal deps referenced by loaded specs exist in the same spec set.
///
/// For M2, deps are always strict: any missing dep is an error.
pub fn validate_deps_exist(specs: &[LoadedSpec]) -> (Vec<SpecError>, Vec<SpecWarning>) {
    validate_deps_exist_with_options(specs, &ValidationOptions::strict())
}

pub fn validate_deps_exist_with_options(
    specs: &[LoadedSpec],
    options: &ValidationOptions,
) -> (Vec<SpecError>, Vec<SpecWarning>) {
    let mut errors = Vec::<SpecError>::new();
    let mut scoped_specs = Vec::new();

    for spec in specs {
        match QualifiedLoadedSpec::local(spec) {
            Ok(scoped_spec) => scoped_specs.push(scoped_spec),
            Err(err) => errors.push(invalid_dep_error(err, spec)),
        }
    }

    let (mut dep_errors, warnings) =
        validate_qualified_deps_exist_with_options(&scoped_specs, options);
    errors.append(&mut dep_errors);

    (errors, warnings)
}

pub fn validate_qualified_deps_exist_with_options(
    specs: &[QualifiedLoadedSpec<'_>],
    options: &ValidationOptions,
) -> (Vec<SpecError>, Vec<SpecWarning>) {
    let ids: HashSet<_> = specs.iter().map(|spec| spec.qualified_id.clone()).collect();

    let mut errors = Vec::<SpecError>::new();
    let mut warnings = Vec::<SpecWarning>::new();
    for spec in specs {
        for dep in &spec.qualified_deps {
            if !ids.contains(dep) {
                let err = if dep.library().is_some() {
                    SpecError::CrossLibraryDepNotFound {
                        dep: dep.to_string(),
                        path: spec.loaded.source.file_path.clone(),
                    }
                } else {
                    SpecError::MissingDep {
                        dep: dep.to_string(),
                        path: spec.loaded.source.file_path.clone(),
                    }
                };
                if options.strict_deps {
                    errors.push(err);
                } else {
                    warnings.push(SpecWarning::MissingDep {
                        dep: dep.to_string(),
                        path: spec.loaded.source.file_path.clone(),
                    });
                }
            }
        }
    }

    let cycle_errors = detect_qualified_cycles(specs);
    errors.extend(cycle_errors);

    (errors, warnings)
}

/// DFS helper for cycle detection. Mutates `visited`, `in_stack`, `stack`, and `errors` in place.
fn dfs_qualified_cycle_check<'a>(
    node_id: &QualifiedUnitRef,
    id_map: &HashMap<QualifiedUnitRef, &'a QualifiedLoadedSpec<'a>>,
    visited: &mut HashSet<QualifiedUnitRef>,
    in_stack: &mut HashSet<QualifiedUnitRef>,
    stack: &mut Vec<QualifiedUnitRef>,
    errors: &mut Vec<SpecError>,
) {
    in_stack.insert(node_id.clone());
    stack.push(node_id.clone());

    if let Some(spec) = id_map.get(node_id) {
        for dep in &spec.qualified_deps {
            if !id_map.contains_key(dep) {
                // Missing dep — already reported by validate_deps_exist; skip during DFS
                continue;
            }
            if in_stack.contains(dep) {
                // Cycle found — reconstruct path from the point where dep appears on the stack
                let cycle_start = stack
                    .iter()
                    .position(|n| n == dep)
                    .expect("dep in in_stack must be in stack");
                let mut qualified_cycle_path = stack[cycle_start..].to_vec();
                qualified_cycle_path.push(dep.clone());
                let cycle_path: Vec<String> = qualified_cycle_path
                    .iter()
                    .map(QualifiedUnitRef::authored)
                    .collect();
                let distinct_libraries: HashSet<Option<&str>> = qualified_cycle_path
                    .iter()
                    .map(|unit| unit.library())
                    .collect();
                let path = spec.loaded.source.file_path.clone();

                if distinct_libraries.len() > 1 {
                    errors.push(SpecError::CrossLibraryCycle { cycle_path, path });
                } else {
                    errors.push(SpecError::CyclicDep { cycle_path, path });
                }
            } else if !visited.contains(dep) {
                dfs_qualified_cycle_check(dep, id_map, visited, in_stack, stack, errors);
            }
        }
    }

    stack.pop();
    in_stack.remove(node_id);
    visited.insert(node_id.clone());
}

/// Detect cycles in the dependency graph using depth-first search.
///
/// Cycles are always errors regardless of ValidationOptions — a cycle causes
/// infinite recursion during graph resolution.
///
/// NOTE: Cycle detection is in-tree only. Deps that reference units outside this
/// spec set (cross-library) are skipped during DFS — they are not in id_map.
/// The cross-library dep schema is locked in DECISIONS.md as namespace-prefixed
/// ids (for example `shared::money/round`), but cross-library cycle detection is
/// still deferred until M5 implements cross-library loading and validation.
pub fn detect_cycles(specs: &[LoadedSpec]) -> Vec<SpecError> {
    let mut errors = Vec::new();
    let mut scoped_specs = Vec::new();

    for spec in specs {
        match QualifiedLoadedSpec::local(spec) {
            Ok(scoped_spec) => scoped_specs.push(scoped_spec),
            Err(err) => errors.push(invalid_dep_error(err, spec)),
        }
    }

    errors.extend(detect_qualified_cycles(&scoped_specs));
    errors
}

pub fn detect_qualified_cycles(specs: &[QualifiedLoadedSpec<'_>]) -> Vec<SpecError> {
    let id_map: HashMap<QualifiedUnitRef, &QualifiedLoadedSpec<'_>> = specs
        .iter()
        .map(|spec| (spec.qualified_id.clone(), spec))
        .collect();

    let mut visited = HashSet::<QualifiedUnitRef>::new();
    let mut errors = Vec::<SpecError>::new();

    for spec in specs {
        if !visited.contains(&spec.qualified_id) {
            let mut in_stack = HashSet::<QualifiedUnitRef>::new();
            let mut stack = Vec::<QualifiedUnitRef>::new();
            dfs_qualified_cycle_check(
                &spec.qualified_id,
                &id_map,
                &mut visited,
                &mut in_stack,
                &mut stack,
                &mut errors,
            );
        }
    }

    errors
}

/// Full validation (schema + semantic)
pub fn validate_full(spec: &LoadedSpec) -> Result<()> {
    validate_full_with_options(spec, &ValidationOptions::strict())
}

/// Full validation (schema + semantic) with explicit options.
pub fn validate_full_with_options(spec: &LoadedSpec, options: &ValidationOptions) -> Result<()> {
    validate_schema(spec)?;
    validate_semantic_with_options(spec, options)?;
    Ok(())
}

fn compiled_test_schema() -> Result<&'static jsonschema::Validator> {
    if let Some(schema) = COMPILED_TEST_SCHEMA.get() {
        return Ok(schema);
    }

    let schema_json: Value = serde_json::from_str(TEST_SCHEMA_JSON).map_err(SpecError::Json)?;
    let schema =
        jsonschema::draft7::new(&schema_json).map_err(|e| SpecError::SchemaValidation {
            message: format!("Test schema compilation failed: {e}"),
            path: "<test.spec.json schema>".to_string(),
        })?;

    let _ = COMPILED_TEST_SCHEMA.set(schema);

    Ok(COMPILED_TEST_SCHEMA
        .get()
        .expect("COMPILED_TEST_SCHEMA must be set after successful compilation"))
}

/// Validate a raw YAML-authored molecule test value against the test.spec JSON Schema.
///
/// Used by the loader before deserialization so unknown fields and authoring mistakes
/// are rejected before serde can apply defaults or drop data.
pub fn validate_raw_molecule_test_yaml(yaml_value: &YamlValue, file_path: &str) -> Result<()> {
    let spec_json = serde_json::to_value(yaml_value).map_err(SpecError::Json)?;
    let schema = compiled_test_schema()?;

    let validation_result = schema.validate(&spec_json);
    match validation_result {
        Ok(()) => Ok(()),
        Err(error) => Err(SpecError::SchemaValidation {
            message: humanize_validation_error(&error),
            path: file_path.to_string(),
        }),
    }
}

/// Perform per-test semantic validation on a loaded molecule test.
///
/// Checks:
/// 1. body.rust must parse as syn::Block
/// 2. body.rust must not contain `unsafe` blocks
/// 3. id segments must not be Rust reserved keywords
/// 4. id segments must not use reserved generated namespace names
pub fn validate_molecule_test_semantic(test: &LoadedMoleculeTest) -> Result<()> {
    syn::parse_str::<syn::Block>(&test.test.body.rust).map_err(|e| {
        SpecError::MoleculeBodyRustMustBeBlock {
            message: e.to_string(),
            test_path: test.source.file_path.clone(),
        }
    })?;

    let tokens = test
        .test
        .body
        .rust
        .parse::<proc_macro2::TokenStream>()
        .map_err(|e| SpecError::MoleculeBodyRustMustBeBlock {
            message: e.to_string(),
            test_path: test.source.file_path.clone(),
        })?;

    if token_stream_contains_unsafe_keyword(&tokens) {
        return Err(SpecError::MoleculeBodyContainsUnsafe {
            test_path: test.source.file_path.clone(),
        });
    }

    validate_rust_keywords(&test.test.id, &test.source.file_path)?;
    validate_reserved_spec_id_segments(&test.test.id, &test.source.file_path)?;

    Ok(())
}

/// Validate that all cover_ids reference real unit IDs in the loaded spec set.
///
/// Returns errors for each missing cover and a warning if covers is empty.
pub fn validate_molecule_test_covers(
    test: &LoadedMoleculeTest,
    unit_ids: &HashSet<&str>,
) -> (Vec<SpecError>, Vec<SpecWarning>) {
    let mut errors = Vec::new();
    let mut warnings = Vec::new();
    let mut existing_covers = Vec::new();

    if test.test.covers.is_empty() {
        warnings.push(SpecWarning::MoleculeTestNoCoveredUnits {
            test_id: test.test.id.clone(),
            test_path: test.source.file_path.clone(),
        });
    }

    for cover_id in &test.test.covers {
        let dep_ref = match DepRef::parse(cover_id) {
            Ok(dep_ref) => dep_ref,
            Err(err) => {
                errors.push(SpecError::SchemaValidation {
                    message: err.to_string(),
                    path: test.source.file_path.clone(),
                });
                continue;
            }
        };

        if dep_ref.library_alias().is_some() {
            errors.push(SpecError::CrossLibraryMoleculeCoverUnsupported {
                cover_id: cover_id.clone(),
                test_id: test.test.id.clone(),
                test_path: test.source.file_path.clone(),
            });
        } else if !unit_ids.contains(dep_ref.unit_id()) {
            errors.push(SpecError::MoleculeCoversNotFound {
                cover_id: cover_id.clone(),
                test_id: test.test.id.clone(),
                test_path: test.source.file_path.clone(),
            });
        } else {
            existing_covers.push(cover_id.clone());
        }
    }

    if let Some((cover1, cover2, fn_name)) = has_callable_collision(&existing_covers) {
        errors.push(SpecError::MoleculeCoversCollision {
            cover1: cover1.clone(),
            cover2: cover2.clone(),
            fn_name: fn_name.to_string(),
            test_id: test.test.id.clone(),
            test_path: test.source.file_path.clone(),
        });
    }

    (errors, warnings)
}

/// Check for duplicate IDs across all loaded molecule tests.
///
/// Returns all duplicate pairs. Each additional file that shares an ID produces
/// a separate error citing the first file as file1.
pub fn validate_no_duplicate_molecule_test_ids(tests: &[LoadedMoleculeTest]) -> Vec<SpecError> {
    let mut seen: HashMap<String, String> = HashMap::new();
    let mut errors = Vec::new();

    for test in tests {
        if let Some(existing_file) = seen.get(&test.test.id) {
            errors.push(SpecError::DuplicateMoleculeTestId {
                id: test.test.id.clone(),
                file1: existing_file.clone(),
                file2: test.source.file_path.clone(),
            });
        } else {
            seen.insert(test.test.id.clone(), test.source.file_path.clone());
        }
    }

    errors
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::types::{
        Body, Contract, Intent, LocalTest, MoleculeTestSource, MoleculeTestStruct,
        QualifiedUnitRef, SpecSource, SpecStruct,
    };

    fn create_test_spec(id: &str, rust_body: &str) -> LoadedSpec {
        LoadedSpec {
            source: SpecSource {
                file_path: format!("test/{}.unit.spec", id),
                id: id.to_string(),
            },
            spec: SpecStruct {
                id: id.to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: format!("Test spec for {}", id),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: rust_body.to_string(),
                },
                local_tests: vec![],
                links: None,
                spec_version: None,
            },
        }
    }

    fn create_molecule_test_spec(id: &str, covers: Vec<&str>) -> LoadedMoleculeTest {
        LoadedMoleculeTest {
            source: MoleculeTestSource {
                file_path: format!("test/{}.test.spec", id),
                id: id.to_string(),
            },
            test: MoleculeTestStruct {
                id: id.to_string(),
                intent: Intent {
                    why: format!("Test molecule spec for {}", id),
                },
                covers: covers.into_iter().map(str::to_string).collect(),
                body: Body {
                    rust: "{ assert!(true); }".to_string(),
                },
                spec_version: None,
            },
        }
    }

    #[test]
    fn test_validate_schema_valid() {
        let spec = create_test_spec("pricing/apply_discount", "{ }");
        let result = validate_schema(&spec);
        assert!(
            result.is_ok(),
            "Schema validation should pass: {:?}",
            result
        );
    }

    #[test]
    fn test_validate_schema_valid_spec_passes() {
        let spec = create_test_spec("pricing/apply_discount", "{ }");
        let result = validate_schema(&spec);
        assert!(
            result.is_ok(),
            "A complete valid spec should pass schema validation: {:?}",
            result
        );
    }

    #[test]
    fn test_validate_raw_yaml_rejects_unknown_fields() {
        let yaml = r#"
id: pricing/apply_discount
kind: function
intent:
  why: Apply a percentage discount.
body:
  rust: |
    pub fn apply_discount() {}
extra_field: should_fail
"#;
        let value: YamlValue = serde_yaml_bw::from_str(yaml).unwrap();

        let result = validate_raw_yaml(&value, "test.unit.spec");
        assert!(result.is_err());
        let err = result.unwrap_err().to_string();
        assert!(err.contains("Schema validation failed"));
        assert!(err.contains("unknown field"));
    }

    #[test]
    fn imports_field_validates_rust_path() {
        let valid = r#"
id: pricing/apply_discount
kind: function
intent:
  why: Apply a percentage discount.
imports:
  - rust_decimal::Decimal
  - std::collections::HashMap
body:
  rust: |
    pub fn apply_discount() {}
"#;
        let value: YamlValue = serde_yaml_bw::from_str(valid).unwrap();
        let result = validate_raw_yaml(&value, "test.unit.spec");
        assert!(
            result.is_ok(),
            "Expected valid imports to pass: {:?}",
            result
        );

        let invalid_bare = r#"
id: pricing/apply_discount
kind: function
intent:
  why: Apply a percentage discount.
imports:
  - Decimal
body:
  rust: |
    pub fn apply_discount() {}
"#;
        let value: YamlValue = serde_yaml_bw::from_str(invalid_bare).unwrap();
        let result = validate_raw_yaml(&value, "test.unit.spec");
        assert!(result.is_err(), "Expected bare import to fail");

        let invalid_leading = r#"
id: pricing/apply_discount
kind: function
intent:
  why: Apply a percentage discount.
imports:
  - ::Decimal
body:
  rust: |
    pub fn apply_discount() {}
"#;
        let value: YamlValue = serde_yaml_bw::from_str(invalid_leading).unwrap();
        let result = validate_raw_yaml(&value, "test.unit.spec");
        assert!(result.is_err(), "Expected leading :: import to fail");
    }

    #[test]
    fn validate_local_test_id_must_be_valid_identifier() {
        let invalid = r#"
id: pricing/apply_discount
kind: function
intent:
  why: Apply a discount.
body:
  rust: |
    pub fn apply_discount() {}
local_tests:
  - id: some case!
    expect: "true"
"#;
        let value: YamlValue = serde_yaml_bw::from_str(invalid).unwrap();
        let result = validate_raw_yaml(&value, "test.unit.spec");
        assert!(result.is_err(), "Expected invalid local_tests id to fail");

        let valid = r#"
id: pricing/apply_discount
kind: function
intent:
  why: Apply a discount.
body:
  rust: |
    pub fn apply_discount() {}
local_tests:
  - id: happy_path
    expect: "true"
"#;
        let value: YamlValue = serde_yaml_bw::from_str(valid).unwrap();
        let result = validate_raw_yaml(&value, "test.unit.spec");
        assert!(result.is_ok(), "Expected valid local_tests id to pass");
    }

    #[test]
    fn test_validate_rust_keywords_in_id() {
        let result = validate_rust_keywords("pricing/type", "test.unit.spec");
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(err.to_string().contains("Rust reserved keyword"));
        assert!(err.to_string().contains("type"));
    }

    #[test]
    fn test_validate_valid_id() {
        let result = validate_rust_keywords("pricing/apply_discount", "test.unit.spec");
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_no_duplicate_ids() {
        let specs = vec![
            create_test_spec("pricing/apply_discount", "{ }"),
            create_test_spec("utils/round", "{ }"),
        ];

        let errors = validate_no_duplicate_ids(&specs);
        assert!(errors.is_empty());
    }

    #[test]
    fn test_validate_duplicate_ids() {
        let specs = vec![
            create_test_spec("pricing/apply_discount", "{ }"),
            create_test_spec("pricing/apply_discount", "{ }"),
        ];

        let errors = validate_no_duplicate_ids(&specs);
        assert_eq!(errors.len(), 1);
        assert!(errors[0].to_string().contains("Duplicate ID"));
        assert!(errors[0].to_string().contains("pricing/apply_discount"));
    }

    #[test]
    fn test_validate_duplicate_ids_all_reported() {
        let specs = vec![
            create_test_spec("pricing/apply_discount", "{ }"),
            create_test_spec("pricing/apply_discount", "{ }"),
            create_test_spec("pricing/apply_discount", "{ }"),
        ];

        let errors = validate_no_duplicate_ids(&specs);
        assert_eq!(errors.len(), 2, "all duplicate pairs should be reported");
    }

    #[test]
    fn test_validate_dep_collision() {
        let mut spec = create_test_spec("pricing/calculate_total", "{ round(1.5) }");
        spec.spec.deps = vec!["money/round".to_string(), "utils/round".to_string()];

        let result = validate_semantic(&spec);
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(err.to_string().contains("collision"));
        assert!(err.to_string().contains("round"));
    }

    #[test]
    fn test_validate_dep_collision_with_unit_callable_name() {
        let mut spec = create_test_spec("money/round", "{ round(value) }");
        spec.spec.deps = vec!["shared::money/round".to_string()];

        let err = validate_semantic(&spec).unwrap_err();
        match err {
            SpecError::DepCollision {
                dep1,
                dep2,
                fn_name,
                path,
            } => {
                assert_eq!(dep1, "shared::money/round");
                assert_eq!(dep2, "money/round");
                assert_eq!(fn_name, "round");
                assert_eq!(path, "test/money/round.unit.spec");
            }
            other => panic!("expected DepCollision, got {other:?}"),
        }
    }

    #[test]
    fn test_validate_semantic_accepts_external_dep_syntax() {
        let mut spec = create_test_spec("pricing/calculate_total", "{ round(1.5) }");
        spec.spec.deps = vec!["shared::money/round".to_string()];

        let result = validate_semantic(&spec);
        assert!(result.is_ok(), "{result:?}");
    }

    #[test]
    fn test_validate_semantic_rejects_invalid_external_dep_syntax() {
        let mut spec = create_test_spec("pricing/calculate_total", "{ round(1.5) }");
        spec.spec.deps = vec!["shared::Money/round".to_string()];

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(err.contains("invalid segment 'Money'"), "{err}");
    }

    #[test]
    fn test_validate_molecule_test_covers_collision() {
        let molecule_test =
            create_molecule_test_spec("pricing/rounding_flow", vec!["money/round", "utils/round"]);
        let unit_ids: HashSet<&str> = ["money/round", "utils/round"].into_iter().collect();

        let (errors, warnings) = validate_molecule_test_covers(&molecule_test, &unit_ids);

        assert!(warnings.is_empty());
        assert_eq!(errors.len(), 1);
        match &errors[0] {
            SpecError::MoleculeCoversCollision {
                cover1,
                cover2,
                fn_name,
                test_id,
                test_path,
            } => {
                assert_eq!(cover1, "money/round");
                assert_eq!(cover2, "utils/round");
                assert_eq!(fn_name, "round");
                assert_eq!(test_id, "pricing/rounding_flow");
                assert_eq!(test_path, "test/pricing/rounding_flow.test.spec");
            }
            other => panic!("expected MoleculeCoversCollision, got {other:?}"),
        }
    }

    #[test]
    fn test_validate_molecule_test_covers_rejects_cross_library_cover_without_collision() {
        let molecule_test = create_molecule_test_spec(
            "pricing/rounding_flow",
            vec!["shared::money/round", "money/round"],
        );
        let unit_ids: HashSet<&str> = ["money/round"].into_iter().collect();

        let (errors, warnings) = validate_molecule_test_covers(&molecule_test, &unit_ids);

        assert!(warnings.is_empty());
        assert_eq!(errors.len(), 1);
        match &errors[0] {
            SpecError::CrossLibraryMoleculeCoverUnsupported {
                cover_id,
                test_id,
                test_path,
            } => {
                assert_eq!(cover_id, "shared::money/round");
                assert_eq!(test_id, "pricing/rounding_flow");
                assert_eq!(test_path, "test/pricing/rounding_flow.test.spec");
            }
            other => panic!("expected CrossLibraryMoleculeCoverUnsupported, got {other:?}"),
        }
    }

    #[test]
    fn test_validate_use_statement_in_body() {
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "test/test".to_string(),
            },
            spec: SpecStruct {
                id: "test/test".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Test spec".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "use std::collections::HashMap; pub fn test() {}".to_string(),
                },
                local_tests: vec![],
                links: None,
                spec_version: None,
            },
        };

        let result = validate_semantic(&spec);
        assert!(result.is_err());
        let err = result.unwrap_err();
        assert!(
            err.to_string()
                .contains("body.rust must not contain use statements")
        );
    }

    #[test]
    fn test_validate_qualified_duplicate_ids_allow_same_unit_in_different_libraries() {
        let local = create_test_spec("money/round", "{ }");
        let shared = create_test_spec("money/round", "{ }");

        let scoped_specs = vec![
            QualifiedLoadedSpec {
                loaded: &local,
                qualified_id: QualifiedUnitRef::new(Some("root".to_string()), "money/round"),
                qualified_deps: Vec::new(),
            },
            QualifiedLoadedSpec {
                loaded: &shared,
                qualified_id: QualifiedUnitRef::new(Some("shared".to_string()), "money/round"),
                qualified_deps: Vec::new(),
            },
        ];

        let errors = validate_no_duplicate_qualified_ids(&scoped_specs);
        assert!(errors.is_empty(), "{errors:?}");
    }

    #[test]
    fn test_validate_deps_exist_local_wrapper_still_passes_local_graph() {
        let mut calculate_total = create_test_spec("pricing/calculate_total", "{ round(1.5) }");
        calculate_total.spec.deps = vec!["money/round".to_string()];
        let round = create_test_spec("money/round", "{ amount }");

        let (errors, warnings) = validate_deps_exist(&[calculate_total, round]);
        assert!(errors.is_empty(), "{errors:?}");
        assert!(warnings.is_empty(), "{warnings:?}");
    }

    #[test]
    fn test_validate_deps_exist_local_wrapper_reports_external_missing_dep() {
        let mut calculate_total = create_test_spec("pricing/calculate_total", "{ round(1.5) }");
        calculate_total.spec.deps = vec!["shared::money/round".to_string()];

        let (errors, warnings) = validate_deps_exist(&[calculate_total]);
        assert_eq!(errors.len(), 1, "{errors:?}");
        assert!(warnings.is_empty(), "{warnings:?}");
        match &errors[0] {
            SpecError::CrossLibraryDepNotFound { dep, .. } => {
                assert_eq!(dep, "shared::money/round")
            }
            other => panic!("expected CrossLibraryDepNotFound, got {other:?}"),
        }
    }

    #[test]
    fn test_validate_semantic_valid_spec() {
        let spec = create_test_spec("pricing/apply_discount", "{ subtotal - subtotal * rate }");
        let result = validate_semantic(&spec);
        assert!(result.is_ok());
    }

    #[test]
    fn test_validate_full() {
        let spec = create_test_spec("utils/round", "{ x.floor() }");
        let result = validate_full(&spec);
        assert!(result.is_ok(), "Full validation should pass: {:?}", result);
    }

    #[test]
    fn test_validate_use_statement_pub_use_in_body() {
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "test/func".to_string(),
            },
            spec: SpecStruct {
                id: "test/func".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Test pub use".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "pub use std::collections::HashMap;\npub fn func() {}".to_string(),
                },
                local_tests: vec![],
                links: None,
                spec_version: None,
            },
        };
        let result = validate_semantic(&spec);
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("body.rust must not contain use statements")
        );
    }

    #[test]
    fn test_validate_rust_keyword_try_in_id() {
        // `try` is reserved since Rust 2018 and was previously missing from the list
        let result = validate_rust_keywords("pricing/try", "test.unit.spec");
        assert!(result.is_err());
        assert!(
            result
                .unwrap_err()
                .to_string()
                .contains("Rust reserved keyword")
        );
    }

    #[test]
    fn validate_body_fn_declaration_emits_migration_error() {
        let spec = create_test_spec("pricing/apply_discount", "pub fn apply_discount() {}");
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("looks like a full function declaration"),
            "{err}"
        );
    }

    #[test]
    fn validate_body_fn_declaration_with_args_emits_migration_error() {
        let spec = create_test_spec(
            "pricing/apply_discount",
            "pub fn apply_discount(subtotal: Decimal, rate: Decimal) -> Decimal { subtotal }",
        );
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("looks like a full function declaration"),
            "{err}"
        );
    }

    #[test]
    fn validate_body_rust_block_valid() {
        let spec = create_test_spec("pricing/apply_discount", "{ subtotal - subtotal * rate }");
        let result = validate_semantic(&spec);
        assert!(result.is_ok(), "{result:?}");
    }

    #[test]
    fn validate_body_rust_invalid_block() {
        let spec = create_test_spec("pricing/apply_discount", "not valid rust at all !!!");
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("body.rust must be a Rust block expression"),
            "{err}"
        );
    }

    #[test]
    fn validate_body_with_macros_in_block_passes() {
        let spec = create_test_spec(
            "pricing/apply_discount",
            r#"{
    let _v = vec![1, 2, 3];
    assert!(true);
    todo!()
}"#,
        );
        let result = validate_semantic(&spec);
        assert!(result.is_ok(), "{result:?}");
    }

    #[test]
    fn validate_local_test_expect_rejects_non_expression() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "injection_attempt".to_string(),
                    expect: "true); } } mod evil { fn steal() {}".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("not a valid Rust expression"),
            "expected injection to be rejected: {err}"
        );
    }

    #[test]
    fn validate_local_test_expect_accepts_valid_expression() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "happy_path".to_string(),
                    expect: "apply_discount() == true".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };
        assert!(validate_semantic(&spec).is_ok());
    }

    #[test]
    fn validate_local_test_expect_allows_block_expr_when_configured() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "block_allowed".to_string(),
                    expect: "{ let ok = apply_discount(); ok }".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };

        let options = ValidationOptions {
            strict_deps: true,
            allow_unsafe_local_test_expect: true,
        };
        assert!(validate_semantic_with_options(&spec, &options).is_ok());
    }

    #[test]
    fn validate_local_test_duplicate_ids_are_rejected() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![
                    LocalTest {
                        id: "happy_path".to_string(),
                        expect: "apply_discount()".to_string(),
                    },
                    LocalTest {
                        id: "happy_path".to_string(),
                        expect: "apply_discount()".to_string(),
                    },
                ],
                links: None,
                spec_version: None,
            },
        };

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("duplicate local_tests id 'happy_path'"),
            "{err}"
        );
    }

    #[test]
    fn validate_local_test_expect_rejects_block_expression() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "block_attempt".to_string(),
                    expect: "{ std::process::exit(1); true }".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("block, unsafe, closure"),
            "expected block expression to be rejected: {err}"
        );
    }

    #[test]
    fn expect_with_unsafe_block_in_call_arg_is_rejected() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "unsafe_in_call_arg".to_string(),
                    expect: "f(unsafe { true })".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("block, unsafe, closure"),
            "expected unsafe block in call arg to be rejected: {err}"
        );
    }

    #[test]
    fn expect_with_block_in_binary_operand_is_rejected() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "block_in_binary_operand".to_string(),
                    expect: "true && { false }".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("block, unsafe, closure"),
            "expected block expression in binary operand to be rejected: {err}"
        );
    }

    #[test]
    fn expect_with_unsafe_block_in_method_call_arg_is_rejected() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "unsafe_in_method_arg".to_string(),
                    expect: "foo.bar(unsafe { true })".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("block, unsafe, closure"),
            "expected unsafe block in method call arg to be rejected: {err}"
        );
    }

    #[test]
    fn expect_with_unsafe_block_in_field_base_is_rejected() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "unsafe_in_field_base".to_string(),
                    expect: "(unsafe { foo }).field".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("block, unsafe, closure"),
            "expected unsafe block in field base to be rejected: {err}"
        );
    }

    #[test]
    fn expect_with_unsafe_block_in_index_is_rejected() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "unsafe_in_index".to_string(),
                    expect: "arr[unsafe { 0 }]".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("block, unsafe, closure"),
            "expected unsafe block in index to be rejected: {err}"
        );
    }

    #[test]
    fn expect_with_unsafe_block_in_unary_is_rejected() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "unsafe_in_unary".to_string(),
                    expect: "!(unsafe { true })".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("block, unsafe, closure"),
            "expected unsafe block in unary operand to be rejected: {err}"
        );
    }

    #[test]
    fn expect_with_unsafe_block_in_cast_is_rejected() {
        use crate::types::{Body, Intent, LocalTest, SpecSource, SpecStruct};
        let spec = LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "unsafe_in_cast".to_string(),
                    expect: "(unsafe { 0 }) as u64".to_string(),
                }],
                links: None,
                spec_version: None,
            },
        };

        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("block, unsafe, closure"),
            "expected unsafe block in cast to be rejected: {err}"
        );
    }

    // --- contract.inputs type validation ---

    fn make_spec_with_contract(
        inputs: Option<indexmap::IndexMap<String, String>>,
        returns: Option<&str>,
    ) -> LoadedSpec {
        LoadedSpec {
            source: SpecSource {
                file_path: "test/pricing/apply_tax.unit.spec".to_string(),
                id: "pricing/apply_tax".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_tax".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply tax.".to_string(),
                },
                contract: Some(Contract {
                    inputs,
                    returns: returns.map(String::from),
                    invariants: vec![],
                }),
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ () }".to_string(),
                },
                local_tests: vec![],
                links: None,
                spec_version: None,
            },
        }
    }

    #[test]
    fn contract_type_validation_passes_for_valid_types() {
        let mut inputs = indexmap::IndexMap::new();
        inputs.insert("subtotal".to_string(), "Decimal".to_string());
        inputs.insert("rate".to_string(), "Decimal".to_string());
        let spec = make_spec_with_contract(Some(inputs), Some("Decimal"));
        assert!(validate_semantic(&spec).is_ok());
    }

    #[test]
    fn contract_type_validation_passes_with_no_contract() {
        let spec = create_test_spec("money/round", "{ () }");
        assert!(validate_semantic(&spec).is_ok());
    }

    #[test]
    fn contract_type_validation_rejects_invalid_input_type() {
        let mut inputs = indexmap::IndexMap::new();
        inputs.insert("amount".to_string(), "Strinng".to_string());
        // "Strinng" is a valid identifier so syn parses it fine as a Type::Path.
        // Use something syntactically invalid to verify the error path:
        inputs.insert("rate".to_string(), "Vec<".to_string());
        let spec = make_spec_with_contract(Some(inputs), Some("Decimal"));
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("contract.inputs.rate") && err.contains("invalid Rust type"),
            "expected ContractTypeInvalid for inputs.rate: {err}"
        );
    }

    #[test]
    fn contract_type_validation_rejects_invalid_return_type() {
        let mut inputs = indexmap::IndexMap::new();
        inputs.insert("subtotal".to_string(), "Decimal".to_string());
        let spec = make_spec_with_contract(Some(inputs), Some("Vec<"));
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("contract.returns") && err.contains("invalid Rust type"),
            "expected ContractTypeInvalid for returns: {err}"
        );
    }

    #[test]
    fn contract_type_validation_rejects_keyword_input_name() {
        let mut inputs = indexmap::IndexMap::new();
        inputs.insert("type".to_string(), "Decimal".to_string());
        let spec = make_spec_with_contract(Some(inputs), None);
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("'type'") && err.contains("not a valid Rust identifier"),
            "expected ContractInputNameInvalid for keyword key: {err}"
        );
    }

    #[test]
    fn contract_type_validation_rejects_hyphenated_input_name() {
        let mut inputs = indexmap::IndexMap::new();
        inputs.insert("bad-name".to_string(), "Decimal".to_string());
        let spec = make_spec_with_contract(Some(inputs), None);
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("'bad-name'") && err.contains("snake_case"),
            "expected ContractInputNameInvalid for hyphenated key: {err}"
        );
    }

    // --- cycle detection ---

    #[test]
    fn test_detect_cycles_no_cycle() {
        // A → B → C (no cycle)
        let mut a = create_test_spec("a/foo", "{ }");
        a.spec.deps = vec!["b/bar".to_string()];
        let mut b = create_test_spec("b/bar", "{ }");
        b.spec.deps = vec!["c/baz".to_string()];
        let c = create_test_spec("c/baz", "{ }");
        let errors = detect_cycles(&[a, b, c]);
        assert!(errors.is_empty(), "expected no cycle errors: {:?}", errors);
    }

    #[test]
    fn test_detect_cycles_simple_cycle() {
        // A → B → A
        let mut a = create_test_spec("a/foo", "{ }");
        a.spec.deps = vec!["b/bar".to_string()];
        let mut b = create_test_spec("b/bar", "{ }");
        b.spec.deps = vec!["a/foo".to_string()];
        let errors = detect_cycles(&[a, b]);
        assert_eq!(
            errors.len(),
            1,
            "expected exactly one cycle error: {:?}",
            errors
        );
        match &errors[0] {
            SpecError::CyclicDep { cycle_path, .. } => {
                assert_eq!(cycle_path, &["a/foo", "b/bar", "a/foo"]);
            }
            other => panic!("expected CyclicDep, got {:?}", other),
        }
    }

    #[test]
    fn test_detect_cycles_self_loop() {
        // A → A
        let mut a = create_test_spec("a/foo", "{ }");
        a.spec.deps = vec!["a/foo".to_string()];
        let errors = detect_cycles(&[a]);
        assert_eq!(
            errors.len(),
            1,
            "expected exactly one cycle error: {:?}",
            errors
        );
        match &errors[0] {
            SpecError::CyclicDep { cycle_path, .. } => {
                assert_eq!(cycle_path, &["a/foo", "a/foo"]);
            }
            other => panic!("expected CyclicDep, got {:?}", other),
        }
    }

    #[test]
    fn test_detect_cycles_longer_cycle() {
        // A → B → C → A
        let mut a = create_test_spec("a/foo", "{ }");
        a.spec.deps = vec!["b/bar".to_string()];
        let mut b = create_test_spec("b/bar", "{ }");
        b.spec.deps = vec!["c/baz".to_string()];
        let mut c = create_test_spec("c/baz", "{ }");
        c.spec.deps = vec!["a/foo".to_string()];
        let errors = detect_cycles(&[a, b, c]);
        assert_eq!(
            errors.len(),
            1,
            "expected exactly one cycle error: {:?}",
            errors
        );
        match &errors[0] {
            SpecError::CyclicDep { cycle_path, .. } => {
                assert_eq!(cycle_path, &["a/foo", "b/bar", "c/baz", "a/foo"]);
            }
            other => panic!("expected CyclicDep, got {:?}", other),
        }
    }

    #[test]
    fn test_detect_cycles_missing_dep_skipped() {
        // A deps B but B is not in the set — no cycle, just a missing dep
        let mut a = create_test_spec("a/foo", "{ }");
        a.spec.deps = vec!["b/bar".to_string()];
        let errors = detect_cycles(&[a]);
        assert!(
            errors.is_empty(),
            "expected no cycle errors for missing dep: {:?}",
            errors
        );
    }

    #[test]
    fn test_detect_cycles_multiple_cycles() {
        // (A → B → A) and (C → D → C)
        let mut a = create_test_spec("a/foo", "{ }");
        a.spec.deps = vec!["b/bar".to_string()];
        let mut b = create_test_spec("b/bar", "{ }");
        b.spec.deps = vec!["a/foo".to_string()];
        let mut c = create_test_spec("c/baz", "{ }");
        c.spec.deps = vec!["d/qux".to_string()];
        let mut d = create_test_spec("d/qux", "{ }");
        d.spec.deps = vec!["c/baz".to_string()];
        let errors = detect_cycles(&[a, b, c, d]);
        assert_eq!(errors.len(), 2, "expected two cycle errors: {:?}", errors);
        for err in &errors {
            assert!(
                matches!(err, SpecError::CyclicDep { .. }),
                "expected CyclicDep, got {:?}",
                err
            );
        }
    }

    #[test]
    fn test_detect_cycles_error_message_format() {
        let mut a = create_test_spec("money/round", "{ }");
        a.spec.deps = vec!["currency/convert".to_string()];
        let mut b = create_test_spec("currency/convert", "{ }");
        b.spec.deps = vec!["money/round".to_string()];
        let errors = detect_cycles(&[a, b]);
        assert_eq!(errors.len(), 1);
        assert!(
            errors[0].to_string().contains("cycle detected"),
            "{}",
            errors[0]
        );
        assert!(
            errors[0].to_string().contains("money/round"),
            "{}",
            errors[0]
        );
        assert!(
            errors[0].to_string().contains("currency/convert"),
            "{}",
            errors[0]
        );
    }

    #[test]
    fn test_detect_qualified_cycles_supports_cross_library_shapes() {
        let local = create_test_spec("pricing/calculate_total", "{ }");
        let shared = create_test_spec("money/round", "{ }");
        let scoped_specs = vec![
            QualifiedLoadedSpec {
                loaded: &local,
                qualified_id: QualifiedUnitRef::new(
                    Some("root".to_string()),
                    "pricing/calculate_total",
                ),
                qualified_deps: vec![QualifiedUnitRef::new(
                    Some("shared".to_string()),
                    "money/round",
                )],
            },
            QualifiedLoadedSpec {
                loaded: &shared,
                qualified_id: QualifiedUnitRef::new(Some("shared".to_string()), "money/round"),
                qualified_deps: vec![QualifiedUnitRef::new(
                    Some("root".to_string()),
                    "pricing/calculate_total",
                )],
            },
        ];

        let errors = detect_qualified_cycles(&scoped_specs);
        assert_eq!(errors.len(), 1, "{errors:?}");
        match &errors[0] {
            SpecError::CrossLibraryCycle { cycle_path, .. } => {
                assert_eq!(
                    cycle_path,
                    &[
                        "root::pricing/calculate_total".to_string(),
                        "shared::money/round".to_string(),
                        "root::pricing/calculate_total".to_string(),
                    ]
                );
            }
            other => panic!("expected CrossLibraryCycle, got {other:?}"),
        }
    }

    #[test]
    fn test_detect_qualified_cycles_classifies_root_and_imported_cycle_as_cross_library() {
        let local = create_test_spec("pricing/apply_discount", "{ }");
        let shared = create_test_spec("money/round", "{ }");
        let scoped_specs = vec![
            QualifiedLoadedSpec {
                loaded: &local,
                qualified_id: QualifiedUnitRef::local("pricing/apply_discount"),
                qualified_deps: vec![QualifiedUnitRef::new(
                    Some("shared".to_string()),
                    "money/round",
                )],
            },
            QualifiedLoadedSpec {
                loaded: &shared,
                qualified_id: QualifiedUnitRef::new(Some("shared".to_string()), "money/round"),
                qualified_deps: vec![QualifiedUnitRef::local("pricing/apply_discount")],
            },
        ];

        let errors = detect_qualified_cycles(&scoped_specs);
        assert_eq!(errors.len(), 1, "{errors:?}");
        match &errors[0] {
            SpecError::CrossLibraryCycle { cycle_path, .. } => {
                assert_eq!(
                    cycle_path,
                    &[
                        "pricing/apply_discount".to_string(),
                        "shared::money/round".to_string(),
                        "pricing/apply_discount".to_string(),
                    ]
                );
            }
            other => panic!("expected CrossLibraryCycle, got {other:?}"),
        }
    }

    // --- check_spec_versions ---

    #[test]
    fn check_spec_versions_warns_on_missing() {
        let spec = create_test_spec("pricing/apply_discount", "{ }");
        assert!(spec.spec.spec_version.is_none());
        let warnings = check_spec_versions(&[spec]);
        assert_eq!(warnings.len(), 1);
        assert!(
            warnings[0].to_string().contains("spec_version not set"),
            "{}",
            warnings[0]
        );
        assert!(
            warnings[0]
                .to_string()
                .contains(&format!("spec_version: \"{AUTHORED_SPEC_VERSION}\"")),
            "{}",
            warnings[0]
        );
    }

    #[test]
    fn check_spec_versions_no_warning_when_set() {
        let mut spec = create_test_spec("pricing/apply_discount", "{ }");
        spec.spec.spec_version = Some("0.3.0".to_string());
        let warnings = check_spec_versions(&[spec]);
        assert!(warnings.is_empty(), "expected no warnings: {:?}", warnings);
    }

    #[test]
    fn check_spec_versions_partial_warning() {
        let spec_with = {
            let mut s = create_test_spec("pricing/apply_discount", "{ }");
            s.spec.spec_version = Some("0.3.0".to_string());
            s
        };
        let spec_without = create_test_spec("money/round", "{ }");
        let warnings = check_spec_versions(&[spec_with, spec_without]);
        assert_eq!(warnings.len(), 1);
        assert!(warnings[0].to_string().contains("money/round"));
    }

    #[test]
    fn spec_version_round_trips_through_serde() {
        let yaml = r#"
id: pricing/apply_discount
kind: function
spec_version: "0.3.0"
intent:
  why: Apply a discount.
body:
  rust: |
    { }
"#;
        let spec: crate::types::SpecStruct = serde_yaml_bw::from_str(yaml).unwrap();
        assert_eq!(spec.spec_version, Some("0.3.0".to_string()));
    }

    #[test]
    fn spec_version_absent_round_trips_as_none() {
        let yaml = r#"
id: pricing/apply_discount
kind: function
intent:
  why: Apply a discount.
body:
  rust: |
    { }
"#;
        let spec: crate::types::SpecStruct = serde_yaml_bw::from_str(yaml).unwrap();
        assert!(spec.spec_version.is_none());
    }

    #[test]
    fn expect_deeply_nested_parens_are_rejected_at_depth_cap() {
        let nested = format!("{}true{}", "(".repeat(200), ")".repeat(200));
        let err = validate_semantic(&LoadedSpec {
            source: SpecSource {
                file_path: "test.unit.spec".to_string(),
                id: "pricing/apply_discount".to_string(),
            },
            spec: SpecStruct {
                id: "pricing/apply_discount".to_string(),
                kind: "function".to_string(),
                intent: Intent {
                    why: "Apply a discount.".to_string(),
                },
                contract: None,
                deps: vec![],
                imports: vec![],
                body: Body {
                    rust: "{ true }".to_string(),
                },
                local_tests: vec![LocalTest {
                    id: "deep".to_string(),
                    expect: nested,
                }],
                links: None,
                spec_version: None,
            },
        })
        .unwrap_err();

        assert!(err.to_string().contains("maximum depth of 128"));
    }

    #[test]
    fn humanize_validation_error_required_field() {
        let yaml = r#"id: pricing/apply_tax
kind: function
body:
  rust: "{ 42 }""#;
        let value: YamlValue = serde_yaml_bw::from_str(yaml).unwrap();
        let err = validate_raw_yaml(&value, "test.unit.spec")
            .unwrap_err()
            .to_string();
        assert!(err.contains("missing required field"), "got: {err}");
        assert!(err.contains("intent"), "got: {err}");
    }

    #[test]
    fn humanize_validation_error_unknown_field() {
        let yaml = r#"id: pricing/apply_tax
kind: function
intent:
  why: test
body:
  rust: "{ 42 }"
extra_field: bad"#;
        let value: YamlValue = serde_yaml_bw::from_str(yaml).unwrap();
        let err = validate_raw_yaml(&value, "test.unit.spec")
            .unwrap_err()
            .to_string();
        assert!(err.contains("unknown field"), "got: {err}");
        assert!(err.contains("extra_field"), "got: {err}");
    }

    #[test]
    fn humanize_validation_error_id_pattern() {
        let yaml = r#"id: BAD_FORMAT
kind: function
intent:
  why: test
body:
  rust: "{ 42 }""#;
        let value: YamlValue = serde_yaml_bw::from_str(yaml).unwrap();
        let err = validate_raw_yaml(&value, "test.unit.spec")
            .unwrap_err()
            .to_string();
        assert!(err.contains("invalid id format"), "got: {err}");
        assert!(err.contains("module/name"), "got: {err}");
    }

    // ── reserved unit name ───────────────────────────────────────────────────

    #[test]
    fn reserved_unit_name_molecule_tests_is_rejected() {
        let spec = create_test_spec("pricing/molecule_tests", "{ }");
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("reserved"),
            "expected 'reserved' in error, got: {err}"
        );
        assert!(
            err.contains("molecule_tests"),
            "expected segment name in error, got: {err}"
        );
    }

    #[test]
    fn reserved_unit_name_nested_molecule_tests_is_rejected() {
        let spec = create_test_spec("pricing/sub/molecule_tests", "{ }");
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("reserved"),
            "expected 'reserved' in error, got: {err}"
        );
    }

    #[test]
    fn reserved_unit_name_namespace_molecule_tests_is_rejected() {
        let spec = create_test_spec("qa/molecule_tests/foo", "{ }");
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("reserved"),
            "expected 'reserved' in error, got: {err}"
        );
        assert!(
            err.contains("molecule_tests"),
            "expected segment name in error, got: {err}"
        );
    }

    #[test]
    fn reserved_unit_name_deep_namespace_molecule_tests_is_rejected() {
        let spec = create_test_spec("qa/sub/molecule_tests/foo", "{ }");
        let err = validate_semantic(&spec).unwrap_err().to_string();
        assert!(
            err.contains("reserved"),
            "expected 'reserved' in error, got: {err}"
        );
    }

    #[test]
    fn non_reserved_similar_unit_name_passes() {
        let spec = create_test_spec("qa/molecule_test_helpers/foo", "{ }");
        assert!(validate_semantic(&spec).is_ok());
    }

    #[test]
    fn non_reserved_unit_name_passes() {
        let spec = create_test_spec("pricing/apply_discount", "{ }");
        assert!(validate_semantic(&spec).is_ok());
    }

    #[test]
    fn reserved_molecule_test_name_molecule_tests_is_rejected() {
        let test = create_molecule_test_spec("pricing/molecule_tests", vec!["money/round"]);
        let err = validate_molecule_test_semantic(&test)
            .unwrap_err()
            .to_string();
        assert!(
            err.contains("reserved"),
            "expected 'reserved' in error, got: {err}"
        );
        assert!(
            err.contains("molecule_tests"),
            "expected segment name in error, got: {err}"
        );
    }

    #[test]
    fn reserved_molecule_test_name_namespace_molecule_tests_is_rejected() {
        let test = create_molecule_test_spec("qa/molecule_tests/foo", vec!["money/round"]);
        let err = validate_molecule_test_semantic(&test)
            .unwrap_err()
            .to_string();
        assert!(
            err.contains("reserved"),
            "expected 'reserved' in error, got: {err}"
        );
        assert!(
            err.contains("molecule_tests"),
            "expected segment name in error, got: {err}"
        );
    }

    #[test]
    fn reserved_molecule_test_name_deep_namespace_molecule_tests_is_rejected() {
        let test = create_molecule_test_spec("qa/sub/molecule_tests/foo", vec!["money/round"]);
        let err = validate_molecule_test_semantic(&test)
            .unwrap_err()
            .to_string();
        assert!(
            err.contains("reserved"),
            "expected 'reserved' in error, got: {err}"
        );
    }

    #[test]
    fn non_reserved_similar_molecule_test_name_passes() {
        let test = create_molecule_test_spec("qa/molecule_test_helpers/foo", vec!["money/round"]);
        assert!(validate_molecule_test_semantic(&test).is_ok());
    }

    // ── molecule body unsafe detection ───────────────────────────────────────

    fn make_molecule_test(id: &str, body: &str) -> crate::types::LoadedMoleculeTest {
        use crate::types::{
            Body, Intent, LoadedMoleculeTest, MoleculeTestSource, MoleculeTestStruct,
        };
        LoadedMoleculeTest {
            source: MoleculeTestSource {
                file_path: format!("test/{}.test.spec", id),
                id: id.to_string(),
            },
            test: MoleculeTestStruct {
                id: id.to_string(),
                intent: Intent {
                    why: "test".to_string(),
                },
                covers: vec![],
                body: Body {
                    rust: body.to_string(),
                },
                spec_version: None,
            },
        }
    }

    #[test]
    fn molecule_body_with_unsafe_block_is_rejected() {
        let test = make_molecule_test("pricing/checkout_flow", "{ unsafe { let x = 1; } }");
        let err = validate_molecule_test_semantic(&test)
            .unwrap_err()
            .to_string();
        assert!(
            err.contains("unsafe"),
            "expected 'unsafe' in error, got: {err}"
        );
    }

    #[test]
    fn molecule_body_with_nested_unsafe_is_rejected() {
        let test = make_molecule_test(
            "pricing/checkout_flow",
            "{ let x = if true { unsafe { 1 } else { 2 } }; }",
        );
        // The body may or may not parse depending on syn's handling of incomplete if/else;
        // either a parse error or an unsafe error is acceptable rejection.
        let result = validate_molecule_test_semantic(&test);
        assert!(result.is_err(), "expected rejection of nested unsafe");
    }

    #[test]
    fn molecule_body_with_unsafe_in_array_index_expr_is_rejected() {
        let test = make_molecule_test(
            "pricing/checkout_flow",
            "{ let _x = [unsafe { std::mem::zeroed::<u8>() }][0]; }",
        );
        let err = validate_molecule_test_semantic(&test)
            .unwrap_err()
            .to_string();
        assert!(
            err.contains("unsafe"),
            "expected 'unsafe' in error, got: {err}"
        );
    }

    #[test]
    fn molecule_body_with_unsafe_fn_item_is_rejected() {
        let test = make_molecule_test(
            "pricing/checkout_flow",
            "{ unsafe fn helper() {} helper(); }",
        );
        let err = validate_molecule_test_semantic(&test)
            .unwrap_err()
            .to_string();
        assert!(
            err.contains("unsafe"),
            "expected 'unsafe' in error, got: {err}"
        );
    }

    #[test]
    fn molecule_body_with_unsafe_inside_macro_body_is_rejected() {
        let test = make_molecule_test("pricing/checkout_flow", "{ m!(unsafe { 1 }); }");
        let err = validate_molecule_test_semantic(&test)
            .unwrap_err()
            .to_string();
        assert!(
            err.contains("unsafe"),
            "expected 'unsafe' in error, got: {err}"
        );
    }

    #[test]
    fn molecule_body_with_unsafe_string_literal_passes() {
        let test = make_molecule_test(
            "pricing/checkout_flow",
            r#"{ let label = "unsafe"; assert_eq!(label, "unsafe"); }"#,
        );
        assert!(validate_molecule_test_semantic(&test).is_ok());
    }

    #[test]
    fn molecule_body_without_unsafe_passes() {
        let test = make_molecule_test(
            "pricing/checkout_flow",
            "{ let x = apply_discount(100, 10); assert_eq!(x, 90); }",
        );
        assert!(validate_molecule_test_semantic(&test).is_ok());
    }

    // ── validate_molecule_test_semantic: MoleculeBodyRustMustBeBlock ──────────

    #[test]
    fn molecule_body_bare_expression_is_rejected() {
        // A bare expression (no braces) is not a syn::Block and must be rejected.
        let test = make_molecule_test("pricing/checkout_flow", "true");
        let result = validate_molecule_test_semantic(&test);
        assert!(result.is_err(), "bare expression should be rejected");
        let err = result.unwrap_err().to_string();
        assert!(
            err.contains("block") || err.contains("Block"),
            "expected block error, got: {err}"
        );
    }

    // ── validate_molecule_test_covers: MoleculeCoversNotFound ────────────────

    #[test]
    fn covers_unknown_unit_id_returns_covers_not_found_error() {
        let molecule_test = create_molecule_test_spec(
            "pricing/checkout_flow",
            vec!["pricing/apply_discount", "pricing/unknown_unit"],
        );
        let unit_ids: HashSet<&str> = ["pricing/apply_discount"].into_iter().collect();

        let (errors, warnings) = validate_molecule_test_covers(&molecule_test, &unit_ids);

        assert!(warnings.is_empty());
        assert_eq!(errors.len(), 1);
        match &errors[0] {
            SpecError::MoleculeCoversNotFound {
                cover_id,
                test_id,
                test_path,
            } => {
                assert_eq!(cover_id, "pricing/unknown_unit");
                assert_eq!(test_id, "pricing/checkout_flow");
                assert!(test_path.ends_with("checkout_flow.test.spec"));
            }
            other => panic!("expected MoleculeCoversNotFound, got {other:?}"),
        }
    }

    #[test]
    fn covers_all_known_unit_ids_returns_no_errors() {
        let molecule_test = create_molecule_test_spec(
            "pricing/checkout_flow",
            vec!["pricing/apply_discount", "pricing/apply_tax"],
        );
        let unit_ids: HashSet<&str> = ["pricing/apply_discount", "pricing/apply_tax"]
            .into_iter()
            .collect();

        let (errors, warnings) = validate_molecule_test_covers(&molecule_test, &unit_ids);

        assert!(errors.is_empty());
        assert!(warnings.is_empty());
    }

    #[test]
    fn covers_empty_list_emits_warning_not_error() {
        let molecule_test = create_molecule_test_spec("pricing/checkout_flow", vec![]);
        let unit_ids: HashSet<&str> = ["pricing/apply_discount"].into_iter().collect();

        let (errors, warnings) = validate_molecule_test_covers(&molecule_test, &unit_ids);

        assert!(errors.is_empty(), "empty covers should not be an error");
        assert_eq!(warnings.len(), 1, "expected one warning for empty covers");
    }

    // ── validate_no_duplicate_molecule_test_ids ───────────────────────────────

    #[test]
    fn duplicate_molecule_test_ids_returns_error() {
        let test_a = create_molecule_test_spec("pricing/checkout_flow", vec!["money/round"]);
        let test_b = create_molecule_test_spec("pricing/checkout_flow", vec!["pricing/apply_tax"]);

        let errors = validate_no_duplicate_molecule_test_ids(&[test_a, test_b]);

        assert_eq!(errors.len(), 1);
        match &errors[0] {
            SpecError::DuplicateMoleculeTestId { id, .. } => {
                assert_eq!(id, "pricing/checkout_flow");
            }
            other => panic!("expected DuplicateMoleculeTestId, got {other:?}"),
        }
    }

    #[test]
    fn unique_molecule_test_ids_returns_no_errors() {
        let test_a = create_molecule_test_spec("pricing/checkout_flow", vec!["money/round"]);
        let test_b = create_molecule_test_spec("pricing/discount_flow", vec!["money/round"]);

        let errors = validate_no_duplicate_molecule_test_ids(&[test_a, test_b]);

        assert!(errors.is_empty());
    }

    #[test]
    fn empty_molecule_test_slice_returns_no_errors() {
        let errors = validate_no_duplicate_molecule_test_ids(&[]);
        assert!(errors.is_empty());
    }

    // ── validate_raw_molecule_test_yaml ───────────────────────────────────────

    #[test]
    fn raw_molecule_test_yaml_valid_input_passes() {
        let yaml = r#"
id: pricing/checkout_flow
intent:
  why: "Verify discount + tax chain."
covers:
  - pricing/apply_discount
body:
  rust: |
    { assert!(true); }
"#;
        let value: serde_yaml_bw::Value = serde_yaml_bw::from_str(yaml).unwrap();
        let result = validate_raw_molecule_test_yaml(&value, "pricing/checkout_flow.test.spec");
        assert!(
            result.is_ok(),
            "valid molecule test YAML should pass: {result:?}"
        );
    }

    #[test]
    fn raw_molecule_test_yaml_missing_body_is_rejected() {
        let yaml = r#"
id: pricing/checkout_flow
intent:
  why: "Missing body."
"#;
        let value: serde_yaml_bw::Value = serde_yaml_bw::from_str(yaml).unwrap();
        let result = validate_raw_molecule_test_yaml(&value, "pricing/checkout_flow.test.spec");
        assert!(result.is_err(), "missing body field should be rejected");
        let err = result.unwrap_err().to_string();
        assert!(
            err.contains("Schema validation failed"),
            "expected schema error, got: {err}"
        );
    }

    #[test]
    fn raw_molecule_test_yaml_unknown_field_is_rejected() {
        let yaml = r#"
id: pricing/checkout_flow
intent:
  why: "Unknown field."
body:
  rust: "{ assert!(true); }"
unknown_field: should_fail
"#;
        let value: serde_yaml_bw::Value = serde_yaml_bw::from_str(yaml).unwrap();
        let result = validate_raw_molecule_test_yaml(&value, "pricing/checkout_flow.test.spec");
        assert!(result.is_err(), "unknown field should be rejected");
    }
}
