use super::*;

#[test]
fn is_streaming_virtual_field_recognizes_all_fields() {
    for field in STREAMING_VIRTUAL_FIELDS {
        assert!(
            is_streaming_virtual_field(field),
            "field '{field}' not recognized as streaming virtual"
        );
    }
}

#[test]
fn is_streaming_virtual_field_rejects_real_fields() {
    assert!(!is_streaming_virtual_field("content"));
    assert!(!is_streaming_virtual_field("choices"));
    assert!(!is_streaming_virtual_field("model"));
    assert!(!is_streaming_virtual_field(""));
}

#[test]
fn is_streaming_virtual_field_rejects_non_root_paths_with_matching_tail() {
    // Regression: prior impl matched any field whose chars-after-root-len started
    // with `[` or `.` — without checking that the field actually starts with the
    // root token. `choices[0].finish_reason` therefore falsely matched root
    // `tool_calls` because byte 10 onward is `.finish_reason`.
    assert!(!is_streaming_virtual_field("choices[0].finish_reason"));
    assert!(!is_streaming_virtual_field("choices[0].message.content"));
    assert!(!is_streaming_virtual_field("data[0].embedding"));
}

#[test]
fn is_streaming_virtual_field_does_not_match_usage() {
    // `usage` is intentionally NOT a streaming-virtual root: chat/embed
    // responses carry `usage.total_tokens` at the response root, so treating
    // it as virtual would drag non-streaming tests into the chunks accessor.
    assert!(!is_streaming_virtual_field("usage"));
    assert!(!is_streaming_virtual_field("usage.total_tokens"));
    assert!(!is_streaming_virtual_field("usage.prompt_tokens"));
}

#[test]
fn event_variant_accessor_requires_stream_item_type() {
    assert_eq!(
        StreamingFieldResolver::accessor_with_module_qualifier(
            "stream.has_page_event",
            "rust",
            "chunks",
            Some("sample_recipe"),
        ),
        None
    );
}

#[test]
fn event_variant_accessor_uses_configured_stream_item_type() {
    let rust = StreamingFieldResolver::accessor_with_streaming_context(
        "stream.has_page_event",
        "rust",
        "chunks",
        Some("sample_recipe"),
        Some("Event"),
    )
    .expect("configured item type emits rust event predicate");
    assert!(rust.contains("sample_recipe::Event::Page"), "rust predicate: {rust}");

    let go = StreamingFieldResolver::accessor_with_streaming_context(
        "stream.has_complete_event",
        "go",
        "chunks",
        None,
        Some("Event"),
    )
    .expect("configured item type emits go event predicate");
    assert!(go.contains("pkg.EventComplete"), "go predicate: {go}");
}

#[test]
fn accessor_chunks_returns_var_name() {
    assert_eq!(
        StreamingFieldResolver::accessor("chunks", "rust", "chunks"),
        Some("chunks".to_string())
    );
    assert_eq!(
        StreamingFieldResolver::accessor("chunks", "node", "chunks"),
        Some("chunks".to_string())
    );
}

#[test]
fn accessor_chunks_length_uses_language_idiom() {
    let rust = StreamingFieldResolver::accessor("chunks.length", "rust", "chunks").unwrap();
    assert!(rust.contains(".len()"), "rust: {rust}");
    let neutral_rust = StreamingFieldResolver::accessor("stream.items.length", "rust", "items").unwrap();
    assert_eq!(neutral_rust, "items.len()", "rust neutral items count: {neutral_rust}");

    let go = StreamingFieldResolver::accessor("chunks.length", "go", "chunks").unwrap();
    assert!(go.starts_with("len("), "go: {go}");

    let node = StreamingFieldResolver::accessor("chunks.length", "node", "chunks").unwrap();
    assert!(node.contains(".length"), "node: {node}");

    let php = StreamingFieldResolver::accessor("chunks.length", "php", "chunks").unwrap();
    assert!(php.starts_with("count("), "php: {php}");
}

#[test]
fn accessor_chunks_length_zig_uses_items_len() {
    let zig = StreamingFieldResolver::accessor("chunks.length", "zig", "chunks").unwrap();
    assert_eq!(zig, "chunks.items.len", "zig chunks.length: {zig}");
}

#[test]
fn accessor_stream_content_zig_uses_content_items() {
    let zig = StreamingFieldResolver::accessor("stream_content", "zig", "chunks").unwrap();
    assert_eq!(zig, "chunks_content.items", "zig stream_content: {zig}");
}

