// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

use {
  crate::process,
  ferrotype::Ferrotype,
  proc_macro2::Span,
  quote::quote,
};

#[test_log::test]
fn test_error_not_a_struct() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub enum NotAStruct {
      Variant1,
      Variant2,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected ParseItemNotStruct error");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      assert!(
        error_message.contains("struct"),
        "Error message should mention 'struct', got: {error_message}",
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_function_instead_of_struct() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub fn not_a_struct() -> i32 {
      42
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error for function instead of struct");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      assert!(
        error_message.contains("struct"),
        "Error message should mention 'struct', got: {error_message}",
      );
    },
  }

  ferrotype::assert!(snapshot);
}

// #[test_log::test]
// fn test_error_invalid_field_type_plain() {
//   let mut snapshot = Ferrotype::new();
//   snapshot.set_expect_errors(true);

//   let input = quote! {
//     #[laburnum_syntax(AST)]
//     pub struct BadField {
//       // These types are not supported by the AST macro
//       invalid_hashmap: HashMap<String, i32>,
//       invalid_result: Result<NodeId, Error>,
//     }
//   };

//   snapshot.add_token_stream("Input", &input);

//   match process(quote!(AST), input) {
//     | Ok(_) => {
//       panic!("Expected InvalidFieldType error");
//     },
//     | Err(e) => {
//       snapshot.add_debug("Error", &e);
//       match &e {
//         | Error::InvalidFieldType(_) | Error::InvalidFieldTypeMessage(..) => {
//           let details = e.get();
//           assert!(details.message.contains("field"));
//         },
//         | _ => panic!("Expected InvalidFieldType error, got {:?}", e),
//       }
//     },
//   }

//   ferrotype::assert!(snapshot);
// }

