use super::external_library_loader::{frb_init_prologue_replacement, rewrite_frb_external_library_loader};
use super::*;

#[test]
fn named_struct_payload_preserves_field0() {
    // The factory `field0` is left in place — see `payload_param_name` for
    // the rationale. Renaming here without also patching lib.freezed.dart
    // creates a constructor-redirect type mismatch.
    let input = r#"sealed class FormatMetadata with _$FormatMetadata {
  const FormatMetadata._();

  const factory FormatMetadata.pdf({required PdfMetadata field0}) =
      FormatMetadata_Pdf;
  const factory FormatMetadata.docx({required DocxMetadata field0}) =
      FormatMetadata_Docx;
}
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(
        out.contains("required PdfMetadata field0"),
        "PdfMetadata payload should keep `field0`, got:\n{out}"
    );
    assert!(
        out.contains("required DocxMetadata field0"),
        "DocxMetadata payload should keep `field0`, got:\n{out}"
    );
}

#[test]
fn primitive_payload_preserves_field0() {
    let input = r#"  const factory OutputFormat.custom({required String field0}) =
      OutputFormat_Custom;
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(
        out.contains("required String field0"),
        "String payload should keep `field0`, got:\n{out}"
    );
}

#[test]
fn multi_field_tuple_preserves_field_indices() {
    let input = r#"  const factory Point.xy({required int field0, required int field1}) =
      Point_Xy;
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(
        out.contains("required int field0"),
        "first tuple field should keep `field0`, got:\n{out}"
    );
    assert!(
        out.contains("required int field1"),
        "second tuple field should keep `field1`, got:\n{out}"
    );
}

#[test]
fn named_struct_field_is_preserved() {
    // frb already emits the struct field name when the variant carries a
    // named struct payload (struct variant, not tuple variant). The
    // rewriter must not touch these.
    let input = r#"  const factory Shape.rect({required double width, required double height}) =
      Shape_Rect;
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(
        out.contains("required double width"),
        "named field `width` must be preserved, got:\n{out}"
    );
    assert!(
        out.contains("required double height"),
        "named field `height` must be preserved, got:\n{out}"
    );
}

#[test]
fn non_variant_lines_are_untouched() {
    // Regular function declarations, top-level classes, comments, etc.
    // must round-trip unchanged.
    let input = r#"// This file is automatically generated.
import 'package:freezed_annotation/freezed_annotation.dart';

Future<int> extractBytes({required List<int> content}) =>
    RustLib.instance.api.crateExtractBytes(content: content);

class Foo {
  final int field0;
  Foo({required this.field0});
}
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert_eq!(out, input, "non-variant code must round-trip unchanged");
}

#[test]
fn unrelated_payload_preserves_field0() {
    let input = r#"  const factory Drawable.image({required Bitmap field0}) =
      Drawable_Image;
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(
        out.contains("required Bitmap field0"),
        "unrelated payload type should keep `field0`, got:\n{out}"
    );
}

#[test]
fn nullable_payload_preserves_field0() {
    let input = r#"  const factory Either.left({required LeftValue? field0}) =
      Either_Left;
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(
        out.contains("required LeftValue? field0"),
        "nullable payload should keep `field0`, got:\n{out}"
    );
}

#[test]
fn realistic_sample_crate_format_metadata_block_preserves_field0() {
    let input = r#"sealed class FormatMetadata with _$FormatMetadata {
  const FormatMetadata._();

  const factory FormatMetadata.pdf({required PdfMetadata field0}) =
      FormatMetadata_Pdf;
  const factory FormatMetadata.docx({required DocxMetadata field0}) =
      FormatMetadata_Docx;
  const factory FormatMetadata.excel({required ExcelMetadata field0}) =
      FormatMetadata_Excel;
  const factory FormatMetadata.code({required String field0}) =
      FormatMetadata_Code;
}
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(out.contains("required PdfMetadata field0"));
    assert!(out.contains("required DocxMetadata field0"));
    assert!(out.contains("required ExcelMetadata field0"));
    assert!(out.contains("required String field0"));
    // Structural lines preserved.
    assert!(out.contains("sealed class FormatMetadata"));
    assert!(out.contains("FormatMetadata_Pdf"));
}

