import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image_picker_platform_interface/image_picker_platform_interface.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
import 'package:web_shell_core/core_app.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_platform_interface/webview_flutter_platform_interface.dart';

const MethodChannel _platformChannel = SystemChannels.platform;
const _permissionChannel = MethodChannel(
  'flutter.baseflow.com/permissions/methods',
);

const _testEnvironment = ShellEnvironment(
  appName: '测试应用',
  appKey: 'test_app',
  accentColor: Color(0xFF3ED37B),
  backgroundColor: Color(0xFFFFFFFF),
  textColor: Color(0xFF1F2937),
  mutedTextColor: Color(0xFF6B7280),
  initialUrl: 'example.com/login',
);

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  late _FakeWebViewPlatform fakeWebViewPlatform;
  late _FakeImagePickerPlatform fakeImagePickerPlatform;
  late _FakeUrlLauncherPlatform fakeUrlLauncherPlatform;
  late List<String> platformCalls;
  late bool cameraPermissionGranted;

  setUp(() {
    platformCalls = <String>[];
    cameraPermissionGranted = true;
    fakeWebViewPlatform = _FakeWebViewPlatform();
    fakeImagePickerPlatform = _FakeImagePickerPlatform();
    fakeUrlLauncherPlatform = _FakeUrlLauncherPlatform();

    WebViewPlatform.instance = fakeWebViewPlatform;
    ImagePickerPlatform.instance = fakeImagePickerPlatform;
    UrlLauncherPlatform.instance = fakeUrlLauncherPlatform;
    shellCoreTestHooks.initializeEnvironment(_testEnvironment);

    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(_platformChannel, (call) async {
          platformCalls.add(call.method);
          return null;
        });

    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(_permissionChannel, (call) async {
          if (call.method != 'requestPermissions') {
            return null;
          }

          final permissions = (call.arguments as List<Object?>).cast<int>();
          final statusValue = cameraPermissionGranted ? 1 : 0;
          return <int, int>{
            for (final permission in permissions) permission: statusValue,
          };
        });
  });

  tearDown(() {
    debugDefaultTargetPlatformOverride = null;
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(_platformChannel, null);
    TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
        .setMockMethodCallHandler(_permissionChannel, null);
  });

  group('运行时与平台', () {
    test('只支持 Android 内嵌 WebView', () {
      expect(
        shellCoreTestHooks.supportsEmbeddedWebView(
          platform: TargetPlatform.android,
        ),
        isTrue,
      );
      expect(
        shellCoreTestHooks.supportsEmbeddedWebView(
          platform: TargetPlatform.iOS,
        ),
        isFalse,
      );
      expect(
        shellCoreTestHooks.supportsEmbeddedWebView(
          platform: TargetPlatform.macOS,
        ),
        isFalse,
      );
      expect(shellCoreTestHooks.supportsEmbeddedWebView(isWeb: true), isFalse);
    });

    test('enterImmersiveMode 会透传到 SystemChrome', () async {
      await shellCoreTestHooks.enterImmersiveMode();

      expect(platformCalls, contains('SystemChrome.setEnabledSystemUIMode'));
    });

    testWidgets('runShellApp 会设置系统 UI 并启动应用', (tester) async {
      try {
        debugDefaultTargetPlatformOverride = TargetPlatform.linux;

        await runShellApp(_testEnvironment);
        await tester.pump();

        expect(
          platformCalls,
          contains('SystemChrome.setPreferredOrientations'),
        );
        expect(platformCalls, contains('SystemChrome.setEnabledSystemUIMode'));
        expect(platformCalls, contains('SystemChrome.setSystemUIOverlayStyle'));
        expect(find.byType(ShellApp), findsOneWidget);
        expect(find.byType(UnsupportedPlatformPage), findsOneWidget);
      } finally {
        debugDefaultTargetPlatformOverride = null;
      }
    });

    testWidgets('ShellApp 在非 Android 下展示兜底页', (tester) async {
      try {
        debugDefaultTargetPlatformOverride = TargetPlatform.windows;

        await tester.pumpWidget(const ShellApp());

        expect(find.byType(UnsupportedPlatformPage), findsOneWidget);
        expect(find.text('当前平台不支持内嵌 WebView'), findsOneWidget);
      } finally {
        debugDefaultTargetPlatformOverride = null;
      }
    });

    testWidgets('ShellApp 在 Android 下展示 WebView 容器', (tester) async {
      try {
        debugDefaultTargetPlatformOverride = TargetPlatform.android;

        await tester.pumpWidget(const ShellApp());
        await tester.pump();

        expect(
          find.byKey(const ValueKey<String>('fake-webview')),
          findsOneWidget,
        );
        expect(fakeWebViewPlatform.createdControllers, isNotEmpty);
      } finally {
        debugDefaultTargetPlatformOverride = null;
      }
    });
  });

  group('URL 与兼容策略', () {
    test('初始化 URL 时会补全 https 协议', () {
      expect(shellCoreTestHooks.initialUrl, 'https://example.com/login');
      expect(
        shellCoreTestHooks.initialUri,
        Uri.parse('https://example.com/login'),
      );
    });

    test('初始化 URL 会处理空值、协议相对地址和非法地址', () {
      shellCoreTestHooks.initializeEnvironment(
        const ShellEnvironment(
          appName: '空地址',
          appKey: 'empty',
          accentColor: Color(0xFF000000),
          backgroundColor: Color(0xFFFFFFFF),
          textColor: Color(0xFF000000),
          mutedTextColor: Color(0xFF888888),
        ),
      );
      expect(
        shellCoreTestHooks.initialUrl,
        'http://xszy.lzzneng.com/login.html',
      );

      shellCoreTestHooks.initializeEnvironment(
        const ShellEnvironment(
          appName: '协议相对',
          appKey: 'protocol_relative',
          accentColor: Color(0xFF000000),
          backgroundColor: Color(0xFFFFFFFF),
          textColor: Color(0xFF000000),
          mutedTextColor: Color(0xFF888888),
          initialUrl: '//example.org/index.html',
        ),
      );
      expect(shellCoreTestHooks.initialUrl, 'https://example.org/index.html');

      shellCoreTestHooks.initializeEnvironment(
        const ShellEnvironment(
          appName: '非法地址',
          appKey: 'invalid',
          accentColor: Color(0xFF000000),
          backgroundColor: Color(0xFFFFFFFF),
          textColor: Color(0xFF000000),
          mutedTextColor: Color(0xFF888888),
          initialUrl: '/relative-path',
        ),
      );
      expect(
        shellCoreTestHooks.initialUrl,
        'http://xszy.lzzneng.com/login.html',
      );

      shellCoreTestHooks.initializeEnvironment(_testEnvironment);
    });

    test('网络 URL 与 URI 归一化逻辑正确', () {
      expect(shellCoreTestHooks.isNetworkUrl('https://example.com'), isTrue);
      expect(shellCoreTestHooks.isNetworkUrl('http://example.com'), isTrue);
      expect(shellCoreTestHooks.isNetworkUrl('file:///tmp/demo.txt'), isFalse);
      expect(shellCoreTestHooks.defaultPortForScheme('https'), 443);
      expect(shellCoreTestHooks.defaultPortForScheme('custom'), -1);
      expect(shellCoreTestHooks.normalizeComparablePath(''), '/');
      expect(shellCoreTestHooks.normalizeComparablePath('/demo/'), '/demo');
      expect(
        shellCoreTestHooks.normalizeComparableUri(
          Uri.parse('https://Example.com/demo/?a=1'),
        ),
        'https://example.com:443/demo?a=1',
      );
    });

    test('AndroidWebViewInfo 能从 map 构建并输出摘要', () {
      final info = AndroidWebViewInfo.fromMap(<Object?, Object?>{
        'sdkInt': '28',
        'manufacturer': 'Yuanxuan',
        'brand': 'YX',
        'model': 'F136A',
        'webViewDataDirectorySuffix': 'shell',
        'webViewPackageName': 'com.google.android.webview',
        'webViewVersionName': '109.0.0',
        'webViewLongVersionCode': '123',
      });

      expect(info.sdkInt, 28);
      expect(info.manufacturer, 'Yuanxuan');
      expect(info.brand, 'YX');
      expect(info.model, 'F136A');
      expect(info.webViewDataDirectorySuffix, 'shell');
      expect(info.webViewPackageName, 'com.google.android.webview');
      expect(info.webViewVersionName, '109.0.0');
      expect(info.webViewLongVersionCode, 123);
      expect(info.webViewMajorVersion, 109);
      expect(info.isLegacyWebView, isTrue);
      expect(info.isF136A, isTrue);
      expect(info.summary, contains('sdk=28'));
      expect(info.summary, contains('device=Yuanxuan F136A'));
      expect(info.summary, contains('webViewVersion=109.0.0'));
    });

    test('AndroidCompatibilityPlan 会根据设备信息产出恢复策略', () {
      final info = AndroidWebViewInfo(
        sdkInt: 28,
        manufacturer: 'YX',
        brand: 'YX',
        model: 'F136A',
        webViewVersionName: '109.0.1',
      );

      final fallbackPlan = AndroidCompatibilityPlan.fallback();
      final plan = AndroidCompatibilityPlan.fromInfo(info);
      final nullInfoPlan = AndroidCompatibilityPlan.fromInfo(null);

      expect(fallbackPlan.renderModes, isNotEmpty);
      expect(nullInfoPlan.renderModes, isNotEmpty);
      expect(plan.renderModes.first, AndroidRenderMode.hybrid);
      expect(AndroidRenderMode.texture.displayName, '标准模式');
      expect(AndroidRenderMode.hybrid.displayName, '兼容模式');
      expect(plan.useWideViewPort, isTrue);
      expect(plan.suggestWebViewUpdate, isTrue);
      expect(plan.prefersAggressiveRecovery, isTrue);
      expect(plan.describe(), contains('modes='));
      expect(plan.describe(), contains('aggressiveRecovery=true'));
    });

    test('WebView 主版本解析兼容空值和正常值', () {
      expect(shellCoreTestHooks.parseWebViewMajorVersion(null), isNull);
      expect(shellCoreTestHooks.parseWebViewMajorVersion(''), isNull);
      expect(
        shellCoreTestHooks.parseWebViewMajorVersion('122.0.6261.86'),
        122,
      );
    });
  });

  group('Bridge 与错误映射', () {
    test('bridge 脚本包含主要能力与兼容层', () {
      final script = shellCoreTestHooks.buildAppShellBridgeScript();

      expect(script, contains('window.AppShell'));
      expect(script, contains('pickImage'));
      expect(script, contains('captureImage'));
      expect(script, contains('pickFile'));
      expect(script, contains('requestPermissions'));
      expect(script, contains(shellCoreTestHooks.legacyCameraCompatScript));
    });

    test('bridge 注入、响应与 alert 都会调用 JS', () async {
      final platformController = _FakePlatformWebViewController(
        const PlatformWebViewControllerCreationParams(),
      );
      final controller = WebViewController.fromPlatform(platformController);

      await shellCoreTestHooks.injectAppShellBridge(
        controller,
        'https://a.com',
      );
      await shellCoreTestHooks.sendBridgeResponse(
        controller,
        requestId: 'req-1',
        success: true,
        data: <String, Object?>{'ok': true},
      );
      await shellCoreTestHooks.showWebAlert(controller, 'hello');

      expect(platformController.javaScriptCalls, hasLength(3));
      expect(
        platformController.javaScriptCalls.first,
        contains('window.AppShell'),
      );
      expect(
        platformController.javaScriptCalls[1],
        contains('window.__appShellReceiveResponse'),
      );
      expect(platformController.javaScriptCalls.last, contains('window.alert'));
    });

    test('bridge 注入和 alert 的异常会被吞掉', () async {
      final platformController = _FakePlatformWebViewController(
        const PlatformWebViewControllerCreationParams(),
      )..throwOnRunJavaScript = true;
      final controller = WebViewController.fromPlatform(platformController);

      await shellCoreTestHooks.injectAppShellBridge(
        controller,
        'https://a.com',
      );
      await shellCoreTestHooks.sendBridgeResponse(
        controller,
        requestId: 'req-2',
        success: false,
        error: 'boom',
      );
      await shellCoreTestHooks.showWebAlert(controller, 'hello');

      expect(platformController.javaScriptCalls, isEmpty);
    });

    test('错误标题与文案映射正确', () {
      const timeoutError = WebResourceError(
        errorCode: -1,
        description: 'timeout',
        errorType: WebResourceErrorType.timeout,
      );
      const connectError = WebResourceError(
        errorCode: -2,
        description: 'connect',
        errorType: WebResourceErrorType.connect,
      );
      const sslError = WebResourceError(
        errorCode: -3,
        description: 'ssl',
        errorType: WebResourceErrorType.failedSslHandshake,
      );
      const customError = WebResourceError(
        errorCode: -4,
        description: 'custom error',
        errorType: WebResourceErrorType.unknown,
      );

      expect(shellCoreTestHooks.friendlyErrorTitle(timeoutError), '请求超时');
      expect(shellCoreTestHooks.friendlyErrorTitle(connectError), '网络连接失败');
      expect(shellCoreTestHooks.friendlyErrorTitle(sslError), '安全连接失败');
      expect(shellCoreTestHooks.friendlyErrorTitle(customError), '页面加载失败');

      expect(
        shellCoreTestHooks.friendlyErrorMessage(timeoutError),
        '当前网络较慢，请稍后重新加载。',
      );
      expect(
        shellCoreTestHooks.friendlyErrorMessage(connectError),
        '没有成功连接到服务器，请检查网络后重试。',
      );
      expect(
        shellCoreTestHooks.friendlyErrorMessage(sslError),
        '当前站点证书校验失败，请稍后再试。',
      );
      expect(
        shellCoreTestHooks.friendlyErrorMessage(customError),
        'custom error',
      );
    });
  });

  group('媒体与权限工具', () {
    test('序列化 XFile 与 PlatformFile', () async {
      final tempDirectory = await Directory.systemTemp.createTemp('web-shell');
      final file = File('${tempDirectory.path}/demo.txt')
        ..writeAsStringSync('hello');
      final binaryFile = File('${tempDirectory.path}/demo.bin')
        ..writeAsBytesSync(<int>[4, 5, 6]);
      addTearDown(() async {
        if (tempDirectory.existsSync()) {
          await tempDirectory.delete(recursive: true);
        }
      });

      final xFile = XFile(file.path, name: 'demo.txt');
      final serializedXFiles = await shellCoreTestHooks.serializeXFiles(
        <XFile>[xFile],
        responseType: 'dataUrl',
      );
      final serializedPlatformFiles = await shellCoreTestHooks
          .serializePlatformFiles(
            <PlatformFile>[
              PlatformFile(
                name: 'demo.apk',
                size: 3,
                bytes: Uint8List.fromList(<int>[1, 2, 3]),
              ),
              PlatformFile(
                name: 'demo.bin',
                path: binaryFile.path,
                size: 3,
              ),
            ],
            responseType: 'dataUrl',
          );
      final uriStrings = shellCoreTestHooks.xFilesToUriStrings(<XFile>[xFile]);

      expect(serializedXFiles.single['name'], 'demo.txt');
      expect(serializedXFiles.single['mimeType'], 'text/plain');
      expect(serializedXFiles.single['dataUrl'], startsWith('data:text/plain'));
      expect(
        serializedPlatformFiles.first['mimeType'],
        'application/vnd.android.package-archive',
      );
      expect(
        serializedPlatformFiles.first['dataUrl'],
        startsWith('data:application/vnd.android.package-archive'),
      );
      expect(
        serializedPlatformFiles.last['dataUrl'],
        startsWith('data:application/octet-stream'),
      );
      expect(uriStrings.single, Uri.file(file.path).toString());
    });

    test('文件 accept、MIME 与布尔转换正确', () {
      expect(shellCoreTestHooks.acceptsImages(<String>[' image/* ']), isTrue);
      expect(
        shellCoreTestHooks.acceptsOnlyImages(<String>['.png', '.jpg']),
        isTrue,
      );
      expect(
        shellCoreTestHooks.acceptsOnlyImages(<String>['.png', '.pdf']),
        isFalse,
      );
      expect(shellCoreTestHooks.isImageAcceptType('.heic'), isTrue);
      expect(shellCoreTestHooks.guessMimeType('photo.jpeg'), 'image/jpeg');
      expect(shellCoreTestHooks.guessMimeType('photo.webp'), 'image/webp');
      expect(shellCoreTestHooks.guessMimeType('photo.gif'), 'image/gif');
      expect(shellCoreTestHooks.guessMimeType('photo.bmp'), 'image/bmp');
      expect(shellCoreTestHooks.guessMimeType('photo.heic'), 'image/heic');
      expect(shellCoreTestHooks.guessMimeType('photo.heif'), 'image/heif');
      expect(shellCoreTestHooks.guessMimeType('book.pdf'), 'application/pdf');
      expect(
        shellCoreTestHooks.guessMimeType('demo.apk'),
        'application/vnd.android.package-archive',
      );
      expect(
        shellCoreTestHooks.guessMimeType('file.bin'),
        'application/octet-stream',
      );
      expect(shellCoreTestHooks.boolValue(true), isTrue);
      expect(shellCoreTestHooks.boolValue('true'), isTrue);
      expect(shellCoreTestHooks.boolValue(1), isTrue);
      expect(
        shellCoreTestHooks.boolValue('other', defaultValue: true),
        isFalse,
      );
      expect(shellCoreTestHooks.boolValue(0), isFalse);
    });

    test('相机权限通过时会调用 image picker', () async {
      final tempDirectory = await Directory.systemTemp.createTemp('camera-ok');
      final file = File('${tempDirectory.path}/camera.jpg')
        ..writeAsBytesSync(<int>[1, 2, 3]);
      addTearDown(() async {
        if (tempDirectory.existsSync()) {
          await tempDirectory.delete(recursive: true);
        }
      });
      fakeImagePickerPlatform.nextImage = XFile(file.path, name: 'camera.jpg');

      final result = await shellCoreTestHooks.pickCameraImage(ImagePicker());

      expect(result, isNotNull);
      expect(result!.path, file.path);
      expect(fakeImagePickerPlatform.lastSource, ImageSource.camera);
    });

    test('相机权限拒绝时返回 null 并可提示 Web alert', () async {
      cameraPermissionGranted = false;
      final platformController = _FakePlatformWebViewController(
        const PlatformWebViewControllerCreationParams(),
      );
      final controller = WebViewController.fromPlatform(platformController);

      final result = await shellCoreTestHooks.pickCameraImage(
        ImagePicker(),
        showPermissionAlert: true,
        controller: controller,
      );

      expect(result, isNull);
      expect(
        platformController.javaScriptCalls.single,
        contains('请先在系统设置中允许相机权限'),
      );
    });

    test('相机打开失败时返回 null 并可提示 Web alert', () async {
      fakeImagePickerPlatform.shouldThrow = true;
      final platformController = _FakePlatformWebViewController(
        const PlatformWebViewControllerCreationParams(),
      );
      final controller = WebViewController.fromPlatform(platformController);

      final result = await shellCoreTestHooks.pickCameraImage(
        ImagePicker(),
        showPermissionAlert: true,
        controller: controller,
      );

      expect(result, isNull);
      expect(platformController.javaScriptCalls.single, contains('无法打开系统相机'));
    });

    test('权限类型映射正确', () {
      expect(shellCoreTestHooks.permissionForType('camera'), Permission.camera);
      expect(
        shellCoreTestHooks.permissionForType('microphone'),
        Permission.microphone,
      );
      expect(
        shellCoreTestHooks.permissionForType('audio'),
        Permission.microphone,
      );
      expect(
        shellCoreTestHooks.permissionForType('location'),
        Permission.location,
      );
      expect(shellCoreTestHooks.permissionForType('photos'), Permission.photos);
      expect(shellCoreTestHooks.permissionForType('videos'), Permission.videos);
      expect(
        shellCoreTestHooks.permissionForType('storage'),
        Permission.storage,
      );
      expect(shellCoreTestHooks.permissionForType('unknown'), isNull);
    });
  });

  group('导航与组件', () {
    test('导航 scheme 判断和外部打开行为正确', () async {
      expect(
        shellCoreTestHooks.shouldStayInWebView(
          Uri.parse('https://example.com'),
        ),
        isTrue,
      );
      expect(
        shellCoreTestHooks.shouldStayInWebView(
          Uri.parse('javascript:alert(1)'),
        ),
        isTrue,
      );
      expect(
        shellCoreTestHooks.shouldStayInWebView(Uri.parse('tel:10086')),
        isFalse,
      );

      fakeUrlLauncherPlatform.shouldSucceed = true;
      expect(
        await shellCoreTestHooks.openExternalUri(Uri.parse('tel:10086')),
        isTrue,
      );
      expect(fakeUrlLauncherPlatform.lastUrl, 'tel:10086');

      fakeUrlLauncherPlatform.shouldThrow = true;
      expect(
        await shellCoreTestHooks.openExternalUri(Uri.parse('weixin://demo')),
        isFalse,
      );
    });

    testWidgets('UnsupportedPlatformPage 展示 Android-only 文案', (tester) async {
      await tester.pumpWidget(
        const MaterialApp(home: UnsupportedPlatformPage()),
      );

      expect(find.text('当前平台不支持内嵌 WebView'), findsOneWidget);
      expect(find.textContaining('当前项目仅支持 Android 平板运行。'), findsOneWidget);
      expect(find.textContaining('https://example.com/login'), findsOneWidget);
    });

    testWidgets('ErrorOverlay 点击重试会触发回调', (tester) async {
      var retried = false;

      await tester.pumpWidget(
        MaterialApp(
          home: ErrorOverlay(
            title: '错误标题',
            message: '错误信息',
            currentUrl: 'https://example.com',
            onRetry: () async {
              retried = true;
            },
          ),
        ),
      );

      expect(find.text('错误标题'), findsOneWidget);
      expect(find.text('错误信息'), findsOneWidget);
      expect(find.text('https://example.com'), findsOneWidget);
      await tester.tap(find.text('重新加载'));
      await tester.pump();
      expect(retried, isTrue);
    });

    testWidgets('LaunchOverlay 与 TopProgressBar 能正确渲染进度', (tester) async {
      await tester.pumpWidget(
        const MaterialApp(
          home: Column(
            children: <Widget>[
              Expanded(
                child: LaunchOverlay(progress: 80, hasMeasuredProgress: true),
              ),
              TopProgressBar(progress: 50, hasMeasuredProgress: true),
            ],
          ),
        ),
      );

      expect(find.text('页面加载中'), findsOneWidget);
      expect(find.byType(LinearProgressIndicator), findsNWidgets(2));

      final indicators = tester.widgetList<LinearProgressIndicator>(
        find.byType(LinearProgressIndicator),
      );
      expect(indicators.first.value, 0.8);
      expect(indicators.last.value, 0.5);
    });

    testWidgets('LaunchOverlay 未量测进度时显示不确定进度', (tester) async {
      await tester.pumpWidget(
        const MaterialApp(
          home: LaunchOverlay(progress: 0, hasMeasuredProgress: false),
        ),
      );

      final indicator = tester.widget<LinearProgressIndicator>(
        find.byType(LinearProgressIndicator),
      );
      expect(indicator.value, isNull);
    });

    testWidgets('TopProgressBar 未量测进度时显示不确定进度', (tester) async {
      await tester.pumpWidget(
        const MaterialApp(
          home: Scaffold(
            body: TopProgressBar(progress: 0, hasMeasuredProgress: false),
          ),
        ),
      );

      final indicator = tester.widget<LinearProgressIndicator>(
        find.byType(LinearProgressIndicator),
      );
      expect(indicator.value, isNull);
    });
  });

  group('导航 scheme 边界场景', () {
    test('data/blob/about/file 协议留在 WebView 中', () {
      expect(
        shellCoreTestHooks.shouldStayInWebView(
          Uri.parse('data:text/html,<h1>hello</h1>'),
        ),
        isTrue,
      );
      expect(
        shellCoreTestHooks.shouldStayInWebView(Uri.parse('about:blank')),
        isTrue,
      );
      expect(
        shellCoreTestHooks.shouldStayInWebView(
          Uri.parse('blob:https://example.com/uuid'),
        ),
        isTrue,
      );
      expect(
        shellCoreTestHooks.shouldStayInWebView(
          Uri.parse('file:///tmp/test.html'),
        ),
        isTrue,
      );
    });

    test('intent/alipay/weixin/sms 等自定义 scheme 走外部应用', () {
      for (final scheme in <String>[
        'intent://demo',
        'alipays://pay',
        'weixin://dl/business',
        'sms:10086',
        'mailto:test@example.com',
      ]) {
        expect(
          shellCoreTestHooks.shouldStayInWebView(Uri.parse(scheme)),
          isFalse,
          reason: 'scheme=$scheme should leave WebView',
        );
      }
    });

    test('openExternalUri 成功返回 true', () async {
      fakeUrlLauncherPlatform
        ..shouldSucceed = true
        ..shouldThrow = false;

      final result = await shellCoreTestHooks.openExternalUri(
        Uri.parse('alipays://pay?orderId=123'),
      );

      expect(result, isTrue);
      expect(fakeUrlLauncherPlatform.lastUrl, 'alipays://pay?orderId=123');
    });
  });

  group('序列化 responseType 边界', () {
    test('base64 模式包含 base64 但不包含 dataUrl', () async {
      final tempDirectory = await Directory.systemTemp.createTemp('ser-b64');
      final file = File('${tempDirectory.path}/data.txt')
        ..writeAsStringSync('abc');
      addTearDown(() async {
        if (tempDirectory.existsSync()) {
          await tempDirectory.delete(recursive: true);
        }
      });

      final result = await shellCoreTestHooks.serializeXFiles(
        <XFile>[XFile(file.path, name: 'data.txt')],
        responseType: 'base64',
      );

      expect(result.single['base64'], isNotNull);
      expect(result.single.containsKey('dataUrl'), isFalse);
    });

    test('uri 模式不包含 base64 也不包含 dataUrl', () async {
      final tempDirectory = await Directory.systemTemp.createTemp('ser-uri');
      final file = File('${tempDirectory.path}/data.txt')
        ..writeAsStringSync('abc');
      addTearDown(() async {
        if (tempDirectory.existsSync()) {
          await tempDirectory.delete(recursive: true);
        }
      });

      final result = await shellCoreTestHooks.serializeXFiles(
        <XFile>[XFile(file.path, name: 'data.txt')],
        responseType: 'uri',
      );

      expect(result.single.containsKey('base64'), isFalse);
      expect(result.single.containsKey('dataUrl'), isFalse);
      expect(result.single['uri'], contains('data.txt'));
    });

    test('空文件列表返回空数组', () async {
      final xResult = await shellCoreTestHooks.serializeXFiles(
        <XFile>[],
        responseType: 'dataUrl',
      );
      final pfResult = await shellCoreTestHooks.serializePlatformFiles(
        <PlatformFile>[],
        responseType: 'base64',
      );

      expect(xResult, isEmpty);
      expect(pfResult, isEmpty);
    });

    test('PlatformFile 无 bytes 也无 path 时 base64 为 null', () async {
      final result = await shellCoreTestHooks.serializePlatformFiles(
        <PlatformFile>[PlatformFile(name: 'ghost.dat', size: 0)],
        responseType: 'base64',
      );

      expect(result.single['base64'], isNull);
      expect(result.single['uri'], isNull);
    });
  });

  group('AndroidCompatibilityPlan 扩展场景', () {
    test('现代设备（高 SDK + 新 WebView）优先使用 texture', () {
      final info = AndroidWebViewInfo(
        sdkInt: 34,
        manufacturer: 'Samsung',
        brand: 'samsung',
        model: 'SM-X810',
        webViewVersionName: '122.0.6261.86',
      );

      final plan = AndroidCompatibilityPlan.fromInfo(info);

      expect(info.isLegacyWebView, isFalse);
      expect(info.isF136A, isFalse);
      expect(plan.renderModes.first, AndroidRenderMode.texture);
      expect(plan.suggestWebViewUpdate, isFalse);
      expect(plan.prefersAggressiveRecovery, isFalse);
    });

    test('低 SDK（≤28）但非 F136A 也会使用 hybrid 优先', () {
      final info = AndroidWebViewInfo(
        sdkInt: 26,
        manufacturer: 'Huawei',
        brand: 'HUAWEI',
        model: 'MediaPad T3',
        webViewVersionName: '120.0.0',
      );

      final plan = AndroidCompatibilityPlan.fromInfo(info);

      expect(plan.renderModes.first, AndroidRenderMode.hybrid);
      expect(plan.suggestWebViewUpdate, isTrue);
      expect(plan.prefersAggressiveRecovery, isTrue);
    });

    test('AndroidWebViewInfo 字段缺失时使用安全默认值', () {
      final info = AndroidWebViewInfo.fromMap(<Object?, Object?>{});

      expect(info.sdkInt, 0);
      expect(info.manufacturer, '');
      expect(info.brand, '');
      expect(info.model, '');
      expect(info.webViewDataDirectorySuffix, isNull);
      expect(info.webViewPackageName, isNull);
      expect(info.webViewVersionName, isNull);
      expect(info.webViewLongVersionCode, isNull);
      expect(info.webViewMajorVersion, isNull);
    });

    test('AndroidRenderMode 枚举属性正确', () {
      expect(AndroidRenderMode.texture.usesHybridComposition, isFalse);
      expect(AndroidRenderMode.hybrid.usesHybridComposition, isTrue);
      expect(AndroidRenderMode.texture.logName, 'texture-layer');
      expect(AndroidRenderMode.hybrid.logName, 'hybrid-composition');
    });
  });

  group('错误映射扩展场景', () {
    test('hostLookup 和 io 错误映射为网络连接失败', () {
      const hostLookupError = WebResourceError(
        errorCode: -10,
        description: 'dns lookup failed',
        errorType: WebResourceErrorType.hostLookup,
      );
      const ioError = WebResourceError(
        errorCode: -11,
        description: 'io interrupted',
        errorType: WebResourceErrorType.io,
      );

      expect(shellCoreTestHooks.friendlyErrorTitle(hostLookupError), '网络连接失败');
      expect(shellCoreTestHooks.friendlyErrorTitle(ioError), '网络连接失败');
      expect(
        shellCoreTestHooks.friendlyErrorMessage(hostLookupError),
        '没有成功连接到服务器，请检查网络后重试。',
      );
      expect(
        shellCoreTestHooks.friendlyErrorMessage(ioError),
        '没有成功连接到服务器，请检查网络后重试。',
      );
    });

    test('空 description 使用默认文案', () {
      const emptyDescError = WebResourceError(
        errorCode: -99,
        description: '   ',
        errorType: WebResourceErrorType.unknown,
      );

      expect(
        shellCoreTestHooks.friendlyErrorMessage(emptyDescError),
        '请稍后重新加载页面。',
      );
    });
  });

  group('Accept 类型边界场景', () {
    test('空 acceptTypes 不接受图片也不仅接受图片', () {
      expect(shellCoreTestHooks.acceptsImages(<String>[]), isFalse);
      expect(shellCoreTestHooks.acceptsOnlyImages(<String>[]), isFalse);
    });

    test('仅空白字符的 acceptTypes 视为空', () {
      expect(shellCoreTestHooks.acceptsImages(<String>['  ', '']), isFalse);
      expect(shellCoreTestHooks.acceptsOnlyImages(<String>['  ', '']), isFalse);
    });

    test('image/* 通配符被识别为图片', () {
      expect(shellCoreTestHooks.isImageAcceptType('image/*'), isTrue);
      expect(shellCoreTestHooks.isImageAcceptType('image/png'), isTrue);
      expect(shellCoreTestHooks.isImageAcceptType('image/svg+xml'), isTrue);
    });

    test('非图片文件扩展名正确返回 false', () {
      expect(shellCoreTestHooks.isImageAcceptType('.pdf'), isFalse);
      expect(shellCoreTestHooks.isImageAcceptType('.apk'), isFalse);
      expect(shellCoreTestHooks.isImageAcceptType('application/pdf'), isFalse);
    });

    test('混合类型列表正确判断', () {
      expect(
        shellCoreTestHooks.acceptsImages(<String>['.pdf', '.png']),
        isTrue,
      );
      expect(
        shellCoreTestHooks.acceptsOnlyImages(<String>['.pdf', '.png']),
        isFalse,
      );
      expect(
        shellCoreTestHooks.acceptsImages(<String>['image/*', '.jpg']),
        isTrue,
      );
      expect(
        shellCoreTestHooks.acceptsOnlyImages(<String>['image/*', '.jpg']),
        isTrue,
      );
    });
  });

  group('boolValue 边界场景', () {
    test('null 与未知类型使用 defaultValue', () {
      expect(shellCoreTestHooks.boolValue(null), isFalse);
      expect(shellCoreTestHooks.boolValue(null, defaultValue: true), isTrue);
      expect(shellCoreTestHooks.boolValue(3.14), isFalse);
      expect(shellCoreTestHooks.boolValue(<int>[]), isFalse);
    });

    test('字符串 false/TRUE 正确解析', () {
      expect(shellCoreTestHooks.boolValue('false'), isFalse);
      expect(shellCoreTestHooks.boolValue('TRUE'), isTrue);
      expect(shellCoreTestHooks.boolValue('False'), isFalse);
    });

    test('int 非零值为 true', () {
      expect(shellCoreTestHooks.boolValue(-1), isTrue);
      expect(shellCoreTestHooks.boolValue(42), isTrue);
    });
  });

  group('MIME 类型扩展场景', () {
    test('所有已支持扩展名都能正确猜测', () {
      expect(shellCoreTestHooks.guessMimeType('FILE.PNG'), 'image/png');
      expect(shellCoreTestHooks.guessMimeType('a.JPG'), 'image/jpeg');
      expect(shellCoreTestHooks.guessMimeType('a.JPEG'), 'image/jpeg');
      expect(shellCoreTestHooks.guessMimeType('a.PDF'), 'application/pdf');
      expect(shellCoreTestHooks.guessMimeType('a.TXT'), 'text/plain');
    });

    test('无扩展名或未知扩展名返回 octet-stream', () {
      expect(
        shellCoreTestHooks.guessMimeType('binary'),
        'application/octet-stream',
      );
      expect(
        shellCoreTestHooks.guessMimeType('data.xyz'),
        'application/octet-stream',
      );
    });
  });

  group('URL 归一化边界场景', () {
    test('isNetworkUrl 对 null/空/非法值返回 false', () {
      expect(shellCoreTestHooks.isNetworkUrl(null), isFalse);
      expect(shellCoreTestHooks.isNetworkUrl(''), isFalse);
      expect(shellCoreTestHooks.isNetworkUrl('not a url'), isFalse);
    });

    test('路径归一化处理各种格式', () {
      expect(shellCoreTestHooks.normalizeComparablePath('/a/b/'), '/a/b');
      expect(shellCoreTestHooks.normalizeComparablePath('/a/b'), '/a/b');
      expect(shellCoreTestHooks.normalizeComparablePath('/'), '/');
      expect(shellCoreTestHooks.normalizeComparablePath('///'), '//');
    });

    test('默认端口映射覆盖 http/https/未知', () {
      expect(shellCoreTestHooks.defaultPortForScheme('http'), 80);
      expect(shellCoreTestHooks.defaultPortForScheme('https'), 443);
      expect(shellCoreTestHooks.defaultPortForScheme('ftp'), -1);
      expect(shellCoreTestHooks.defaultPortForScheme(''), -1);
    });

    test('URI 归一化保留查询参数并忽略大小写', () {
      expect(
        shellCoreTestHooks.normalizeComparableUri(
          Uri.parse('HTTP://EXAMPLE.COM:80/Path/?q=1'),
        ),
        'http://example.com:80/Path?q=1',
      );
    });
  });

  group('ShellEnvironment 配置验证', () {
    test('字段正确传递到全局环境', () {
      expect(_testEnvironment.appName, '测试应用');
      expect(_testEnvironment.appKey, 'test_app');
      expect(_testEnvironment.accentColor, const Color(0xFF3ED37B));
      expect(_testEnvironment.backgroundColor, const Color(0xFFFFFFFF));
      expect(_testEnvironment.textColor, const Color(0xFF1F2937));
      expect(_testEnvironment.mutedTextColor, const Color(0xFF6B7280));
      expect(_testEnvironment.initialUrl, 'example.com/login');
    });

    test('initialUrl 可选字段缺省时使用默认地址', () {
      const noUrlEnv = ShellEnvironment(
        appName: '无URL',
        appKey: 'no_url',
        accentColor: Color(0xFF000000),
        backgroundColor: Color(0xFFFFFFFF),
        textColor: Color(0xFF000000),
        mutedTextColor: Color(0xFF888888),
      );

      expect(noUrlEnv.initialUrl, isNull);
      shellCoreTestHooks.initializeEnvironment(noUrlEnv);
      expect(
        shellCoreTestHooks.initialUrl,
        'http://xszy.lzzneng.com/login.html',
      );

      // 恢复测试环境
      shellCoreTestHooks.initializeEnvironment(_testEnvironment);
    });

    test('https 地址直接使用不做修改', () {
      shellCoreTestHooks.initializeEnvironment(
        const ShellEnvironment(
          appName: 'HTTPS',
          appKey: 'https',
          accentColor: Color(0xFF000000),
          backgroundColor: Color(0xFFFFFFFF),
          textColor: Color(0xFF000000),
          mutedTextColor: Color(0xFF888888),
          initialUrl: 'https://app.example.com/start',
        ),
      );
      expect(
        shellCoreTestHooks.initialUrl,
        'https://app.example.com/start',
      );
      expect(
        shellCoreTestHooks.initialUri,
        Uri.parse('https://app.example.com/start'),
      );

      // 恢复测试环境
      shellCoreTestHooks.initializeEnvironment(_testEnvironment);
    });
  });

  group('Bridge JS 协议格式', () {
    test('脚本包含版本号和 app-shell-ready 事件', () {
      final script = shellCoreTestHooks.buildAppShellBridgeScript();

      expect(script, contains('__nativeShellVersion'));
      expect(script, contains("'1.0.0'"));
      expect(script, contains('app-shell-ready'));
      expect(script, contains('isNativeShell: true'));
    });

    test('脚本包含通道守卫和去重逻辑', () {
      final script = shellCoreTestHooks.buildAppShellBridgeScript();

      expect(script, contains('AppShellBridge'));
      expect(script, contains("typeof channel.postMessage !== 'function'"));
      expect(script, contains('window.AppShell'));
    });

    test('脚本暴露全部 8 个 Action', () {
      final script = shellCoreTestHooks.buildAppShellBridgeScript();

      for (final action in <String>[
        'pickImage',
        'captureImage',
        'pickFile',
        'openExternal',
        'requestPermissions',
        'reloadPage',
        'goBack',
        'closeApp',
      ]) {
        expect(script, contains(action), reason: 'Missing action: $action');
      }
    });

    test('bridge 响应包含 requestId/success/data 字段', () async {
      final platformController = _FakePlatformWebViewController(
        const PlatformWebViewControllerCreationParams(),
      );
      final controller = WebViewController.fromPlatform(platformController);

      await shellCoreTestHooks.sendBridgeResponse(
        controller,
        requestId: 'test-123',
        success: true,
        data: <String, Object?>{'count': 5},
      );

      expect(platformController.javaScriptCalls, hasLength(1));
      final js = platformController.javaScriptCalls.first;
      expect(js, contains('__appShellReceiveResponse'));
      expect(js, contains('"requestId":"test-123"'));
      expect(js, contains('"success":true'));
      expect(js, contains('"count":5'));
    });

    test('bridge 失败响应包含 error 字段', () async {
      final platformController = _FakePlatformWebViewController(
        const PlatformWebViewControllerCreationParams(),
      );
      final controller = WebViewController.fromPlatform(platformController);

      await shellCoreTestHooks.sendBridgeResponse(
        controller,
        requestId: 'err-456',
        success: false,
        error: 'Something went wrong',
      );

      final js = platformController.javaScriptCalls.first;
      expect(js, contains('"success":false'));
      expect(js, contains('"error":"Something went wrong"'));
    });
  });

  group('AndroidCompatibilityPlan describe 格式', () {
    test('fallback 策略描述包含必要字段', () {
      final fallbackPlan = AndroidCompatibilityPlan.fallback();
      final description = fallbackPlan.describe();

      expect(description, contains('modes='));
      expect(description, contains('wideViewport=true'));
      expect(description, contains('aggressiveRecovery=true'));
    });

    test('现代设备策略不建议激进恢复', () {
      final info = AndroidWebViewInfo(
        sdkInt: 34,
        manufacturer: 'Google',
        brand: 'google',
        model: 'Pixel Tablet',
        webViewVersionName: '120.0.0',
      );
      final plan = AndroidCompatibilityPlan.fromInfo(info);
      final description = plan.describe();

      expect(description, contains('texture-layer -> hybrid-composition'));
      expect(description, contains('aggressiveRecovery=false'));
    });

    test('F136A 设备策略建议更新 WebView', () {
      final info = AndroidWebViewInfo(
        sdkInt: 30,
        manufacturer: 'Yuanxuan',
        brand: 'YX',
        model: 'F136A',
        webViewVersionName: '109.0.1',
      );
      final plan = AndroidCompatibilityPlan.fromInfo(info);

      expect(plan.suggestWebViewUpdate, isTrue);
      expect(plan.prefersAggressiveRecovery, isTrue);
      expect(plan.renderModes.first, AndroidRenderMode.hybrid);
    });

    test('summary 对空制造商不重复空格', () {
      final info = AndroidWebViewInfo(
        sdkInt: 30,
        manufacturer: '',
        brand: '',
        model: 'TestDevice',
      );

      expect(info.summary, contains('device=TestDevice'));
      expect(info.summary, isNot(contains('device= ')));
    });

    test('summary 对空型号不显示 device 字段', () {
      final info = AndroidWebViewInfo(
        sdkInt: 30,
        manufacturer: '',
        brand: '',
        model: '',
      );

      expect(info.summary, isNot(contains('device=')));
      expect(info.summary, startsWith('sdk=30'));
    });
  });

  group('相机功能边界场景', () {
    test('权限拒绝且不带 controller 时静默返回 null', () async {
      cameraPermissionGranted = false;

      final result = await shellCoreTestHooks.pickCameraImage(ImagePicker());

      expect(result, isNull);
    });

    test('权限拒绝且 showPermissionAlert=false 时不弹窗', () async {
      cameraPermissionGranted = false;
      final platformController = _FakePlatformWebViewController(
        const PlatformWebViewControllerCreationParams(),
      );
      final controller = WebViewController.fromPlatform(platformController);

      final result = await shellCoreTestHooks.pickCameraImage(
        ImagePicker(),
        controller: controller,
      );

      expect(result, isNull);
      expect(platformController.javaScriptCalls, isEmpty);
    });

    test('相机返回 null 时结果也为 null', () async {
      fakeImagePickerPlatform.nextImage = null;

      final result = await shellCoreTestHooks.pickCameraImage(ImagePicker());

      expect(result, isNull);
    });
  });

  group('多文件序列化', () {
    test('多个 XFile 正确序列化为数组', () async {
      final tempDirectory = await Directory.systemTemp.createTemp('multi');
      final file1 = File('${tempDirectory.path}/a.png')
        ..writeAsBytesSync(<int>[1, 2]);
      final file2 = File('${tempDirectory.path}/b.jpg')
        ..writeAsBytesSync(<int>[3, 4]);
      addTearDown(() async {
        if (tempDirectory.existsSync()) {
          await tempDirectory.delete(recursive: true);
        }
      });

      final result = await shellCoreTestHooks.serializeXFiles(
        <XFile>[
          XFile(file1.path, name: 'a.png'),
          XFile(file2.path, name: 'b.jpg'),
        ],
        responseType: 'dataUrl',
      );

      expect(result, hasLength(2));
      expect(result[0]['name'], 'a.png');
      expect(result[0]['mimeType'], 'image/png');
      expect(result[1]['name'], 'b.jpg');
      expect(result[1]['mimeType'], 'image/jpeg');
    });

    test('空文件路径列表转换为空 URI 列表', () {
      final uris = shellCoreTestHooks.xFilesToUriStrings(<XFile>[]);
      expect(uris, isEmpty);
    });

    test('XFile 路径正确转换为 file:// URI', () {
      final uris = shellCoreTestHooks.xFilesToUriStrings(<XFile>[
        XFile('/tmp/a.txt'),
        XFile('/tmp/b.png'),
      ]);

      expect(uris, hasLength(2));
      expect(uris[0], 'file:///tmp/a.txt');
      expect(uris[1], 'file:///tmp/b.png');
    });
  });

  group('看门狗常量验证', () {
    test('旧相机兼容脚本包含 legacy 标记和重试逻辑', () {
      final script = shellCoreTestHooks.legacyCameraCompatScript;

      expect(script, contains('__appShellLegacyCameraCompatInstalled'));
      expect(script, contains('installLegacyCameraCompat'));
      expect(script, contains('wrappedOpenCamera'));
      expect(script, contains('capturePhoto'));
      expect(script, contains('setInterval'));
    });

    test('WebView 主版本解析能正确处理各种版本格式', () {
      expect(shellCoreTestHooks.parseWebViewMajorVersion('83'), 83);
      expect(shellCoreTestHooks.parseWebViewMajorVersion('122.0.6261'), 122);
      expect(
        shellCoreTestHooks.parseWebViewMajorVersion('not-a-version'),
        isNull,
      );
    });
  });
}