#[test]
fn collect_snippet_zig_drains_via_ffi() {
    assert!(
        StreamingFieldResolver::collect_snippet("zig", "_stream_handle", "chunks").is_none(),
        "generic zig collect must require adapter metadata"
    );
    let snip = StreamingFieldResolver::collect_snippet_zig(
        "_stream_handle",
        "chunks",
        "sample",
        "sample",
        "StreamClient",
        "events",
        "StreamEvent",
    );
    assert!(snip.contains("std.ArrayList([]u8)"), "zig collect: {snip}");
    assert!(
        snip.contains("stream_client_events_next(_stream_handle)"),
        "zig collect: {snip}"
    );
    assert!(snip.contains("stream_event_to_json(_nc)"), "zig collect: {snip}");
    assert!(snip.contains("chunks_content"), "zig collect: {snip}");
    assert!(
        snip.contains("chunks.append(std.heap.c_allocator"),
        "zig collect: {snip}"
    );
    assert!(snip.contains(".empty;"), "zig collect (Zig 0.16 unmanaged): {snip}");
}

#[test]
fn accessor_stream_content_rust_uses_iterator() {
    let expr = StreamingFieldResolver::accessor("stream_content", "rust", "chunks").unwrap();
    assert!(expr.contains(".collect::<String>()"), "rust stream_content: {expr}");
}

#[test]
fn accessor_no_chunks_after_done_returns_true() {
    for lang in ["rust", "go", "java", "php", "node", "wasm", "elixir"] {
        let expr = StreamingFieldResolver::accessor("no_chunks_after_done", lang, "chunks").unwrap();
        assert_eq!(expr, "true", "lang {lang}: expected 'true', got '{expr}'");
    }
}

#[test]
fn accessor_elixir_chunks_length_uses_length_function() {
    let expr = StreamingFieldResolver::accessor("chunks.length", "elixir", "chunks").unwrap();
    assert_eq!(expr, "length(chunks)", "elixir chunks.length: {expr}");
}

#[test]
fn accessor_elixir_stream_content_uses_pipe() {
    let expr = StreamingFieldResolver::accessor("stream_content", "elixir", "chunks").unwrap();
    assert!(expr.contains("|> Enum.join"), "elixir stream_content: {expr}");
    assert!(expr.contains("|> Enum.map"), "elixir stream_content: {expr}");
    // Elixir lists do not support bracket access — must use Enum.at, never choices[0]
    assert!(
        !expr.contains("choices[0]"),
        "elixir stream_content must not use bracket access on list: {expr}"
    );
    assert!(
        expr.contains("Enum.at("),
        "elixir stream_content must use Enum.at for list index: {expr}"
    );
}

#[test]
fn accessor_elixir_stream_complete_uses_list_last() {
    let expr = StreamingFieldResolver::accessor("stream_complete", "elixir", "chunks").unwrap();
    assert!(expr.contains("List.last(chunks)"), "elixir stream_complete: {expr}");
    assert!(expr.contains("finish_reason != nil"), "elixir stream_complete: {expr}");
    // Elixir lists do not support bracket access — must use Enum.at, never choices[0]
    assert!(
        !expr.contains("choices[0]"),
        "elixir stream_complete must not use bracket access on list: {expr}"
    );
    assert!(
        expr.contains("Enum.at("),
        "elixir stream_complete must use Enum.at for list index: {expr}"
    );
}

#[test]
fn accessor_elixir_finish_reason_uses_list_last() {
    let expr = StreamingFieldResolver::accessor("finish_reason", "elixir", "chunks").unwrap();
    assert!(expr.contains("List.last(chunks)"), "elixir finish_reason: {expr}");
    assert!(expr.contains("finish_reason"), "elixir finish_reason: {expr}");
    // Elixir lists do not support bracket access — must use Enum.at, never choices[0]
    assert!(
        !expr.contains("choices[0]"),
        "elixir finish_reason must not use bracket access on list: {expr}"
    );
    assert!(
        expr.contains("Enum.at("),
        "elixir finish_reason must use Enum.at for list index: {expr}"
    );
}

#[test]
fn collect_snippet_elixir_uses_enum_to_list() {
    let snip = StreamingFieldResolver::collect_snippet("elixir", "result", "chunks").unwrap();
    assert!(snip.contains("Enum.to_list(result)"), "elixir: {snip}");
    assert!(snip.contains("chunks ="), "elixir: {snip}");
}

#[test]
fn collect_snippet_rust_uses_tokio_stream() {
    let snip = StreamingFieldResolver::collect_snippet("rust", "result", "chunks").unwrap();
    assert!(snip.contains("tokio_stream::StreamExt::collect"), "rust: {snip}");
    assert!(snip.contains("let chunks"), "rust: {snip}");
    // Items are Result<stream item, _> — unwrap so chunks is Vec<stream item>.
    assert!(snip.contains(".expect("), "rust must unwrap Result items: {snip}");
}