#[test]
fn idempotent_when_run_twice() {
    let input = r#"  const factory FormatMetadata.pdf({required PdfMetadata field0}) =
      FormatMetadata_Pdf;
"#;
    let once = rewrite_frb_sealed_variants(input);
    let twice = rewrite_frb_sealed_variants(&once);
    assert_eq!(once, twice, "rewriter must be idempotent");
}

#[test]
fn multiple_distinct_sealed_class_variants_preserve_field0() {
    let input = r#"sealed class FormatMetadata with _$FormatMetadata {
  const FormatMetadata._();

  const factory FormatMetadata.pdf({required PdfMetadata field0}) =
      FormatMetadata_Pdf;
  const factory FormatMetadata.docx({required DocxMetadata field0}) =
      FormatMetadata_Docx;
}

sealed class OutputFormat with _$OutputFormat {
  const OutputFormat._();

  const factory OutputFormat.custom({required String field0}) =
      OutputFormat_Custom;
  const factory OutputFormat.json({required JsonConfig field0}) =
      OutputFormat_Json;
}
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(out.contains("required PdfMetadata field0"));
    assert!(out.contains("required DocxMetadata field0"));
    assert!(out.contains("required String field0"));
    assert!(out.contains("required JsonConfig field0"));
}

/// A minimal `frb_generated.dart` carrying the FRB entrypoint + loader config,
/// mirroring the real flutter_rust_bridge 2.x output shape.
fn frb_generated_fixture() -> &'static str {
    r#"// @generated by `flutter_rust_bridge`@ 2.12.0.

import 'dart:async';
import 'dart:convert';
import 'frb_generated.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';

class RustLib extends BaseEntrypoint<RustLibApi, RustLibApiImpl, RustLibWire> {
  RustLib._();

  /// Initialize flutter_rust_bridge
  static Future<void> init({
    RustLibApi? api,
    BaseHandler? handler,
    ExternalLibrary? externalLibrary,
    bool forceSameCodegenVersion = true,
  }) async {
    await instance.initImpl(
      api: api,
      handler: handler,
      externalLibrary: externalLibrary,
      forceSameCodegenVersion: forceSameCodegenVersion,
    );
  }

  static const kDefaultExternalLibraryLoaderConfig =
      ExternalLibraryLoaderConfig(
        stem: 'sample_router_dart',
        ioDirectory: 'rust/target/release/',
        webPrefix: 'pkg/',
        wasmBindgenName: 'wasm_bindgen',
      );
}
"#
}

#[test]
fn loader_rewrite_injecsample_package_relative_resolution() {
    let out = rewrite_frb_external_library_loader(
        frb_generated_fixture(),
        "sample_router",
        "sample_router",
        "sample_router_dart",
    );
    assert!(
        out.contains("externalLibrary ??= await _alefResolveExternalLibrary();"),
        "init must resolve the package-relative library, got:\n{out}"
    );
    assert!(
        out.contains("Isolate.resolvePackageUri(_DartCore.Uri.parse('package:sample_router/sample_router.dart'))"),
        "loader must resolve the package URI, got:\n{out}"
    );
    assert!(
        out.contains("src/sample_router_bridge_generated/"),
        "loader must target the bridge-generated dir, got:\n{out}"
    );
    assert!(
        out.contains("'sample_router_dart.framework'"),
        "missing macOS framework candidate, got:\n{out}"
    );
    assert!(
        out.contains("Directory(libPath).existsSync()"),
        "framework bundle candidates must use directory-aware existence checks, got:\n{out}"
    );
    assert!(
        out.contains("'libsample_router_dart.dylib'"),
        "missing macOS dylib candidate, got:\n{out}"
    );
    assert!(
        out.contains("'libsample_router_dart.so'"),
        "missing linux candidate, got:\n{out}"
    );
    assert!(
        out.contains("'sample_router_dart.dll'"),
        "missing windows candidate, got:\n{out}"
    );
    assert!(out.contains("import 'dart:io';"), "must import dart:io, got:\n{out}");
    assert!(
        out.contains("import 'dart:isolate';"),
        "must import dart:isolate, got:\n{out}"
    );
    assert!(
        out.contains("import 'dart:ffi';"),
        "must import dart:ffi for Abi, got:\n{out}"
    );
}