class _FakeUrlLauncherPlatform extends UrlLauncherPlatform {
  @override
  LinkDelegate? get linkDelegate => null;

  String? lastUrl;
  bool shouldSucceed = true;
  bool shouldThrow = false;

  @override
  Future<bool> launch(
    String url, {
    required bool useSafariVC,
    required bool useWebView,
    required bool enableJavaScript,
    required bool enableDomStorage,
    required bool universalLinksOnly,
    required Map<String, String> headers,
    String? webOnlyWindowName,
  }) async {
    lastUrl = url;
    if (shouldThrow) {
      throw Exception('launch failed');
    }
    return shouldSucceed;
  }
}

class _FakeImagePickerPlatform extends ImagePickerPlatform {
  XFile? nextImage;
  bool shouldThrow = false;
  ImageSource? lastSource;

  @override
  Future<XFile?> getImageFromSource({
    required ImageSource source,
    ImagePickerOptions options = const ImagePickerOptions(),
  }) async {
    lastSource = source;
    if (shouldThrow) {
      throw PlatformException(code: 'pick-failed');
    }
    return nextImage;
  }
}

class _FakeWebViewPlatform extends WebViewPlatform {
  final createdControllers = <_FakePlatformWebViewController>[];

  @override
  PlatformNavigationDelegate createPlatformNavigationDelegate(
    PlatformNavigationDelegateCreationParams params,
  ) {
    return _FakePlatformNavigationDelegate(params);
  }

