import 'dart:async';
import 'dart:convert';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';

const String _defaultInitialUrl = 'http://xszy.lzzneng.com/login.html';
const String _configuredInitialUrl = String.fromEnvironment(
  'APP_INITIAL_URL',
  defaultValue: _defaultInitialUrl,
);
const String _appShellBridgeChannel = 'AppShellBridge';
const String _appShellBridgeVersion = '1.0.0';
const Duration _releaseStartupWatchdogDuration = Duration(seconds: 15);
const Duration _debugStartupWatchdogDuration = Duration(seconds: 15);
const double _pickedImageMaxWidth = 1600;
const double _pickedImageMaxHeight = 1600;
const int _pickedImageQuality = 85;
const MethodChannel _appShellDeviceChannel = MethodChannel('app_shell/device');
const int _legacyWebViewMajorVersionThreshold = 110;
const Color _shellAccentColor = Color(0xFF3ED37B);
const Color _shellBackgroundColor = Color(0xFFFFFFFF);
const Color _shellTextColor = Color(0xFF1F2937);
const Color _shellMutedTextColor = Color(0xFF6B7280);

final Uri _initialUri = _resolveInitialUri();
final String _initialUrl = _initialUri.toString();

Uri _resolveInitialUri() {
  final String candidate = _configuredInitialUrl.trim();
  if (candidate.isEmpty) {
    return Uri.parse(_defaultInitialUrl);
  }

  final Uri? directUri = Uri.tryParse(candidate);
  if (directUri != null && directUri.hasScheme) {
    return directUri;
  }

  if (candidate.startsWith('//')) {
    final Uri? protocolRelativeUri = Uri.tryParse('https:$candidate');
    if (protocolRelativeUri != null && protocolRelativeUri.hasScheme) {
      return protocolRelativeUri;
    }
  }

  final Uri? httpsUri = Uri.tryParse('https://$candidate');
  if (httpsUri != null && httpsUri.hasScheme && httpsUri.host.isNotEmpty) {
    return httpsUri;
  }
  return Uri.parse(_defaultInitialUrl);
}

bool _supportsEmbeddedWebView({bool isWeb = kIsWeb, TargetPlatform? platform}) {
  if (isWeb) {
    return false;
  }

  final TargetPlatform target = platform ?? defaultTargetPlatform;
  return target == TargetPlatform.android;
}

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SystemChrome.setPreferredOrientations(const [
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  await _enterImmersiveMode();
  runApp(const WebShellApp());
}

Future<void> _enterImmersiveMode() async {
  await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  SystemChrome.setSystemUIOverlayStyle(
    const SystemUiOverlayStyle(
      statusBarColor: Colors.transparent,
      statusBarIconBrightness: Brightness.light,
      statusBarBrightness: Brightness.dark,
      systemNavigationBarColor: Colors.black,
      systemNavigationBarDividerColor: Colors.black,
      systemNavigationBarIconBrightness: Brightness.light,
    ),
  );
}

enum _AndroidRenderMode {
  texture,
  hybrid;

  bool get usesHybridComposition => this == _AndroidRenderMode.hybrid;

  String get logName => switch (this) {
    _AndroidRenderMode.texture => 'texture-layer',
    _AndroidRenderMode.hybrid => 'hybrid-composition',
  };

  String get displayName => switch (this) {
    _AndroidRenderMode.texture => '标准模式',
    _AndroidRenderMode.hybrid => '兼容模式',
  };
}

class _AndroidWebViewInfo {
  _AndroidWebViewInfo({
    required this.sdkInt,
    required this.manufacturer,
    required this.brand,
    required this.model,
    this.webViewDataDirectorySuffix,
    this.webViewPackageName,
    this.webViewVersionName,
    this.webViewLongVersionCode,
  }) : webViewMajorVersion = _parseWebViewMajorVersion(webViewVersionName);

  factory _AndroidWebViewInfo.fromMap(Map<Object?, Object?> raw) {
    return _AndroidWebViewInfo(
      sdkInt: _readInt(raw['sdkInt']),
      manufacturer: _readString(raw['manufacturer']),
      brand: _readString(raw['brand']),
      model: _readString(raw['model']),
      webViewDataDirectorySuffix: _readNullableString(
        raw['webViewDataDirectorySuffix'],
      ),
      webViewPackageName: _readNullableString(raw['webViewPackageName']),
      webViewVersionName: _readNullableString(raw['webViewVersionName']),
      webViewLongVersionCode: _readNullableInt(raw['webViewLongVersionCode']),
    );
  }

  final int sdkInt;
  final String manufacturer;
  final String brand;
  final String model;
  final String? webViewDataDirectorySuffix;
  final String? webViewPackageName;
  final String? webViewVersionName;
  final int? webViewLongVersionCode;
  final int? webViewMajorVersion;

  bool get isLegacyWebView =>
      webViewMajorVersion != null &&
      webViewMajorVersion! <= _legacyWebViewMajorVersionThreshold;

  bool get isF136A => model.toUpperCase() == 'F136A';

  String get summary {
    final List<String> parts = <String>[
      'sdk=$sdkInt',
      if (manufacturer.isNotEmpty || model.isNotEmpty)
        'device=${[manufacturer, model].where((part) => part.isNotEmpty).join(' ')}',
      if (webViewPackageName case final String packageName
          when packageName.isNotEmpty)
        'webViewPackage=$packageName',
      if (webViewVersionName case final String versionName
          when versionName.isNotEmpty)
        'webViewVersion=$versionName',
      if (webViewDataDirectorySuffix case final String suffix
          when suffix.isNotEmpty)
        'webViewSuffix=$suffix',
    ];
    return parts.join(', ');
  }

  static int _readInt(Object? value, {int fallback = 0}) {
    if (value is int) {
      return value;
    }
    return int.tryParse((value ?? '').toString()) ?? fallback;
  }

  static int? _readNullableInt(Object? value) {
    if (value == null) {
      return null;
    }
    if (value is int) {
      return value;
    }
    return int.tryParse(value.toString());
  }

  static String _readString(Object? value) {
    return (value ?? '').toString().trim();
  }

  static String? _readNullableString(Object? value) {
    final String normalized = _readString(value);
    return normalized.isEmpty ? null : normalized;
  }
}

class _AndroidCompatibilityPlan {
  const _AndroidCompatibilityPlan({
    required this.renderModes,
    required this.useWideViewPort,
    required this.suggestWebViewUpdate,
    required this.prefersAggressiveRecovery,
  });

  factory _AndroidCompatibilityPlan.fallback() {
    return _AndroidCompatibilityPlan(
      renderModes: kDebugMode
          ? const <_AndroidRenderMode>[
              _AndroidRenderMode.texture,
              _AndroidRenderMode.hybrid,
            ]
          : const <_AndroidRenderMode>[
              _AndroidRenderMode.hybrid,
              _AndroidRenderMode.texture,
            ],
      useWideViewPort: true,
      suggestWebViewUpdate: false,
      prefersAggressiveRecovery: true,
    );
  }

  factory _AndroidCompatibilityPlan.fromInfo(_AndroidWebViewInfo? info) {
    if (info == null) {
      return _AndroidCompatibilityPlan.fallback();
    }

    final bool legacyAndroid = info.sdkInt <= 28;
    final bool legacyWebView = info.isLegacyWebView;
    final bool preferHybridFirst =
        info.isF136A || legacyAndroid || legacyWebView;

    return _AndroidCompatibilityPlan(
      renderModes: preferHybridFirst
          ? const <_AndroidRenderMode>[
              _AndroidRenderMode.hybrid,
              _AndroidRenderMode.texture,
            ]
          : const <_AndroidRenderMode>[
              _AndroidRenderMode.texture,
              _AndroidRenderMode.hybrid,
            ],
      useWideViewPort: true,
      suggestWebViewUpdate: info.isF136A || legacyWebView || info.sdkInt <= 26,
      prefersAggressiveRecovery: info.isF136A || legacyAndroid || legacyWebView,
    );
  }

  final List<_AndroidRenderMode> renderModes;
  final bool useWideViewPort;
  final bool suggestWebViewUpdate;
  final bool prefersAggressiveRecovery;

  String describe() {
    return [
      'modes=${renderModes.map((mode) => mode.logName).join(' -> ')}',
      'wideViewport=$useWideViewPort',
      'aggressiveRecovery=$prefersAggressiveRecovery',
    ].join(', ');
  }
}

int? _parseWebViewMajorVersion(String? versionName) {
  if (versionName == null || versionName.isEmpty) {
    return null;
  }
  final String candidate = versionName.split('.').first.trim();
  return int.tryParse(candidate);
}

class WebShellApp extends StatelessWidget {
  const WebShellApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: _supportsEmbeddedWebView()
          ? const WebShellPage()
          : const UnsupportedPlatformPage(),
    );
  }
}