#[test]
fn loader_rewrite_is_idempotent() {
    let once = rewrite_frb_external_library_loader(
        frb_generated_fixture(),
        "sample_router",
        "sample_router",
        "sample_router_dart",
    );
    let twice = rewrite_frb_external_library_loader(&once, "sample_router", "sample_router", "sample_router_dart");
    assert_eq!(once, twice, "loader rewrite must be idempotent");
    assert_eq!(
        twice.matches("import 'dart:io';").count(),
        1,
        "imports must not duplicate"
    );
    assert_eq!(
        twice.matches("_alefResolveExternalLibrary() async").count(),
        1,
        "helper must not be injected twice"
    );
}

#[test]
fn loader_rewrite_is_noop_without_init_prologue() {
    // lib.dart has no FRB entrypoint — must round-trip unchanged.
    let input = "// just some dart\nFuture<int> foo() async => 1;\n";
    assert_eq!(
        rewrite_frb_external_library_loader(input, "sample_router", "sample_router", "sample_router_dart"),
        input
    );
}

#[test]
fn sealed_variant_rewrite_also_applies_loader_fix_via_stem() {
    // `rewrite_frb_sealed_variants` (the wired post-processor) must apply the
    // loader fix when the file carries the FRB loader config, deriving the
    // package/module from the embedded stem.
    let out = rewrite_frb_sealed_variants(frb_generated_fixture());
    assert!(
        out.contains("externalLibrary ??= await _alefResolveExternalLibrary();"),
        "sealed-variant pass must also inject the loader, got:\n{out}"
    );
    assert!(
        out.contains("Isolate.resolvePackageUri(_DartCore.Uri.parse('package:sample_router/sample_router.dart'))"),
        "package derived from stem must be `sample_router`, got:\n{out}"
    );
}

#[test]
fn sealed_variant_rewrite_leaves_lib_dart_loader_untouched() {
    // lib.dart (no loader config) must not gain a loader injection.
    let input = r#"import 'frb_generated.dart';

Future<int> extractBytes({required List<int> content}) =>
    RustLib.instance.api.crateExtractBytes(content: content);
"#;
    let out = rewrite_frb_sealed_variants(input);
    assert!(
        !out.contains("_alefResolveExternalLibrary"),
        "lib.dart must not get a loader, got:\n{out}"
    );
}

#[test]
fn loader_rewrite_includes_rid_aware_path() {
    let out = rewrite_frb_external_library_loader(
        frb_generated_fixture(),
        "sample_router",
        "sample_router",
        "sample_router_dart",
    );
    assert!(
        out.contains("src/native/"),
        "loader must check RID-aware path (src/native/<rid>/), got:\n{out}"
    );
    assert!(
        out.contains("computeRid()"),
        "loader must compute RID from platform and arch, got:\n{out}"
    );
    assert!(
        out.contains("Platform.operatingSystem"),
        "loader must detect operating system, got:\n{out}"
    );
    assert!(
        out.contains("'linux-x64'") || out.contains("linux-"),
        "loader must support linux RID variants, got:\n{out}"
    );
    assert!(
        out.contains("'macos-arm64'") || out.contains("macos-"),
        "loader must support macos RID variants, got:\n{out}"
    );
    assert!(
        out.contains("'windows-x64'") || out.contains("windows-"),
        "loader must support windows RID variants, got:\n{out}"
    );
    // RID-aware check should come before legacy fallback
    let rid_pos = out.find("src/native/").expect("RID path must exist");
    let legacy_pos = out
        .find("src/sample_router_bridge_generated/")
        .expect("legacy path must exist");
    assert!(
        rid_pos < legacy_pos,
        "RID-aware check must come before legacy fallback, got:\n{out}"
    );
}

#[test]
fn filter_excluded_functions_removes_multiline_functions() {
    let input = r#"/// Pass `metadata` as `null` when the caller has no extraction metadata available;
/// the metadata bonus simply isn't applied in that case. Texts shorter than
/// `MIN_TEXT_LENGTH` short-circuit to `0.1` regardless of metadata.
Future<double> calculateQualityScore({
  required String text,
  Map<String, String>? metadata,
}) => RustLib.instance.api.crateCalculateQualityScore(
  text: text,
  metadata: metadata,
);

Future<ExtractionResult> extractBytes(
  {required Uint8List content, required String mimeType}) =>
    RustLib.instance.api.crateExtractBytes(content: content, mimeType: mimeType);
"#;
    let exclude_set = std::collections::HashSet::from(["calculate_quality_score"]);
    let out = filter_excluded_functions(input, &exclude_set);

    // The excluded function and its doc comments should be removed
    assert!(
        !out.contains("calculateQualityScore"),
        "excluded function calculateQualityScore must be removed, got:\n{out}"
    );
    assert!(
        !out.contains("MIN_TEXT_LENGTH"),
        "doc comments for excluded function must be removed, got:\n{out}"
    );

    // Other functions should remain
    assert!(
        out.contains("extractBytes"),
        "non-excluded function extractBytes must remain, got:\n{out}"
    );
}