#[test]
fn collect_snippet_go_drains_channel() {
    assert!(
        StreamingFieldResolver::collect_snippet("go", "stream", "chunks").is_none(),
        "typed Go collect must require an item type"
    );
    let snip = StreamingFieldResolver::collect_snippet_typed("go", "stream", "chunks", Some("StreamEvent")).unwrap();
    assert!(snip.contains("for chunk := range stream"), "go: {snip}");
    assert!(snip.contains("[]pkg.StreamEvent"), "go: {snip}");
}

#[test]
fn collect_snippet_java_uses_iterator() {
    assert!(
        StreamingFieldResolver::collect_snippet("java", "result", "chunks").is_none(),
        "typed Java collect must require an item type"
    );
    let snip = StreamingFieldResolver::collect_snippet_typed("java", "result", "chunks", Some("StreamEvent")).unwrap();
    // Must call .iterator() on the Stream<T> before using hasNext()/next() —
    // Stream does not implement those methods directly.
    assert!(
        snip.contains(".iterator()"),
        "java snippet must call .iterator() on stream: {snip}"
    );
    assert!(snip.contains("ArrayList<StreamEvent>"), "java: {snip}");
    assert!(snip.contains("hasNext()"), "java: {snip}");
    assert!(snip.contains(".next()"), "java: {snip}");
}

#[test]
fn collect_snippet_php_decodes_json_or_iterates() {
    let snip = StreamingFieldResolver::collect_snippet("php", "result", "chunks").unwrap();
    // PHP binding's chat_stream_async returns a JSON string today; collect-snippet
    // decodes it.  iterator_to_array is retained as the fallback branch so a
    // future binding that exposes a real iterator continues to work without
    // regenerating the e2e tests.
    assert!(snip.contains("json_decode"), "php must decode JSON: {snip}");
    assert!(
        snip.contains("iterator_to_array"),
        "php must keep iterator_to_array fallback: {snip}"
    );
    assert!(snip.contains("$chunks ="), "php must bind $chunks: {snip}");
}

#[test]
fn collect_snippet_node_uses_for_await() {
    let snip = StreamingFieldResolver::collect_snippet("node", "result", "chunks").unwrap();
    assert!(snip.contains("for await"), "node: {snip}");
}

#[test]
fn collect_snippet_python_uses_async_for() {
    let snip = StreamingFieldResolver::collect_snippet("python", "result", "chunks").unwrap();
    assert!(snip.contains("async for chunk in result"), "python: {snip}");
    assert!(snip.contains("chunks.append(chunk)"), "python: {snip}");
}

#[test]
fn accessor_stream_content_python_uses_join() {
    let expr = StreamingFieldResolver::accessor("stream_content", "python", "chunks").unwrap();
    assert!(expr.contains("\"\".join("), "python stream_content: {expr}");
    assert!(expr.contains("c.choices"), "python stream_content: {expr}");
}

#[test]
fn accessor_stream_complete_python_uses_finish_reason() {
    let expr = StreamingFieldResolver::accessor("stream_complete", "python", "chunks").unwrap();
    assert!(
        expr.contains("finish_reason is not None"),
        "python stream_complete: {expr}"
    );
}

#[test]
fn accessor_finish_reason_python_uses_last_chunk() {
    let expr = StreamingFieldResolver::accessor("finish_reason", "python", "chunks").unwrap();
    assert!(expr.contains("chunks[-1]"), "python finish_reason: {expr}");
    // Must wrap in str() so FinishReason enum objects support .strip() comparisons
    assert!(
        expr.starts_with("(str(") || expr.contains("str(chunks"),
        "python finish_reason must wrap in str(): {expr}"
    );
}

#[test]
fn accessor_tool_calls_python_uses_list_comprehension() {
    let expr = StreamingFieldResolver::accessor("tool_calls", "python", "chunks").unwrap();
    assert!(expr.contains("for c in chunks"), "python tool_calls: {expr}");
    assert!(expr.contains("tool_calls"), "python tool_calls: {expr}");
}

#[test]
fn accessor_usage_python_uses_last_chunk() {
    let expr = StreamingFieldResolver::accessor("usage", "python", "chunks").unwrap();
    assert!(
        expr.contains("chunks[-1].usage"),
        "python usage: expected chunks[-1].usage, got: {expr}"
    );
}