#[test_log::test]
fn test_error_unsupported_container_type() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct UnsupportedContainer {
      // HashMap is not a supported container type
      map_field: HashMap<String, NodeId<crate::Type>>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error for unsupported container type");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      assert!(
        error_message.contains("literal types do not take any arguments"),
        "Error message should mention literal types, got: {error_message}",
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_missing_node_id_generic() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct MissingGeneric {
      // NodeId without type parameter
      bad_node: NodeId,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error for NodeId without generic parameter");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      // Check that error message is helpful
      assert!(
        !error_message.is_empty(),
        "Error message should not be empty"
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_conflicting_flags() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  // Can't be both AST and CST
  let input = quote! {
    #[laburnum_syntax(AST, CST)]
    pub struct Conflicting {
      field: NodeId<crate::Type>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  // This should fail in some way
  match process(quote!(AST, CST), input) {
    | Ok(_) => {
      // If it doesn't fail, that might be okay too
      snapshot.add("Note", "Conflicting flags were accepted".to_string());
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_no_syntax_type() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax()]
    pub struct NoType {
      field: NodeId<crate::Type>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(), input) {
    | Ok(_) => {
      panic!("Expected error for missing AST/CST flag");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_span_preservation() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct SpanTest {
      good_field: NodeId<crate::Type>,
      // This field should cause an error
      bad_field: HashMap<String, i32>,
      another_good: Option<NodeId<crate::Type>>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error for bad field");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      // The error span should point to the bad field, not the whole struct
      let error_message = e.to_string();
      // The error span should point to the bad field, not the whole struct
      // We can't easily test the exact span, but we can ensure the error
      // message exists
      assert!(
        !error_message.is_empty(),
        "Error message should not be empty"
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_with_help_message() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub trait NotAStruct {
      fn method();
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error for trait instead of struct");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      // Check that we have a meaningful error message
      assert!(
        !error_message.is_empty(),
        "Error message should not be empty"
      );
      snapshot.add("Error Message", error_message);
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_multiple_errors() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct MultipleErrors {
      bad1: String,
      bad2: i32,
      bad3: HashMap<String, i32>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected errors for multiple bad fields");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      // Even if we only get the first error, that's okay
      // The important thing is that we get a clear error
      let error_message = e.to_string();
      assert!(
        !error_message.is_empty(),
        "Error message should not be empty"
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_complex_invalid_type() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct ComplexInvalid {
      // Valid
      good: NodeId<crate::Type>,
      // Invalid: Result is not a supported wrapper
      bad_result: Result<NodeId<crate::Type>, Error>,
      // Invalid: Box is not directly supported
      bad_box: Box<NodeId<crate::Type>>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error for unsupported wrapper types");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      assert!(
        error_message.contains("field") || error_message.contains("type"),
        "Error message should mention 'field' or 'type', got: {error_message}",
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_recovery_suggestions() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct NeedsSuggestion {
      // User probably meant NodeId<crate::Type> or Vec<NodeId>
      almost_right: HashMap<String, crate::Type>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error with suggestion");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();

      // Check that we have a helpful error message
      assert!(
        !error_message.is_empty(),
        "Error message should not be empty"
      );
      snapshot.add("Error Message", error_message);
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_empty_struct() {
  let mut snapshot = Ferrotype::new();

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct Empty {}
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(output) => {
      // Empty structs might be valid
      snapshot.add_token_stream("Output", &output);
    },
    | Err(e) => {
      // Or they might be an error
      snapshot.add_debug("Error", &e);
      snapshot.set_expect_errors(true);
    },
  }

  ferrotype::assert!(snapshot);
}

// Test that we can combine errors using syn::Error::combine
#[test_log::test]
fn test_combined_errors() {
  use syn::Error as SynError;

  let span = Span::call_site();
  let mut main_error = SynError::new(span, "main error");
  let secondary_error = SynError::new(span, "secondary error");

  main_error.combine(secondary_error);

  let error_string = main_error.to_string();
  assert!(error_string.contains("main error"));
  // Note: combined errors might be formatted differently
}

// Test custom error creation following blog post pattern
#[test_log::test]
fn test_custom_error_creation() {
  // Test creating errors with new_spanned (as shown in blog)
  let token_stream = quote! { invalid_token };
  let error = syn::Error::new_spanned(token_stream, "custom error message");

  assert!(error.to_string().contains("custom error"));
}

#[test_log::test]
fn test_cst_missing_span_field() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(CST)]
    pub struct MissingSpan {
      // Missing required span field
      other_field: NodeId<crate::Type>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(CST), input) {
    | Ok(_) => {
      panic!("Expected MissingRequiredSpanField error");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      assert!(
        error_message.contains("span") && error_message.contains("Span"),
        "Error message should mention 'span' and 'Span', got: {error_message}",
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_cst_invalid_field_naming_convention() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(CST)]
    pub struct InvalidNaming {
      span: Span,
      // ControlSpan field should end with _token
      bad_control: ControlSpan,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(CST), input) {
    | Ok(_) => {
      panic!("Expected InvalidFieldNamingConvention error");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      assert!(
        error_message.contains("bad_control")
          && error_message.contains("_token"),
        "Error message should mention 'bad_control' and '_token', got: {error_message}",
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_cst_keyword_span_naming() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(CST)]
    pub struct KeywordNaming {
      span: Span,
      // KeywordSpan field should end with _keyword
      bad_name: KeywordSpan,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(CST), input) {
    | Ok(_) => {
      panic!("Expected InvalidFieldNamingConvention error for KeywordSpan");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();
      assert!(
        error_message.contains("bad_name")
          && error_message.contains("_keyword"),
        "Error message should mention 'bad_name' and '_keyword', got: {error_message}",
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_reserved_keyword_field_name_error_details() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct ReservedKeyword {
      self_: NodeId<crate::Type>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected ReservedKeywordFieldName error");
    },
    | Err(e) => {
      snapshot.add_debug("Error", &e);
      let error_message = e.to_string();

      // Check that we have helpful error message
      assert!(
        error_message.contains("self_")
          && error_message.contains("Self")
          && error_message.contains("reserved keyword"),
        "Error message should mention 'self_', 'Self', and 'reserved keyword', got: {error_message}",
      );

      snapshot.add("Error Message", error_message);
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_ast_error_accumulation_multiple_field_errors() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct MultipleErrors {
      // Error 1: Invalid field type (unknown type with generic args)
      bad_field1: HashMap<String, i32>,
      // Error 2: Another invalid field type
      bad_field2: Result<NodeId<crate::Type>, Error>,
      // Error 3: Reserved keyword field name
      self_: NodeId<crate::Type>,
      // This should be valid
      good_field: NodeId<crate::Type>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  // Test with error accumulation - should collect all errors
  match process(quote!(AST), input.clone()) {
    | Ok(_) => {
      panic!("Expected multiple accumulated errors");
    },
    | Err(accumulated_error) => {
      snapshot.add("Accumulated Errors", accumulated_error.to_string());

      // The error should contain information about issues
      let error_str = accumulated_error.to_string();
      assert!(
        !error_str.is_empty(),
        "Expected non-empty error message"
      );

      // The compiler output should contain multiple errors
      let compiler_output = accumulated_error.to_compile_error().to_string();
      assert!(
        compiler_output.len() > 50,
        "Expected detailed compiler error output with multiple issues"
      );
    },
  }

  // Also test the regular process function for comparison
  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error from regular processing too");
    },
    | Err(single_error) => {
      snapshot.add_debug("Single Error (Regular Processing)", &single_error);
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_cst_error_accumulation_multiple_naming_errors() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(CST)]
    pub struct MultipleNamingErrors {
      span: Span,
      // Error 1: ControlSpan without _token suffix
      bad_control: ControlSpan,
      // Error 2: KeywordSpan without _keyword suffix
      bad_keyword: KeywordSpan,
      // Error 3: ScalarSpan without _scalar suffix
      bad_scalar: ScalarSpan,
      // This should be valid
      good_token: ControlSpan,
    }
  };

  snapshot.add_token_stream("Input", &input);

  // Test with error accumulation
  match process(quote!(CST), input.clone()) {
    | Ok(_) => {
      panic!("Expected multiple accumulated naming errors");
    },
    | Err(accumulated_error) => {
      snapshot.add("Accumulated Naming Errors", accumulated_error.to_string());

      // Should contain multiple naming convention errors
      let error_str = accumulated_error.to_string();
      assert!(
        error_str.contains("bad_control") || error_str.contains("naming"),
        "Should mention field naming issues"
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_accumulation_vs_single_error() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct CompareErrorModes {
      // Multiple invalid fields to trigger multiple errors
      bad1: String,
      bad2: i32,
      bad3: HashMap<String, Vec<i32>>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  // Test single error mode (regular processing)
  match process(quote!(AST), input.clone()) {
    | Ok(_) => panic!("Expected error in single mode"),
    | Err(single_error) => {
      snapshot.add_debug("Single Error Mode", &single_error);
    },
  }

  // Test accumulation mode
  match process(quote!(AST), input) {
    | Ok(_) => panic!("Expected errors in accumulation mode"),
    | Err(accumulated_errors) => {
      snapshot.add("Accumulated Error Mode", accumulated_errors.to_string());

      // Accumulated errors should be more comprehensive
      let acc_str = accumulated_errors.to_string();
      assert!(
        !acc_str.is_empty(),
        "Accumulated errors should not be empty"
      );
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_accumulation_with_valid_fields() {
  let mut snapshot = Ferrotype::new();

  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct MixedValidInvalid {
      // Valid fields
      good1: NodeId<crate::Type>,
      good2: Option<NodeId<crate::Expression>>,
      good3: Vec<NodeId<crate::Statement>>,
      // Invalid field - should not prevent processing of valid ones
      bad1: HashMap<String, i32>,
      // More valid fields
      good4: Field<bool>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  match process(quote!(AST), input) {
    | Ok(_) => {
      // If it succeeds despite having invalid fields, that would be unexpected
      // but we should document this behavior
      snapshot.add("Result", "Unexpectedly succeeded".to_string());
    },
    | Err(error) => {
      snapshot.add("Error with Mixed Fields", error.to_string());
      // The error should mention the invalid field but not prevent
      // the macro from understanding the valid fields
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_accumulator_utility() {
  use crate::error::{
    Error,
    ErrorAccumulator,
  };

  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  // Test the ErrorAccumulator utility directly
  let mut accumulator = ErrorAccumulator::new();

  // Add multiple errors
  accumulator
    .add_error(Error::MissingFieldIdentifier(proc_macro2::Span::call_site()));
  accumulator.add_error(Error::EmptyTypePath(proc_macro2::Span::call_site()));
  accumulator.add_error(Error::ReservedKeywordFieldName(
    proc_macro2::Span::call_site(),
    "self_".to_string(),
    "Self".to_string(),
  ));

  assert_eq!(accumulator.error_count(), 3);
  assert!(accumulator.has_errors());

  // Convert to result
  match accumulator.into_result(()) {
    | Ok(_) => panic!("Should have failed with errors"),
    | Err(combined_error) => {
      snapshot.add("Combined Error", combined_error.to_string());

      // The combined error should contain information from all errors
      let error_str = combined_error.to_string();
      assert!(!error_str.is_empty());
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_accumulation_guaranteed_multiple_failures() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  // Create a struct with multiple guaranteed parsing failures
  // Unknown types with generic arguments are rejected by parse_field_literal
  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct GuaranteedFailures {
      // These should definitely cause parsing errors in sequence
      field1: UnsupportedType1<i32>,
      field2: UnsupportedType2<String>,
      field3: UnsupportedType3<bool>,
      field4: CompletelyInvalidType<u8>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  // Test with error accumulation to see if we collect multiple errors
  match process(quote!(AST), input.clone()) {
    | Ok(_) => {
      panic!("Expected multiple parsing failures");
    },
    | Err(accumulated_error) => {
      snapshot.add("Accumulated Parse Errors", accumulated_error.to_string());

      // Count approximate number of errors by counting error indicators
      let error_str = accumulated_error.to_string();
      let error_count = error_str.matches("error").count()
        + error_str.matches("failed").count()
        + error_str.matches("invalid").count();

      snapshot.add("Approximate Error Count", error_count.to_string());
    },
  }

  // Compare with regular parsing
  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected error from regular parsing too");
    },
    | Err(single_error) => {
      snapshot.add_debug("Single Parse Error", &single_error);
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_error_accumulator_detailed_behavior() {
  use crate::error::{
    Error,
    ErrorAccumulator,
  };

  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  // Test ErrorAccumulator behavior in detail
  let mut accumulator = ErrorAccumulator::new();

  // Add exactly 3 different types of errors
  accumulator.add_error(Error::UnsupportedFieldType(
    proc_macro2::Span::call_site(),
    "HashMap<String, i32>".to_string(),
  ));

  accumulator.add_error(Error::ReservedKeywordFieldName(
    proc_macro2::Span::call_site(),
    "self_".to_string(),
    "Self".to_string(),
  ));

  accumulator.add_error(Error::InvalidFieldNamingConvention(
    proc_macro2::Span::call_site(),
    "bad_field".to_string(),
    "_token".to_string(),
  ));

  snapshot.add("Error Count", accumulator.error_count().to_string());
  snapshot.add("Has Errors", accumulator.has_errors().to_string());

  // Test the combined result
  match accumulator.into_result(()) {
    | Ok(_) => panic!("Should have failed with multiple errors"),
    | Err(combined) => {
      snapshot.add("Combined Error Message", combined.to_string());

      // Check if the combined error contains traces of multiple issues
      let msg = combined.to_string();
      let contains_field_type = msg.contains("field") || msg.contains("type");
      let contains_keyword = msg.contains("keyword") || msg.contains("Self");
      let contains_naming =
        msg.contains("naming") || msg.contains("convention");

      snapshot
        .add("Contains Field Type Error", contains_field_type.to_string());
      snapshot.add("Contains Keyword Error", contains_keyword.to_string());
      snapshot.add("Contains Naming Error", contains_naming.to_string());
    },
  }

  ferrotype::assert!(snapshot);
}

#[test_log::test]
fn test_multiple_errors_in_compiler_output() {
  let mut snapshot = Ferrotype::new();
  snapshot.set_expect_errors(true);

  // Create a struct guaranteed to have multiple validation errors
  let input = quote! {
    #[laburnum_syntax(AST)]
    pub struct MultipleCompilerErrors {
      // Error 1: Invalid field type
      bad_field1: HashMap<String, i32>,
      // Error 2: Another invalid field type
      bad_field2: Result<NodeId<crate::Type>, Error>,
      // Error 3: Reserved keyword field name
      self_: NodeId<crate::Type>,
    }
  };

  snapshot.add_token_stream("Input", &input);

  // Test that process_with_accumulation shows multiple errors
  match process(quote!(AST), input) {
    | Ok(_) => {
      panic!("Expected multiple errors in compiler output");
    },
    | Err(combined_error) => {
      let error_output = combined_error.to_compile_error().to_string();
      snapshot.add("Compiler Error Output", error_output.clone());

      // Verify the combined error contains multiple distinct issues
      let error_str = combined_error.to_string();
      snapshot.add("Error String", error_str.clone());

      // Count different types of error indicators
      let has_field_errors =
        error_str.contains("field") || error_str.contains("struct");
      let has_type_errors =
        error_str.contains("type") || error_str.contains("HashMap");
      let has_keyword_errors =
        error_str.contains("keyword") || error_str.contains("self_");

      snapshot.add("Has Field Errors", has_field_errors.to_string());
      snapshot.add("Has Type Errors", has_type_errors.to_string());
      snapshot.add("Has Keyword Errors", has_keyword_errors.to_string());

      // The compiler output should be substantial (indicating multiple errors)
      assert!(
        error_output.len() > 100,
        "Compiler output should be substantial for multiple errors"
      );
    },
  }

  ferrotype::assert!(snapshot);
}