#[test]
fn fix_handler_executor_calls_adds_async_to_closures() {
    // When `handler` is a callback function parameter, handler.executeNormal/executeSync
    // calls should be rewritten. The handler receives the task result, not the task itself,
    // so we invoke the task executor and let Rust handle invoking the callback.
    let input = r#"Future<String> processRequest(Request req, FutureOr<String> Function(String) handler) {
  return handler.executeNormal(
    SyncTask(request: req),
  );
}

int handleRoute(RouteData route, FutureOr<int> Function(String) handler) {
  return handler.executeSync(
    RouteTask(data: route),
  );
}
"#;
    let out = fix_handler_executor_calls(input);

    // The handler is serialized and passed to Rust, so we invoke the task executor, not the handler
    assert!(
        out.contains(".executeNormal();") || out.contains(".executeSync();"),
        "expected task executor method calls in output, got:\n{out}"
    );
    assert!(
        !out.contains("handler("),
        "handler callback should NOT be invoked directly (it's serialized), got:\n{out}"
    );
    assert!(
        !out.contains("handler.executeSync(") && !out.contains("handler.executeNormal("),
        "handler.executeSync/executeNormal method calls should be removed (moved to task), got:\n{out}"
    );

    // Verify no double-await
    assert!(
        !out.contains("await await"),
        "should not have duplicate awaits, got:\n{out}"
    );
}

#[test]
fn fix_handler_executor_calls_preserves_super_handler_without_parameter() {
    // When a method does NOT have `handler` as a parameter, handler.execute* calls
    // refer to `super.handler` (the class field) and should NOT be rewritten.
    let input = r#"  @override
  App crateServiceApiAppNew() {
    return handler.executeSync(
      SyncTask(
        callFfi: () {
          final serializer = SseSerializer(generalizedFrbRustBinding);
          return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 5)!;
        },
        codec: SseCodec(
          decodeSuccessData: sse_decode_app,
          decodeErrorData: null,
        ),
        constMeta: kConstMeta,
        argValues: [],
        apiImpl: this,
      ),
    );
  }
"#;
    let out = fix_handler_executor_calls(input);

    // The method has no `handler` parameter, so handler.executeSync should NOT be rewritten
    assert!(
        out.contains("handler.executeSync("),
        "handler.executeSync should be preserved when handler is not a parameter, got:\n{out}"
    );
    assert!(
        !out.contains("handler.executeNormal("),
        "no executeNormal should be present in input"
    );
}

#[test]
fn fix_handler_executor_calls_rewrites_when_handler_is_parameter() {
    // When a method HAS `handler` as a parameter, handler.execute* calls should be rewritten
    let input = r#"  @override
  int crateServiceApiAppConnect({
    required App that,
    required String path,
    required FutureOr<String> Function(String) handler,
  }) {
    return handler.executeSync(
      SyncTask(
        callFfi: () {
          final serializer = SseSerializer(generalizedFrbRustBinding);
          sse_encode_App(that, serializer);
          sse_encode_String(path, serializer);
          return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1)!;
        },
        codec: SseCodec(
          decodeSuccessData: sse_decode_i_32,
          decodeErrorData: null,
        ),
        constMeta: kConstMeta,
        argValues: [that, path, handler],
        apiImpl: this,
      ),
    );
  }
"#;
    let out = fix_handler_executor_calls(input);

    // The method has `handler` as a parameter, so handler.executeSync should be rewritten
    // to call executeSync on the task instead (the handler is serialized, not invoked directly).
    assert!(
        !out.contains("handler.executeSync("),
        "handler.executeSync should be rewritten when handler is a parameter, got:\n{out}"
    );
    assert!(
        out.contains(".executeSync();"),
        "expected `.executeSync();` on the task in output (rewritten), got:\n{out}"
    );
    assert!(
        !out.contains("handler("),
        "handler callback should NOT be invoked directly (it's serialized), got:\n{out}"
    );
}