  @override
  PlatformWebViewController createPlatformWebViewController(
    PlatformWebViewControllerCreationParams params,
  ) {
    final controller = _FakePlatformWebViewController(params);
    createdControllers.add(controller);
    return controller;
  }

  @override
  PlatformWebViewWidget createPlatformWebViewWidget(
    PlatformWebViewWidgetCreationParams params,
  ) {
    return _FakePlatformWebViewWidget(params);
  }

  @override
  PlatformWebViewCookieManager createPlatformCookieManager(
    PlatformWebViewCookieManagerCreationParams params,
  ) {
    return _FakePlatformWebViewCookieManager(params);
  }
}

class _FakePlatformWebViewCookieManager extends PlatformWebViewCookieManager {
  _FakePlatformWebViewCookieManager(super.params) : super.implementation();

  @override
  Future<bool> clearCookies() async => false;

  @override
  Future<void> setCookie(WebViewCookie cookie) async {}
}

class _FakePlatformNavigationDelegate extends PlatformNavigationDelegate {
  _FakePlatformNavigationDelegate(super.params) : super.implementation();

  @override
  Future<void> setOnNavigationRequest(
    NavigationRequestCallback onNavigationRequest,
  ) async {}

  @override
  Future<void> setOnPageStarted(PageEventCallback onPageStarted) async {}