#[test]
fn accessor_usage_total_tokens_does_not_route_via_chunks() {
    // `usage` is intentionally NOT a streaming-virtual root (it overlaps the
    // non-streaming response shape). The accessor must return None so the
    // assertion falls through to the normal field-path codegen.
    assert!(StreamingFieldResolver::accessor("usage.total_tokens", "python", "chunks").is_none());
}

#[test]
fn accessor_unknown_field_returns_none() {
    assert_eq!(
        StreamingFieldResolver::accessor("nonexistent_field", "rust", "chunks"),
        None
    );
}

// -----------------------------------------------------------------------
// Deep-path tests: tool_calls[0].function.name and tool_calls[0].id
// -----------------------------------------------------------------------

#[test]
fn is_streaming_virtual_field_recognizes_deep_tool_calls_paths() {
    assert!(
        is_streaming_virtual_field("tool_calls[0].function.name"),
        "tool_calls[0].function.name should be recognized"
    );
    assert!(
        is_streaming_virtual_field("tool_calls[0].id"),
        "tool_calls[0].id should be recognized"
    );
    assert!(
        is_streaming_virtual_field("tool_calls[1].function.arguments"),
        "tool_calls[1].function.arguments should be recognized"
    );
    // bare root still recognized
    assert!(is_streaming_virtual_field("tool_calls"));
    // unrelated deep path must NOT be recognized
    assert!(!is_streaming_virtual_field("tool_calls_extra.name"));
    assert!(!is_streaming_virtual_field("nonexistent[0].field"));
}

/// Snapshot: `tool_calls[0].function.name` for Rust, Kotlin, TypeScript.
///
/// These three languages cover the main accessor styles:
/// - Rust: snake_case field, explicit `[0]` index on collected Vec
/// - Kotlin: camelCase method calls with `.first()` for index 0
/// - TypeScript/Node: camelCase properties with `[0]` bracket
#[test]
fn deep_tool_calls_function_name_snapshot_rust_kotlin_ts() {
    let field = "tool_calls[0].function.name";

    let rust = StreamingFieldResolver::accessor(field, "rust", "chunks").unwrap();
    // Rust: Option-aware chain over the iterator — `.nth(0)` then `.and_then`
    // on each Option-wrapped field (function is Option<StreamFunctionCall>,
    // name is Option<String>). Final `.unwrap_or("")` yields `&str`.
    assert!(
        rust.contains(".nth(0)"),
        "rust deep tool_calls: expected .nth(0) iterator index, got: {rust}"
    );
    assert!(
        rust.contains("x.function.as_ref()"),
        "rust deep tool_calls: expected Option-aware function access, got: {rust}"
    );
    assert!(
        rust.contains("x.name.as_deref()"),
        "rust deep tool_calls: expected Option-aware name leaf, got: {rust}"
    );
    assert!(
        !rust.contains("// skipped"),
        "rust deep tool_calls: must not emit skip comment, got: {rust}"
    );

    let kotlin = StreamingFieldResolver::accessor(field, "kotlin", "chunks").unwrap();
    // Kotlin: uses .first() for index 0, then .function().name()
    assert!(
        kotlin.contains(".first()"),
        "kotlin deep tool_calls: expected .first() for index 0, got: {kotlin}"
    );
    assert!(
        kotlin.contains(".function()"),
        "kotlin deep tool_calls: expected .function() method call, got: {kotlin}"
    );
    assert!(
        kotlin.contains(".name()"),
        "kotlin deep tool_calls: expected .name() method call, got: {kotlin}"
    );

    let ts = StreamingFieldResolver::accessor(field, "node", "chunks").unwrap();
    // TypeScript/Node: uses [0] then .function.name (camelCase)
    assert!(
        ts.contains("[0]"),
        "ts/node deep tool_calls: expected [0] index, got: {ts}"
    );
    assert!(
        ts.contains(".function"),
        "ts/node deep tool_calls: expected .function segment, got: {ts}"
    );
    assert!(
        ts.contains(".name"),
        "ts/node deep tool_calls: expected .name segment, got: {ts}"
    );
}