#[test]
fn fix_handler_executor_calls_strips_trailing_comma_in_wrapper() {
    // The original FRB pattern is: handler.executeNormal(NormalTask(...),)
    // With parentheses on separate lines due to multiline formatting.
    // The rewrite must emit: NormalTask(...).executeNormal();
    let input = r#"  Future<DartHandlerHandler> crateServiceApiDartHandlerHandlerNew({
    required FutureOr<String> Function(String) handler,
  }) {
    return await handler.executeNormal(
      NormalTask(
        callFfi: (port_) {
          final serializer = SseSerializer(generalizedFrbRustBinding);
          sse_encode_DartFn_Inputs_String_Output_String_AnyhowException(
            handler,
            serializer,
          );
        },
        codec: SseCodec(
          decodeSuccessData: sse_decode_Auto_Owned_RustOpaque,
          decodeErrorData: null,
        ),
        constMeta: kConstMeta,
        argValues: [handler],
        apiImpl: this,
      ),
    );
  }
"#;
    let out = fix_handler_executor_calls(input);

    // Should be rewritten to have NormalTask at the start
    assert!(
        out.contains("return await") && out.contains("NormalTask("),
        "NormalTask should follow return await, got:\n{out}"
    );
    // Should NOT have the handler.executeNormal wrapper
    assert!(
        !out.contains("handler.executeNormal("),
        "handler.executeNormal should be removed, got:\n{out}"
    );
    // Should have .executeNormal(); on the task
    assert!(
        out.contains(".executeNormal();"),
        "should have .executeNormal(); on the task, got:\n{out}"
    );
    // Should not have double closing parens or other syntax errors
    assert!(
        !out.contains(")).executeNormal();"),
        "should not have double closing parens, got:\n{out}"
    );
}

#[test]
fn fix_handler_executor_calls_removes_invalid_async_from_class_declaration() {
    let input = r#"class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi async {
  RustLibApiImpl({
    required super.handler,
    required super.wire,
    required super.generalizedFrbRustBinding,
    required super.portManager,
  });

  @override
  int crateServiceApiAppConnect({
    required App that,
    required String path,
    required FutureOr<String> Function(String) handler,
  }) async {
    return await handler("test");
  }
}
"#;
    let out = fix_handler_executor_calls(input);

    // Verify the invalid `async` keyword is removed from class declaration
    assert!(
        !out.contains("implements RustLibApi async {"),
        "class declaration should not have `async` keyword, got:\n{out}"
    );
    assert!(
        out.contains("implements RustLibApi {"),
        "class declaration should have closing brace without `async`, got:\n{out}"
    );

    // Verify the method can still be async
    assert!(
        out.contains(") async {") || out.contains("async {"),
        "method signatures should still be able to use `async`, got:\n{out}"
    );
}

#[test]
fn default_field_rewriter_is_identity_without_ir_metadata() {
    let input = r#"class GenericOptions {
  final String mode;
  final bool enabled;
  final PlatformInt64 retryCount;

  const GenericOptions({
    required this.mode,
    required this.enabled,
    required this.retryCount,
  });
}
"#;
    let out = make_struct_fields_with_defaults_optional(input);

    assert_eq!(out, input, "post-FRB rewriter must not infer defaults by class name");
}

#[test]
fn prologue_replacement_helper_closes_before_init_opens() {
    let replacement = frb_init_prologue_replacement("test_pkg", "test_mod", "test_stem");

    // The format string uses {{ and }} to escape braces (literal { and } in output).
    // Count escaped braces: each {{ becomes { and each }} becomes }.
    let escaped_open = replacement.matches("{{").count();
    let escaped_close = replacement.matches("}}").count();
    assert_eq!(
        escaped_open, escaped_close,
        "escaped brace mismatch (must be in pairs for format escaping): {} {{ vs {} }}",
        escaped_open, escaped_close
    );

    // Check structure: the helper method should close with }} before init() opens.
    // Look for the sequence: `} catch (_) { ... } return null; } ... init({`
    assert!(
        replacement.contains("static Future<ExternalLibrary?> _alefResolveExternalLibrary()"),
        "helper method signature must exist"
    );
    assert!(
        replacement.contains("static Future<void> init({"),
        "init method signature must exist"
    );

    // Verify the closing }} for the helper precedes the init opening
    let helper_ret_null = replacement.find("return null;").expect("helper must have return null");
    let init_sig = replacement
        .find("static Future<void> init({")
        .expect("init sig must exist");
    assert!(helper_ret_null < init_sig, "helper return must precede init signature");
}