  @override
  Future<void> setOnPageFinished(PageEventCallback onPageFinished) async {}

  @override
  Future<void> setOnHttpError(HttpResponseErrorCallback onHttpError) async {}

  @override
  Future<void> setOnProgress(ProgressCallback onProgress) async {}

  @override
  Future<void> setOnWebResourceError(
    WebResourceErrorCallback onWebResourceError,
  ) async {}

  @override
  Future<void> setOnUrlChange(UrlChangeCallback onUrlChange) async {}
}

class _FakePlatformWebViewController extends PlatformWebViewController {
  _FakePlatformWebViewController(super.params) : super.implementation();

  final javaScriptCalls = <String>[];
  PlatformNavigationDelegate? delegate;
  bool throwOnRunJavaScript = false;
  bool canGoBackValue = false;
  bool didGoBack = false;
  Uri? lastLoadedUri;

  @override
  Future<void> addJavaScriptChannel(JavaScriptChannelParams params) async {}

  @override
  Future<void> clearCache() async {}

  @override
  Future<void> clearLocalStorage() async {}

  @override
  Future<bool> canGoBack() async => canGoBackValue;

  @override
  Future<void> enableZoom(bool enabled) async {}

  @override
  Future<void> goBack() async {
    didGoBack = true;
  }

  @override
  Future<void> loadRequest(LoadRequestParams params) async {
    lastLoadedUri = params.uri;
  }

  @override
  Future<void> reload() async {}

  @override
  Future<void> runJavaScript(String javaScript) async {
    if (throwOnRunJavaScript) {
      throw Exception('javascript failed');
    }
    javaScriptCalls.add(javaScript);
  }

  @override
  Future<Object> runJavaScriptReturningResult(String javaScript) async => '';

  @override
  Future<void> setBackgroundColor(Color color) async {}

  @override
  Future<void> setJavaScriptMode(JavaScriptMode javaScriptMode) async {}

  @override
  Future<void> setPlatformNavigationDelegate(
    PlatformNavigationDelegate delegate,
  ) async {
    this.delegate = delegate;
  }
}

class _FakePlatformWebViewWidget extends PlatformWebViewWidget {
  _FakePlatformWebViewWidget(super.params) : super.implementation();

  @override
  Widget build(BuildContext context) {
    return const SizedBox(key: ValueKey<String>('fake-webview'));
  }
}