#[test]
fn deep_tool_calls_id_snapshot_all_langs() {
    let field = "tool_calls[0].id";

    let rust = StreamingFieldResolver::accessor(field, "rust", "chunks").unwrap();
    assert!(rust.contains(".nth(0)"), "rust: {rust}");
    assert!(rust.contains("x.id.as_deref()"), "rust: {rust}");

    let go = StreamingFieldResolver::accessor(field, "go", "chunks").unwrap();
    assert!(go.contains("[0]"), "go: {go}");
    // Go: ID is a well-known initialism → uppercase
    assert!(go.contains(".ID"), "go: expected .ID initialism, got: {go}");

    let python = StreamingFieldResolver::accessor(field, "python", "chunks").unwrap();
    assert!(python.contains("[0]"), "python: {python}");
    assert!(python.contains(".id"), "python: {python}");

    let php = StreamingFieldResolver::accessor(field, "php", "chunks").unwrap();
    assert!(php.contains("[0]"), "php: {php}");
    assert!(php.contains("->id"), "php: expected ->id, got: {php}");

    let java = StreamingFieldResolver::accessor(field, "java", "chunks").unwrap();
    assert!(java.contains(".get(0)"), "java: expected .get(0), got: {java}");
    assert!(java.contains(".id()"), "java: expected .id() method call, got: {java}");

    let csharp = StreamingFieldResolver::accessor(field, "csharp", "chunks").unwrap();
    assert!(csharp.contains("[0]"), "csharp: {csharp}");
    assert!(
        csharp.contains(".Id"),
        "csharp: expected .Id (PascalCase), got: {csharp}"
    );

    let elixir = StreamingFieldResolver::accessor(field, "elixir", "chunks").unwrap();
    assert!(elixir.contains("Enum.at("), "elixir: expected Enum.at(, got: {elixir}");
    assert!(elixir.contains(".id"), "elixir: {elixir}");
}

#[test]
fn deep_tool_calls_function_name_snapshot_python_elixir_zig() {
    let field = "tool_calls[0].function.name";

    let python = StreamingFieldResolver::accessor(field, "python", "chunks").unwrap();
    assert!(python.contains("[0]"), "python: {python}");
    assert!(python.contains(".function"), "python: {python}");
    assert!(python.contains(".name"), "python: {python}");

    let elixir = StreamingFieldResolver::accessor(field, "elixir", "chunks").unwrap();
    // Elixir: Enum.at(…, 0).function.name
    assert!(elixir.contains("Enum.at("), "elixir: {elixir}");
    assert!(elixir.contains(".function"), "elixir: {elixir}");
    assert!(elixir.contains(".name"), "elixir: {elixir}");

    // Zig stores chunks as JSON strings, not typed records — deep
    // tool_calls paths are unsupported and resolve to None so the
    // assertion site can skip them.
    assert!(
        StreamingFieldResolver::accessor(field, "zig", "chunks").is_none(),
        "zig: expected None for deep tool_calls path"
    );
}

#[test]
fn parse_tail_parses_index_then_field_segments() {
    let segs = parse_tail("[0].function.name");
    assert_eq!(segs.len(), 3, "expected 3 segments, got: {segs:?}");
    assert_eq!(segs[0], TailSeg::Index(0));
    assert_eq!(segs[1], TailSeg::Field("function".to_string()));
    assert_eq!(segs[2], TailSeg::Field("name".to_string()));
}

#[test]
fn parse_tail_parses_simple_index_field() {
    let segs = parse_tail("[0].id");
    assert_eq!(segs.len(), 2, "expected 2 segments, got: {segs:?}");
    assert_eq!(segs[0], TailSeg::Index(0));
    assert_eq!(segs[1], TailSeg::Field("id".to_string()));
}

#[test]
fn parse_tail_handles_nonzero_index() {
    let segs = parse_tail("[2].function.arguments");
    assert_eq!(segs[0], TailSeg::Index(2));
    assert_eq!(segs[1], TailSeg::Field("function".to_string()));
    assert_eq!(segs[2], TailSeg::Field("arguments".to_string()));
}

// -----------------------------------------------------------------------
// Swift-specific accessor tests
// -----------------------------------------------------------------------

#[test]
fn accessor_chunks_length_swift_uses_count() {
    let swift = StreamingFieldResolver::accessor("chunks.length", "swift", "chunks").unwrap();
    assert_eq!(swift, "chunks.count", "swift chunks.length: {swift}");
}