#[test]
fn prologue_replacement_uses_abi_current_for_arch_detection() {
    // The generated loader must use Abi.current() from dart:ffi for reliable
    // architecture detection, not fragile Platform.version string parsing.
    let replacement = frb_init_prologue_replacement("test_pkg", "test_mod", "test_stem");

    // Verify Abi.current() is used.
    assert!(
        replacement.contains("final abi = Abi.current();"),
        "must use Abi.current() for architecture detection, got:\n{replacement}"
    );

    // Verify platform-specific Abi constants are mapped correctly.
    assert!(replacement.contains("Abi.linuxX64"), "must check Abi.linuxX64");
    assert!(replacement.contains("Abi.linuxArm64"), "must check Abi.linuxArm64");
    assert!(replacement.contains("Abi.macosX64"), "must check Abi.macosX64");
    assert!(replacement.contains("Abi.macosArm64"), "must check Abi.macosArm64");
    assert!(replacement.contains("Abi.windowsX64"), "must check Abi.windowsX64");
    assert!(replacement.contains("Abi.windowsArm64"), "must check Abi.windowsArm64");

    // Verify RID strings are correctly mapped.
    assert!(replacement.contains("'linux-x64'"), "must map to linux-x64 RID");
    assert!(replacement.contains("'macos-x64'"), "must map to macos-x64 RID");
}

#[test]
fn fix_handler_executor_strips_orphaned_paren_sync_task() {
    // FRB 2.x generates code with orphaned closing paren before .executeSync()
    // Pattern: `),\n).executeSync();`
    // Should become: `).executeSync();`
    let input = r#"  @override
  int crateServiceApiAppConnect({
    required App that,
    required String path,
    required FutureOr<String> Function(String) handler,
  }) {
    return
      SyncTask(
        callFfi: () {
          final serializer = SseSerializer(generalizedFrbRustBinding);
          sse_encode_App(that, serializer);
          sse_encode_String(path, serializer);
          return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1)!;
        },
        codec: SseCodec(
          decodeSuccessData: sse_decode_i_32,
          decodeErrorData: null,
        ),
        constMeta: kConstMeta,
        argValues: [that, path, handler],
        apiImpl: this,
      ),
    ).executeSync();
  }
"#;
    let out = fix_handler_executor_calls(input);

    // The orphaned `)` on the line before `.executeSync()` should be removed
    // Pattern `),\n)` should become just `)`
    assert!(
        out.contains(").executeSync();"),
        "should have ).executeSync(); without orphaned paren, got:\n{out}"
    );
    // Make sure we didn't accidentally create double parens or syntax errors
    let lint_check = out.matches(")).executeSync()").count();
    assert_eq!(
        lint_check, 0,
        "should not have )).executeSync() (double paren), got:\n{out}"
    );
}

#[test]
fn fix_handler_executor_strips_orphaned_paren_async_task() {
    // Same fix for AsyncTask/.executeNormal() pattern
    let input = r#"  @override
  Future<String> crateServiceApiAppHandlerCall({
    required App that,
    required String path,
    required FutureOr<String> Function(String) handler,
  }) async {
    return await
      AsyncTask(
        callFfi: (port_) {
          final serializer = SseSerializer(generalizedFrbRustBinding);
          sse_encode_App(that, serializer);
          sse_encode_String(path, serializer);
          return pdeCallFfiAsync(generalizedFrbRustBinding, serializer, funcId: 42);
        },
        codec: SseCodec(
          decodeSuccessData: sse_decode_String,
          decodeErrorData: null,
        ),
        constMeta: kConstMeta,
        argValues: [that, path, handler],
        apiImpl: this,
      ),
    ).executeNormal();
  }
"#;
    let out = fix_handler_executor_calls(input);

    // The orphaned `)` should be removed
    assert!(
        out.contains(").executeNormal();"),
        "should have ).executeNormal(); without orphaned paren, got:\n{out}"
    );
    let lint_check = out.matches(")).executeNormal()").count();
    assert_eq!(
        lint_check, 0,
        "should not have )).executeNormal() (double paren), got:\n{out}"
    );
}