class WebShellPage extends StatefulWidget {
  const WebShellPage({super.key});

  @override
  State<WebShellPage> createState() => _WebShellPageState();
}

class _WebShellPageState extends State<WebShellPage>
    with WidgetsBindingObserver {
  final ImagePicker _imagePicker = ImagePicker();
  final WebViewCookieManager _cookieManager = WebViewCookieManager();

  late WebViewController _controller;
  late WebViewWidget _webViewWidget;
  late Future<void> _controllerSetupFuture;
  late final Future<void> _androidCompatibilityFuture;
  _AndroidWebViewInfo? _androidWebViewInfo;
  _AndroidCompatibilityPlan _androidCompatibilityPlan =
      _AndroidCompatibilityPlan.fallback();
  _AndroidRenderMode? _configuredRenderMode;
  int _webViewGeneration = 0;
  int _renderModeIndex = 0;

  bool _hasTriggeredInitialLoad = false;
  bool _hasAppliedCompatibilityPlan = false;
  bool _isLoadingPage = false;
  bool _hasBootstrapped = false;
  bool _hasStartedRemoteMainFrame = false;
  bool _hasMainFrameError = false;
  bool _hasMeasuredProgress = false;
  Timer? _startupWatchdogTimer;
  int _progress = 0;
  int _startupRetryCount = 0;
  String _currentUrl = _initialUrl;
  String _errorTitle = '页面加载失败';
  String _errorMessage = '请检查网络后重试。';
  String _lastWebViewEvent = '应用启动';

  @override
  void initState() {
    super.initState();
    debugPrint('WebShell 初始化，初始地址=$_initialUrl');
    WidgetsBinding.instance.addObserver(this);
    _androidCompatibilityFuture = _prepareAndroidCompatibility();
    _recreateWebView();

    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted || _hasTriggeredInitialLoad) {
        return;
      }
      _hasTriggeredInitialLoad = true;
      unawaited(_loadInitialPage());
    });
  }

  int get _safeRenderModeIndex {
    if (_renderModeIndex < 0) {
      return 0;
    }
    final int maxIndex = _androidCompatibilityPlan.renderModes.length - 1;
    if (_renderModeIndex > maxIndex) {
      return maxIndex;
    }
    return _renderModeIndex;
  }

  _AndroidRenderMode get _activeRenderMode =>
      _androidCompatibilityPlan.renderModes[_safeRenderModeIndex];

  Duration get _effectiveStartupWatchdogDuration {
    if (defaultTargetPlatform != TargetPlatform.android) {
      return _releaseStartupWatchdogDuration;
    }
    if (kDebugMode) {
      return _debugStartupWatchdogDuration;
    }
    return _releaseStartupWatchdogDuration;
  }

  Future<void> _prepareAndroidCompatibility() async {
    if (defaultTargetPlatform != TargetPlatform.android) {
      return;
    }

    try {
      final Map<Object?, Object?>? rawInfo = await _appShellDeviceChannel
          .invokeMapMethod<Object?, Object?>('getAndroidWebViewInfo');
      if (rawInfo != null) {
        _androidWebViewInfo = _AndroidWebViewInfo.fromMap(rawInfo);
      }
    } catch (error, stackTrace) {
      debugPrint('读取 Android WebView 信息失败：$error\n$stackTrace');
    }

    _androidCompatibilityPlan = _AndroidCompatibilityPlan.fromInfo(
      _androidWebViewInfo,
    );
    _renderModeIndex = 0;
    debugPrint(
      'WebShell Android WebView 信息：'
      '${_androidWebViewInfo?.summary ?? '不可用'}',
    );
    debugPrint(
      'WebShell 兼容策略：${_androidCompatibilityPlan.describe()}',
    );
  }

  Future<void> _ensureCompatibilityPlanApplied() async {
    await _androidCompatibilityFuture;
    if (!mounted ||
        _hasAppliedCompatibilityPlan ||
        defaultTargetPlatform != TargetPlatform.android) {
      return;
    }

    _hasAppliedCompatibilityPlan = true;
    if (_configuredRenderMode == _activeRenderMode) {
      debugPrint(
        'WebShell 兼容策略保持当前 WebView '
        '(${_activeRenderMode.logName})',
      );
      return;
    }
    _recreateWebView();
  }

  bool _switchToNextRenderMode() {
    final int nextIndex = _safeRenderModeIndex + 1;
    if (nextIndex >= _androidCompatibilityPlan.renderModes.length) {
      return false;
    }

    _renderModeIndex = nextIndex;
    debugPrint('WebShell 已切换渲染模式为 ${_activeRenderMode.logName}');
    return true;
  }

  String _buildCompatibilityGuidance() {
    if (defaultTargetPlatform != TargetPlatform.android) {
      return '';
    }

    final List<String> lines = <String>[
      '当前渲染策略：${_activeRenderMode.displayName}',
      if (_androidWebViewInfo?.model case final String model
          when model.isNotEmpty)
        '当前设备：$model',
      if (_androidWebViewInfo?.webViewVersionName case final String version
          when version.isNotEmpty)
        '系统 WebView：$version',
      if (_androidCompatibilityPlan.suggestWebViewUpdate)
        '如果内嵌页面仍异常，建议更新 Android System WebView 或 Chrome。',
    ];
    return lines.join('\n');
  }

  void _recreateWebView() {
    final PlatformWebViewControllerCreationParams controllerParams =
        const PlatformWebViewControllerCreationParams();
    final WebViewController controller =
        WebViewController.fromPlatformCreationParams(controllerParams);
    final int generation = ++_webViewGeneration;
    final _AndroidRenderMode renderMode = _activeRenderMode;

    _controller = controller;
    _controllerSetupFuture = _configureController(controller, generation);
    _configuredRenderMode = renderMode;
    debugPrint(
      'WebShell 正在以 ${renderMode.logName} 重建 WebView #$generation',
    );
    PlatformWebViewWidgetCreationParams widgetParams =
        PlatformWebViewWidgetCreationParams(
          key: ValueKey<String>('webview-$generation-${renderMode.logName}'),
          controller: controller.platform,
          layoutDirection: TextDirection.ltr,
        );

    if (defaultTargetPlatform == TargetPlatform.android) {
      widgetParams =
          AndroidWebViewWidgetCreationParams.fromPlatformWebViewWidgetCreationParams(
            widgetParams,
            displayWithHybridComposition: renderMode.usesHybridComposition,
          );
    }

    _webViewWidget = WebViewWidget.fromPlatformCreationParams(
      params: widgetParams,
    );
  }

  bool _isActiveWebViewGeneration(int generation) {
    return generation == _webViewGeneration;
  }

  Future<void> _configureController(
    WebViewController controller,
    int generation,
  ) async {
    await controller.setJavaScriptMode(JavaScriptMode.unrestricted);
    await controller.enableZoom(false);
    await controller.setBackgroundColor(Colors.white);
    await controller.addJavaScriptChannel(
      _appShellBridgeChannel,
      onMessageReceived: (JavaScriptMessage message) {
        if (!_isActiveWebViewGeneration(generation)) {
          return;
        }
        unawaited(_handleBridgeMessage(message.message));
      },
    );
    await controller.setNavigationDelegate(
      _buildNavigationDelegate(generation),
    );

    if (controller.platform is AndroidWebViewController) {
      final AndroidWebViewController androidController =
          controller.platform as AndroidWebViewController;

      await AndroidWebViewController.enableDebugging(
        kDebugMode && !_androidCompatibilityPlan.prefersAggressiveRecovery,
      );
      await androidController.setMediaPlaybackRequiresUserGesture(false);
      await androidController.setMixedContentMode(MixedContentMode.alwaysAllow);
      await androidController.setOverScrollMode(WebViewOverScrollMode.never);
      await androidController.setUseWideViewPort(
        _androidCompatibilityPlan.useWideViewPort,
      );
      await androidController.setTextZoom(100);
      await androidController.setVerticalScrollBarEnabled(false);
      await androidController.setHorizontalScrollBarEnabled(false);
      await androidController.setOnShowFileSelector(_handleFileSelector);
      await androidController.setOnPlatformPermissionRequest(
        _handlePlatformPermissionRequest,
      );
      await androidController.setGeolocationPermissionsPromptCallbacks(
        onShowPrompt: _handleGeolocationPermissionRequest,
      );
      await androidController.setOnConsoleMessage((message) {
        debugPrint(
          'WebView 控制台 [${message.level.name}] ${message.message}',
        );
      });
    }
  }

  NavigationDelegate _buildNavigationDelegate(int generation) {
    return NavigationDelegate(
      onProgress: (int progress) {
        if (!_isActiveWebViewGeneration(generation)) {
          return;
        }
        if (progress == 10 ||
            progress == 30 ||
            progress == 60 ||
            progress == 90) {
          _recordWebViewEvent('加载进度 $progress%');
        }
        if (!mounted) {
          return;
        }
        setState(() {
          _progress = progress;
          if (_hasStartedRemoteMainFrame || progress >= 100) {
            _hasMeasuredProgress = true;
          }
        });
      },
      onNavigationRequest: (NavigationRequest request) async {
        if (!_isActiveWebViewGeneration(generation)) {
          return NavigationDecision.navigate;
        }
        return _handleNavigationRequest(request);
      },
      onUrlChange: (UrlChange change) {
        if (!_isActiveWebViewGeneration(generation)) {
          return;
        }
        final String? url = change.url;
        if (url == null || url.isEmpty) {
          return;
        }
        _recordWebViewEvent('地址变化：$url');
        if (!mounted) {
          return;
        }
        setState(() {
          _currentUrl = url;
        });
      },
      onPageStarted: (url) {
        if (!_isActiveWebViewGeneration(generation)) {
          return;
        }
        if (!_isNetworkUrl(url)) {
          debugPrint('WebView 忽略非网络页面开始事件：$url');
          return;
        }
        _hasStartedRemoteMainFrame = true;
        _cancelStartupWatchdog();
        _recordWebViewEvent('页面开始加载：$url');
        if (!mounted) {
          return;
        }
        setState(() {
          _currentUrl = url;
          if (_hasMeasuredProgress) {
            _progress = _progress < 30 ? 30 : _progress;
          }
          _isLoadingPage = true;
          _hasBootstrapped = true;
          _hasMainFrameError = false;
          _errorTitle = '页面加载失败';
          _errorMessage = '请检查网络后重试。';
        });
      },
      onPageFinished: (url) {
        if (!_isActiveWebViewGeneration(generation)) {
          return;
        }
        if (!_isNetworkUrl(url)) {
          debugPrint('WebView 忽略非网络页面完成事件：$url');
          return;
        }
        _recordWebViewEvent('页面加载完成：$url');
        _cancelStartupWatchdog();
        unawaited(_injectAppShellBridge(url));
        if (!mounted) {
          return;
        }
        setState(() {
          _currentUrl = url;
          _hasMeasuredProgress = true;
          _progress = 100;
          _isLoadingPage = false;
          _hasBootstrapped = true;
          _hasMainFrameError = false;
          _startupRetryCount = 0;
        });
      },
      onHttpError: (HttpResponseError error) {
        if (!_isActiveWebViewGeneration(generation)) {
          return;
        }
        final int? statusCode = error.response?.statusCode;
        final Uri? requestUri = error.request?.uri;
        if (!_shouldTreatHttpErrorAsMainFrame(error)) {
          debugPrint(
            '忽略子资源 HTTP 错误：'
            '${statusCode ?? '未知'} ${requestUri ?? '未知'}',
          );
          return;
        }
        _recordWebViewEvent(
          'HTTP 错误：${statusCode ?? '未知'} ${requestUri ?? _currentUrl}',
        );
        _setMainFrameError(
          title: statusCode == null ? '服务器响应异常' : '服务器异常 $statusCode',
          message: statusCode == null
              ? '页面返回异常，请稍后重试。'
              : '页面返回了 $statusCode 状态码，请稍后重试。',
        );
      },
      onWebResourceError: (error) {
        if (!_isActiveWebViewGeneration(generation)) {
          return;
        }
        _recordWebViewEvent(
          '资源错误：code=${error.errorCode}, '
          'type=${error.errorType}, '
          'mainFrame=${error.isForMainFrame}, '
          'url=${error.url}, '
          'description=${error.description}',
        );
        if (!mounted || error.isForMainFrame == false) {
          return;
        }
        _setMainFrameError(
          title: _friendlyErrorTitle(error),
          message: _friendlyErrorMessage(error),
        );
      },
    );
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _enterImmersiveMode();
    }
  }

  @override
  void dispose() {
    _cancelStartupWatchdog();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  Future<void> _loadInitialPage() async {
    if (!mounted) {
      return;
    }
    await _ensureCompatibilityPlanApplied();
    if (!mounted) {
      return;
    }
    debugPrint('WebShell 首帧已就绪，开始初始加载');
    await _startLoadSequence(rebuildWebView: false, resetRetryCount: true);
  }

  Future<void> _handleBackPressed() async {
    if (await _controller.canGoBack()) {
      await _controller.goBack();
      return;
    }
    await SystemNavigator.pop();
  }

  Future<NavigationDecision> _handleNavigationRequest(
    NavigationRequest request,
  ) async {
    final Uri? uri = Uri.tryParse(request.url);
    if (uri == null || _shouldStayInWebView(uri)) {
      return NavigationDecision.navigate;
    }

    final bool opened = await _openExternalUri(uri);
    if (!opened) {
      await _showWebAlert('无法打开外部应用：${uri.scheme}');
    }
    return NavigationDecision.prevent;
  }

  bool _isNetworkUrl(String? url) {
    final Uri? uri = Uri.tryParse(url ?? '');
    if (uri == null) {
      return false;
    }
    return uri.scheme == 'http' || uri.scheme == 'https';
  }

  bool _shouldStayInWebView(Uri uri) {
    return <String>{
      'http',
      'https',
      'about',
      'data',
      'javascript',
      'file',
      'blob',
    }.contains(uri.scheme.toLowerCase());
  }

  void _recordWebViewEvent(String event) {
    _lastWebViewEvent = event;
    debugPrint('WebView 事件：$event');
  }

  void _armStartupWatchdog() {
    _cancelStartupWatchdog();
    _startupWatchdogTimer = Timer(_effectiveStartupWatchdogDuration, () {
      unawaited(_handleStartupTimeout());
    });
  }

  void _cancelStartupWatchdog() {
    _startupWatchdogTimer?.cancel();
    _startupWatchdogTimer = null;
  }

  Future<void> _handleStartupTimeout() async {
    if (!mounted ||
        !_isLoadingPage ||
        _hasMainFrameError ||
        _hasStartedRemoteMainFrame) {
      return;
    }

    final bool switchedRenderMode = _switchToNextRenderMode();
    final int maxRetryCount =
        _androidCompatibilityPlan.prefersAggressiveRecovery ? 2 : 1;

    if (switchedRenderMode || _startupRetryCount < maxRetryCount) {
      final int nextRetryCount = _startupRetryCount + 1;
      final String recoveryAction = switchedRenderMode
          ? '切换到${_activeRenderMode.displayName}'
          : '深度清理 WebView 状态';
      _recordWebViewEvent('启动超时，$recoveryAction 并自动重试第 $nextRetryCount 次');
      setState(() {
        _startupRetryCount = nextRetryCount;
        _hasMeasuredProgress = false;
        _progress = 0;
        _isLoadingPage = true;
      });
      await _recoverFromBrokenStartupState(deepReset: true);
      await _startLoadSequence(rebuildWebView: true, resetRetryCount: false);
      return;
    }

    _setMainFrameError(
      title: '页面启动超时',
      message: <String>[
        '${_effectiveStartupWatchdogDuration.inSeconds} 秒内没有完成页面加载。',
        '最近事件：$_lastWebViewEvent',
        _buildCompatibilityGuidance(),
      ].where((line) => line.isNotEmpty).join('\n'),
    );
  }

  void _setMainFrameError({required String title, required String message}) {
    _cancelStartupWatchdog();
    if (!mounted) {
      return;
    }
    setState(() {
      _isLoadingPage = false;
      _hasMainFrameError = true;
      _hasMeasuredProgress = false;
      _errorTitle = title;
      _errorMessage = message;
      _progress = 0;
    });
  }

  Future<void> _recoverFromBrokenStartupState({bool deepReset = false}) async {
    final Future<void> controllerSetupFuture = _controllerSetupFuture;
    try {
      await controllerSetupFuture;
    } catch (error, stackTrace) {
      debugPrint('等待 WebView 控制器初始化失败：$error\n$stackTrace');
    }

    try {
      await _controller.clearCache();
    } catch (error, stackTrace) {
      debugPrint('清理 WebView 缓存失败：$error\n$stackTrace');
    }

    try {
      await _controller.clearLocalStorage();
    } catch (error, stackTrace) {
      debugPrint('清理 WebView 本地存储失败：$error\n$stackTrace');
    }

    try {
      await _cookieManager.clearCookies();
    } catch (error, stackTrace) {
      debugPrint('清理 WebView Cookie 失败：$error\n$stackTrace');
    }

    if (!deepReset || defaultTargetPlatform != TargetPlatform.android) {
      return;
    }

    try {
      await _appShellDeviceChannel.invokeMethod<void>(
        'resetAndroidWebViewState',
      );
    } catch (error, stackTrace) {
      debugPrint('重置 Android WebView 状态失败：$error\n$stackTrace');
    }
  }

  String _friendlyErrorTitle(WebResourceError error) {
    switch (error.errorType) {
      case WebResourceErrorType.timeout:
        return '请求超时';
      case WebResourceErrorType.hostLookup:
      case WebResourceErrorType.connect:
      case WebResourceErrorType.io:
        return '网络连接失败';
      case WebResourceErrorType.failedSslHandshake:
        return '安全连接失败';
      default:
        return '页面加载失败';
    }
  }

  String _friendlyErrorMessage(WebResourceError error) {
    switch (error.errorType) {
      case WebResourceErrorType.timeout:
        return '当前网络较慢，请稍后重新加载。';
      case WebResourceErrorType.hostLookup:
      case WebResourceErrorType.connect:
      case WebResourceErrorType.io:
        return '没有成功连接到服务器，请检查网络后重试。';
      case WebResourceErrorType.failedSslHandshake:
        return '当前站点证书校验失败，请稍后再试。';
      default:
        final String description = error.description.trim();
        return description.isEmpty ? '请稍后重新加载页面。' : description;
    }
  }

  bool _shouldTreatHttpErrorAsMainFrame(HttpResponseError error) {
    final Uri? requestUri = error.request?.uri;
    if (requestUri == null) {
      return true;
    }

    if (!_isNetworkUrl(requestUri.toString())) {
      return false;
    }

    final Uri? currentUri = Uri.tryParse(_currentUrl);
    if (_isSameDocumentRequest(requestUri, currentUri)) {
      return true;
    }

    if (!_hasBootstrapped && _isSameDocumentRequest(requestUri, _initialUri)) {
      return true;
    }

    return false;
  }

  bool _isSameDocumentRequest(Uri left, Uri? right) {
    if (right == null) {
      return false;
    }

    return _normalizeComparableUri(left) == _normalizeComparableUri(right);
  }

  String _normalizeComparableUri(Uri uri) {
    final String scheme = uri.scheme.toLowerCase();
    final String host = uri.host.toLowerCase();
    final int port = uri.hasPort ? uri.port : _defaultPortForScheme(scheme);
    final String path = _normalizeComparablePath(uri.path);
    final String query = uri.query;
    return '$scheme://$host:$port$path?$query';
  }

  int _defaultPortForScheme(String scheme) {
    return switch (scheme) {
      'https' => 443,
      'http' => 80,
      _ => -1,
    };
  }

  String _normalizeComparablePath(String path) {
    if (path.isEmpty) {
      return '/';
    }
    if (path.length > 1 && path.endsWith('/')) {
      return path.substring(0, path.length - 1);
    }
    return path;
  }

  Future<void> _injectAppShellBridge(String url) async {
    try {
      await _controller.runJavaScript(_buildAppShellBridgeScript());
      debugPrint('已为 $url 注入 AppShell 桥接');
    } catch (error, stackTrace) {
      debugPrint('注入 AppShell 桥接失败：$error\n$stackTrace');
    }
  }

  Future<void> _handleBridgeMessage(String rawMessage) async {
    String requestId = '';

    try {
      final dynamic decoded = jsonDecode(rawMessage);
      if (decoded is! Map) {
        return;
      }

      final Map<String, dynamic> message = Map<String, dynamic>.from(decoded);
      requestId = (message['requestId'] ?? '').toString();
      final String action = (message['action'] ?? '').toString();
      final Map<String, dynamic> payload = message['payload'] is Map
          ? Map<String, dynamic>.from(message['payload'] as Map)
          : <String, dynamic>{};

      if (requestId.isEmpty || action.isEmpty) {
        return;
      }

      debugPrint('AppShell 桥接请求：action=$action payload=$payload');

      late final Object? data;
      switch (action) {
        case 'pickImage':
          data = await _pickImagesFromBridge(
            source: ImageSource.gallery,
            payload: payload,
          );
        case 'captureImage':
          data = await _pickImagesFromBridge(
            source: ImageSource.camera,
            payload: payload,
          );
        case 'pickFile':
          data = await _pickFilesFromBridge(payload);
        case 'openExternal':
          data = await _openExternalFromBridge(payload);
        case 'requestPermissions':
          data = await _requestPermissionsFromBridge(payload);
        case 'reloadPage':
          await _reloadPage();
          data = true;
        case 'goBack':
          data = await _goBackFromBridge();
        case 'closeApp':
          await _sendBridgeResponse(requestId: requestId, success: true);
          await SystemNavigator.pop();
          return;
        default:
          throw UnsupportedError('Unsupported AppShell action: $action');
      }

      await _sendBridgeResponse(
        requestId: requestId,
        success: true,
        data: data,
      );
    } catch (error, stackTrace) {
      debugPrint('处理 AppShell 桥接请求失败：$error\n$stackTrace');
      if (requestId.isNotEmpty) {
        await _sendBridgeResponse(
          requestId: requestId,
          success: false,
          error: error.toString(),
        );
      }
    }
  }

  Future<Object?> _pickImagesFromBridge({
    required ImageSource source,
    required Map<String, dynamic> payload,
  }) async {
    final bool multiple =
        source == ImageSource.gallery && _boolValue(payload['multiple']);
    final String responseType = (payload['responseType'] ?? 'dataUrl')
        .toString();

    List<XFile> files = <XFile>[];
    if (source == ImageSource.camera) {
      final XFile? file = await _pickCameraImage(showPermissionAlert: true);
      if (file != null) {
        files = <XFile>[file];
      }
    } else if (multiple) {
      files = await _imagePicker.pickMultiImage(
        imageQuality: _pickedImageQuality,
        maxWidth: _pickedImageMaxWidth,
        maxHeight: _pickedImageMaxHeight,
      );
    } else {
      final XFile? file = await _imagePicker.pickImage(
        source: ImageSource.gallery,
        imageQuality: _pickedImageQuality,
        maxWidth: _pickedImageMaxWidth,
        maxHeight: _pickedImageMaxHeight,
      );
      if (file != null) {
        files = <XFile>[file];
      }
    }

    final List<Map<String, dynamic>> serialized = await _serializeXFiles(
      files,
      responseType: responseType,
    );
    return multiple ? serialized : serialized.firstOrNull;
  }

  Future<Object?> _pickFilesFromBridge(Map<String, dynamic> payload) async {
    final String responseType = (payload['responseType'] ?? 'uri').toString();
    final bool includeBinary =
        responseType == 'dataUrl' || responseType == 'base64';

    final FilePickerResult? result = await FilePicker.platform.pickFiles(
      allowMultiple: _boolValue(payload['multiple']),
      withData: includeBinary,
      type: FileType.any,
    );

    if (result == null) {
      return _boolValue(payload['multiple']) ? <Map<String, dynamic>>[] : null;
    }

    final List<Map<String, dynamic>> serialized = await _serializePlatformFiles(
      result.files,
      responseType: responseType,
    );
    return _boolValue(payload['multiple'])
        ? serialized
        : serialized.firstOrNull;
  }

  Future<bool> _openExternalFromBridge(Map<String, dynamic> payload) async {
    final String url = (payload['url'] ?? '').toString();
    final Uri? uri = Uri.tryParse(url);
    if (uri == null) {
      return false;
    }
    if (_isNetworkUrl(uri.toString())) {
      return false;
    }
    return _openExternalUri(uri);
  }

  Future<Map<String, String>> _requestPermissionsFromBridge(
    Map<String, dynamic> payload,
  ) async {
    final List<String> types =
        (payload['types'] as List<dynamic>? ?? const <dynamic>[])
            .map((type) => type.toString())
            .toList();

    final Map<String, Permission> permissions = <String, Permission>{
      for (final String type in types)
        if (_permissionForType(type) case final Permission permission)
          type: permission,
    };

    if (permissions.isEmpty) {
      return <String, String>{};
    }

    final Map<Permission, PermissionStatus> statuses = await permissions.values
        .toSet()
        .toList()
        .request();

    return <String, String>{
      for (final MapEntry<String, Permission> entry in permissions.entries)
        entry.key: (statuses[entry.value] ?? PermissionStatus.denied).name,
    };
  }

  Future<bool> _goBackFromBridge() async {
    if (await _controller.canGoBack()) {
      await _controller.goBack();
      return true;
    }
    return false;
  }

  Future<void> _sendBridgeResponse({
    required String requestId,
    required bool success,
    Object? data,
    String? error,
  }) async {
    final Map<String, dynamic> response = <String, dynamic>{
      'requestId': requestId,
      'success': success,
      'data': data,
      'error': error,
    };

    try {
      await _controller.runJavaScript(
        'window.__appShellReceiveResponse(${jsonEncode(response)});',
      );
    } catch (bridgeError, stackTrace) {
      debugPrint(
        '发送 AppShell 桥接响应失败：$bridgeError\n$stackTrace',
      );
    }
  }

  Future<List<String>> _handleFileSelector(FileSelectorParams params) async {
    debugPrint(
      'WebView 文件选择： '
      'accept=${params.acceptTypes}, '
      'capture=${params.isCaptureEnabled}, '
      'mode=${params.mode.name}',
    );

    if (params.mode == FileSelectorMode.save) {
      return <String>[];
    }

    try {
      final bool acceptsImages = _acceptsImages(params.acceptTypes);
      final bool imagesOnly = _acceptsOnlyImages(params.acceptTypes);

      if (params.isCaptureEnabled && acceptsImages) {
        final XFile? capturedImage = await _pickCameraImage();
        return _xFilesToUriStrings(
          capturedImage == null ? const <XFile>[] : <XFile>[capturedImage],
        );
      }

      if (imagesOnly) {
        if (params.mode == FileSelectorMode.openMultiple) {
          final List<XFile> images = await _imagePicker.pickMultiImage(
            imageQuality: _pickedImageQuality,
            maxWidth: _pickedImageMaxWidth,
            maxHeight: _pickedImageMaxHeight,
          );
          return _xFilesToUriStrings(images);
        }

        final XFile? image = await _imagePicker.pickImage(
          source: ImageSource.gallery,
          imageQuality: _pickedImageQuality,
          maxWidth: _pickedImageMaxWidth,
          maxHeight: _pickedImageMaxHeight,
        );
        return _xFilesToUriStrings(
          image == null ? const <XFile>[] : <XFile>[image],
        );
      }

      final FilePickerResult? result = await FilePicker.platform.pickFiles(
        allowMultiple: params.mode == FileSelectorMode.openMultiple,
        type: FileType.any,
      );

      if (result == null) {
        return <String>[];
      }

      return result.files
          .map((file) => file.path)
          .whereType<String>()
          .map((path) => Uri.file(path).toString())
          .toList();
    } catch (error, stackTrace) {
      debugPrint('处理文件选择失败：$error\n$stackTrace');
      return <String>[];
    }
  }

  Future<void> _handlePlatformPermissionRequest(
    PlatformWebViewPermissionRequest request,
  ) async {
    debugPrint(
      'WebView 权限请求：'
      '${request.types.map((type) => type.name).join(', ')}',
    );

    final List<Permission> permissions = <Permission>[
      if (request.types.contains(WebViewPermissionResourceType.camera))
        Permission.camera,
      if (request.types.contains(WebViewPermissionResourceType.microphone))
        Permission.microphone,
    ];

    if (permissions.isEmpty) {
      await request.deny();
      return;
    }

    final Map<Permission, PermissionStatus> statuses = await permissions
        .request();
    final bool allGranted = statuses.values.every((status) => status.isGranted);

    if (allGranted) {
      await request.grant();
      return;
    }

    await request.deny();
  }

  Future<GeolocationPermissionsResponse> _handleGeolocationPermissionRequest(
    GeolocationPermissionsRequestParams request,
  ) async {
    debugPrint('WebView 地理位置权限请求：${request.origin}');
    final PermissionStatus status = await Permission.location.request();
    return GeolocationPermissionsResponse(
      allow: status.isGranted,
      retain: status.isGranted,
    );
  }

  Future<XFile?> _pickCameraImage({bool showPermissionAlert = false}) async {
    final PermissionStatus cameraStatus = await Permission.camera.request();
    if (!cameraStatus.isGranted) {
      if (showPermissionAlert) {
        await _showWebAlert('请先在系统设置中允许相机权限');
      }
      return null;
    }

    try {
      return await _imagePicker.pickImage(
        source: ImageSource.camera,
        preferredCameraDevice: CameraDevice.rear,
        imageQuality: _pickedImageQuality,
        maxWidth: _pickedImageMaxWidth,
        maxHeight: _pickedImageMaxHeight,
      );
    } catch (error, stackTrace) {
      debugPrint('调用相机拍照失败：$error\n$stackTrace');
      if (showPermissionAlert) {
        await _showWebAlert('无法打开系统相机，请稍后重试');
      }
      return null;
    }
  }

  Future<List<Map<String, dynamic>>> _serializeXFiles(
    List<XFile> files, {
    required String responseType,
  }) async {
    final bool includeBase64 =
        responseType == 'base64' || responseType == 'dataUrl';
    final bool includeDataUrl = responseType == 'dataUrl';

    final List<Map<String, dynamic>> serialized = <Map<String, dynamic>>[];
    for (final XFile file in files) {
      String? base64Value;
      final String mimeType = _guessMimeType(file.name);

      if (includeBase64 || includeDataUrl) {
        base64Value = base64Encode(await file.readAsBytes());
      }

      serialized.add(<String, dynamic>{
        'name': file.name,
        'uri': Uri.file(file.path).toString(),
        'mimeType': mimeType,
        'size': await file.length(),
        if (responseType == 'base64') 'base64': base64Value,
        if (includeDataUrl && base64Value != null)
          'dataUrl': 'data:$mimeType;base64,$base64Value',
      });
    }
    return serialized;
  }

  Future<List<Map<String, dynamic>>> _serializePlatformFiles(
    List<PlatformFile> files, {
    required String responseType,
  }) async {
    final bool includeBase64 =
        responseType == 'base64' || responseType == 'dataUrl';
    final bool includeDataUrl = responseType == 'dataUrl';

    final List<Map<String, dynamic>> serialized = <Map<String, dynamic>>[];
    for (final PlatformFile file in files) {
      final String mimeType = _guessMimeType(file.name);
      String? base64Value;

      if (includeBase64 || includeDataUrl) {
        final List<int>? bytes =
            file.bytes ??
            (file.path == null ? null : await XFile(file.path!).readAsBytes());
        if (bytes != null) {
          base64Value = base64Encode(bytes);
        }
      }

      serialized.add(<String, dynamic>{
        'name': file.name,
        'uri': file.path == null ? null : Uri.file(file.path!).toString(),
        'mimeType': mimeType,
        'size': file.size,
        if (responseType == 'base64') 'base64': base64Value,
        if (includeDataUrl && base64Value != null)
          'dataUrl': 'data:$mimeType;base64,$base64Value',
      });
    }
    return serialized;
  }

  Future<bool> _openExternalUri(Uri uri) async {
    try {
      return await launchUrl(uri, mode: LaunchMode.externalApplication);
    } catch (error, stackTrace) {
      debugPrint('外部打开 URI 失败：$error\n$stackTrace');
      return false;
    }
  }

  Permission? _permissionForType(String type) {
    return switch (type.toLowerCase()) {
      'camera' => Permission.camera,
      'microphone' || 'audio' => Permission.microphone,
      'location' => Permission.location,
      'photos' || 'images' => Permission.photos,
      'videos' => Permission.videos,
      'storage' => Permission.storage,
      _ => null,
    };
  }

  bool _boolValue(Object? value, {bool defaultValue = false}) {
    return switch (value) {
      bool boolValue => boolValue,
      String stringValue => stringValue.toLowerCase() == 'true',
      int intValue => intValue != 0,
      _ => defaultValue,
    };
  }

  Future<void> _showWebAlert(String message) async {
    try {
      await _controller.runJavaScript('window.alert(${jsonEncode(message)});');
    } catch (error, stackTrace) {
      debugPrint('展示网页弹窗失败：$error\n$stackTrace');
    }
  }

  List<String> _xFilesToUriStrings(List<XFile> files) {
    return files.map((file) => Uri.file(file.path).toString()).toList();
  }

  bool _acceptsImages(List<String> acceptTypes) {
    return acceptTypes
        .map((type) => type.trim())
        .where((type) => type.isNotEmpty)
        .any(_isImageAcceptType);
  }

  bool _acceptsOnlyImages(List<String> acceptTypes) {
    final List<String> normalizedTypes = acceptTypes
        .map((type) => type.trim())
        .where((type) => type.isNotEmpty)
        .toList();
    if (normalizedTypes.isEmpty) {
      return false;
    }
    return normalizedTypes.every(_isImageAcceptType);
  }

  bool _isImageAcceptType(String acceptType) {
    final String value = acceptType.toLowerCase();
    return value.startsWith('image/') ||
        const <String>{
          '.png',
          '.jpg',
          '.jpeg',
          '.webp',
          '.gif',
          '.bmp',
          '.heic',
          '.heif',
        }.contains(value);
  }

  String _guessMimeType(String fileName) {
    final String lower = fileName.toLowerCase();
    if (lower.endsWith('.png')) {
      return 'image/png';
    }
    if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) {
      return 'image/jpeg';
    }
    if (lower.endsWith('.webp')) {
      return 'image/webp';
    }
    if (lower.endsWith('.gif')) {
      return 'image/gif';
    }
    if (lower.endsWith('.bmp')) {
      return 'image/bmp';
    }
    if (lower.endsWith('.heic')) {
      return 'image/heic';
    }
    if (lower.endsWith('.heif')) {
      return 'image/heif';
    }
    if (lower.endsWith('.pdf')) {
      return 'application/pdf';
    }
    if (lower.endsWith('.txt')) {
      return 'text/plain';
    }
    if (lower.endsWith('.apk')) {
      return 'application/vnd.android.package-archive';
    }
    return 'application/octet-stream';
  }

  String _buildAppShellBridgeScript() {
    return '''
(() => {
  const channel = window.$_appShellBridgeChannel;
  if (!channel || typeof channel.postMessage !== 'function') {
    return;
  }

  if (window.AppShell && window.AppShell.__nativeShellVersion === '$_appShellBridgeVersion') {
    return;
  }

  const pending = new Map();

  window.__appShellReceiveResponse = function(response) {
    if (!response || !response.requestId) {
      return;
    }

    const task = pending.get(response.requestId);
    if (!task) {
      return;
    }

    pending.delete(response.requestId);

    if (response.success) {
      task.resolve(response.data ?? null);
      return;
    }

    task.reject(new Error(response.error || 'Native request failed'));
  };

  const send = (action, payload = {}) => new Promise((resolve, reject) => {
    const requestId =
      Date.now().toString(36) + Math.random().toString(36).slice(2);
    pending.set(requestId, { resolve, reject });
    channel.postMessage(JSON.stringify({ requestId, action, payload }));
  });

  window.AppShell = {
    __nativeShellVersion: '$_appShellBridgeVersion',
    isNativeShell: true,
    version: '$_appShellBridgeVersion',
    pickImage: (options = {}) => send('pickImage', options),
    captureImage: (options = {}) => send('captureImage', options),
    pickFile: (options = {}) => send('pickFile', options),
    openExternal: (url) => send('openExternal', { url }),
    requestPermissions: (types = []) => send('requestPermissions', { types }),
    reloadPage: () => send('reloadPage'),
    goBack: () => send('goBack'),
    closeApp: () => send('closeApp'),
  };

  window.dispatchEvent(new CustomEvent('app-shell-ready', {
    detail: {
      version: '$_appShellBridgeVersion',
      isNativeShell: true,
    },
  }));

  if (!window.__appShellLegacyCameraCompatInstalled) {
    window.__appShellLegacyCameraCompatInstalled = true;

    const installLegacyCameraCompat = () => {
      if (typeof window.openCamera !== 'function') {
        return false;
      }
      if (window.openCamera.__appShellCompatWrapped) {
        return true;
      }

      const originalOpenCamera = window.openCamera.bind(window);
      const originalCapturePhoto =
        typeof window.capturePhoto === 'function'
          ? window.capturePhoto.bind(window)
          : null;

      const deliverCaptureResult = (args, result) => {
        const detail = {
          args: Array.from(args),
          result,
        };

        if (typeof window.handleNativeCapture === 'function') {
          window.handleNativeCapture(detail);
          return true;
        }

        const targetId =
          detail.args[0] ??
          window.currentUploadId ??
          null;

        if (
          targetId != null &&
          result &&
          result.dataUrl &&
          typeof window.addPreview === 'function'
        ) {
          window.currentUploadId = targetId;
          window.addPreview(targetId, result.dataUrl);
          return true;
        }

        window.dispatchEvent(
          new CustomEvent('app-shell-image-captured', {
            detail,
          }),
        );
        return false;
      };

      const closeLegacyCameraModal = () => {
        if (typeof window.closeCamera === 'function') {
          try {
            window.closeCamera();
          } catch (_) {}
        }
      };

      const wrappedOpenCamera = async function(...args) {
        try {
          const result = await window.AppShell.captureImage({
            responseType: 'dataUrl',
          });
          const delivered = deliverCaptureResult(args, result);
          if (!delivered) {
            return result;
          }
          closeLegacyCameraModal();
          return result;
        } catch (error) {
          console.warn(
            '[AppShell] legacy openCamera fallback to page implementation',
            error,
          );
          return originalOpenCamera(...args);
        }
      };

      wrappedOpenCamera.__appShellCompatWrapped = true;
      window.openCamera = wrappedOpenCamera;

      if (originalCapturePhoto) {
        const wrappedCapturePhoto = function(...args) {
          if (
            window.currentUploadId != null &&
            window.__lastAppShellCaptureResult &&
            window.__lastAppShellCaptureResult.dataUrl &&
            typeof window.addPreview === 'function'
          ) {
            window.addPreview(
              window.currentUploadId,
              window.__lastAppShellCaptureResult.dataUrl,
            );
            closeLegacyCameraModal();
            return;
          }
          return originalCapturePhoto(...args);
        };
        wrappedCapturePhoto.__appShellCompatWrapped = true;
        window.capturePhoto = wrappedCapturePhoto;
      }

      return true;
    };

    const originalCaptureImage = window.AppShell.captureImage;
    window.AppShell.captureImage = async function(options = {}) {
      const result = await originalCaptureImage(options);
      window.__lastAppShellCaptureResult = result;
      return result;
    };

    installLegacyCameraCompat();

    let attempts = 0;
    const compatTimer = setInterval(() => {
      attempts += 1;
      const installed = installLegacyCameraCompat();
      if (installed || attempts >= 20) {
        clearInterval(compatTimer);
      }
    }, 500);
  }
})();
''';
  }

  Future<void> _startLoadSequence({
    required bool rebuildWebView,
    required bool resetRetryCount,
  }) async {
    _cancelStartupWatchdog();
    if (!mounted) {
      return;
    }
    if (rebuildWebView) {
      _recreateWebView();
    }

    final int generation = _webViewGeneration;
    final Future<void> controllerSetupFuture = _controllerSetupFuture;

    _hasStartedRemoteMainFrame = false;
    setState(() {
      _isLoadingPage = true;
      _hasMainFrameError = false;
      _hasMeasuredProgress = false;
      _progress = 0;
      if (resetRetryCount) {
        _startupRetryCount = 0;
      }
      _errorTitle = '页面加载失败';
      _errorMessage = '请检查网络后重试。';
      _currentUrl = _initialUrl;
    });

    await controllerSetupFuture;
    if (!mounted || !_isActiveWebViewGeneration(generation)) {
      return;
    }

    await _waitForWebViewMount(generation);

    debugPrint(
      'WebShell 开始在 WebView #$generation 加载真实地址 '
      '(${_activeRenderMode.logName}): $_initialUrl',
    );
    try {
      _armStartupWatchdog();
      await _controller.loadRequest(_initialUri);
    } catch (error, stackTrace) {
      debugPrint('初始地址加载失败：$error\n$stackTrace');
      _setMainFrameError(
        title: '地址无法加载',
        message: '无法打开 $_initialUrl，请检查地址格式或网络后重试。',
      );
    }
  }

  Future<void> _waitForWebViewMount(int generation) async {
    final bool isAndroidDebug =
        kDebugMode && defaultTargetPlatform == TargetPlatform.android;
    final bool usesHybridComposition =
        defaultTargetPlatform == TargetPlatform.android &&
        _activeRenderMode.usesHybridComposition;
    final int framesToWait = isAndroidDebug
        ? (usesHybridComposition ? 6 : 4)
        : (usesHybridComposition ? 2 : 1);
    final Duration settleDelay = isAndroidDebug
        ? (usesHybridComposition
              ? const Duration(milliseconds: 250)
              : const Duration(milliseconds: 140))
        : (usesHybridComposition
              ? const Duration(milliseconds: 80)
              : const Duration(milliseconds: 40));

    for (int index = 0; index < framesToWait; index += 1) {
      await SchedulerBinding.instance.endOfFrame;
      if (!mounted || !_isActiveWebViewGeneration(generation)) {
        return;
      }
      await Future<void>.delayed(settleDelay);
      if (!mounted || !_isActiveWebViewGeneration(generation)) {
        return;
      }
    }
  }

  Future<void> _reloadPage() async {
    if (!mounted) {
      return;
    }
    await _ensureCompatibilityPlanApplied();
    if (!mounted) {
      return;
    }
    if (!_hasBootstrapped) {
      await _recoverFromBrokenStartupState(deepReset: true);
      await _startLoadSequence(rebuildWebView: true, resetRetryCount: true);
      return;
    }
    setState(() {
      _hasStartedRemoteMainFrame = false;
      _isLoadingPage = true;
      _hasMainFrameError = false;
      _hasMeasuredProgress = false;
      _progress = 0;
      _startupRetryCount = 0;
      _errorTitle = '页面加载失败';
      _errorMessage = '请检查网络后重试。';
    });
    try {
      _armStartupWatchdog();
      await _controller.reload();
    } catch (error, stackTrace) {
      debugPrint('重新加载当前页面失败：$error\n$stackTrace');
      await _startLoadSequence(rebuildWebView: true, resetRetryCount: true);
    }
  }

  @override
  Widget build(BuildContext context) {
    final bool showProgressBar =
        _isLoadingPage && (!_hasMeasuredProgress || _progress < 100);
    final bool showLaunchOverlay = !_hasBootstrapped && !_hasMainFrameError;

    return PopScope<void>(
      canPop: false,
      onPopInvokedWithResult: (didPop, result) {
        if (!didPop) {
          unawaited(_handleBackPressed());
        }
      },
      child: Scaffold(
        backgroundColor: _shellBackgroundColor,
        body: DecoratedBox(
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              colors: <Color>[_shellBackgroundColor, Color(0xFFF4FBF7)],
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
            ),
          ),
          child: SafeArea(
            top: false,
            bottom: false,
            child: Stack(
              children: <Widget>[
                Positioned.fill(
                  child: ColoredBox(
                    color: _shellBackgroundColor,
                    child: _webViewWidget,
                  ),
                ),
                if (showProgressBar)
                  Positioned(
                    top: 0,
                    left: 0,
                    right: 0,
                    child: _TopProgressBar(
                      progress: _progress,
                      hasMeasuredProgress: _hasMeasuredProgress,
                    ),
                  ),
                if (showLaunchOverlay)
                  Positioned.fill(
                    child: _LaunchOverlay(
                      progress: _progress,
                      hasMeasuredProgress: _hasMeasuredProgress,
                    ),
                  ),
                if (_hasMainFrameError)
                  Positioned.fill(
                    child: _ErrorOverlay(
                      title: _errorTitle,
                      message: _errorMessage,
                      currentUrl: _currentUrl,
                      onRetry: _reloadPage,
                    ),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class UnsupportedPlatformPage extends StatelessWidget {
  const UnsupportedPlatformPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: _shellBackgroundColor,
      body: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 28),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Container(
                width: 84,
                height: 84,
                decoration: BoxDecoration(
                  color: _shellAccentColor.withValues(alpha: 0.12),
                  borderRadius: BorderRadius.circular(24),
                ),
                alignment: Alignment.center,
                child: const Icon(
                  Icons.language_rounded,
                  size: 42,
                  color: _shellAccentColor,
                ),
              ),
              const SizedBox(height: 20),
              const Text(
                '当前平台不支持内嵌 WebView',
                style: TextStyle(
                  fontSize: 22,
                  fontWeight: FontWeight.w700,
                  color: _shellTextColor,
                ),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 12),
              Text(
                '当前项目仅支持 Android 平板运行。\n$_initialUrl',
                textAlign: TextAlign.center,
                style: const TextStyle(
                  height: 1.6,
                  color: _shellMutedTextColor,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _TopProgressBar extends StatelessWidget {
  const _TopProgressBar({
    required this.progress,
    required this.hasMeasuredProgress,
  });

  final int progress;
  final bool hasMeasuredProgress;

  @override
  Widget build(BuildContext context) {
    return LinearProgressIndicator(
      minHeight: 3,
      value: hasMeasuredProgress ? progress / 100 : null,
      backgroundColor: Colors.white.withValues(alpha: 0.8),
      valueColor: const AlwaysStoppedAnimation<Color>(_shellAccentColor),
    );
  }
}

class _LaunchOverlay extends StatelessWidget {
  const _LaunchOverlay({
    required this.progress,
    required this.hasMeasuredProgress,
  });

  final int progress;
  final bool hasMeasuredProgress;

  @override
  Widget build(BuildContext context) {
    return ColoredBox(
      color: _shellBackgroundColor,
      child: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 32),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Container(
                width: 88,
                height: 88,
                decoration: BoxDecoration(
                  gradient: const LinearGradient(
                    colors: <Color>[Color(0xFF66E59A), _shellAccentColor],
                    begin: Alignment.topLeft,
                    end: Alignment.bottomRight,
                  ),
                  borderRadius: BorderRadius.circular(28),
                  boxShadow: const <BoxShadow>[
                    BoxShadow(
                      color: Color(0x263ED37B),
                      blurRadius: 24,
                      offset: Offset(0, 14),
                    ),
                  ],
                ),
                alignment: Alignment.center,
                child: const Icon(
                  Icons.public_rounded,
                  size: 40,
                  color: Colors.white,
                ),
              ),
              const SizedBox(height: 24),
              const Text(
                '页面加载中',
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.w800,
                  color: _shellTextColor,
                ),
              ),
              const SizedBox(height: 10),
              const Text(
                '正在为你启动 H5 页面',
                style: TextStyle(color: _shellMutedTextColor, fontSize: 14),
              ),
              const SizedBox(height: 24),
              ClipRRect(
                borderRadius: BorderRadius.circular(999),
                child: SizedBox(
                  width: 220,
                  child: LinearProgressIndicator(
                    minHeight: 6,
                    value: hasMeasuredProgress ? progress / 100 : null,
                    backgroundColor: const Color(0xFFE7F3EB),
                    valueColor: const AlwaysStoppedAnimation<Color>(
                      _shellAccentColor,
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 10),
              Text(
                hasMeasuredProgress ? '$progress%' : '连接中...',
                style: const TextStyle(
                  color: Color(0xFF94A3B8),
                  fontSize: 12,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _ErrorOverlay extends StatelessWidget {
  const _ErrorOverlay({
    required this.title,
    required this.message,
    required this.currentUrl,
    required this.onRetry,
  });

  final String title;
  final String message;
  final String currentUrl;
  final Future<void> Function() onRetry;

  @override
  Widget build(BuildContext context) {
    return ColoredBox(
      color: _shellBackgroundColor,
      child: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 28),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Container(
                width: 92,
                height: 92,
                decoration: BoxDecoration(
                  color: const Color(0xFFFEF2F2),
                  borderRadius: BorderRadius.circular(28),
                ),
                alignment: Alignment.center,
                child: const Icon(
                  Icons.wifi_off_rounded,
                  size: 44,
                  color: Color(0xFFEF4444),
                ),
              ),
              const SizedBox(height: 22),
              Text(
                title,
                style: const TextStyle(
                  fontSize: 22,
                  fontWeight: FontWeight.w800,
                  color: _shellTextColor,
                ),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 10),
              Text(
                message,
                style: const TextStyle(
                  fontSize: 14,
                  height: 1.6,
                  color: _shellMutedTextColor,
                ),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 12),
              Text(
                currentUrl,
                style: const TextStyle(fontSize: 12, color: Color(0xFF94A3B8)),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 24),
              FilledButton.icon(
                onPressed: onRetry,
                style: FilledButton.styleFrom(
                  backgroundColor: _shellAccentColor,
                  foregroundColor: Colors.black,
                ),
                icon: const Icon(Icons.refresh_rounded),
                label: const Text('重新加载'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