#[test]
fn accessor_stream_content_swift_uses_swift_closures() {
    let expr = StreamingFieldResolver::accessor("stream_content", "swift", "chunks").unwrap();
    // Must use Swift closure syntax (`{ ... }`) not JS arrow (`=>`)
    assert!(
        expr.contains("{ c in"),
        "swift stream_content must use Swift closure syntax, got: {expr}"
    );
    assert!(
        !expr.contains("=>"),
        "swift stream_content must not contain JS arrow `=>`, got: {expr}"
    );
    // Fields are accessed as first-class Codable struct properties (no parens).
    assert!(
        expr.contains("c.choices"),
        "swift stream_content must use property access for choices, got: {expr}"
    );
    assert!(
        expr.contains("ch.delta"),
        "swift stream_content must use property access for delta, got: {expr}"
    );
    assert!(
        expr.contains("ch.delta.content"),
        "swift stream_content must use property access for content, got: {expr}"
    );
    // First-class Codable struct fields are native Swift strings — no .toString() wrap.
    assert!(
        !expr.contains(".toString()"),
        "swift stream_content must NOT wrap first-class String fields with .toString(), got: {expr}"
    );
    assert!(
        expr.contains(".joined()"),
        "swift stream_content must join with .joined(), got: {expr}"
    );
    // Must not use JS .length or .join('')
    assert!(
        !expr.contains(".length"),
        "swift stream_content must not use JS .length, got: {expr}"
    );
    assert!(
        !expr.contains(".join("),
        "swift stream_content must not use JS .join(, got: {expr}"
    );
}

#[test]
fn accessor_stream_complete_swift_uses_swift_syntax() {
    let expr = StreamingFieldResolver::accessor("stream_complete", "swift", "chunks").unwrap();
    // Must use Swift isEmpty / last! syntax, not JS .length
    assert!(
        expr.contains("isEmpty"),
        "swift stream_complete must use .isEmpty, got: {expr}"
    );
    assert!(
        expr.contains(".last!"),
        "swift stream_complete must use .last!, got: {expr}"
    );
    // Property access for first-class fields (no parens, camelCase).
    assert!(
        expr.contains(".choices.first"),
        "swift stream_complete must use property access on choices, got: {expr}"
    );
    assert!(
        expr.contains("finishReason"),
        "swift stream_complete must reference lowerCamelCase finishReason, got: {expr}"
    );
    assert!(
        !expr.contains(".length"),
        "swift stream_complete must not use JS .length, got: {expr}"
    );
    assert!(
        !expr.contains("!= null"),
        "swift stream_complete must not use JS `!= null`, got: {expr}"
    );
}

#[test]
fn accessor_tool_calls_swift_uses_swift_flatmap() {
    let expr = StreamingFieldResolver::accessor("tool_calls", "swift", "chunks").unwrap();
    // Must use Swift closure syntax, not JS arrow
    assert!(
        !expr.contains("=>"),
        "swift tool_calls must not contain JS arrow `=>`, got: {expr}"
    );
    assert!(
        expr.contains("flatMap"),
        "swift tool_calls must use flatMap, got: {expr}"
    );
    // First-class struct property access (no parens, lowerCamelCase).
    assert!(
        expr.contains("c.choices.first"),
        "swift tool_calls must use property access on choices, got: {expr}"
    );
    assert!(
        expr.contains("ch.delta.toolCalls"),
        "swift tool_calls must use lowerCamelCase toolCalls property, got: {expr}"
    );
}

#[test]
fn accessor_tool_calls_deep_path_swift_uses_method_calls_with_optional_chain() {
    // `tool_calls[0].function.name`: StreamToolCall is a first-class Codable
    // struct, so deep fields use lowerCamelCase property access. The first
    // field segment after `[N]` is non-optional (array index yields a value),
    // so `.function` uses plain `.`; subsequent segments chain with `?.`
    // because `function` itself is `Optional<StreamFunctionCall>`.
    let expr = StreamingFieldResolver::accessor("tool_calls[0].function.name", "swift", "chunks").unwrap();
    assert!(
        expr.contains("[0].function"),
        "swift deep tool_calls must use plain `.function` directly after array index (non-optional), got: {expr}"
    );
    assert!(
        expr.contains("?.name"),
        "swift deep tool_calls must use ?.name property access, got: {expr}"
    );
    assert!(
        !expr.contains(".toString()"),
        "swift deep tool_calls must NOT wrap first-class String fields with .toString(), got: {expr}"
    );
    assert!(
        !expr.contains("=>"),
        "swift deep tool_calls must not use JS arrow syntax, got: {expr}"
    );
}

#[test]
fn accessor_finish_reason_swift_uses_swift_syntax() {
    let expr = StreamingFieldResolver::accessor("finish_reason", "swift", "chunks").unwrap();
    // Must use Swift isEmpty / last! syntax, not JS .length / undefined
    assert!(
        expr.contains("isEmpty"),
        "swift finish_reason must use .isEmpty, got: {expr}"
    );
    assert!(
        expr.contains(".last!"),
        "swift finish_reason must use .last!, got: {expr}"
    );
    assert!(
        expr.contains("finishReason"),
        "swift finish_reason must use lowerCamelCase finishReason property, got: {expr}"
    );
    // First-class Swift enum: use .rawValue for the serde wire string, not .toString().
    assert!(
        expr.contains(".rawValue"),
        "swift finish_reason must read enum .rawValue, got: {expr}"
    );
    assert!(
        !expr.contains("undefined"),
        "swift finish_reason must not use JS `undefined`, got: {expr}"
    );
    assert!(
        !expr.contains(".length"),
        "swift finish_reason must not use JS .length, got: {expr}"
    );
}

#[test]
fn accessor_usage_swift_uses_swift_syntax() {
    let expr = StreamingFieldResolver::accessor("usage", "swift", "chunks").unwrap();
    // Must use Swift isEmpty / last! syntax, not JS .length / undefined
    assert!(expr.contains("isEmpty"), "swift usage must use .isEmpty, got: {expr}");
    assert!(expr.contains(".last!"), "swift usage must use .last!, got: {expr}");
    // First-class Codable property access (no parens).
    assert!(
        expr.contains(".usage"),
        "swift usage must reference .usage property, got: {expr}"
    );
    assert!(
        !expr.contains("usage()"),
        "swift usage must NOT use method-call syntax, got: {expr}"
    );
    assert!(
        !expr.contains("undefined"),
        "swift usage must not use JS `undefined`, got: {expr}"
    );
    assert!(
        !expr.contains(".length"),
        "swift usage must not use JS .length, got: {expr}"
    );
}

// ---------------------------------------------------------------------------
// Bug regression: kotlin_android streaming assertions use property access
// ---------------------------------------------------------------------------

#[test]
fn kotlin_android_collect_snippet_uses_flow_to_list() {
    let snip = StreamingFieldResolver::collect_snippet("kotlin_android", "result", "chunks").unwrap();
    // Flow.toList() — not Iterator.asSequence().toList()
    assert!(
        snip.contains("result.toList()"),
        "kotlin_android collect must use Flow.toList(), got: {snip}"
    );
    assert!(
        !snip.contains("asSequence()"),
        "kotlin_android collect must NOT use asSequence(), got: {snip}"
    );
}

#[test]
fn kotlin_android_stream_content_uses_property_access() {
    let expr = StreamingFieldResolver::accessor("stream_content", "kotlin_android", "chunks").unwrap();
    assert!(
        expr.contains(".choices"),
        "kotlin_android stream_content must use .choices property, got: {expr}"
    );
    assert!(
        !expr.contains(".choices()"),
        "kotlin_android stream_content must NOT use .choices() getter, got: {expr}"
    );
    assert!(
        expr.contains(".delta"),
        "kotlin_android stream_content must use .delta property, got: {expr}"
    );
    assert!(
        !expr.contains(".delta()"),
        "kotlin_android stream_content must NOT use .delta() getter, got: {expr}"
    );
    assert!(
        expr.contains(".content"),
        "kotlin_android stream_content must use .content property, got: {expr}"
    );
    assert!(
        !expr.contains(".content()"),
        "kotlin_android stream_content must NOT use .content() getter, got: {expr}"
    );
}

#[test]
fn kotlin_android_finish_reason_uses_name_lowercase_not_get_value() {
    let expr = StreamingFieldResolver::accessor("finish_reason", "kotlin_android", "chunks").unwrap();
    assert!(
        expr.contains(".finishReason"),
        "kotlin_android finish_reason must use .finishReason property, got: {expr}"
    );
    assert!(
        !expr.contains(".finishReason()"),
        "kotlin_android finish_reason must NOT use .finishReason() getter, got: {expr}"
    );
    assert!(
        expr.contains(".name"),
        "kotlin_android finish_reason must use .name for enum wire value, got: {expr}"
    );
    assert!(
        expr.contains(".lowercase()"),
        "kotlin_android finish_reason must use .lowercase(), got: {expr}"
    );
    assert!(
        !expr.contains(".getValue()"),
        "kotlin_android finish_reason must NOT use .getValue(), got: {expr}"
    );
}

#[test]
fn kotlin_android_usage_uses_property_access() {
    let expr = StreamingFieldResolver::accessor("usage", "kotlin_android", "chunks").unwrap();
    assert!(
        expr.contains(".usage"),
        "kotlin_android usage must use .usage property, got: {expr}"
    );
    assert!(
        !expr.contains(".usage()"),
        "kotlin_android usage must NOT use .usage() getter, got: {expr}"
    );
}

#[test]
fn kotlin_android_deep_tool_calls_uses_property_access() {
    let expr = StreamingFieldResolver::accessor("tool_calls[0].function.name", "kotlin_android", "chunks").unwrap();
    assert!(
        expr.contains(".function"),
        "kotlin_android deep tool_calls must use .function property, got: {expr}"
    );
    assert!(
        !expr.contains(".function()"),
        "kotlin_android deep tool_calls must NOT use .function() getter, got: {expr}"
    );
    assert!(
        expr.contains(".name"),
        "kotlin_android deep tool_calls must use .name property, got: {expr}"
    );
    assert!(
        !expr.contains(".name()"),
        "kotlin_android deep tool_calls must NOT use .name() getter, got: {expr}"
    );
}

// ---------------------------------------------------------------------------
// Ruby-specific accessor tests
// ---------------------------------------------------------------------------

#[test]
fn ruby_stream_content_uses_ruby_block_syntax() {
    let expr = StreamingFieldResolver::accessor("stream_content", "ruby", "chunks").unwrap();
    // Must use Ruby block syntax, not JS arrow function
    assert!(
        !expr.contains("=>"),
        "ruby stream_content must not contain JS arrow `=>`, got: {expr}"
    );
    assert!(
        expr.contains("{ |c|"),
        "ruby stream_content must use Ruby block `{{ |c|`, got: {expr}"
    );
    assert!(
        expr.contains(".join"),
        "ruby stream_content must use .join, got: {expr}"
    );
    assert!(
        expr.contains("c.choices"),
        "ruby stream_content must access .choices, got: {expr}"
    );
    assert!(
        expr.contains(".delta"),
        "ruby stream_content must access .delta, got: {expr}"
    );
    assert!(
        expr.contains(".content"),
        "ruby stream_content must access .content, got: {expr}"
    );
    // Must not use TypeScript optional-chaining syntax
    assert!(
        !expr.contains("?.["),
        "ruby stream_content must not use TS optional chaining `?.[`, got: {expr}"
    );
}

#[test]
fn ruby_stream_complete_uses_ruby_nil_predicate() {
    let expr = StreamingFieldResolver::accessor("stream_complete", "ruby", "chunks").unwrap();
    // Must use Ruby nil? not JS != null
    assert!(
        !expr.contains("!= null"),
        "ruby stream_complete must not use JS `!= null`, got: {expr}"
    );
    assert!(
        expr.contains(".nil?"),
        "ruby stream_complete must use .nil?, got: {expr}"
    );
    assert!(
        expr.contains(".empty?"),
        "ruby stream_complete must use .empty?, got: {expr}"
    );
    assert!(
        expr.contains("finish_reason"),
        "ruby stream_complete must reference finish_reason, got: {expr}"
    );
    // Must not use TypeScript optional-chaining syntax
    assert!(
        !expr.contains("?.["),
        "ruby stream_complete must not use TS optional chaining `?.[`, got: {expr}"
    );
}

#[test]
fn ruby_tool_calls_uses_ruby_flat_map_block() {
    let expr = StreamingFieldResolver::accessor("tool_calls", "ruby", "chunks").unwrap();
    // Must use Ruby flat_map with block syntax, not JS flatMap with arrow
    assert!(
        !expr.contains("=>"),
        "ruby tool_calls must not contain JS arrow `=>`, got: {expr}"
    );
    assert!(
        expr.contains("flat_map"),
        "ruby tool_calls must use flat_map, got: {expr}"
    );
    assert!(
        expr.contains("{ |c|"),
        "ruby tool_calls must use Ruby block `{{ |c|`, got: {expr}"
    );
    assert!(
        expr.contains("tool_calls"),
        "ruby tool_calls must reference tool_calls, got: {expr}"
    );
    // Must not use TypeScript optional-chaining syntax
    assert!(
        !expr.contains("?.["),
        "ruby tool_calls must not use TS optional chaining `?.[`, got: {expr}"
    );
}

#[test]
fn ruby_finish_reason_uses_to_s_not_get_value() {
    let expr = StreamingFieldResolver::accessor("finish_reason", "ruby", "chunks").unwrap();
    // Must use Ruby .to_s, not JS undefined or TS syntax
    assert!(
        !expr.contains("undefined"),
        "ruby finish_reason must not use JS `undefined`, got: {expr}"
    );
    assert!(
        !expr.contains(".length"),
        "ruby finish_reason must not use JS .length, got: {expr}"
    );
    assert!(
        expr.contains(".empty?"),
        "ruby finish_reason must use .empty?, got: {expr}"
    );
    assert!(
        expr.contains("finish_reason"),
        "ruby finish_reason must reference finish_reason, got: {expr}"
    );
    assert!(
        expr.contains(".to_s"),
        "ruby finish_reason must call .to_s on the enum, got: {expr}"
    );
}
