import 'dart:async';
import 'dart:math' show max, pow;
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:gal/gal.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'dart:io';

import 'package:qr_flutter/qr_flutter.dart';

import '../../../core/config/app_config.dart';
import '../../../core/l10n/app_localizations.dart';
import '../../../core/network/dio_client.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/utils/number_format.dart';
import '../../../core/utils/symbol_display.dart';
import '../../../core/utils/top_toast.dart';
import '../../../data/services/auth_service.dart';
import '../../../providers/auth_provider.dart';
import '../../../providers/copy_trading_provider.dart';
import '../../../providers/futures_provider.dart';
import '../../../providers/spot_provider.dart';
import '../../widgets/common/app_refresh_indicator.dart';
import '../../widgets/common/app_shimmer.dart';
import '../../widgets/common/kline_toolbar_icon.dart';
import '../../widgets/common/symbol_picker_sheet.dart';

/// 将仓位 marginMode 字符串（来自后端）映射为 l10n 标签
String _marginModeLabel(String mode, AppLocalizations l10n) {
  switch (mode) {
    case '分仓':
      return l10n.splitMargin;
    default:
      return l10n.crossMargin;
  }
}

/// marginMode → 标签颜色：全仓→蓝，分仓/逐仓→紫
Color _marginModeColor(String mode) {
  switch (mode) {
    case '分仓':
    case '逐仓':
      return AppColors.rankPurple;
    default:
      return AppColors.tagBlue;
  }
}

/// 从合约 symbol 提取基础币种名（BTC/USDT → BTC，ETCUSDT → ETC）
String _baseCoin(String sym) {
  if (sym.contains('/')) return sym.split('/').first;
  return sym.toUpperCase().replaceFirst(RegExp(r'USDT$'), '');
}

/// 未登录时跳转登录页，返回 false 表示操作被拦截
bool _requireLogin(BuildContext context, WidgetRef ref) {
  final isLoggedIn = ref.read(isLoggedInProvider);
  if (!isLoggedIn) {
    context.push('/login');
    return false;
  }
  return true;
}

/// 对齐原型的确认弹窗：上方消息 + 可选红色副文本，下方取消/确定按钮
Future<bool> _showFuturesConfirm(
  BuildContext context, {
  required String message,
  String? subMessage,
  String? cancelLabel,
  String? confirmLabel,
}) async {
  final l10n = AppLocalizations.of(context)!;
  final cancel = cancelLabel ?? l10n.cancelLabel;
  final confirm = confirmLabel ?? l10n.confirmLabel;
  final result = await showDialog<bool>(
    context: context,
    barrierDismissible: true,
    builder: (ctx) {
      final cs = Theme.of(ctx).colorScheme;
      return Dialog(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        backgroundColor: cs.surface,
        insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Padding(
              padding: const EdgeInsets.fromLTRB(24, 24, 24, 20),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    message,
                    style: TextStyle(
                        color: cs.onSurface,
                        fontSize: 15,
                        fontWeight: FontWeight.w600),
                  ),
                  if (subMessage != null) ...[
                    const SizedBox(height: 8),
                    Text(
                      subMessage,
                      style: const TextStyle(
                          color: AppColors.fall, fontSize: 13, height: 1.5),
                    ),
                  ],
                ],
              ),
            ),
            Divider(height: 1, thickness: 0.5, color: cs.outline.withAlpha(80)),
            IntrinsicHeight(
              child: Row(
                children: [
                  Expanded(
                    child: TextButton(
                      onPressed: () => Navigator.of(ctx).pop(false),
                      style: TextButton.styleFrom(
                        padding: const EdgeInsets.symmetric(vertical: 14),
                        shape: const RoundedRectangleBorder(
                          borderRadius: BorderRadius.only(
                              bottomLeft: Radius.circular(16)),
                        ),
                      ),
                      child: Text(
                        cancel,
                        style: TextStyle(
                            color: cs.onSurface.withAlpha(153), fontSize: 15),
                      ),
                    ),
                  ),
                  VerticalDivider(
                      width: 1,
                      thickness: 0.5,
                      color: cs.outline.withAlpha(80)),
                  Expanded(
                    child: TextButton(
                      onPressed: () => Navigator.of(ctx).pop(true),
                      style: TextButton.styleFrom(
                        padding: const EdgeInsets.symmetric(vertical: 14),
                        shape: const RoundedRectangleBorder(
                          borderRadius: BorderRadius.only(
                              bottomRight: Radius.circular(16)),
                        ),
                      ),
                      child: Text(
                        confirm,
                        style: const TextStyle(
                            color: AppColors.brand,
                            fontSize: 15,
                            fontWeight: FontWeight.w700),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      );
    },
  );
  return result == true;
}

class FuturesScreen extends ConsumerStatefulWidget {
  const FuturesScreen({
    super.key,
    required this.symbol,
    this.showSpotSwitcher = true,
  });
  final String symbol;
  final bool showSpotSwitcher;

  @override
  ConsumerState<FuturesScreen> createState() => _FuturesScreenState();
}

class _FuturesScreenState extends ConsumerState<FuturesScreen> {
  late final ScrollController _scroll;
  int _obRowCount = 7;
  double _obRowH = 22.0; // 订单薄每行高度（动态计算使左右精确对齐）
  double _leftPanelHeight = 490.0; // 左侧面板实测高度
  final _orderPanelKey = GlobalKey<_OrderPanelState>();

  void _onLeftPanelHeight(double h) {
    // countFixedH=150：保守余量，确保 N 行内容不溢出
    // rowH = (h - 150) / (2N)：让行高略小于最大值，Spacer 吸收余量
    const countFixedH = 150.0;
    final n = ((h - countFixedH) / 44).floor().clamp(4, 14);
    final rh = ((h - 150.0) / (n * 2)).clamp(18.0, 28.0);
    if (n != _obRowCount ||
        (rh - _obRowH).abs() > 0.1 ||
        h != _leftPanelHeight) {
      setState(() {
        _obRowCount = n;
        _obRowH = rh;
        _leftPanelHeight = h;
      });
    }
  }

  @override
  void initState() {
    super.initState();
    _scroll = ScrollController();
    _scroll.addListener(_onScroll);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      ref.read(futuresActiveSymbolProvider.notifier).state = widget.symbol;
      if (widget.showSpotSwitcher) {
        ref.read(lastTradingRouteProvider.notifier).state =
            '/futures/${widget.symbol}';
      }
    });
  }

  @override
  void didUpdateWidget(FuturesScreen oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.symbol != oldWidget.symbol) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted) return;
        ref.read(futuresActiveSymbolProvider.notifier).state = widget.symbol;
      });
    }
  }

  @override
  void dispose() {
    _scroll.removeListener(_onScroll);
    _scroll.dispose();
    super.dispose();
  }

  Future<void> _pushAndPausePolling(BuildContext context, String path) async {
    final notifier = ref.read(futuresProvider(widget.symbol).notifier);
    notifier.stopPolling();
    await context.push(path);
    if (mounted) notifier.resumePolling(widget.symbol);
  }

  void _onScroll() {
    if (_scroll.position.pixels < _scroll.position.maxScrollExtent - 200)
      return;
    final s = ref.read(futuresProvider(widget.symbol));
    if (s.activeTab == FuturesTab.orders) {
      ref.read(futuresProvider(widget.symbol).notifier).loadMoreOrders();
    }
  }

  @override
  Widget build(BuildContext context) {
    final symbol = widget.symbol;
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        elevation: 0,
        toolbarHeight: 44,
        titleSpacing: 16,
        title: widget.showSpotSwitcher
            ? _SpotFuturesTabHeader(
                activeIndex: 1,
                onTap: (i) {
                  if (i == 0) {
                    final spotSym = ref.read(spotActiveSymbolProvider);
                    context.go('/spot/${spotSym.isNotEmpty ? spotSym : 'BTCUSDT'}');
                  }
                },
              )
            : Text(
                l10n.perpetualContract,
                style: TextStyle(
                  color: cs.onSurface,
                  fontSize: 17,
                  fontWeight: FontWeight.w600,
                ),
              ),
        centerTitle: false,
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(1),
          child: Container(height: 1, color: cs.outline.withAlpha(40)),
        ),
        actions: [
          // K 线图标，点击进入行情详情页
          IconButton(
            icon: KlineToolbarIcon(color: cs.onSurface.withAlpha(180)),
            onPressed: () =>
                _pushAndPausePolling(context, '/market/futures/$symbol'),
            padding: const EdgeInsets.symmetric(horizontal: 8),
            constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
          ),
        ],
      ),
      body: Listener(
        onPointerDown: (_) => FocusScope.of(context).unfocus(),
        child: Builder(builder: (context) {
          final isLoading =
              ref.watch(futuresProvider(symbol).select((s) => s.isLoading));
          if (isLoading) return const _FuturesShimmer();
          return AppRefreshIndicator(
            onRefresh: () =>
                ref.read(futuresProvider(symbol).notifier).refresh(),
            child: SingleChildScrollView(
              controller: _scroll,
              physics: const ClampingScrollPhysics(),
              child: Column(
                children: [
                  Row(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Expanded(
                        flex: 55,
                        child: _SizeReporter(
                          onHeight: _onLeftPanelHeight,
                          child: DecoratedBox(
                            decoration: BoxDecoration(
                              border: Border(
                                right: BorderSide(
                                  color: cs.outline.withAlpha(40),
                                  width: 1,
                                ),
                              ),
                            ),
                            child: _OrderPanel(
                              key: _orderPanelKey,
                              symbol: symbol,
                              showSpotSwitcher: widget.showSpotSwitcher,
                            ),
                          ),
                        ),
                      ),
                      Expanded(
                        flex: 45,
                        child: SizedBox(
                          height: _leftPanelHeight,
                          child: RepaintBoundary(
                            child: _OrderBookPanel(
                              symbol: symbol,
                              rowCount: _obRowCount,
                              rowHeight: _obRowH,
                              onPriceTap: (price) => _orderPanelKey.currentState
                                  ?.setBookPrice(price),
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  _BottomSection(symbol: symbol),
                  const SizedBox(height: 16),
                ],
              ),
            ),
          ); // RefreshIndicator
        }), // Builder
      ),
    );
  }
}

class _OrderPanel extends ConsumerStatefulWidget {
  const _OrderPanel({
    super.key,
    required this.symbol,
    required this.showSpotSwitcher,
  });
  final String symbol;
  final bool showSpotSwitcher;

  @override
  ConsumerState<_OrderPanel> createState() => _OrderPanelState();
}

class _OrderPanelState extends ConsumerState<_OrderPanel> {
  final _priceController = TextEditingController();
  final _amountController = TextEditingController();
  final _triggerPriceController = TextEditingController();
  final _tpController = TextEditingController();
  final _slController = TextEditingController();
  bool _updatingFromSlider = false;
  bool _priceFilled = false; // 是否已完成首次价格回显
  double _lastPrice = 0; // 最新价缓存，用 ref.listen 维护，不触发 rebuild
  // 可开多/开空最大值快照
  String _maxOpenAmt = '--';
  // 市价单：每 5 秒自动刷新最大可开量
  Timer? _marketRefreshTimer;

  // 监听路由 secondaryAnimation，导航返回时清除焦点
  Animation<double>? _secondaryAnim;
  bool _isCoveredByRoute = false;

  @override
  void initState() {
    super.initState();
    _amountController.addListener(_onAmountChanged);
    // 进入页面后首帧快照最大可开量，并根据当前下单类型决定是否启动定时器
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      _refreshMaxAmount();
      _updateMarketTimer();
    });
    // 价格输入框变化时（非市价单）刷新最大可开量
    _priceController.addListener(_onPriceChanged);
    _triggerPriceController.addListener(_onPriceChanged);
  }

  void _onPriceChanged() {
    final s = ref.read(futuresProvider(widget.symbol));
    if (s.orderType != OrderType.market) _refreshMaxAmount();
  }

  void _refreshMaxAmount() {
    if (!mounted) return;
    final s = ref.read(futuresProvider(widget.symbol));
    setState(() {
      _maxOpenAmt = _calcMaxOpenAmount(s);
    });
  }

  /// 市价单开启 5 秒定时刷新，其他类型取消定时器（由价格输入框变化驱动）
  void _updateMarketTimer() {
    final s = ref.read(futuresProvider(widget.symbol));
    if (s.orderType == OrderType.market) {
      _marketRefreshTimer ??= Timer.periodic(
          const Duration(seconds: 5), (_) => _refreshMaxAmount());
    } else {
      _marketRefreshTimer?.cancel();
      _marketRefreshTimer = null;
    }
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final anim = ModalRoute.of(context)?.secondaryAnimation;
    if (anim != _secondaryAnim) {
      _secondaryAnim?.removeStatusListener(_onSecondaryAnimation);
      _secondaryAnim = anim;
      _secondaryAnim?.addStatusListener(_onSecondaryAnimation);
    }
  }

  void _onSecondaryAnimation(AnimationStatus status) {
    if (status == AnimationStatus.forward ||
        status == AnimationStatus.completed) {
      _isCoveredByRoute = true;
    } else if (status == AnimationStatus.dismissed && _isCoveredByRoute) {
      _isCoveredByRoute = false;
      // postFrameCallback 确保在 Flutter 自动恢复焦点之后再清除
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) FocusManager.instance.primaryFocus?.unfocus();
      });
    }
  }

  @override
  void didUpdateWidget(_OrderPanel oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.symbol != widget.symbol) {
      // 切换币对：延迟到下一帧再清空，重置首次价格回显标记
      _priceFilled = false;
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted) return;
        _priceController.clear();
        _amountController.clear();
        _triggerPriceController.clear();
        _tpController.clear();
        _slController.clear();
      });
    }
  }

  void _onAmountChanged() {
    if (_updatingFromSlider) return;
    final state = ref.read(futuresProvider(widget.symbol));
    final amount = double.tryParse(_amountController.text.replaceAll(',', ''));
    final notifier = ref.read(futuresProvider(widget.symbol).notifier);
    if (amount == null || amount <= 0) {
      notifier.setSliderPercentFromInput(0);
      return;
    }
    final refPrice = _refPrice(state);
    // 限价/计划委托未填价格时，滑块归零但不弹 toast（toast 在点下单时才提示）
    if (refPrice <= 0) {
      notifier.setSliderPercentFromInput(0);
      return;
    }
    final contractSize = state.contractSize > 0 ? state.contractSize : 1.0;

    // ── 平仓模式：以仓位可平量（BTC）为 max ──
    if (state.positionMode == PositionMode.close) {
      final maxBtc = _closeMaxBtc(state);
      if (maxBtc <= 0) return;
      final double pct;
      switch (state.amountUnit) {
        case AmountUnit.btc:
          pct = (amount / maxBtc).clamp(0.0, 1.0);
        case AmountUnit.usdt:
          final maxUsdt = maxBtc * refPrice;
          pct = maxUsdt > 0 ? (amount / maxUsdt).clamp(0.0, 1.0) : 0.0;
        case AmountUnit.lots:
          final maxLots = contractSize > 0 && refPrice > 0
              ? maxBtc * refPrice / contractSize
              : 0.0;
          pct = maxLots > 0 ? (amount / maxLots).clamp(0.0, 1.0) : 0.0;
      }
      notifier.setSliderPercentFromInput(pct);
      return;
    }

    // ── 开仓模式：以可用保证金为 max ──
    final availableMargin = state.accountInfo.availableMargin;
    final leverage = state.leverage;
    if (availableMargin <= 0 || refPrice <= 0) return;
    // maxNotional = 最大名义仓位价值（保证金 × 杠杆）
    final maxNotional = availableMargin * leverage;
    if (maxNotional <= 0) return;
    final double pct;
    switch (state.amountUnit) {
      case AmountUnit.lots:
        if (contractSize <= 0) return;
        final maxL = (maxNotional / contractSize).floor();
        pct = maxL > 0 ? (amount / maxL).clamp(0.0, 1.0) : 0.0;
      case AmountUnit.usdt:
        pct = (amount / maxNotional).clamp(0.0, 1.0);
      case AmountUnit.btc:
        pct = (amount * refPrice / maxNotional).clamp(0.0, 1.0);
    }
    notifier.setSliderPercentFromInput(pct);
  }

  /// 计算数量上限时使用的参考价格。
  /// 返回 0 表示必填价格未填，调用方应提示用户先输入价格。
  ///   市价        → 最新成交价
  ///   限价 / 计划限价 → 价格输入框（未填返回 0）
  ///   计划市价    → 触发价输入框（未填返回 0）
  double _refPrice(FuturesState state) {
    switch (state.orderType) {
      case OrderType.market:
        return state.lastPrice;
      case OrderType.limit:
      case OrderType.conditionalLimit:
        final v = double.tryParse(_priceController.text.replaceAll(',', ''));
        return (v != null && v > 0) ? v : 0.0;
      case OrderType.conditionalMarket:
        final v =
            double.tryParse(_triggerPriceController.text.replaceAll(',', ''));
        return (v != null && v > 0) ? v : 0.0;
    }
  }

  /// 检查参考价格是否有效，无效时弹出提示并返回 false
  bool _checkRefPrice(BuildContext context, FuturesState state) {
    if (_refPrice(state) > 0) return true;
    final l10n = AppLocalizations.of(context)!;
    final hint = state.orderType == OrderType.conditionalMarket
        ? l10n.enterTriggerPrice
        : l10n.enterPrice;
    showTopToast(context, message: hint, backgroundColor: AppColors.fall);
    return false;
  }

  /// 当前符合 symbol 的最大可平量（单位：BTC/基础币），取多空中较大的一方
  double _closeMaxBtc(FuturesState state) {
    final normSym =
        state.symbol.replaceAll('/', '').replaceAll('-', '').toUpperCase();
    return state.positions
        .where((p) => p.symbol.replaceAll('/', '').toUpperCase() == normSym)
        .fold(0.0, (m, p) => p.availableSize > m ? p.availableSize : m);
  }

  @override
  void dispose() {
    _secondaryAnim?.removeStatusListener(_onSecondaryAnimation);
    _amountController.removeListener(_onAmountChanged);
    _priceController.removeListener(_onPriceChanged);
    _triggerPriceController.removeListener(_onPriceChanged);
    _marketRefreshTimer?.cancel();
    _priceController.dispose();
    _amountController.dispose();
    _triggerPriceController.dispose();
    _tpController.dispose();
    _slController.dispose();
    super.dispose();
  }

  /// 订单薄点击回调：将价格填入对应输入框
  /// 计划市价 → 回显到触发价格；其余情况（含计划限价）→ 回显到委托价格
  void setBookPrice(double price) {
    if (!mounted) return;
    final state = ref.read(futuresProvider(widget.symbol));
    final precision = state.pricePrecision;
    final formatted = price.toStringAsFixed(precision);
    if (state.orderType == OrderType.conditionalMarket) {
      _triggerPriceController.text = formatted;
    } else {
      _priceController.text = formatted;
    }
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final provider = futuresProvider(widget.symbol);
    final notifier = ref.read(provider.notifier);

    // 切换开仓/平仓模式时，清空数量输入框和滑块
    ref.listen(provider.select((s) => s.positionMode), (prev, next) {
      if (prev != next) {
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (!mounted) return;
          _amountController.clear();
          _priceController.clear();
          notifier.setSliderPercent(0);
        });
      }
    });

    // 切换下单类型时，清空所有输入框和滑块；
    // 重置 _priceFilled，限价/计划限价会在下一帧自动回显最新价；
    // 同时重新快照最大可开量
    ref.listen(provider.select((s) => s.orderType), (prev, next) {
      if (prev != next) {
        _priceFilled = false;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (!mounted) return;
          FocusManager.instance.primaryFocus?.unfocus();
          _priceController.clear();
          _amountController.clear();
          _triggerPriceController.clear();
          _tpController.clear();
          _slController.clear();
          notifier.setSliderPercent(0);
          _refreshMaxAmount();
          _updateMarketTimer();
        });
      }
    });

    // 账户数据加载完成时刷新最大可开量（进入页面时账户信息可能尚未到达）
    ref.listen(provider.select((s) => s.accountInfo.availableMargin),
        (prev, next) {
      if (prev != next) _refreshMaxAmount();
    });

    // 最新价：仅缓存到字段，不触发 rebuild（消除每 ~120ms 一次的整面板重建）
    // _priceFilled 逻辑也移到此处
    ref.listen<double>(provider.select((s) => s.lastPrice), (_, next) {
      _lastPrice = next;
      if (!_priceFilled && next > 0) {
        _priceFilled = true;
        final precision = ref.read(provider).pricePrecision;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (!mounted) return;
          if (_priceController.text.isEmpty) {
            _priceController.text = next.toStringAsFixed(precision);
          }
        });
      }
    });
    // 首次 build 时同步一次（ref.listen 不回调当前值）
    if (_lastPrice == 0) {
      _lastPrice = ref.read(provider).lastPrice;
      if (!_priceFilled && _lastPrice > 0) {
        _priceFilled = true;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (!mounted) return;
          if (_priceController.text.isEmpty) {
            _priceController.text =
                _lastPrice.toStringAsFixed(ref.read(provider).pricePrecision);
          }
        });
      }
    }

    final change24h = ref.watch(provider.select((s) => s.change24h));
    final isConditionalOrder =
        ref.watch(provider.select((s) => s.isConditionalOrder));
    final showPriceInput = ref.watch(provider.select((s) => s.showPriceInput));
    final isMarketOrder =
        ref.watch(provider.select((s) => s.orderType == OrderType.market));
    final amountUnit = ref.watch(provider.select((s) => s.amountUnit));
    final amountUnitLabelRaw =
        ref.watch(provider.select((s) => s.amountUnitLabel));
    final sliderPercent = ref.watch(provider.select((s) => s.sliderPercent));
    final tpslEnabled = ref.watch(provider.select((s) => s.tpslEnabled));
    final leverage = ref.watch(provider.select((s) => s.leverage));
    final availableMargin =
        ref.watch(provider.select((s) => s.accountInfo.availableMargin));
    final positionMode = ref.watch(provider.select((s) => s.positionMode));
    final isClose = positionMode == PositionMode.close;
    final coinPrecision = ref.watch(provider.select((s) => s.coinPrecision));
    final normSym =
        widget.symbol.replaceAll('/', '').replaceAll('-', '').toUpperCase();
    final longAvail = ref.watch(provider.select((s) => s.positions
        .where((p) =>
            p.symbol.replaceAll('/', '').toUpperCase() == normSym &&
            p.side == OrderSide.long)
        .fold(0.0, (v, p) => v + p.availableSize)));
    final shortAvail = ref.watch(provider.select((s) => s.positions
        .where((p) =>
            p.symbol.replaceAll('/', '').toUpperCase() == normSym &&
            p.side == OrderSide.short)
        .fold(0.0, (v, p) => v + p.availableSize)));
    final coinSymbol = _baseCoin(widget.symbol);
    final pricePrecision = ref.watch(provider.select((s) => s.pricePrecision));
    final amountPrecision =
        ref.watch(provider.select((s) => s.currentAmountPrecision));
    final priceFormatter = _PrecisionInputFormatter(pricePrecision);
    final amountFormatter = _PrecisionInputFormatter(amountPrecision);
    final isLoggedIn = ref.watch(isLoggedInProvider);
    final isSwitchingMode =
        ref.watch(provider.select((s) => s.isSwitchingMode));
    final marginMode = ref.watch(provider.select((s) => s.marginMode));

    // ── 高频复用样式（每次 rebuild 计算一次，复用 N 次）─────────
    final labelStyle =
        TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 11);
    final valueStyle = TextStyle(
        color: cs.onSurface, fontSize: 11, fontWeight: FontWeight.w600);

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // ── Symbol row（v2：在左侧表单内）──────────────
          GestureDetector(
            onTap: () => _showSymbolPicker(context),
            behavior: HitTestBehavior.opaque,
            child: Padding(
              padding: const EdgeInsets.only(bottom: 4),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Flexible(
                    child: Text(
                      formatUsdtPairDisplay(widget.symbol),
                      overflow: TextOverflow.ellipsis,
                      style: TextStyle(
                          color: cs.onSurface,
                          fontSize: 17,
                          fontWeight: FontWeight.w700),
                    ),
                  ),
                  Icon(Icons.keyboard_arrow_down,
                      color: cs.onSurface.withAlpha(153), size: 16),
                  const SizedBox(width: 6),
                  Text(
                    formatChange(change24h),
                    style: TextStyle(
                        color: AppColors.changeColor(change24h),
                        fontSize: 12,
                        fontWeight: FontWeight.w500,
                        fontFeatures: const [FontFeature.tabularFigures()]),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 8),

          _MarginLeverageRow(symbol: widget.symbol),
          const SizedBox(height: 10),

          // TODO: 平仓 tab 功能暂时隐藏，待接口完善后恢复
          // _PositionModePills(symbol: widget.symbol),
          // const SizedBox(height: 10),

          _OrderTypeDropdown(symbol: widget.symbol),
          const SizedBox(height: 8),

          if (isConditionalOrder) ...[
            _LargeInput(
              controller: _triggerPriceController,
              label: AppLocalizations.of(context)!.triggerPrice,
              unit: 'USDT',
              suffixDropdown: AppLocalizations.of(context)!.markLabel,
              inputFormatters: [priceFormatter],
            ),
            const SizedBox(height: 8),
          ],

          if (isMarketOrder) ...[
            // 市价委托：显示只读"市价"占位框
            Builder(builder: (context) {
              final cs = Theme.of(context).colorScheme;
              return Container(
                height: 52,
                decoration: BoxDecoration(
                  color: cs.onSurface.withAlpha(8),
                  borderRadius: BorderRadius.circular(8),
                ),
                padding: const EdgeInsets.symmetric(horizontal: 12),
                child: Row(
                  children: [
                    Expanded(
                      child: Text(
                        AppLocalizations.of(context)!.marketPrice,
                        style: TextStyle(
                          color: cs.onSurface,
                          fontSize: 15,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                    ),
                    Text(
                      'USDT',
                      style: TextStyle(
                        color: cs.onSurface.withAlpha(100),
                        fontSize: 13,
                      ),
                    ),
                  ],
                ),
              );
            }),
            const SizedBox(height: 8),
          ] else if (showPriceInput) ...[
            _LargeInput(
              controller: _priceController,
              label: AppLocalizations.of(context)!.priceLabel2,
              unit: 'USDT',
              inputFormatters: [priceFormatter],
            ),
            const SizedBox(height: 8),
          ],

          // ── 数量输入 ──
          _LargeInput(
            key: const ValueKey('amount_input'),
            controller: _amountController,
            label: AppLocalizations.of(context)!.quantityLabel,
            unit: amountUnit == AmountUnit.lots
                ? AppLocalizations.of(context)!.lotsLabel
                : amountUnitLabelRaw,
            showUnitDropdown: true,
            onUnitTap: () => _showAmountUnitSheet(context, notifier),
            inputFormatters: [amountFormatter],
          ),
          const SizedBox(height: 10),

          // ── 百分比滑动条 ──
          _PercentSlider(
            percent: sliderPercent,
            enabled: true,
            onChanged: (pct) {
              // 始终从 ref.read 取最新状态，避免闭包捕获旧值导致联动失效
              final s = ref.read(futuresProvider(widget.symbol));
              // 限价/计划委托未填价格时拦截，提示用户
              if (pct > 0 && !_checkRefPrice(context, s)) return;
              notifier.setSliderPercent(pct);
              _updatingFromSlider = true;
              if (pct == 0) {
                _amountController.clear();
              } else {
                final contractSize = s.contractSize > 0 ? s.contractSize : 1.0;
                final curPrice = _refPrice(s);

                // ── 平仓模式：以仓位可平量（BTC）为基准 ──
                if (s.positionMode == PositionMode.close) {
                  final maxBtc = _closeMaxBtc(s);
                  if (maxBtc > 0) {
                    switch (s.amountUnit) {
                      case AmountUnit.btc:
                        final rawBtcClose = maxBtc * pct;
                        final btcCloseFactor =
                            pow(10, s.coinPrecision).toDouble();
                        final truncBtcClose =
                            (rawBtcClose * btcCloseFactor).truncateToDouble() /
                                btcCloseFactor;
                        _amountController.text =
                            truncBtcClose.toStringAsFixed(s.coinPrecision);
                      case AmountUnit.usdt:
                        final rawCloseUsdt = maxBtc * curPrice * pct;
                        final closeDisplayDp =
                            s.usdtPrecision < 2 ? s.usdtPrecision : 2;
                        final closeFactor = pow(10, closeDisplayDp).toDouble();
                        final flooredCloseUsdt =
                            (rawCloseUsdt * closeFactor).floor() / closeFactor;
                        _amountController.text =
                            flooredCloseUsdt.toStringAsFixed(closeDisplayDp);
                      case AmountUnit.lots:
                        if (contractSize > 0 && curPrice > 0) {
                          final maxLots = maxBtc * curPrice / contractSize;
                          final lots = maxLots * pct;
                          _amountController.text = s.volScale > 0
                              ? lots.toStringAsFixed(s.volScale)
                              : lots.floor().toString();
                        }
                    }
                  }
                } else {
                  // ── 开仓模式：以可用保证金为基准 ──
                  final curMargin = s.accountInfo.availableMargin;
                  final curLeverage = s.leverage;
                  if (curMargin > 0 && curPrice > 0) {
                    final feeMultiplier = 1 + s.openFeeRate * curLeverage;
                    final effectiveMargin = curMargin / feeMultiplier * pct;
                    final maxNotional = effectiveMargin * curLeverage;
                    switch (s.amountUnit) {
                      case AmountUnit.usdt:
                        final displayDp =
                            s.usdtPrecision < 2 ? s.usdtPrecision : 2;
                        final factor = pow(10, displayDp).toDouble();
                        final flooredUsdt =
                            (maxNotional * factor).floor() / factor;
                        _amountController.text =
                            flooredUsdt.toStringAsFixed(displayDp);
                      case AmountUnit.btc:
                        final rawBtcOpen = maxNotional / curPrice;
                        final btcOpenFactor =
                            pow(10, s.coinPrecision).toDouble();
                        final truncBtcOpen =
                            (rawBtcOpen * btcOpenFactor).truncateToDouble() /
                                btcOpenFactor;
                        _amountController.text =
                            truncBtcOpen.toStringAsFixed(s.coinPrecision);
                      case AmountUnit.lots:
                        if (contractSize <= 0) break;
                        final maxL =
                            (effectiveMargin * curLeverage / contractSize)
                                .floor();
                        _amountController.text = s.volScale > 0
                            ? maxL.toStringAsFixed(s.volScale)
                            : maxL.toString();
                    }
                  }
                }
              }
              _updatingFromSlider = false;
            },
          ),

          ...[
            const SizedBox(height: 10),
            Row(
              children: [
                Text('${AppLocalizations.of(context)!.availableLabel} ',
                    style: TextStyle(
                        color: cs.onSurface.withAlpha(153), fontSize: 12)),
                Text(
                  '${formatAmount(availableMargin)} USDT',
                  style: TextStyle(
                      color: cs.onSurface,
                      fontSize: 12,
                      fontWeight: FontWeight.w600),
                ),
                const Spacer(),
                GestureDetector(
                  onTap: () async {
                    final notifier =
                        ref.read(futuresProvider(widget.symbol).notifier);
                    notifier.stopPolling();
                    await context.push('/asset/transfer');
                    if (context.mounted) notifier.resumePolling(widget.symbol);
                  },
                  child: Icon(Icons.swap_horiz,
                      color: cs.onSurface.withAlpha(153), size: 16),
                ),
              ],
            ),
            const SizedBox(height: 6),
          ],

          // 可开多 + 保证金（开多按钮上方）
          if (isLoggedIn && !isClose) ...[
            Row(children: [
              Text('${AppLocalizations.of(context)!.canOpenLong} ',
                  style: labelStyle),
              Flexible(
                  child: Text('$_maxOpenAmt $amountUnitLabelRaw',
                      style: valueStyle, overflow: TextOverflow.ellipsis)),
            ]),
            const SizedBox(height: 2),
            ListenableBuilder(
              listenable: Listenable.merge([
                _priceController,
                _amountController,
                _triggerPriceController
              ]),
              builder: (context, _) {
                final marginStr = _calcMargin(_lastPrice, leverage);
                return Text(
                    AppLocalizations.of(context)!.marginBalance(marginStr),
                    style: labelStyle);
              },
            ),
            const SizedBox(height: 6),
          ],
          SizedBox(
            width: double.infinity,
            height: 40,
            child: ElevatedButton(
              onPressed: isSwitchingMode
                  ? null
                  : isLoggedIn
                      ? () => isClose
                          ? _closeOrder(context, notifier, OrderSide.long)
                          : _placeOrder(context, notifier, OrderSide.long)
                      : () => context.push('/login'),
              style: ElevatedButton.styleFrom(
                backgroundColor: isClose ? AppColors.fall : AppColors.rise,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(8)),
                elevation: 0,
              ),
              child: Text(
                isClose
                    ? AppLocalizations.of(context)!.closeLong
                    : AppLocalizations.of(context)!.openLong,
                style: const TextStyle(
                    color: Colors.white,
                    fontSize: 15,
                    fontWeight: FontWeight.w600),
              ),
            ),
          ),
          const SizedBox(height: 8),
          // 可开空 + 保证金（开空按钮上方）
          if (isLoggedIn && !isClose) ...[
            Row(children: [
              Text('${AppLocalizations.of(context)!.canOpenShort} ',
                  style: labelStyle),
              Flexible(
                  child: Text('$_maxOpenAmt $amountUnitLabelRaw',
                      style: valueStyle, overflow: TextOverflow.ellipsis)),
            ]),
            const SizedBox(height: 2),
            ListenableBuilder(
              listenable: Listenable.merge([
                _priceController,
                _amountController,
                _triggerPriceController
              ]),
              builder: (context, _) {
                final marginStr = _calcMargin(_lastPrice, leverage);
                return Text(
                    AppLocalizations.of(context)!.marginBalance(marginStr),
                    style: labelStyle);
              },
            ),
            const SizedBox(height: 6),
          ],
          SizedBox(
            width: double.infinity,
            height: 40,
            child: ElevatedButton(
              onPressed: isSwitchingMode
                  ? null
                  : isLoggedIn
                      ? () => isClose
                          ? _closeOrder(context, notifier, OrderSide.short)
                          : _placeOrder(context, notifier, OrderSide.short)
                      : () => context.push('/login'),
              style: ElevatedButton.styleFrom(
                backgroundColor: isClose ? AppColors.rise : AppColors.fall,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(8)),
                elevation: 0,
              ),
              child: Text(
                isClose
                    ? AppLocalizations.of(context)!.closeShort
                    : AppLocalizations.of(context)!.openShort,
                style: const TextStyle(
                    color: Colors.white,
                    fontSize: 15,
                    fontWeight: FontWeight.w600),
              ),
            ),
          ),
          const SizedBox(height: 12),
        ],
      ),
    );
  }

  Future<void> _placeOrder(
      BuildContext context, FuturesNotifier notifier, OrderSide side) async {
    if (!_requireLogin(context, ref)) return;
    // formatPrice 会加千分符，解析时需先去掉逗号
    final price = double.tryParse(_priceController.text.replaceAll(',', ''));
    final amount = double.tryParse(_amountController.text.replaceAll(',', ''));
    final triggerPrice =
        double.tryParse(_triggerPriceController.text.replaceAll(',', ''));
    final tpPrice = double.tryParse(_tpController.text.replaceAll(',', ''));
    final slPrice = double.tryParse(_slController.text.replaceAll(',', ''));

    final err = await notifier.placeOpenOrder(
      side: side,
      entrustPrice: price,
      triggerPrice: triggerPrice,
      volume: amount,
      tpPrice: tpPrice,
      slPrice: slPrice,
    );

    if (!context.mounted) return;
    if (err == null) {
      // 下单成功后清空输入框
      _priceController.clear();
      _amountController.clear();
      _triggerPriceController.clear();
      _tpController.clear();
      _slController.clear();
      notifier.setSliderPercent(0);
    }
    final l10n0 = AppLocalizations.of(context)!;
    showTopToast(context,
        message: err != null
            ? (resolveProviderError(err, l10n0) ?? err)
            : l10n0.orderSuccess,
        backgroundColor: err != null ? AppColors.fall : AppColors.rise);
  }

  Future<void> _closeOrder(
      BuildContext context, FuturesNotifier notifier, OrderSide side) async {
    if (!_requireLogin(context, ref)) return;
    final price = double.tryParse(_priceController.text.replaceAll(',', ''));
    final triggerPrice =
        double.tryParse(_triggerPriceController.text.replaceAll(',', ''));
    // 将用户输入量转为基础币（BTC）后传入；0/null 表示全仓平
    final inputAmount =
        double.tryParse(_amountController.text.replaceAll(',', ''));
    final s = ref.read(futuresProvider(widget.symbol));
    final double? volumeInBtc;
    if (inputAmount != null && inputAmount > 0) {
      final cs = s.contractSize > 0 ? s.contractSize : 1.0;
      final isMarketClose = s.orderType == OrderType.market;
      final ep =
          (!isMarketClose && price != null && price > 0) ? price : s.lastPrice;
      volumeInBtc = switch (s.amountUnit) {
        AmountUnit.btc => inputAmount,
        AmountUnit.usdt => ep > 0 ? inputAmount / ep : null,
        AmountUnit.lots => ep > 0 ? inputAmount * cs / ep : null,
      };
    } else {
      volumeInBtc = null; // 全仓平
    }

    final isConditional = s.orderType == OrderType.conditionalMarket ||
        s.orderType == OrderType.conditionalLimit;

    String? err;
    if (isConditional && triggerPrice != null && triggerPrice > 0) {
      err = await notifier.closeConditionalByDirection(
        side,
        triggerPrice: triggerPrice,
        volume: volumeInBtc,
        entrustPrice: (price != null && price > 0) ? price : 0,
      );
    } else {
      final isMarket = s.orderType == OrderType.market;
      err = await notifier.closeByDirection(
        side,
        price: isMarket ? null : price,
        volume: volumeInBtc,
      );
    }

    if (!context.mounted) return;
    if (err == null) {
      _amountController.clear();
      _triggerPriceController.clear();
      notifier.setSliderPercent(0);
    }
    final l10n1 = AppLocalizations.of(context)!;
    showTopToast(context,
        message: resolveProviderError(err, l10n1) ?? l10n1.closeOrderSubmitted,
        backgroundColor: err != null ? AppColors.fall : AppColors.rise);
  }

  String _calcMargin(double lastPrice, double leverage) {
    final state = ref.read(futuresProvider(widget.symbol));
    // 计划市价用触发价，计划限价/限价用价格输入框，市价用最新价
    final double price;
    if (state.orderType == OrderType.conditionalMarket) {
      price =
          double.tryParse(_triggerPriceController.text.replaceAll(',', '')) ??
              lastPrice;
    } else {
      price = double.tryParse(_priceController.text.replaceAll(',', '')) ??
          lastPrice;
    }
    final amount =
        double.tryParse(_amountController.text.replaceAll(',', '')) ?? 0;
    if (amount == 0 || leverage == 0) return '0.00';
    final contractSize = state.contractSize > 0 ? state.contractSize : 1.0;
    // 换算为 USDT 名义价值再除以杠杆
    // 张: lots × contractSize(USDT/张) = USDT（面值已是 USDT，无需再乘价格）
    // USDT: 直接
    // BTC/ETH/ETC: 数量 × price = USDT
    final availableMargin =
        ref.read(futuresProvider(widget.symbol)).accountInfo.availableMargin;
    final double rawMargin;
    switch (state.amountUnit) {
      case AmountUnit.usdt:
        // USDT 输入为名义仓位价值，除以杠杆得保证金
        rawMargin = amount / leverage;
      case AmountUnit.lots:
        // 张 × contractSize = 名义价值，再除杠杆得保证金
        rawMargin = amount * contractSize / leverage;
      case AmountUnit.btc:
        // 基础币 × 价格 = 名义价值，再除杠杆得保证金
        rawMargin = amount * price / leverage;
    }
    // clamp 后再截断（floor），防止 formatAmount 进位后显示值 > 可用
    final clamped =
        availableMargin > 0 ? rawMargin.clamp(0.0, availableMargin) : rawMargin;
    final margin = (clamped * 100).floorToDouble() / 100;
    return formatAmount(margin, decimals: 2);
  }

  /// 可开多/可开空数量，与老版 Android 公式一致：
  /// availableBalance / (((1/leverage) + openFee) * lastPrice)
  String _calcMaxOpenAmount(FuturesState state) {
    final refPrice = _refPrice(state);
    final avail = state.accountInfo.availableMargin;
    final leverage = state.leverage;
    final cs = state.contractSize > 0 ? state.contractSize : 1.0;
    if (avail <= 0 || refPrice <= 0) return '--';
    final feeMultiplier = 1 + state.openFeeRate * leverage;
    final maxNotional = avail / feeMultiplier * leverage;
    switch (state.amountUnit) {
      case AmountUnit.usdt:
        return formatAmount(maxNotional);
      case AmountUnit.lots:
        return (maxNotional / cs).floor().toString();
      case AmountUnit.btc:
        return formatAmount(maxNotional / refPrice,
            decimals: state.coinPrecision);
    }
  }

  /// 可平量（基于仓位可平 size，换算为当前单位）
  String _calcMaxCloseAmount(FuturesState state, double availSize) {
    final refPrice = _refPrice(state);
    final cs = state.contractSize > 0 ? state.contractSize : 1.0;
    switch (state.amountUnit) {
      case AmountUnit.btc:
        return formatAmount(availSize, decimals: state.coinPrecision);
      case AmountUnit.usdt:
        return refPrice > 0 ? formatAmount(availSize * refPrice) : '--';
      case AmountUnit.lots:
        return (cs > 0 && refPrice > 0)
            ? (availSize * refPrice / cs).floor().toString()
            : '--';
    }
  }

  void _showSymbolPicker(BuildContext context) {
    FocusScope.of(context).unfocus();
    final targetBasePath = widget.showSpotSwitcher ? '/futures' : '/contracts';
    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      isScrollControlled: true,
      backgroundColor: Theme.of(context).colorScheme.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (sheetCtx) => SymbolPickerSheet(
        currentSymbol: widget.symbol,
        visibleTabs: const [SymbolPickerTab.futures],
        onSelected: (newSymbol) {
          Navigator.pop(sheetCtx);
          context.go('$targetBasePath/$newSymbol');
        },
        onSpotSelected: (newSymbol) {
          Navigator.pop(sheetCtx);
          context.go('$targetBasePath/$newSymbol');
        },
      ),
    );
  }

  void _showAmountUnitSheet(BuildContext context, FuturesNotifier notifier) {
    FocusScope.of(context).unfocus();
    final cs = Theme.of(context).colorScheme;
    final coinLabel = ref.read(futuresProvider(widget.symbol).select((s) {
      if (s.coinSymbol.isNotEmpty) return s.coinSymbol;
      final base =
          widget.symbol.toUpperCase().replaceFirst(RegExp(r'USDT$'), '');
      return base.isNotEmpty ? base : 'BTC';
    }));
    final currentUnit =
        ref.read(futuresProvider(widget.symbol).select((s) => s.amountUnit));

    // 各单位的标题和描述
    final l10n = AppLocalizations.of(context)!;
    String unitTitle(AmountUnit unit) {
      switch (unit) {
        case AmountUnit.btc:
          return l10n.contractUnitCoin(coinLabel);
        case AmountUnit.usdt:
          return l10n.contractUnitUsdt;
        case AmountUnit.lots:
          return l10n.contractUnitSheets;
      }
    }

    String unitDesc(AmountUnit unit) {
      switch (unit) {
        case AmountUnit.btc:
          return l10n.contractUnitHintCoin(coinLabel);
        case AmountUnit.usdt:
          return l10n.contractUnitHintUsdt;
        case AmountUnit.lots:
          return l10n.contractUnitHintSheets;
      }
    }

    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      isScrollControlled: true,
      backgroundColor: cs.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (_) => StatefulBuilder(
        builder: (ctx, setState) {
          AmountUnit selected = currentUnit;
          return SafeArea(
            child: SingleChildScrollView(
              padding: const EdgeInsets.fromLTRB(16, 20, 16, 16),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    l10n.contractUnitSetting,
                    style: TextStyle(
                      color: cs.onSurface,
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                  const SizedBox(height: 16),
                  for (final unit in AmountUnit.values) ...[
                    GestureDetector(
                      onTap: () {
                        notifier.setAmountUnit(unit);
                        // 单位切换后清空输入和滑块，避免旧值被误用
                        _amountController.clear();
                        notifier.setSliderPercent(0);
                        Navigator.pop(ctx);
                      },
                      child: Container(
                        width: double.infinity,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 14, vertical: 14),
                        decoration: BoxDecoration(
                          border: Border.all(
                            color: selected == unit
                                ? cs.primary
                                : cs.outline.withAlpha(80),
                            width: selected == unit ? 1.5 : 1,
                          ),
                          borderRadius: BorderRadius.circular(8),
                          color: selected == unit
                              ? cs.primary.withAlpha(15)
                              : Colors.transparent,
                        ),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              unitTitle(unit),
                              style: TextStyle(
                                color: cs.onSurface,
                                fontSize: 14,
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                            const SizedBox(height: 4),
                            Text(
                              unitDesc(unit),
                              style: TextStyle(
                                color: cs.onSurface.withAlpha(140),
                                fontSize: 12,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                    if (unit != AmountUnit.lots) const SizedBox(height: 10),
                  ],
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

class _MarginLeverageRow extends ConsumerWidget {
  const _MarginLeverageRow({required this.symbol});
  final String symbol;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final provider = futuresProvider(symbol);
    final marginMode = ref.watch(provider.select((s) => s.marginMode));
    final leverage = ref.watch(provider.select((s) => s.leverage));
    final leverageMin = ref.watch(provider.select((s) => s.leverageMin));
    final leverageMax = ref.watch(provider.select((s) => s.leverageMax));
    final leverageOptions =
        ref.watch(provider.select((s) => s.leverageOptions));
    final isDiscrete = ref.watch(provider.select((s) => s.isDiscreteLeverage));
    final notifier = ref.read(provider.notifier);
    final isLoggedIn = ref.watch(isLoggedInProvider);
    final isTrader = ref.watch(copyTradingProvider.select((s) => s.isTrader));

    return Row(
      children: [
        Builder(builder: (ctx) {
          final isDark = Theme.of(ctx).brightness == Brightness.dark;
          return GestureDetector(
            onTap: () => isLoggedIn
                ? _showMarginModeSheet(context, marginMode, notifier,
                    isTrader: isTrader)
                : context.push('/login'),
            child: Container(
              height: 26,
              padding: const EdgeInsets.symmetric(horizontal: 7),
              decoration: BoxDecoration(
                color: isDark
                    ? AppColors.darkBgTertiary
                    : AppColors.lightBgTertiary,
                borderRadius: BorderRadius.circular(4),
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(
                      switch (marginMode) {
                        MarginMode.cross =>
                          AppLocalizations.of(context)!.crossMargin,
                        MarginMode.isolated =>
                          AppLocalizations.of(context)!.isolatedMargin,
                        MarginMode.split =>
                          AppLocalizations.of(context)!.splitMargin,
                      },
                      style: TextStyle(
                          color: cs.onSurface.withAlpha(153),
                          fontSize: 13,
                          fontWeight: FontWeight.w700)),
                  Icon(Icons.keyboard_arrow_down,
                      color: cs.onSurface.withAlpha(153), size: 13),
                ],
              ),
            ),
          );
        }),
        const SizedBox(width: 8),
        GestureDetector(
          onTap: () {
            if (!isLoggedIn) {
              context.push('/login');
              return;
            }
            _showLeverageSheet(context, leverage, leverageMin, leverageMax,
                leverageOptions, isDiscrete, notifier);
          },
          child: Container(
            height: 26,
            padding: const EdgeInsets.symmetric(horizontal: 7),
            decoration: const BoxDecoration(
              color: AppColors.leverageGoldBg,
              borderRadius: BorderRadius.all(Radius.circular(4)),
            ),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  '${leverage.toInt()}X',
                  style: const TextStyle(
                    color: AppColors.leverageGold,
                    fontSize: 13,
                    fontWeight: FontWeight.w700,
                    fontFeatures: [FontFeature.tabularFigures()],
                  ),
                ),
                const Icon(Icons.keyboard_arrow_down,
                    color: AppColors.leverageGold, size: 13),
              ],
            ),
          ),
        ),
      ],
    );
  }

  void _showLeverageSheet(
      BuildContext context,
      double leverage,
      int leverageMin,
      int leverageMax,
      List<int> leverageOptions,
      bool isDiscrete,
      FuturesNotifier notifier) {
    FocusScope.of(context).unfocus();
    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      backgroundColor: Theme.of(context).colorScheme.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (_) => _LeverageSheet(
        symbol: symbol,
        current: leverage,
        leverageMin: leverageMin,
        leverageMax: leverageMax,
        leverageOptions: leverageOptions,
        isDiscrete: isDiscrete,
        onChanged: notifier.setLeverage,
      ),
    );
  }

  void _showMarginModeSheet(
      BuildContext context, MarginMode current, FuturesNotifier notifier,
      {bool isTrader = false}) {
    FocusScope.of(context).unfocus();
    final cs = Theme.of(context).colorScheme;
    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      isScrollControlled: true,
      backgroundColor: cs.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (sheetCtx) => SafeArea(
        child: SingleChildScrollView(
            child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
              child: Text(AppLocalizations.of(context)!.marginMode,
                  style: TextStyle(
                      color: cs.onSurface,
                      fontSize: 16,
                      fontWeight: FontWeight.w600)),
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16, 4, 16, 8),
              child: Column(
                children: [
                  for (final entry in [
                    (
                      MarginMode.cross,
                      AppLocalizations.of(context)!.crossMargin,
                      AppLocalizations.of(context)!.crossMarginDesc,
                      _MarginModeIcon.cross
                    ),
                    // 带单员不支持分仓，隐藏分仓选项
                    if (!isTrader)
                      (
                        MarginMode.split,
                        AppLocalizations.of(context)!.splitMargin,
                        AppLocalizations.of(context)!.splitMarginDesc,
                        _MarginModeIcon.split
                      ),
                  ]) ...[
                    GestureDetector(
                      onTap: () async {
                        Navigator.pop(sheetCtx);
                        final err = await notifier.setMarginMode(entry.$1);
                        if (err != null && context.mounted) {
                          showTopToast(context,
                              message: AppLocalizations.of(context)!
                                  .switchMarginModeFailed);
                        }
                      },
                      child: Container(
                        width: double.infinity,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 14, vertical: 12),
                        decoration: BoxDecoration(
                          border: Border.all(
                            color:
                                current == entry.$1 ? cs.onSurface : cs.outline,
                            width: current == entry.$1 ? 1.5 : 1,
                          ),
                          borderRadius: BorderRadius.circular(10),
                          color: Colors.transparent,
                        ),
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            _MarginModeIconWidget(type: entry.$4),
                            const SizedBox(width: 12),
                            Expanded(
                              child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                  Row(
                                    children: [
                                      Text(entry.$2,
                                          style: TextStyle(
                                              color: cs.onSurface,
                                              fontWeight: FontWeight.w600,
                                              fontSize: 14)),
                                      if (current == entry.$1) ...[
                                        const SizedBox(width: 6),
                                        Icon(Icons.check_circle,
                                            size: 16, color: cs.onSurface),
                                      ],
                                    ],
                                  ),
                                  const SizedBox(height: 4),
                                  Text(entry.$3,
                                      style: TextStyle(
                                          color: cs.onSurface.withAlpha(120),
                                          fontSize: 12,
                                          height: 1.4)),
                                ],
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                    const SizedBox(height: 10),
                  ],
                ],
              ),
            ),
            Padding(
              padding: const EdgeInsets.fromLTRB(16, 4, 16, 16),
              child: Center(
                child: Text(
                  AppLocalizations.of(context)!.marginModeNote,
                  style: TextStyle(
                    color:
                        Theme.of(sheetCtx).colorScheme.onSurface.withAlpha(120),
                    fontSize: 11,
                    decoration: TextDecoration.underline,
                    decorationColor:
                        Theme.of(sheetCtx).colorScheme.onSurface.withAlpha(120),
                  ),
                ),
              ),
            ),
          ],
        )),
      ),
    );
  }
}

class _LeverageSheet extends StatefulWidget {
  const _LeverageSheet({
    required this.symbol,
    required this.current,
    required this.onChanged,
    this.leverageMin = 1,
    this.leverageMax = 125,
    this.leverageOptions = const [],
    this.isDiscrete = false,
  });
  final String symbol;
  final double current;
  final Future<String?> Function(double) onChanged;
  final int leverageMin;
  final int leverageMax;
  final List<int> leverageOptions;

  /// true=分离倍数（只能选指定档位）false=连续倍数（区间内任意值）
  final bool isDiscrete;

  @override
  State<_LeverageSheet> createState() => _LeverageSheetState();
}

class _LeverageSheetState extends State<_LeverageSheet> {
  late double _value;
  bool _loading = false;

  /// 预设档位：优先用接口返回的 leverageOptions，否则用默认
  List<int> get _presets {
    if (widget.leverageOptions.isNotEmpty) return widget.leverageOptions;
    return [1, 5, 10, 30, 50, 75, 100, 125]
        .where((v) => v >= widget.leverageMin && v <= widget.leverageMax)
        .toList();
  }

  @override
  void initState() {
    super.initState();
    final clamped = widget.current
        .clamp(widget.leverageMin.toDouble(), widget.leverageMax.toDouble());
    if (widget.isDiscrete && widget.leverageOptions.isNotEmpty) {
      // 离散模式：对齐到最近档位
      final cur = clamped.toInt();
      final nearest = widget.leverageOptions.reduce(
        (a, b) => (a - cur).abs() <= (b - cur).abs() ? a : b,
      );
      _value = nearest.toDouble();
    } else {
      _value = clamped;
    }
  }

  void _step(int delta) {
    if (widget.isDiscrete && _presets.isNotEmpty) {
      // 离散模式：跳到相邻档位
      final cur = _value.toInt();
      final idx = _presets.indexOf(cur);
      int nextIdx;
      if (idx < 0) {
        // 当前值不在档位中，找最近的
        nextIdx = delta > 0
            ? _presets.indexWhere((v) => v > cur)
            : _presets.lastIndexWhere((v) => v < cur);
      } else {
        nextIdx = idx + delta.sign;
      }
      if (nextIdx >= 0 && nextIdx < _presets.length) {
        setState(() => _value = _presets[nextIdx].toDouble());
      }
    } else {
      final next = (_value + delta)
          .clamp(widget.leverageMin.toDouble(), widget.leverageMax.toDouble());
      setState(() => _value = next);
    }
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    return SafeArea(
      child: Padding(
        padding: const EdgeInsets.fromLTRB(20, 0, 20, 24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // ── 顶部标题栏 ──────────────────────────────────
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16),
              child: Row(
                children: [
                  const SizedBox(width: 32),
                  Expanded(
                    child: Column(
                      children: [
                        Text(
                          AppLocalizations.of(context)!
                              .symbolPerpetual(widget.symbol),
                          style: TextStyle(
                            color: cs.onSurface,
                            fontSize: 16,
                            fontWeight: FontWeight.w700,
                          ),
                          textAlign: TextAlign.center,
                        ),
                        const SizedBox(height: 2),
                        Text(
                          AppLocalizations.of(context)!.adjustLeverage,
                          style: TextStyle(
                            color: cs.onSurface.withAlpha(120),
                            fontSize: 12,
                          ),
                          textAlign: TextAlign.center,
                        ),
                      ],
                    ),
                  ),
                  SizedBox(
                    width: 32,
                    child: GestureDetector(
                      onTap: () => Navigator.pop(context),
                      child: Icon(Icons.close,
                          size: 20, color: cs.onSurface.withAlpha(153)),
                    ),
                  ),
                ],
              ),
            ),

            // ── - | 倍数 | + ────────────────────────────────
            Container(
              height: 60,
              decoration: BoxDecoration(
                color: isDark ? AppColors.darkBgTertiary : Colors.white,
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: cs.outline.withAlpha(80)),
              ),
              child: Row(
                children: [
                  _LeverageStepBtn(
                    label: '−',
                    onTap: () => _step(-1),
                    onLongPress: () => _step(-5),
                    isLeft: true,
                  ),
                  Container(
                      width: 1,
                      color:
                          Theme.of(context).colorScheme.outline.withAlpha(80)),
                  Expanded(
                    child: Center(
                      child: Text(
                        '${_value.toInt()}x',
                        style: TextStyle(
                          color: cs.onSurface,
                          fontSize: 26,
                          fontWeight: FontWeight.w700,
                        ),
                      ),
                    ),
                  ),
                  Container(
                      width: 1,
                      color:
                          Theme.of(context).colorScheme.outline.withAlpha(80)),
                  // + 按钮
                  _LeverageStepBtn(
                    label: '+',
                    onTap: () => _step(1),
                    onLongPress: () => _step(5),
                    isLeft: false,
                  ),
                ],
              ),
            ),
            const SizedBox(height: 20),

            // ── 滑轨（连续模式显示，离散模式隐藏）────────────
            if (!widget.isDiscrete) ...[
              SliderTheme(
                data: SliderTheme.of(context).copyWith(
                  activeTrackColor: AppColors.brand,
                  inactiveTrackColor: cs.outline.withAlpha(50),
                  thumbColor: Colors.white,
                  thumbShape:
                      const RoundSliderThumbShape(enabledThumbRadius: 10),
                  overlayShape:
                      const RoundSliderOverlayShape(overlayRadius: 18),
                  overlayColor: cs.onSurface.withAlpha(20),
                  trackHeight: 3,
                ),
                child: Slider(
                  value: _value,
                  min: widget.leverageMin.toDouble(),
                  max: widget.leverageMax.toDouble(),
                  divisions:
                      (widget.leverageMax - widget.leverageMin).clamp(1, 999),
                  onChanged: (v) => setState(() => _value = v.roundToDouble()),
                ),
              ),
              const SizedBox(height: 12),
            ],

            // ── 档位按钮（两种模式都显示，离散时为唯一选择方式）
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: _presets.map((v) {
                final active = _value.toInt() == v;
                return GestureDetector(
                  onTap: () => setState(() => _value = v.toDouble()),
                  child: Container(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
                    decoration: BoxDecoration(
                      color:
                          active ? AppColors.brand : cs.outline.withAlpha(25),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Text(
                      '${v}x',
                      style: TextStyle(
                        color:
                            active ? Colors.black : cs.onSurface.withAlpha(153),
                        fontSize: 12,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                );
              }).toList(),
            ),
            const SizedBox(height: 24),

            // ── 确定按钮 ─────────────────────────────────────
            SizedBox(
              width: double.infinity,
              height: 52,
              child: ElevatedButton(
                onPressed: _loading
                    ? null
                    : () async {
                        setState(() => _loading = true);
                        final err = await widget.onChanged(_value);
                        if (!context.mounted) return;
                        setState(() => _loading = false);
                        showTopToast(
                          context,
                          message: err ??
                              AppLocalizations.of(context)!
                                  .leverageAdjustedMsg(_value.toInt()),
                          backgroundColor:
                              err != null ? AppColors.fall : AppColors.rise,
                        );
                        if (err == null) Navigator.pop(context);
                      },
                style: ElevatedButton.styleFrom(
                  backgroundColor: AppColors.brand,
                  elevation: 0,
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(26)),
                ),
                child: _loading
                    ? SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(
                            strokeWidth: 2, color: Colors.black),
                      )
                    : Text(AppLocalizations.of(context)!.confirmLabel,
                        style: TextStyle(
                            color: Colors.black,
                            fontSize: 16,
                            fontWeight: FontWeight.w600)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// 杠杆调节 -/+ 按钮
class _LeverageStepBtn extends StatelessWidget {
  const _LeverageStepBtn({
    required this.label,
    required this.onTap,
    required this.onLongPress,
    this.isLeft = true,
  });
  final String label;
  final VoidCallback onTap;
  final VoidCallback onLongPress;
  final bool isLeft;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final radius = Radius.circular(11);
    return GestureDetector(
      onTap: onTap,
      onLongPress: onLongPress,
      child: Container(
        width: 60,
        alignment: Alignment.center,
        decoration: BoxDecoration(
          color: cs.outline.withAlpha(25),
          borderRadius: isLeft
              ? BorderRadius.only(topLeft: radius, bottomLeft: radius)
              : BorderRadius.only(topRight: radius, bottomRight: radius),
        ),
        child: Text(
          label,
          style: TextStyle(
            color: cs.onSurface,
            fontSize: 24,
            fontWeight: FontWeight.w400,
          ),
        ),
      ),
    );
  }
}

class _PositionModePills extends ConsumerWidget {
  const _PositionModePills({required this.symbol});
  final String symbol;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final provider = futuresProvider(symbol);
    final positionMode = ref.watch(provider.select((s) => s.positionMode));
    final notifier = ref.read(provider.notifier);
    final isOpen = positionMode == PositionMode.open;

    return Container(
      height: 30,
      padding: const EdgeInsets.all(2),
      decoration: BoxDecoration(
        color: cs.outline.withAlpha(80),
        borderRadius: BorderRadius.circular(999),
      ),
      child: Row(
        children: [
          _SegPill(
            label: AppLocalizations.of(context)!.openLabel,
            active: isOpen,
            activeColor: AppColors.rise,
            onTap: () => notifier.setPositionMode(PositionMode.open),
          ),
          _SegPill(
            label: AppLocalizations.of(context)!.closeLabel,
            active: !isOpen,
            activeColor: AppColors.fall,
            onTap: () => notifier.setPositionMode(PositionMode.close),
          ),
        ],
      ),
    );
  }
}

class _SegPill extends StatelessWidget {
  const _SegPill({
    required this.label,
    required this.active,
    required this.activeColor,
    required this.onTap,
  });
  final String label;
  final bool active;
  final Color activeColor;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Expanded(
      child: GestureDetector(
        onTap: onTap,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 150),
          decoration: BoxDecoration(
            color: active ? activeColor : Colors.transparent,
            borderRadius: BorderRadius.circular(999),
          ),
          alignment: Alignment.center,
          child: Text(
            label,
            style: TextStyle(
              color: active ? Colors.white : cs.onSurface.withAlpha(153),
              fontSize: 12,
              fontWeight: FontWeight.w600,
            ),
          ),
        ),
      ),
    );
  }
}

class _OrderTypeDropdown extends ConsumerWidget {
  const _OrderTypeDropdown({required this.symbol});
  final String symbol;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final provider = futuresProvider(symbol);
    final orderType = ref.watch(provider.select((s) => s.orderType));
    final isClose =
        ref.watch(provider.select((s) => s.positionMode == PositionMode.close));
    final notifier = ref.read(provider.notifier);

    final isDark = Theme.of(context).brightness == Brightness.dark;
    return GestureDetector(
      onTap: () => _showOrderTypeMenu(context, orderType, isClose, notifier),
      child: Container(
        height: 36,
        padding: const EdgeInsets.symmetric(horizontal: 10),
        decoration: BoxDecoration(
          color:
              isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(
              () {
                final l10n = AppLocalizations.of(context)!;
                switch (orderType) {
                  case OrderType.market:
                    return l10n.marketOrder;
                  case OrderType.limit:
                    return l10n.limitOrder;
                  case OrderType.conditionalMarket:
                    return l10n.conditionalMarketOrder;
                  case OrderType.conditionalLimit:
                    return l10n.conditionalLimitOrder;
                }
              }(),
              style: TextStyle(color: cs.onSurface, fontSize: 13),
            ),
            Icon(Icons.keyboard_arrow_down,
                color: cs.onSurface.withAlpha(153), size: 18),
          ],
        ),
      ),
    );
  }

  void _showOrderTypeMenu(BuildContext context, OrderType currentType,
      bool isClose, FuturesNotifier notifier) {
    FocusScope.of(context).unfocus();
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    final types = [
      (OrderType.market, l10n.marketOrder),
      (OrderType.limit, l10n.limitOrder),
      if (!isClose) (OrderType.conditionalMarket, l10n.conditionalMarketOrder),
      if (!isClose) (OrderType.conditionalLimit, l10n.conditionalLimitOrder),
    ];

    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      backgroundColor: cs.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (sheetCtx) => SafeArea(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: types
              .map((t) => GestureDetector(
                    onTap: () {
                      notifier.setOrderType(t.$1);
                      Navigator.pop(sheetCtx);
                    },
                    child: Padding(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 16, vertical: 14),
                      child: Row(
                        children: [
                          Expanded(
                            child: Text(t.$2,
                                style: TextStyle(
                                    color: currentType == t.$1
                                        ? AppColors.brand
                                        : cs.onSurface,
                                    fontSize: 14)),
                          ),
                          if (currentType == t.$1)
                            const Icon(Icons.check,
                                color: AppColors.brand, size: 18),
                        ],
                      ),
                    ),
                  ))
              .toList(),
        ),
      ),
    );
  }
}

// ── 保证金模式图标 ─────────────────────────────────────────────

enum _MarginModeIcon { cross, split }

class _MarginModeIconWidget extends StatelessWidget {
  const _MarginModeIconWidget({required this.type});
  final _MarginModeIcon type;

  @override
  Widget build(BuildContext context) {
    final asset = switch (type) {
      _MarginModeIcon.cross => 'assets/images/eg_ticket_dark.png',
      _MarginModeIcon.split => 'assets/images/eg_split.png',
    };
    return Image.asset(asset, width: 52, height: 52, fit: BoxFit.contain);
  }
}

class _LargeInput extends StatefulWidget {
  const _LargeInput({
    super.key,
    required this.controller,
    required this.label,
    required this.unit,
    this.suffixDropdown,
    this.showUnitDropdown = false,
    this.onUnitTap,
    this.inputFormatters,
  });
  final TextEditingController controller;
  final String label;
  final String unit;
  final String? suffixDropdown;
  final bool showUnitDropdown;
  final VoidCallback? onUnitTap;
  final List<TextInputFormatter>? inputFormatters;

  @override
  State<_LargeInput> createState() => _LargeInputState();
}

class _LargeInputState extends State<_LargeInput>
    with SingleTickerProviderStateMixin {
  final _focusNode = FocusNode();
  late final AnimationController _animCtrl;
  late final Animation<double> _curvedAnim;

  // 是否处于激活态（聚焦 或 有内容）
  bool get _isActive =>
      _focusNode.hasFocus || widget.controller.text.isNotEmpty;

  @override
  void initState() {
    super.initState();
    _animCtrl = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 220),
    );
    _curvedAnim = CurvedAnimation(
      parent: _animCtrl,
      curve: const Cubic(0.4, 0.0, 0.2, 1.0), // 与原型一致
    );
    _focusNode.addListener(_onChanged);
    widget.controller.addListener(_onChanged);
    // 若初始时已有内容，跳过动画直接到激活态
    if (_isActive) _animCtrl.value = 1.0;
  }

  @override
  void didUpdateWidget(_LargeInput oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.controller != widget.controller) {
      // controller 实例变化时：移除旧监听、绑定新监听，并同步动画状态
      oldWidget.controller.removeListener(_onChanged);
      widget.controller.addListener(_onChanged);
      if (_isActive) {
        _animCtrl.value = 1.0;
      } else {
        _animCtrl.value = 0.0;
      }
    }
  }

  void _onChanged() {
    if (_isActive) {
      _animCtrl.forward();
    } else {
      _animCtrl.reverse();
    }
    setState(() {});
  }

  @override
  void dispose() {
    _focusNode.removeListener(_onChanged);
    widget.controller.removeListener(_onChanged);
    _focusNode.dispose();
    _animCtrl.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;

    final unfocusedBg =
        isDark ? AppColors.darkBgTertiary : AppColors.lightBgSecondary;
    final focusedBg = isDark ? AppColors.darkBgSecondary : Colors.white;
    final activeBorderColor = isDark
        ? AppColors.darkTextPrimary.withAlpha(200)
        : const Color(0xFF383838);

    return GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: () => _focusNode.requestFocus(),
      child: Container(
        height: 44,
        padding: const EdgeInsets.symmetric(horizontal: 12),
        decoration: BoxDecoration(
          color: _focusNode.hasFocus ? focusedBg : unfocusedBg,
          borderRadius: BorderRadius.circular(8),
          border: _focusNode.hasFocus
              ? Border.all(color: activeBorderColor, width: 1.5)
              : null,
        ),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Expanded(
              child: AnimatedBuilder(
                animation: _curvedAnim,
                builder: (context, inputChild) {
                  final t = _curvedAnim.value;
                  final labelSize = 13.0 + (10.0 - 13.0) * t;
                  final labelColor = isDark
                      ? AppColors.darkTextSecondary
                      : AppColors.lightTextSecondary;
                  final labelHeight = labelSize * 1.0;
                  final centerTop = (44.0 - labelHeight) / 2;
                  const activeTop = 5.0;
                  final labelTop = centerTop + (activeTop - centerTop) * t;
                  final inputOpacity = t;

                  return SizedBox(
                    height: 44,
                    child: Stack(
                      clipBehavior: Clip.none,
                      children: [
                        Positioned(
                          top: labelTop,
                          left: 0,
                          right: 0,
                          child: Text(
                            widget.label,
                            style: TextStyle(
                              color: labelColor,
                              fontSize: labelSize,
                              height: 1.0,
                            ),
                          ),
                        ),
                        Positioned(
                          bottom: 5,
                          left: 0,
                          right: 0,
                          child: Opacity(
                            opacity: inputOpacity,
                            child: inputChild,
                          ),
                        ),
                      ],
                    ),
                  );
                },
                child: TextField(
                  focusNode: _focusNode,
                  controller: widget.controller,
                  keyboardType:
                      const TextInputType.numberWithOptions(decimal: true),
                  inputFormatters: widget.inputFormatters,
                  style: TextStyle(
                    color: cs.onSurface,
                    fontSize: 14,
                    fontWeight: FontWeight.w500,
                  ),
                  decoration: const InputDecoration(
                    border: InputBorder.none,
                    focusedBorder: InputBorder.none,
                    enabledBorder: InputBorder.none,
                    filled: false,
                    isDense: true,
                    contentPadding: EdgeInsets.zero,
                  ),
                ),
              ),
            ),
            const SizedBox(width: 6),
            GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: widget.showUnitDropdown
                  ? () {
                      _focusNode.unfocus();
                      widget.onUnitTap?.call();
                    }
                  : null,
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(widget.unit,
                      style: TextStyle(
                          color: cs.onSurface.withAlpha(153), fontSize: 13)),
                  if (widget.showUnitDropdown)
                    Icon(Icons.keyboard_arrow_down,
                        color: cs.onSurface.withAlpha(153), size: 14),
                ],
              ),
            ),
            if (widget.suffixDropdown != null) ...[
              const SizedBox(width: 6),
              Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(widget.suffixDropdown!,
                      style: TextStyle(
                          color: cs.onSurface.withAlpha(153), fontSize: 12)),
                  Icon(Icons.keyboard_arrow_down,
                      color: cs.onSurface.withAlpha(153), size: 14),
                ],
              ),
            ],
          ],
        ),
      ),
    );
  }
}

class _PercentSlider extends StatefulWidget {
  const _PercentSlider({
    required this.percent,
    required this.onChanged,
    this.showQuickButtons = true,
    this.enabled = true,
  });
  final double percent;
  final ValueChanged<double> onChanged;
  final bool showQuickButtons;

  /// false 时忽略所有手势，仅展示当前进度
  final bool enabled;

  @override
  State<_PercentSlider> createState() => _PercentSliderState();
}

class _PercentSliderState extends State<_PercentSlider> {
  // 行业标准快捷百分比（25/50/75/100，与主流交易所一致）
  static const _quickPcts = [0.0, 0.25, 0.50, 0.75, 1.00];
  static const _tickPcts = [0.0, 0.25, 0.50, 0.75, 1.00];
  static const _thumbSize = 18.0;
  static const _trackH = _thumbSize;

  bool _isDragging = false;
  double _lastHapticPct = -1;

  // OverlayPortal：气泡渲染到 Overlay 层，不受任何祖先裁剪影响
  final _overlayCtrl = OverlayPortalController();
  // CompositedTransformTarget key，用于获取滑块在屏幕中的绝对位置
  final _layerLink = LayerLink();
  double _trackWidth = 0;

  void _onDrag(double dx, double w) {
    // 有效轨道范围：两端各留半个滑块
    const r = _thumbSize / 2;
    final trackW = w - _thumbSize;
    final newPct = ((dx - r) / trackW).clamp(0.0, 1.0);
    final lastStep = (_lastHapticPct * 100).floor();
    final newStep = (newPct * 100).floor();
    if (newStep != lastStep) HapticFeedback.selectionClick();
    _lastHapticPct = newPct;
    widget.onChanged(newPct);
  }

  void _startDrag(double dx, double w) {
    _trackWidth = w;
    setState(() => _isDragging = true);
    _overlayCtrl.show();
    _onDrag(dx, w);
  }

  void _endDrag() {
    setState(() => _isDragging = false);
    _overlayCtrl.hide();
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final percent = widget.percent;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        // ── 滑轨区 ───────────────────────────────────────────
        SizedBox(
          height: _trackH,
          child: LayoutBuilder(
            builder: (_, box) {
              final w = box.maxWidth;
              // 轨道有效宽度：两端各留半个滑块，使 0%/100% 时滑块完整显示
              const r = _thumbSize / 2;
              final trackW = w - _thumbSize;
              final thumbX = r + trackW * percent;

              return CompositedTransformTarget(
                link: _layerLink,
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onHorizontalDragStart: widget.enabled
                      ? (d) => _startDrag(d.localPosition.dx, w)
                      : null,
                  onHorizontalDragUpdate: widget.enabled
                      ? (d) => _onDrag(d.localPosition.dx, w)
                      : null,
                  onHorizontalDragEnd:
                      widget.enabled ? (_) => _endDrag() : null,
                  onTapDown: widget.enabled
                      ? (d) {
                          // 触点转换为有效轨道范围内的百分比
                          final pct = ((d.localPosition.dx - r) / trackW)
                              .clamp(0.0, 1.0);
                          HapticFeedback.selectionClick();
                          _lastHapticPct = pct;
                          widget.onChanged(pct);
                        }
                      : null,
                  // OverlayPortal：气泡挂载到 Overlay，脱离布局树
                  child: OverlayPortal(
                    controller: _overlayCtrl,
                    overlayChildBuilder: (_) {
                      final tw = _trackWidth > 0 ? _trackWidth : w;
                      // 滑块圆心的 X：与 thumbX 计算方式一致
                      const bubbleR = _thumbSize / 2;
                      final effW = tw - _thumbSize;
                      final thumbCx = bubbleR + effW * percent;
                      // 气泡宽40，对齐滑块中心，边界夹紧
                      final bubbleOffsetX =
                          (thumbCx - 20).clamp(0.0, tw - 40.0);
                      // 气泡高30（24容器+6箭头），置于滑块上方 4px
                      const bubbleH = 30.0;
                      return Positioned(
                        left: 0,
                        top: 0,
                        child: CompositedTransformFollower(
                          link: _layerLink,
                          showWhenUnlinked: false,
                          // 从 target 左上角偏移：x=气泡位置，y=轨道上方
                          offset: Offset(bubbleOffsetX, -(bubbleH + 4)),
                          child: _PercentBubble(percent: percent),
                        ),
                      );
                    },
                    child: Stack(
                      children: [
                        // 背景轨道（两端与有效轨道对齐，即 r ~ w-r）
                        Positioned(
                          top: (_trackH - 3) / 2,
                          left: r,
                          right: r,
                          height: 3,
                          child: Container(
                            decoration: BoxDecoration(
                              color: cs.outline.withAlpha(50),
                              borderRadius: BorderRadius.circular(2),
                            ),
                          ),
                        ),
                        // 已填充轨道（从 r 开始到滑块圆心）
                        Positioned(
                          top: (_trackH - 3) / 2,
                          left: r,
                          width: thumbX - r,
                          height: 3,
                          child: Container(
                            decoration: BoxDecoration(
                              color: AppColors.brand,
                              borderRadius: BorderRadius.circular(2),
                            ),
                          ),
                        ),
                        // 刻度点（在有效轨道范围内按百分比定位）
                        for (final p in _tickPcts)
                          Positioned(
                            left: r + trackW * p - 3,
                            top: (_trackH - 6) / 2,
                            child: Container(
                              width: 6,
                              height: 6,
                              decoration: BoxDecoration(
                                shape: BoxShape.circle,
                                color: p <= percent
                                    ? AppColors.brand
                                    : cs.outline.withAlpha(80),
                              ),
                            ),
                          ),
                        // 滑块把手（圆心在 thumbX）
                        Positioned(
                          left: thumbX - r,
                          top: 0,
                          width: _thumbSize,
                          height: _thumbSize,
                          child: Container(
                            decoration: BoxDecoration(
                              color: Colors.white,
                              shape: BoxShape.circle,
                              border: Border.all(
                                  color: AppColors.brand, width: 2.5),
                              boxShadow: [
                                BoxShadow(
                                  color: Colors.black.withAlpha(70),
                                  blurRadius: 5,
                                  spreadRadius: 1,
                                  offset: const Offset(0, 1.5),
                                ),
                              ],
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              );
            },
          ),
        ),

        if (widget.showQuickButtons) ...[
          const SizedBox(height: 4),
          // ── 快捷按钮 0/25/50/75/100：Stack+Positioned，按钮中心与刻度对齐 ──
          LayoutBuilder(builder: (_, box) {
            final w = box.maxWidth;
            const r = _thumbSize / 2;
            final trackW = w - _thumbSize;
            // 相邻刻度间距，减去固定间隙 6px，保证各机型按钮间隔一致
            final tickInterval = trackW / (_quickPcts.length - 1);
            final btnW = (tickInterval - 6).clamp(20.0, 64.0);

            return SizedBox(
              height: 22,
              child: Stack(
                children: [
                  for (final pct in _quickPcts)
                    Positioned(
                      // 按钮中心对齐刻度圆心，边界夹紧防止溢出
                      left: (r + trackW * pct - btnW / 2).clamp(0.0, w - btnW),
                      top: 0,
                      width: btnW,
                      height: 22,
                      child: Builder(builder: (_) {
                        final isSelected = (percent - pct).abs() < 0.001;
                        return GestureDetector(
                          onTap: widget.enabled
                              ? () {
                                  HapticFeedback.selectionClick();
                                  _lastHapticPct = pct;
                                  widget.onChanged(pct);
                                }
                              : null,
                          child: Container(
                            decoration: BoxDecoration(
                              color: isSelected
                                  ? AppColors.brand
                                  : cs.outline.withAlpha(25),
                              borderRadius: BorderRadius.circular(4),
                            ),
                            alignment: Alignment.center,
                            child: Text(
                              '${(pct * 100).toInt()}%',
                              style: TextStyle(
                                color: isSelected
                                    ? Colors.black
                                    : cs.onSurface.withAlpha(102),
                                fontSize: 10,
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                          ),
                        );
                      }),
                    ),
                ],
              ),
            );
          }),
        ],
      ],
    );
  }
}

/// 拖动时显示在滑块上方的百分比气泡（气泡 + 向下三角箭头）
class _PercentBubble extends StatelessWidget {
  const _PercentBubble({required this.percent});
  final double percent;

  @override
  Widget build(BuildContext context) {
    final label = '${(percent * 100).round()}%';
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Container(
          width: 40,
          height: 24,
          alignment: Alignment.center,
          decoration: BoxDecoration(
            color: AppColors.brand,
            borderRadius: BorderRadius.circular(6),
          ),
          child: Text(
            label,
            style: const TextStyle(
              color: Colors.black,
              fontSize: 11,
              fontWeight: FontWeight.w700,
            ),
          ),
        ),
        CustomPaint(
          size: const Size(8, 6),
          painter: _BubbleArrowPainter(),
        ),
      ],
    );
  }
}

class _BubbleArrowPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = AppColors.brand;
    final path = Path()
      ..moveTo(0, 0)
      ..lineTo(size.width / 2, size.height)
      ..lineTo(size.width, 0)
      ..close();
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(_BubbleArrowPainter _) => false;
}

class _TpslSection extends StatelessWidget {
  const _TpslSection({
    required this.enabled,
    required this.onToggle,
    required this.tpController,
    required this.slController,
  });
  final bool enabled;
  final VoidCallback onToggle;
  final TextEditingController tpController;
  final TextEditingController slController;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Column(
      children: [
        GestureDetector(
          onTap: onToggle,
          child: Row(
            children: [
              Container(
                width: 16,
                height: 16,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(4),
                  border: Border.all(
                    color:
                        enabled ? AppColors.brand : cs.onSurface.withAlpha(153),
                    width: 1.5,
                  ),
                  color: enabled ? AppColors.brand : Colors.transparent,
                ),
                child: enabled
                    ? const Icon(Icons.check, size: 12, color: Colors.black)
                    : null,
              ),
              const SizedBox(width: 6),
              Text(AppLocalizations.of(context)!.takeProfitStopLoss,
                  style: TextStyle(color: cs.onSurface, fontSize: 13)),
            ],
          ),
        ),
        if (enabled) ...[
          const SizedBox(height: 6),
          _SmallInput(
            controller: tpController,
            hint: '${AppLocalizations.of(context)!.takeProfitPrice} (USDT)',
          ),
          const SizedBox(height: 4),
          _SmallInput(
            controller: slController,
            hint: '${AppLocalizations.of(context)!.stopLossPrice} (USDT)',
          ),
        ],
      ],
    );
  }
}

class _SmallInput extends StatefulWidget {
  const _SmallInput({required this.controller, required this.hint});
  final TextEditingController controller;
  final String hint;

  @override
  State<_SmallInput> createState() => _SmallInputState();
}

class _SmallInputState extends State<_SmallInput> {
  final _focusNode = FocusNode();

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(() {
      if (mounted) setState(() {});
    });
  }

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final isFocused = _focusNode.hasFocus;
    final bgColor = isFocused
        ? (isDark ? AppColors.darkBgSecondary : Colors.white)
        : (isDark ? AppColors.darkBgTertiary : AppColors.lightBgSecondary);
    final activeBorder = isDark
        ? AppColors.darkTextPrimary.withAlpha(200)
        : const Color(0xFF383838);
    return Container(
      height: 34,
      padding: const EdgeInsets.symmetric(horizontal: 10),
      decoration: BoxDecoration(
        color: bgColor,
        borderRadius: BorderRadius.circular(6),
        border: isFocused ? Border.all(color: activeBorder, width: 1.5) : null,
      ),
      child: TextField(
        controller: widget.controller,
        focusNode: _focusNode,
        keyboardType: const TextInputType.numberWithOptions(decimal: true),
        style: TextStyle(color: cs.onSurface, fontSize: 12),
        decoration: InputDecoration(
          hintText: widget.hint,
          hintStyle:
              TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 12),
          border: InputBorder.none,
          focusedBorder: InputBorder.none,
          enabledBorder: InputBorder.none,
          filled: false,
          isDense: true,
          contentPadding:
              const EdgeInsets.symmetric(vertical: 8, horizontal: 0),
        ),
      ),
    );
  }
}

// 资金费率行 — 只订阅 fundingRate + fundingCountdown，每秒更新一次，不触发盘口重建
class _FundingRateRow extends ConsumerWidget {
  const _FundingRateRow({required this.symbol});
  final String symbol;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final provider = futuresProvider(symbol);
    final fundingRate = ref.watch(provider.select((s) => s.fundingRate));
    final fundingCountdown =
        ref.watch(provider.select((s) => s.fundingCountdown));
    return Row(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        Column(
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Text(AppLocalizations.of(context)!.fundingRateCountdown,
                style: TextStyle(
                    color: cs.onSurface.withAlpha(153), fontSize: 11)),
            Text(
              '${fundingRate >= 0 ? '+' : ''}${(fundingRate * 100).toStringAsFixed(4)}% / $fundingCountdown',
              style: TextStyle(
                  color: cs.onSurface,
                  fontSize: 13,
                  fontFeatures: const [FontFeature.tabularFigures()]),
            ),
          ],
        ),
      ],
    );
  }
}

class _OrderBookPanel extends ConsumerStatefulWidget {
  const _OrderBookPanel({
    required this.symbol,
    required this.rowCount,
    this.rowHeight = 22.0,
    this.onPriceTap,
  });
  final String symbol;
  final int rowCount;
  final double rowHeight;
  final ValueChanged<double>? onPriceTap;

  @override
  ConsumerState<_OrderBookPanel> createState() => _OrderBookPanelState();
}

/// 预计算后的单行盘口数据，避免 build() 内重复调用格式化函数
class _BookRowData {
  const _BookRowData({
    required this.price,
    required this.qty,
    required this.formattedPrice,
    required this.depthPercent,
  });
  final double price;
  final double qty;
  final String formattedPrice;
  final double depthPercent;
}

class _OrderBookPanelState extends ConsumerState<_OrderBookPanel> {
  // 订单薄显示模式: 0=双向, 1=仅卖盘, 2=仅买盘
  int _bookMode = 0;

  // 缓存：只在 rawAsks/rawBids/bookMode 变化时重新计算
  List<Map<String, dynamic>>? _prevAsks;
  List<Map<String, dynamic>>? _prevBids;
  int _prevBookMode = -1;
  int _prevRowCount = -1;
  int _prevPricePrecision = -1;
  List<_BookRowData> _askRows = const [];
  List<_BookRowData> _bidRows = const [];
  double _bidRatio = 0.5;

  void _recompute(
    List<Map<String, dynamic>> rawAsks,
    List<Map<String, dynamic>> rawBids,
    double minTick,
    int decimalDigits,
  ) {
    _prevAsks = rawAsks;
    _prevBids = rawBids;
    _prevBookMode = _bookMode;
    _prevRowCount = widget.rowCount;
    _prevPricePrecision = decimalDigits;

    final aggAsks = _bookMode == 2 ? <Map<String, dynamic>>[] : rawAsks;
    final aggBids = _bookMode == 1 ? <Map<String, dynamic>>[] : rawBids;
    final askRows = _bookMode == 2
        ? 0
        : (_bookMode == 1 ? widget.rowCount * 2 : widget.rowCount);
    final bidRows = _bookMode == 1
        ? 0
        : (_bookMode == 2 ? widget.rowCount * 2 : widget.rowCount);

    final askSlice = aggAsks.take(askRows).toList();
    final bidSlice = aggBids.take(bidRows).toList();

    double totalAsk = 0, totalBid = 0, maxQ = 0.001;
    for (var e in askSlice) {
      final q = _toDouble(e['quantity']);
      totalAsk += q;
      if (q > maxQ) maxQ = q;
    }
    for (var e in bidSlice) {
      final q = _toDouble(e['quantity']);
      totalBid += q;
      if (q > maxQ) maxQ = q;
    }
    final total = totalAsk + totalBid;
    _bidRatio = total > 0 ? totalBid / total : 0.5;

    // 卖盘：slice 升序，UI 反转（最高价在顶）
    _askRows = List.generate(askRows, (i) {
      final ri = askSlice.length - 1 - i;
      if (ri < 0)
        return const _BookRowData(
            price: 0, qty: 0, formattedPrice: '', depthPercent: 0);
      final price = _toDouble(askSlice[ri]['price']);
      final qty = _toDouble(askSlice[ri]['quantity']);
      return _BookRowData(
        price: price,
        qty: qty,
        formattedPrice: _fmtPrice(price, minTick, decimalDigits),
        depthPercent: qty / maxQ,
      );
    });

    _bidRows = List.generate(bidRows, (i) {
      if (i >= bidSlice.length)
        return const _BookRowData(
            price: 0, qty: 0, formattedPrice: '', depthPercent: 0);
      final price = _toDouble(bidSlice[i]['price']);
      final qty = _toDouble(bidSlice[i]['quantity']);
      return _BookRowData(
        price: price,
        qty: qty,
        formattedPrice: _fmtPrice(price, minTick, decimalDigits),
        depthPercent: qty / maxQ,
      );
    });
  }

  // 根据合约价格精度计算小数位数
  int _decimalDigits(int pricePrecision) {
    if (pricePrecision <= 0) return 0;
    return pricePrecision;
  }

  // 将价格按精度对齐（始终使用合约最小精度档）
  double _roundToPrecision(double price, double minTick) {
    return (price / minTick).round() * minTick;
  }

  String _fmtPrice(double price, double minTick, int decimalDigits) {
    final str = price.toString();
    // 如果原始字符串中有小数点，直接返回；否则返回固定格式
    return str.contains('.') ? str : price.toStringAsFixed(0);
  }

  // 将原始订单列表按精度合并：相同价格档位的数量求和
  // isSell=true  → 按价格升序（卖盘，低价在前）
  // isSell=false → 按价格降序（买盘，高价在前）
  List<Map<String, dynamic>> _aggregate(
      List<Map<String, dynamic>> raw, bool isSell, double minTick) {
    final Map<double, double> bucket = {};
    for (final o in raw) {
      final p = _toDouble(o['price']);
      final q = _toDouble(o['quantity']);
      if (p <= 0 || q <= 0) continue;
      final key = _roundToPrecision(p, minTick);
      bucket[key] = (bucket[key] ?? 0) + q;
    }
    final entries = bucket.entries.toList()
      ..sort((a, b) => isSell
          ? a.key.compareTo(b.key) // 卖盘升序
          : b.key.compareTo(a.key)); // 买盘降序
    return entries.map((e) => {'price': e.key, 'quantity': e.value}).toList();
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final provider = futuresProvider(widget.symbol);
    final lastPrice = ref.watch(provider.select((s) => s.lastPrice));
    final lastPriceStr = ref.watch(provider.select((s) => s.lastPriceStr));
    final rawAsks = ref.watch(provider.select((s) => s.orderBookAsks));
    final rawBids = ref.watch(provider.select((s) => s.orderBookBids));
    final pricePrecision = ref.watch(provider.select((s) => s.pricePrecision));

    final decimalDigits = _decimalDigits(pricePrecision);
    final minTick = pricePrecision >= 0
        ? pow(10, pricePrecision).toDouble()
        : 1.0 / pow(10, -pricePrecision).toDouble();

    // 只在数据或模式变化时重新计算行数据（避免每帧重复 O(n) 工作）
    if (!identical(rawAsks, _prevAsks) ||
        !identical(rawBids, _prevBids) ||
        _bookMode != _prevBookMode ||
        widget.rowCount != _prevRowCount ||
        decimalDigits != _prevPricePrecision) {
      _recompute(rawAsks, rawBids, minTick, decimalDigits);
    }

    final askRatio = 1.0 - _bidRatio;
    final labelStyle =
        TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 11);

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisSize: MainAxisSize.max,
        children: [
          // 资金费率独立 widget，每秒重建，不影响盘口行
          RepaintBoundary(child: _FundingRateRow(symbol: widget.symbol)),
          const SizedBox(height: 2),

          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Flexible(
                  child: Text(AppLocalizations.of(context)!.priceUsdt,
                      overflow: TextOverflow.ellipsis, style: labelStyle)),
              const SizedBox(width: 4),
              Flexible(
                  child: Text(
                      AppLocalizations.of(context)!
                          .amountLabel2(_baseCoin(widget.symbol)),
                      overflow: TextOverflow.ellipsis,
                      style: labelStyle)),
            ],
          ),
          const SizedBox(height: 4),

          // 卖盘
          if (_bookMode != 2)
            for (var i = 0; i < _askRows.length; i++)
              _askRows[i].price > 0
                  ? RepaintBoundary(
                      key: ValueKey('ask_$i'),
                      child: _BookRow(
                        price: _askRows[i].price,
                        isSell: true,
                        amount: _askRows[i].qty.toStringAsFixed(4),
                        depthPercent: _askRows[i].depthPercent,
                        formattedPrice: _askRows[i].formattedPrice,
                        rowHeight: widget.rowHeight,
                        onTap: widget.onPriceTap != null
                            ? () => widget.onPriceTap!(_askRows[i].price)
                            : null,
                      ),
                    )
                  : _BookRowPlaceholder(
                      key: ValueKey('ask_ph_$i'), rowHeight: widget.rowHeight),

          Padding(
            padding: const EdgeInsets.symmetric(vertical: 5),
            child: FittedBox(
              fit: BoxFit.scaleDown,
              alignment: Alignment.centerLeft,
              child: Row(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text(
                    lastPriceStr != null
                        ? formatRawPrice(lastPriceStr)
                        : formatPrice(lastPrice),
                    maxLines: 1,
                    overflow: TextOverflow.clip,
                    style: TextStyle(
                      color: cs.onSurface,
                      fontSize: 16,
                      fontWeight: FontWeight.w700,
                      fontFeatures: const [FontFeature.tabularFigures()],
                    ),
                  ),
                  const SizedBox(width: 4),
                  Text(
                    '≈\$${formatPrice(lastPrice * 0.9999)}',
                    maxLines: 1,
                    overflow: TextOverflow.clip,
                    style: TextStyle(
                      color: cs.onSurface.withAlpha(153),
                      fontSize: 10,
                      fontFeatures: const [FontFeature.tabularFigures()],
                    ),
                  ),
                ],
              ),
            ),
          ),
          // 买盘
          if (_bookMode != 1)
            for (var i = 0; i < _bidRows.length; i++)
              _bidRows[i].price > 0
                  ? RepaintBoundary(
                      key: ValueKey('bid_$i'),
                      child: _BookRow(
                        price: _bidRows[i].price,
                        isSell: false,
                        amount: _bidRows[i].qty.toStringAsFixed(4),
                        depthPercent: _bidRows[i].depthPercent,
                        formattedPrice: _bidRows[i].formattedPrice,
                        rowHeight: widget.rowHeight,
                        onTap: widget.onPriceTap != null
                            ? () => widget.onPriceTap!(_bidRows[i].price)
                            : null,
                      ),
                    )
                  : _BookRowPlaceholder(
                      key: ValueKey('bid_ph_$i'), rowHeight: widget.rowHeight),

          const Spacer(), // 撑开剩余空间，让底部元素贴底

          ClipRRect(
            borderRadius: BorderRadius.circular(2),
            child: Row(
              children: [
                Flexible(
                  flex: (_bidRatio * 100).round().clamp(1, 99),
                  child: Container(
                      height: 4, color: AppColors.rise.withAlpha(180)),
                ),
                Flexible(
                  flex: (askRatio * 100).round().clamp(1, 99),
                  child: Container(
                      height: 4, color: AppColors.fall.withAlpha(180)),
                ),
              ],
            ),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text('${(_bidRatio * 100).toStringAsFixed(2)}%',
                  style: const TextStyle(color: AppColors.rise, fontSize: 10)),
              Text('${(askRatio * 100).toStringAsFixed(2)}%',
                  style: const TextStyle(color: AppColors.fall, fontSize: 10)),
            ],
          ),
          const SizedBox(height: 2),

          Row(
            children: [
              const Spacer(),
              // 订单薄模式切换
              GestureDetector(
                onTap: () => setState(() => _bookMode = (_bookMode + 1) % 3),
                child: Container(
                  width: 28,
                  height: 20,
                  padding:
                      const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
                  decoration: BoxDecoration(
                    border: Border.all(color: cs.outline.withAlpha(80)),
                    borderRadius: BorderRadius.circular(4),
                  ),
                  child: _BookModeIcon(mode: _bookMode),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class _BookRow extends StatelessWidget {
  const _BookRow({
    super.key,
    required this.price,
    required this.isSell,
    required this.amount,
    this.depthPercent = 0.3,
    this.formattedPrice,
    this.rowHeight = 22.0,
    this.onTap,
  });
  final double price;
  final bool isSell;
  final String amount;
  final double depthPercent;
  final String? formattedPrice;
  final double rowHeight;
  final VoidCallback? onTap;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final color = isSell ? AppColors.fall : AppColors.rise;
    return GestureDetector(
      onTap: onTap,
      behavior: HitTestBehavior.opaque,
      child: SizedBox(
        height: rowHeight,
        child: LayoutBuilder(
          builder: (_, constraints) {
            final barW = constraints.maxWidth * depthPercent.clamp(0.0, 1.0);
            return Stack(
              children: [
                Positioned(
                  right: 0,
                  top: 1,
                  bottom: 1,
                  child: AnimatedContainer(
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeOut,
                    width: barW,
                    decoration: BoxDecoration(
                      color: color.withAlpha(38),
                      borderRadius: BorderRadius.circular(2),
                    ),
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 1),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text(formattedPrice ?? formatPrice(price),
                          style: TextStyle(
                              color: color,
                              fontSize: 13,
                              fontWeight: FontWeight.w500,
                              fontFeatures: const [
                                FontFeature.tabularFigures()
                              ])),
                      Text(amount,
                          style: TextStyle(
                              color: cs.onSurface,
                              fontSize: 13,
                              fontFeatures: const [
                                FontFeature.tabularFigures()
                              ])),
                    ],
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

/// 订单薄模式图标：3种视觉样式
/// mode=0 双向 | mode=1 仅卖盘 | mode=2 仅买盘
class _BookModeIcon extends StatelessWidget {
  const _BookModeIcon({required this.mode});
  final int mode;

  @override
  Widget build(BuildContext context) {
    const sellColor = AppColors.fall;
    const buyColor = AppColors.rise;
    const emptyColor = Color(0xFFCCCCCC);
    final double lineH = 2.0;
    final double gap = 1.5;

    Widget line(Color color) => Container(
          height: lineH,
          decoration: BoxDecoration(
            color: color,
            borderRadius: BorderRadius.circular(1),
          ),
        );

    // 3 lines: top=sell side, bottom=buy side
    // mode0: top 1.5 sell lines + 1.5 buy lines
    // mode1: all sell (red)
    // mode2: all buy (green)
    final List<Widget> lines;
    if (mode == 0) {
      lines = [
        line(sellColor),
        SizedBox(height: gap),
        line(sellColor),
        SizedBox(height: gap),
        line(buyColor),
        SizedBox(height: gap),
        line(buyColor),
      ];
    } else if (mode == 1) {
      lines = [
        line(sellColor),
        SizedBox(height: gap),
        line(sellColor),
        SizedBox(height: gap),
        line(sellColor),
        SizedBox(height: gap),
        line(emptyColor),
      ];
    } else {
      lines = [
        line(emptyColor),
        SizedBox(height: gap),
        line(buyColor),
        SizedBox(height: gap),
        line(buyColor),
        SizedBox(height: gap),
        line(buyColor),
      ];
    }

    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: lines,
    );
  }
}

/// 无数据时的占位行，高度与 _BookRow 保持一致
class _BookRowPlaceholder extends StatelessWidget {
  const _BookRowPlaceholder({super.key, this.rowHeight = 22.0});
  final double rowHeight;

  @override
  Widget build(BuildContext context) {
    return AppShimmer(
      child: SizedBox(
        height: rowHeight,
        child: Row(
          children: [
            Expanded(
                child: Align(
              alignment: Alignment.centerLeft,
              child: shimmerBox(60, 10),
            )),
            Expanded(
                child: Align(
              alignment: Alignment.centerRight,
              child: shimmerBox(50, 10),
            )),
          ],
        ),
      ),
    );
  }
}

// ── 自适应高度辅助 ─────────────────────────────────────────
// 用 OverflowBox 解除父级高度约束，让子内容按自然高度布局，
// 再通过 addPostFrameCallback 读取真实 RenderBox 高度上报给父级。
// PageView 本身会 clip，所以 OverflowBox 超出部分不会显示。
class _MeasureSize extends StatefulWidget {
  const _MeasureSize({required this.child, required this.onSize});
  final Widget child;
  final ValueChanged<double> onSize;

  @override
  State<_MeasureSize> createState() => _MeasureSizeState();
}

class _MeasureSizeState extends State<_MeasureSize> {
  final _key = GlobalKey();
  double? _last;

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      final box = _key.currentContext?.findRenderObject() as RenderBox?;
      if (box == null || !box.hasSize) return;
      final h = box.size.height;
      if (h != _last) {
        _last = h;
        widget.onSize(h);
      }
    });
    // OverflowBox：宽度跟父级一致，高度不设上限，让子内容自由伸展。
    // 这样 RenderBox.size.height 反映的是内容的真实高度，而不是父级约束高度。
    return OverflowBox(
      alignment: Alignment.topLeft,
      minHeight: 0,
      maxHeight: double.infinity,
      child: KeyedSubtree(key: _key, child: widget.child),
    );
  }
}

class _BottomSection extends ConsumerStatefulWidget {
  const _BottomSection({required this.symbol});
  final String symbol;

  @override
  ConsumerState<_BottomSection> createState() => _BottomSectionState();
}

class _BottomSectionState extends ConsumerState<_BottomSection> {
  late PageController _pageController;
  bool _programmaticSwitch = false;
  // 三个 Tab 各自测量到的高度。
  // 初始值需能容纳 shimmer 占位内容（positions rows=2 ≈252px，orders rows=3 ≈380px，
  // assets ≈260px），避免第一帧溢出。OverflowBox 测量到真实高度后会立即更新。
  final _tabHeights = [280.0, 420.0, 280.0];

  static int _tabToIndex(FuturesTab tab) {
    switch (tab) {
      case FuturesTab.positions:
        return 0;
      case FuturesTab.orders:
        return 1;
      case FuturesTab.assets:
        return 2;
    }
  }

  static FuturesTab _indexToTab(int index) {
    switch (index) {
      case 0:
        return FuturesTab.positions;
      case 1:
        return FuturesTab.orders;
      default:
        return FuturesTab.assets;
    }
  }

  @override
  void initState() {
    super.initState();
    final initial = ref.read(futuresProvider(widget.symbol)).activeTab;
    _pageController = PageController(initialPage: _tabToIndex(initial));
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  void _onTabTap(FuturesTab tab) {
    _programmaticSwitch = true;
    ref.read(futuresProvider(widget.symbol).notifier).setTab(tab);
    _pageController.animateToPage(
      _tabToIndex(tab),
      duration: const Duration(milliseconds: 280),
      curve: Curves.easeOut,
    );
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final symbol = widget.symbol;
    final provider = futuresProvider(symbol);
    final activeTab = ref.watch(provider.select((s) => s.activeTab));
    final positionsCount =
        ref.watch(provider.select((s) => s.positions.length));
    final ordersCount = ref.watch(provider.select((s) => s.openOrders.length));
    final isTabLoading = ref.watch(provider.select((s) => s.isTabLoading));

    return Container(
      decoration: BoxDecoration(
        border: Border(top: BorderSide(color: cs.outline)),
      ),
      child: Column(
        children: [
          // Tab 头
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            child: Row(
              children: [
                _BottomTab(
                  label: AppLocalizations.of(context)!.positionsTab,
                  count: positionsCount > 0 ? '($positionsCount)' : null,
                  countColor: AppColors.fall,
                  active: activeTab == FuturesTab.positions,
                  onTap: () => _onTabTap(FuturesTab.positions),
                ),
                const SizedBox(width: 16),
                _BottomTab(
                  label: AppLocalizations.of(context)!.currentOrders,
                  count: ordersCount > 0 ? '($ordersCount)' : null,
                  active: activeTab == FuturesTab.orders,
                  onTap: () => _onTabTap(FuturesTab.orders),
                ),
                const SizedBox(width: 16),
                _BottomTab(
                  label: AppLocalizations.of(context)!.assets,
                  active: activeTab == FuturesTab.assets,
                  onTap: () => _onTabTap(FuturesTab.assets),
                ),
                const Spacer(),
                GestureDetector(
                  onTap: () async {
                    final notifier = ref.read(futuresProvider(symbol).notifier);
                    notifier.stopPolling();
                    await context.push('/futures/$symbol/history');
                    if (context.mounted) notifier.resumePolling(symbol);
                  },
                  child: Icon(Icons.access_time,
                      color: cs.onSurface.withAlpha(153), size: 18),
                ),
              ],
            ),
          ),
          // 工具栏（持仓/委托各有操作按钮）
          AnimatedSwitcher(
            duration: const Duration(milliseconds: 200),
            child: activeTab == FuturesTab.positions
                ? _PositionToolbar(
                    key: const ValueKey('pos_toolbar'), symbol: symbol)
                : activeTab == FuturesTab.orders
                    ? _OrderToolbar(
                        key: const ValueKey('ord_toolbar'), symbol: symbol)
                    : const SizedBox.shrink(key: ValueKey('no_toolbar')),
          ),
          // 滑动内容区
          // PageView 在 Column 内必须有有界高度。
          // 通过 _MeasureSize 让每个 Tab 上报自身真实高度，取三者最大值，
          // 避免硬编码估算值在内容变化时溢出。
          SizedBox(
            height: _tabHeights.reduce(max).clamp(100.0, 2000.0),
            child: RepaintBoundary(
              child: PageView(
                controller: _pageController,
                physics: const ClampingScrollPhysics(),
                onPageChanged: (index) {
                  if (_programmaticSwitch) {
                    _programmaticSwitch = false;
                    return;
                  }
                  final tab = _indexToTab(index);
                  ref.read(futuresProvider(symbol).notifier).setTab(tab);
                },
                children: [
                  _MeasureSize(
                    onSize: (h) {
                      if (_tabHeights[0] != h)
                        setState(() => _tabHeights[0] = h);
                    },
                    child: _BottomTabContent(
                        symbol: symbol, tab: FuturesTab.positions),
                  ),
                  _MeasureSize(
                    onSize: (h) {
                      if (_tabHeights[1] != h)
                        setState(() => _tabHeights[1] = h);
                    },
                    child: _BottomTabContent(
                        symbol: symbol, tab: FuturesTab.orders),
                  ),
                  _MeasureSize(
                    onSize: (h) {
                      if (_tabHeights[2] != h)
                        setState(() => _tabHeights[2] = h);
                    },
                    child: _BottomTabContent(
                        symbol: symbol, tab: FuturesTab.assets),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _BottomTabContent extends ConsumerWidget {
  const _BottomTabContent({required this.symbol, required this.tab});
  final String symbol;
  final FuturesTab tab;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final provider = futuresProvider(symbol);
    final isTabLoading = ref.watch(provider.select((s) => s.isTabLoading));

    switch (tab) {
      case FuturesTab.positions:
        final positions = ref.watch(provider.select((s) => s.displayPositions));
        if (isTabLoading && positions.isEmpty)
          return const _TabShimmer(rows: 2);
        return _PositionsList(symbol: symbol, positions: positions);
      case FuturesTab.orders:
        final openOrders = ref.watch(provider.select((s) => s.displayOrders));
        if (isTabLoading && openOrders.isEmpty)
          return const _TabShimmer(rows: 3);
        return _OrdersList(symbol: symbol, orders: openOrders);
      case FuturesTab.assets:
        final accountInfo = ref.watch(provider.select((s) => s.accountInfo));
        if (isTabLoading && accountInfo.totalBalance == 0)
          return const _AssetShimmer();
        return _AssetsPanel(info: accountInfo);
    }
  }
}

class _PositionToolbar extends ConsumerWidget {
  const _PositionToolbar({super.key, required this.symbol});
  final String symbol;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final notifier = ref.read(futuresProvider(symbol).notifier);
    final hideOther =
        ref.watch(futuresProvider(symbol).select((s) => s.hideOtherSymbols));
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      child: Row(
        children: [
          GestureDetector(
            onTap: notifier.toggleHideOtherSymbols,
            child: Row(
              children: [
                _CheckBox(checked: hideOther),
                const SizedBox(width: 4),
                Text(AppLocalizations.of(context)!.hideOtherPairs,
                    style: TextStyle(
                        color: cs.onSurface.withAlpha(153), fontSize: 11)),
              ],
            ),
          ),
          const Spacer(),
          GestureDetector(
            onTap: () => _closeAll(context, ref, notifier),
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
              decoration: BoxDecoration(
                color: cs.inverseSurface,
                borderRadius: BorderRadius.circular(4),
              ),
              child: Text(AppLocalizations.of(context)!.closeAllPositions,
                  style: TextStyle(
                      color: cs.onInverseSurface,
                      fontSize: 11,
                      fontWeight: FontWeight.w500)),
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _closeAll(
      BuildContext context, WidgetRef ref, FuturesNotifier notifier) async {
    if (!_requireLogin(context, ref)) return;
    final l10n = AppLocalizations.of(context)!;
    final confirmed = await _showFuturesConfirm(
      context,
      message: l10n.closeAllConfirm,
      subMessage: l10n.closeAllSubMsg,
    );
    if (!confirmed || !context.mounted) return;
    final err = await notifier.closeAllPositions();
    if (!context.mounted) return;
    final l10n2 = AppLocalizations.of(context)!;
    showTopToast(context,
        message: resolveProviderError(err, l10n2) ?? l10n2.closeAllSubmitted,
        backgroundColor: err != null ? AppColors.fall : AppColors.rise);
  }
}

class _OrderToolbar extends ConsumerWidget {
  const _OrderToolbar({super.key, required this.symbol});
  final String symbol;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final notifier = ref.read(futuresProvider(symbol).notifier);
    final hideOther =
        ref.watch(futuresProvider(symbol).select((s) => s.hideOtherSymbols));
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
      child: Row(
        children: [
          GestureDetector(
            onTap: notifier.toggleHideOtherSymbols,
            child: Row(
              children: [
                _CheckBox(checked: hideOther),
                const SizedBox(width: 4),
                Text(AppLocalizations.of(context)!.hideOtherPairs,
                    style: TextStyle(
                        color: cs.onSurface.withAlpha(153), fontSize: 11)),
              ],
            ),
          ),
          const Spacer(),
          GestureDetector(
            onTap: () => _cancelAll(context, ref, notifier),
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
              decoration: BoxDecoration(
                color: cs.inverseSurface,
                borderRadius: BorderRadius.circular(4),
              ),
              child: Text(AppLocalizations.of(context)!.cancelAllOrders,
                  style: TextStyle(
                      color: cs.onInverseSurface,
                      fontSize: 11,
                      fontWeight: FontWeight.w500)),
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _cancelAll(
      BuildContext context, WidgetRef ref, FuturesNotifier notifier) async {
    if (!_requireLogin(context, ref)) return;
    final err = await notifier.cancelAllOrders();
    if (!context.mounted) return;
    final l10n3 = AppLocalizations.of(context)!;
    showTopToast(context,
        message: resolveProviderError(err, l10n3) ?? l10n3.cancelAllSuccess,
        backgroundColor: err != null ? AppColors.fall : AppColors.rise);
  }
}

class _BottomTab extends StatelessWidget {
  const _BottomTab({
    required this.label,
    required this.active,
    required this.onTap,
    this.count,
    this.countColor,
  });
  final String label;
  final bool active;
  final VoidCallback onTap;

  /// 数量 badge 文字，如 "(2)"
  final String? count;

  /// badge 文字颜色，默认灰色
  final Color? countColor;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final labelColor = active ? cs.onSurface : cs.onSurface.withAlpha(153);
    return GestureDetector(
      onTap: onTap,
      child: Container(
        padding: const EdgeInsets.symmetric(vertical: 10),
        decoration: BoxDecoration(
          border: Border(
            bottom: BorderSide(
              color: active ? AppColors.brand : Colors.transparent,
              width: 2,
            ),
          ),
        ),
        child: count != null
            ? RichText(
                text: TextSpan(
                  children: [
                    TextSpan(
                      text: label,
                      style: TextStyle(
                        color: labelColor,
                        fontSize: 13,
                        fontWeight: active ? FontWeight.w600 : FontWeight.w400,
                      ),
                    ),
                    TextSpan(
                      text: count,
                      style: TextStyle(
                        color: countColor ?? cs.onSurface.withAlpha(153),
                        fontSize: 13,
                        fontWeight: active ? FontWeight.w600 : FontWeight.w400,
                      ),
                    ),
                  ],
                ),
              )
            : Text(
                label,
                style: TextStyle(
                  color: labelColor,
                  fontSize: 13,
                  fontWeight: active ? FontWeight.w600 : FontWeight.w400,
                ),
              ),
      ),
    );
  }
}

class _PositionsList extends StatelessWidget {
  const _PositionsList({required this.symbol, required this.positions});
  final String symbol;
  final List<FuturesPosition> positions;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    if (positions.isEmpty) {
      return Padding(
        padding: const EdgeInsets.symmetric(vertical: 40),
        child: Center(
          child: Text(AppLocalizations.of(context)!.noPositions,
              style:
                  TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13)),
        ),
      );
    }
    return ListView.builder(
      padding: const EdgeInsets.fromLTRB(12, 4, 12, 8),
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      itemCount: positions.length,
      itemBuilder: (_, i) => RepaintBoundary(
        key: ValueKey('pos_${positions[i].id}'),
        child: _PositionCard(symbol: symbol, position: positions[i]),
      ),
    );
  }
}

class _PositionCard extends ConsumerWidget {
  const _PositionCard({required this.symbol, required this.position});
  final String symbol;
  final FuturesPosition position;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final notifier = ref.read(futuresProvider(symbol).notifier);
    final coinSymbol = _baseCoin(position.symbol);
    final isLong = position.side == OrderSide.long;
    final pnlColor =
        position.unrealizedPnl >= 0 ? AppColors.rise : AppColors.fall;
    final sideColor = isLong ? AppColors.rise : AppColors.fall;
    final coinName = coinSymbol;

    // 扁平卡片：border-bottom 分割，padding 10 12，与原型 .pos-card 对齐
    return GestureDetector(
        onTap: () async {
          notifier.stopPolling();
          await context.push('/futures/$symbol/position-detail',
              extra: position);
          if (context.mounted) notifier.resumePolling(symbol);
        },
        child: Container(
          decoration: BoxDecoration(
            border: Border(bottom: BorderSide(color: cs.outline.withAlpha(50))),
          ),
          padding: const EdgeInsets.fromLTRB(12, 10, 12, 16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // ── 标题行 ──────────────────────────────────────────
              Row(
                children: [
                  // 多/空 彩色徽标
                  Container(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
                    decoration: BoxDecoration(
                      color: sideColor,
                      borderRadius: BorderRadius.circular(3),
                    ),
                    child: Text(
                      isLong
                          ? AppLocalizations.of(context)!.openLong
                          : AppLocalizations.of(context)!.openShort,
                      style: const TextStyle(
                          color: Colors.white,
                          fontSize: 11,
                          fontWeight: FontWeight.w700),
                    ),
                  ),
                  const SizedBox(width: 4),
                  // Expanded 包裹所有中间信息，分享按钮固定在最右
                  Expanded(
                    child: Row(
                      children: [
                        Flexible(
                          child: Text(
                            '${coinName}USDT',
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                            style: TextStyle(
                                color: cs.onSurface,
                                fontSize: 13,
                                fontWeight: FontWeight.w700),
                          ),
                        ),
                        const SizedBox(width: 4),
                        _SmallTag(
                            text: AppLocalizations.of(context)!.perpetual),
                        const SizedBox(width: 3),
                        _SmallTag(
                            text: _marginModeLabel(position.marginMode,
                                AppLocalizations.of(context)!),
                            color: _marginModeColor(position.marginMode)),
                        const SizedBox(width: 3),
                        // 杠杆 黄色徽标
                        Container(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 5, vertical: 1),
                          decoration: BoxDecoration(
                            color: AppColors.leverageGoldBg,
                            borderRadius: BorderRadius.circular(3),
                          ),
                          child: Text(
                            '${position.leverage.toInt()}X',
                            style: const TextStyle(
                                color: AppColors.leverageGold,
                                fontSize: 10,
                                fontWeight: FontWeight.w700),
                          ),
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(width: 8),
                  // 分享按钮固定在最右
                  GestureDetector(
                    onTap: () => _sharePosition(context),
                    child: Icon(Icons.share_outlined,
                        size: 16, color: cs.onSurface.withAlpha(153)),
                  ),
                ],
              ),
              const SizedBox(height: 8),

              // ── 未实现盈亏 + 收益率 ──────────────────────────────
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                          '${AppLocalizations.of(context)!.unrealizedPnl} (USDT)',
                          style: TextStyle(
                              color: cs.onSurface.withAlpha(153), fontSize: 9)),
                      Text(
                        formatAmount(position.unrealizedPnl),
                        style: TextStyle(
                            color: pnlColor,
                            fontSize: 16,
                            fontWeight: FontWeight.w700),
                      ),
                    ],
                  ),
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.end,
                    children: [
                      Text(AppLocalizations.of(context)!.returnRate,
                          style: TextStyle(
                              color: cs.onSurface.withAlpha(153), fontSize: 9)),
                      Text(
                        '${formatAmount(position.roe)}%',
                        style: TextStyle(
                            color: pnlColor,
                            fontSize: 13,
                            fontWeight: FontWeight.w600),
                      ),
                    ],
                  ),
                ],
              ),
              const SizedBox(height: 6),

              // ── 数据行 1 ────────────────────────────────────────
              Row(
                children: [
                  _DataCol(
                      label:
                          '${AppLocalizations.of(context)!.positionSize}($coinSymbol)',
                      value: formatQuantity(position.size),
                      align: CrossAxisAlignment.start),
                  _DataCol(
                      label:
                          '${AppLocalizations.of(context)!.marginLabel}(USDT)',
                      value: formatAmount(position.margin),
                      align: CrossAxisAlignment.center),
                  _DataCol(
                      label: AppLocalizations.of(context)!.marginRatioLabel,
                      value: '${formatAmount(position.marginRatio)}%',
                      align: CrossAxisAlignment.end),
                ],
              ),
              const SizedBox(height: 4),

              // ── 数据行 2 ────────────────────────────────────────
              Row(
                children: [
                  _DataCol(
                      label:
                          '${AppLocalizations.of(context)!.openAvgPrice}(USDT)',
                      value: formatPrice(position.entryPrice),
                      align: CrossAxisAlignment.start),
                  _DataCol(
                      label:
                          '${AppLocalizations.of(context)!.latestLabel}(USDT)',
                      value: formatPrice(position.markPrice),
                      align: CrossAxisAlignment.center),
                  _DataCol(
                    label: '${AppLocalizations.of(context)!.liqPrice}(USDT)',
                    value: position.liquidationPrice > 0
                        ? formatPrice(position.liquidationPrice)
                        : '--',
                    valueColor: position.liquidationPrice > 0
                        ? AppColors.fall
                        : cs.onSurface.withAlpha(153),
                    align: CrossAxisAlignment.end,
                  ),
                ],
              ),
              const SizedBox(height: 8),

              // ── 操作按钮 ──
              Row(
                children: [
                  _ActionBtn(
                      text: AppLocalizations.of(context)!.takeProfitStopLossBtn,
                      onTap: () => _showTpslSheet(context, ref, notifier)),
                  const SizedBox(width: 5),
                  _ActionBtn(
                      text: AppLocalizations.of(context)!.reversePositionBtn,
                      onTap: () => _reverse(context, ref, notifier)),
                  const SizedBox(width: 5),
                  _ActionBtn(
                      text: AppLocalizations.of(context)!.closePositionBtn,
                      onTap: () => _showCloseSheet(context, ref, notifier)),
                  const SizedBox(width: 5),
                  Expanded(
                    child: SizedBox(
                      height: 28,
                      child: ElevatedButton(
                        onPressed: () => _closeMarket(context, notifier),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: AppColors.brand,
                          shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(4)),
                          elevation: 0,
                          padding: EdgeInsets.zero,
                        ),
                        child: Text(
                            AppLocalizations.of(context)!.closeAllMarket,
                            style: const TextStyle(
                                color: Colors.black,
                                fontSize: 11,
                                fontWeight: FontWeight.w500)),
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        )); // GestureDetector + Container
  }

  /// 分享仓位：弹出预览底部弹窗，用户确认后生成图片并调用系统分享
  void _sharePosition(BuildContext context) {
    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      backgroundColor: Colors.transparent,
      isScrollControlled: true,
      builder: (_) => SharePositionSheet(position: position),
    );
  }

  Future<void> _closeMarket(
      BuildContext context, FuturesNotifier notifier) async {
    final l10n = AppLocalizations.of(context)!;
    final confirmed = await _showFuturesConfirm(
      context,
      message: l10n.closeAllMarketConfirm,
      subMessage: l10n.closeAllMarketSubMsg,
    );
    if (!confirmed || !context.mounted) return;
    final err = await notifier.closeMarket(position);
    if (!context.mounted) return;
    final l10n4 = AppLocalizations.of(context)!;
    showTopToast(context,
        message:
            resolveProviderError(err, l10n4) ?? l10n4.closeAllMarketSuccess,
        backgroundColor: err != null ? AppColors.fall : AppColors.rise);
  }

  void _showCloseSheet(
      BuildContext context, WidgetRef ref, FuturesNotifier notifier) {
    if (!_requireLogin(context, ref)) return;
    FocusScope.of(context).unfocus();
    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      isScrollControlled: true,
      backgroundColor: Theme.of(context).colorScheme.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (_) => _ClosePositionSheet(
          position: position, notifier: notifier, symbol: symbol),
    );
  }

  void _showTpslSheet(
      BuildContext context, WidgetRef ref, FuturesNotifier notifier) {
    if (!_requireLogin(context, ref)) return;
    FocusScope.of(context).unfocus();
    final pricePrecision =
        ref.read(futuresProvider(symbol).select((s) => s.pricePrecision));
    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      isScrollControlled: true,
      backgroundColor: Theme.of(context).colorScheme.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (_) => _TpslSheet(
          position: position,
          notifier: notifier,
          symbol: symbol,
          pricePrecision: pricePrecision),
    );
  }

  Future<void> _reverse(
      BuildContext context, WidgetRef ref, FuturesNotifier notifier) async {
    if (!_requireLogin(context, ref)) return;
    final isLong = position.side == OrderSide.long;
    final l10n = AppLocalizations.of(context)!;
    final confirmed = await _showFuturesConfirm(
      context,
      message: l10n.reverseConfirm(
        isLong ? l10n.longLabel : l10n.shortLabel,
        isLong ? l10n.openShort : l10n.openLong,
      ),
    );
    if (!confirmed || !context.mounted) return;
    final err = await notifier.reversePosition(position);
    if (!context.mounted) return;
    final l10n5 = AppLocalizations.of(context)!;
    showTopToast(context,
        message: resolveProviderError(err, l10n5) ?? l10n5.reverseSuccess,
        backgroundColor: err != null ? AppColors.fall : AppColors.rise);
  }
}

/// 自定义 checkbox（方形边框，选中时填充品牌色+勾）
class _CheckBox extends StatelessWidget {
  const _CheckBox({required this.checked});
  final bool checked;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Container(
      width: 14,
      height: 14,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(3),
        color: checked ? AppColors.brand : Colors.transparent,
        border: Border.all(
          color: checked ? AppColors.brand : cs.onSurface.withAlpha(153),
          width: 1,
        ),
      ),
      child: checked
          ? const Icon(Icons.check, size: 10, color: Colors.white)
          : null,
    );
  }
}

class _DataCol extends StatelessWidget {
  const _DataCol({
    required this.label,
    required this.value,
    this.valueColor,
    this.align = CrossAxisAlignment.start,
  });
  final String label;
  final String value;
  final Color? valueColor;
  final CrossAxisAlignment align;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final textAlign = align == CrossAxisAlignment.end
        ? TextAlign.right
        : align == CrossAxisAlignment.center
            ? TextAlign.center
            : TextAlign.left;
    return Expanded(
      child: Column(
        crossAxisAlignment: align,
        children: [
          Text(label,
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              textAlign: textAlign,
              style:
                  TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 9)),
          Text(value,
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              textAlign: textAlign,
              style: TextStyle(
                  color: valueColor ?? cs.onSurface,
                  fontSize: 12,
                  fontWeight: FontWeight.w500)),
        ],
      ),
    );
  }
}

/// 委托卡片数据行：全宽，label 靠左，value 靠右
class _DataLine extends StatelessWidget {
  const _DataLine({required this.label, required this.value, this.valueColor});
  final String label;
  final String value;
  final Color? valueColor;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Row(
      children: [
        Expanded(
          child: Text(label,
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              style:
                  TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 11)),
        ),
        Text(value,
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
                color: valueColor ?? cs.onSurface,
                fontSize: 11,
                fontWeight: FontWeight.w500)),
      ],
    );
  }
}

class _ActionBtn extends StatelessWidget {
  const _ActionBtn({required this.text, required this.onTap});
  final String text;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Expanded(
      child: SizedBox(
        height: 28,
        child: OutlinedButton(
          onPressed: onTap,
          style: OutlinedButton.styleFrom(
            side: BorderSide(color: cs.outline),
            shape:
                RoundedRectangleBorder(borderRadius: BorderRadius.circular(6)),
            padding: EdgeInsets.zero,
          ),
          child: Text(text,
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                  color: cs.onSurface,
                  fontSize: 11,
                  fontWeight: FontWeight.w500)),
        ),
      ),
    );
  }
}

/// 持仓卡片小标签（边框样式，对应原型 .pos-type-tag）
class _SmallTag extends StatelessWidget {
  const _SmallTag({required this.text, this.color});
  final String text;
  final Color? color;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final c = color;
    if (c != null) {
      return Container(
        padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
        decoration: BoxDecoration(
          color: c.withAlpha(isDark ? 45 : 25),
          borderRadius: BorderRadius.circular(3),
        ),
        child: Text(text,
            style:
                TextStyle(color: c, fontSize: 9, fontWeight: FontWeight.w500)),
      );
    }
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
      decoration: BoxDecoration(
        border: Border.all(color: cs.outline.withAlpha(80), width: 0.5),
        borderRadius: BorderRadius.circular(3),
      ),
      child: Text(
        text,
        style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 9),
      ),
    );
  }
}

class _OrdersList extends ConsumerWidget {
  const _OrdersList({required this.symbol, required this.orders});
  final String symbol;
  final List<FuturesOrder> orders;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final hasMore =
        ref.watch(futuresProvider(symbol).select((s) => s.ordersHasMore));
    final loadingMore =
        ref.watch(futuresProvider(symbol).select((s) => s.ordersLoadingMore));

    if (orders.isEmpty) {
      return Padding(
        padding: const EdgeInsets.symmetric(vertical: 40),
        child: Center(
          child: Text(AppLocalizations.of(context)!.noOrders,
              style:
                  TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 13)),
        ),
      );
    }
    final footerCount = (loadingMore || !hasMore) ? 1 : 0;
    return ListView.builder(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      itemCount: orders.length + footerCount,
      itemBuilder: (_, i) {
        if (i < orders.length) {
          return RepaintBoundary(
            key: ValueKey('ord_${orders[i].id}'),
            child: _OrderCard(symbol: symbol, order: orders[i]),
          );
        }
        if (loadingMore) {
          return const Padding(
            padding: EdgeInsets.symmetric(vertical: 12),
            child: Center(
              child: SizedBox(
                width: 16,
                height: 16,
                child: CircularProgressIndicator(strokeWidth: 2),
              ),
            ),
          );
        }
        return Padding(
          padding: const EdgeInsets.symmetric(vertical: 8),
          child: Center(
            child: Text(AppLocalizations.of(context)!.allLoaded,
                style:
                    TextStyle(color: cs.onSurface.withAlpha(80), fontSize: 11)),
          ),
        );
      },
    );
  }
}

class _OrderCard extends ConsumerWidget {
  const _OrderCard({required this.symbol, required this.order});
  final String symbol;
  final FuturesOrder order;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final notifier = ref.read(futuresProvider(symbol).notifier);
    final coinSymbol = _baseCoin(order.symbol);
    // 颜色规则：开多/平空=绿（买方向）；开空/平多=红（卖方向）
    final isOpen = order.isOpenOrder;
    final isLong = order.side == OrderSide.long;
    final actionColor = isOpen
        ? (isLong ? AppColors.rise : AppColors.fall)
        : (isLong ? AppColors.fall : AppColors.rise);

    final hasProfit = order.profitPrice != null && order.profitPrice! > 0;
    final hasLoss = order.lossPrice != null && order.lossPrice! > 0;

    // 展示用价格：市价/计划市价→"市价"，限价/计划限价→原始值
    String _fmt(double v) =>
        v == v.truncateToDouble() ? '${v.toInt()}' : v.toString();
    final l10n = AppLocalizations.of(context)!;
    final priceText = (order.type == OrderType.market ||
            order.type == OrderType.conditionalMarket)
        ? l10n.marketPrice
        : (order.price > 0 ? _fmt(order.price) : '--');

    // 对应原型 .order-card { border-bottom: 6px solid var(--color-bg-page) }
    return GestureDetector(
        onTap: () async {
          notifier.stopPolling();
          await context.push('/futures/$symbol/order-detail', extra: order);
          if (context.mounted) notifier.resumePolling(symbol);
        },
        child: Container(
          decoration: BoxDecoration(
            border: Border(
              bottom: BorderSide(
                color: Theme.of(context).scaffoldBackgroundColor,
                width: 6,
              ),
            ),
          ),
          padding: const EdgeInsets.fromLTRB(12, 10, 12, 10),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // ── 标题行（与持仓卡片结构一致）──────────────────────────
              Row(
                children: [
                  // 开多/开空/平多/平空 — 实心色块 chip
                  Container(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
                    decoration: BoxDecoration(
                      color: actionColor,
                      borderRadius: BorderRadius.circular(3),
                    ),
                    child: Text(
                      () {
                        final isLongSide = order.side == OrderSide.long;
                        if (isOpen)
                          return isLongSide ? l10n.openLong : l10n.openShort;
                        return isLongSide ? l10n.closeLong : l10n.closeShort;
                      }(),
                      style: const TextStyle(
                          color: Colors.white,
                          fontSize: 11,
                          fontWeight: FontWeight.w700),
                    ),
                  ),
                  const SizedBox(width: 4),
                  // Expanded 包裹币对+标签，撤单按钮固定在最右
                  Expanded(
                    child: Row(
                      children: [
                        Flexible(
                          child: Text(
                            order.symbol.toUpperCase(),
                            maxLines: 1,
                            overflow: TextOverflow.ellipsis,
                            style: TextStyle(
                                color: cs.onSurface,
                                fontSize: 13,
                                fontWeight: FontWeight.w700),
                          ),
                        ),
                        const SizedBox(width: 4),
                        _SmallTag(
                            text: order.type == OrderType.market
                                ? l10n.marketHint
                                : order.type == OrderType.limit
                                    ? l10n.limitLabel
                                    : l10n.planOrderLabel),
                        const SizedBox(width: 3),
                        _SmallTag(
                            text: _marginModeLabel(order.marginMode, l10n),
                            color: _marginModeColor(order.marginMode)),
                        const SizedBox(width: 3),
                        Container(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 5, vertical: 1),
                          decoration: BoxDecoration(
                            color: AppColors.leverageGoldBg,
                            borderRadius: BorderRadius.circular(3),
                          ),
                          child: Text(
                            '${order.leverage.toInt()}X',
                            style: const TextStyle(
                                color: AppColors.leverageGold,
                                fontSize: 10,
                                fontWeight: FontWeight.w700),
                          ),
                        ),
                        if (hasProfit) ...[
                          const SizedBox(width: 4),
                          _TpSlChip(
                              label: l10n.takeProfit, color: AppColors.rise),
                        ],
                        if (hasLoss) ...[
                          const SizedBox(width: 4),
                          _TpSlChip(
                              label: l10n.stopLoss, color: AppColors.fall),
                        ],
                      ],
                    ),
                  ),
                  const SizedBox(width: 8),
                  // 撤单按钮固定在最右
                  GestureDetector(
                    onTap: () => _cancelOrder(context, ref, notifier),
                    child: Container(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 8, vertical: 3),
                      decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(4),
                        border: Border.all(color: cs.outline),
                      ),
                      child: Text(l10n.cancelOrder,
                          style: TextStyle(color: cs.onSurface, fontSize: 11)),
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 8),

              // ── 数据块（label 靠左，value 靠右，全宽行）────────────
              _DataLine(label: l10n.orderPriceLabel, value: priceText),
              const SizedBox(height: 3),
              _DataLine(
                  label: l10n.orderSizeCoin(coinSymbol),
                  value: formatQuantity(order.size)),
              const SizedBox(height: 3),
              _DataLine(
                  label: l10n.filledSizeCoin(coinSymbol),
                  value: formatQuantity(order.filledSize)),
              if (order.triggerPrice > 0) ...[
                const SizedBox(height: 3),
                _DataLine(
                    label: '${l10n.triggerPrice}(USDT)',
                    value: formatPrice(order.triggerPrice)),
              ],
              if (hasProfit) ...[
                const SizedBox(height: 3),
                _DataLine(
                    label: l10n.takeProfitPrice,
                    value: formatPrice(order.profitPrice!),
                    valueColor: AppColors.rise),
              ],
              if (hasLoss) ...[
                const SizedBox(height: 3),
                _DataLine(
                    label: l10n.stopLossPrice,
                    value: formatPrice(order.lossPrice!),
                    valueColor: AppColors.fall),
              ],
            ],
          ),
        )); // GestureDetector + Container
  }

  Future<void> _cancelOrder(
      BuildContext context, WidgetRef ref, FuturesNotifier notifier) async {
    if (!_requireLogin(context, ref)) return;
    final err = await notifier.cancelOrder(order);
    if (!context.mounted) return;
    final l10n6 = AppLocalizations.of(context)!;
    showTopToast(context,
        message: resolveProviderError(err, l10n6) ?? l10n6.cancelOrderSuccess,
        backgroundColor: err != null ? AppColors.fall : AppColors.rise);
  }
}

/// 止盈止损指示小标签（行内 chip）
class _TpSlChip extends StatelessWidget {
  const _TpSlChip({required this.label, required this.color});
  final String label;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
      decoration: BoxDecoration(
        color: color.withAlpha(28),
        borderRadius: BorderRadius.circular(3),
        border: Border.all(color: color.withAlpha(120), width: 0.5),
      ),
      child: Text(
        label,
        style:
            TextStyle(color: color, fontSize: 9, fontWeight: FontWeight.w600),
      ),
    );
  }
}

class _TpslSheet extends ConsumerStatefulWidget {
  const _TpslSheet(
      {required this.position,
      required this.notifier,
      required this.symbol,
      required this.pricePrecision});
  final FuturesPosition position;
  final FuturesNotifier notifier;
  final String symbol;
  final int pricePrecision;

  @override
  ConsumerState<_TpslSheet> createState() => _TpslSheetState();
}

class _TpslSheetState extends ConsumerState<_TpslSheet> {
  late final TextEditingController _tpController;
  late final TextEditingController _slController;
  bool _tpEnabled = true;
  bool _slEnabled = true;

  @override
  void initState() {
    super.initState();
    final pos = widget.position;
    final p = widget.pricePrecision;
    _tpController = TextEditingController(
        text: pos.profitPrice != null && pos.profitPrice! > 0
            ? pos.profitPrice!.toStringAsFixed(p)
            : '');
    _slController = TextEditingController(
        text: pos.lossPrice != null && pos.lossPrice! > 0
            ? pos.lossPrice!.toStringAsFixed(p)
            : '');
    _tpEnabled = pos.profitPrice != null && pos.profitPrice! > 0 ? true : true;
    _slEnabled = pos.lossPrice != null && pos.lossPrice! > 0 ? true : true;
  }

  @override
  void dispose() {
    _tpController.dispose();
    _slController.dispose();
    super.dispose();
  }

  double get _entryPrice => widget.position.entryPrice;
  double get _availableSize => widget.position.availableSize;
  bool get _isLong => widget.position.side == OrderSide.long;

  /// 预估盈利（止盈触发时）
  double? _estimatedProfit() {
    final tp = double.tryParse(_tpController.text);
    if (tp == null || tp <= 0) return null;
    return _isLong
        ? (tp - _entryPrice) * _availableSize
        : (_entryPrice - tp) * _availableSize;
  }

  /// 预估亏损（止损触发时）
  double? _estimatedLoss() {
    final sl = double.tryParse(_slController.text);
    if (sl == null || sl <= 0) return null;
    return _isLong
        ? (sl - _entryPrice) * _availableSize
        : (_entryPrice - sl) * _availableSize;
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    // 标记价格取仓位自身的 markPrice（仓位持有的币种），而非当前切换到的币对
    final markPrice = widget.position.markPrice;
    final coinPrecision = ref
        .watch(futuresProvider(widget.symbol).select((s) => s.coinPrecision));
    final pricePrecision = widget.pricePrecision;
    final l10n = AppLocalizations.of(context)!;
    final coinSymbol = _baseCoin(widget.position.symbol);
    final isLong = _isLong;
    final position = widget.position;
    final sideLabel = isLong ? l10n.longHeadLabel : l10n.shortHeadLabel;
    final leverageLabel = '${position.leverage.toInt()}X';
    final coinName = coinSymbol;

    final profit = _estimatedProfit();
    final loss = _estimatedLoss();

    return Padding(
      padding: EdgeInsets.only(
        bottom: MediaQuery.of(context).viewInsets.bottom + 24,
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          // ── 头部 ──
          Row(
            children: [
              const SizedBox(width: 40),
              Expanded(
                child: Column(
                  children: [
                    const SizedBox(height: 16),
                    Text(l10n.takeProfitStopLoss,
                        style: TextStyle(
                            color: cs.onSurface,
                            fontSize: 16,
                            fontWeight: FontWeight.w600)),
                    const SizedBox(height: 2),
                    Text(
                      '${coinName}${l10n.perpetual} $sideLabel $leverageLabel ${_marginModeLabel(position.marginMode, l10n)}',
                      style: TextStyle(
                          color: cs.onSurface.withAlpha(153), fontSize: 12),
                    ),
                    const SizedBox(height: 4),
                  ],
                ),
              ),
              SizedBox(
                width: 40,
                child: IconButton(
                  onPressed: () => Navigator.pop(context),
                  icon: Icon(Icons.close,
                      color: cs.onSurface.withAlpha(153), size: 20),
                ),
              ),
            ],
          ),
          // ── 开仓均价 | 标记价格 ──
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 4, 16, 12),
            child: Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('${l10n.openAvgPrice}(USDT)',
                          style: TextStyle(
                              color: cs.onSurface.withAlpha(120),
                              fontSize: 11)),
                      const SizedBox(height: 2),
                      Text(formatPrice(_entryPrice),
                          style: TextStyle(
                              color: cs.onSurface,
                              fontSize: 14,
                              fontWeight: FontWeight.w600)),
                    ],
                  ),
                ),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.end,
                    children: [
                      Text('${l10n.markLabel}(USDT)',
                          style: TextStyle(
                              color: cs.onSurface.withAlpha(120),
                              fontSize: 11)),
                      const SizedBox(height: 2),
                      Text(formatPrice(markPrice > 0 ? markPrice : _entryPrice),
                          style: TextStyle(
                              color: cs.onSurface,
                              fontSize: 14,
                              fontWeight: FontWeight.w600)),
                    ],
                  ),
                ),
              ],
            ),
          ),
          // ── 复选框行 ──
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Row(
              children: [
                _TpslCheckbox(
                  label: l10n.setTakeProfit,
                  value: _tpEnabled,
                  onChanged: (v) => setState(() => _tpEnabled = v),
                ),
                const SizedBox(width: 24),
                _TpslCheckbox(
                  label: l10n.setStopLoss,
                  value: _slEnabled,
                  onChanged: (v) => setState(() => _slEnabled = v),
                ),
              ],
            ),
          ),
          const SizedBox(height: 14),
          // ── 止盈输入 ──
          if (_tpEnabled) ...[
            _TpslInput(
              controller: _tpController,
              hint: l10n.tpTriggerPrice,
              pricePrecision: pricePrecision,
              onLatest: () {
                final price = markPrice > 0 ? markPrice : _entryPrice;
                final text = price.toStringAsFixed(pricePrecision);
                _tpController.value = TextEditingValue(
                  text: text,
                  selection: TextSelection.collapsed(offset: text.length),
                );
                setState(() {});
              },
              onChanged: (_) => setState(() {}),
            ),
            const SizedBox(height: 10),
          ],
          // ── 止损输入 ──
          if (_slEnabled) ...[
            _TpslInput(
              controller: _slController,
              hint: l10n.slTriggerPrice,
              pricePrecision: pricePrecision,
              onLatest: () {
                final price = markPrice > 0 ? markPrice : _entryPrice;
                final text = price.toStringAsFixed(pricePrecision);
                _slController.value = TextEditingValue(
                  text: text,
                  selection: TextSelection.collapsed(offset: text.length),
                );
                setState(() {});
              },
              onChanged: (_) => setState(() {}),
            ),
            const SizedBox(height: 10),
          ],
          // ── 数据行 ──
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 6, 16, 4),
            child: Column(
              children: [
                _TpslDataRow(
                  label: l10n.closeableSizeCoin(coinSymbol),
                  value: _availableSize > 0
                      ? formatAmount(_availableSize, decimals: coinPrecision)
                      : '--',
                  valueColor: cs.onSurface,
                ),
                const SizedBox(height: 8),
                _TpslDataRow(
                  label: l10n.estProfit,
                  value: profit != null
                      ? '${profit >= 0 ? '+' : ''}${formatPrice(profit)}'
                      : '0.00',
                  valueColor: profit != null && profit >= 0
                      ? AppColors.rise
                      : AppColors.fall,
                ),
                const SizedBox(height: 8),
                _TpslDataRow(
                  label: l10n.estLoss,
                  value: loss != null
                      ? '${loss >= 0 ? '+' : ''}${formatPrice(loss)}'
                      : '0.00',
                  valueColor: loss != null && loss < 0
                      ? AppColors.fall
                      : AppColors.rise,
                ),
              ],
            ),
          ),
          const SizedBox(height: 16),
          // ── 确定按钮 ──
          Padding(
            padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
            child: SizedBox(
              width: double.infinity,
              height: 48,
              child: ElevatedButton(
                onPressed: () => _submit(context),
                style: ElevatedButton.styleFrom(
                  backgroundColor: cs.inverseSurface,
                  elevation: 0,
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(8)),
                ),
                child: Text(l10n.confirmLabel,
                    style: TextStyle(
                        color: cs.onInverseSurface,
                        fontSize: 15,
                        fontWeight: FontWeight.w600)),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _submit(BuildContext context) async {
    final tp = _tpEnabled ? double.tryParse(_tpController.text) : null;
    final sl = _slEnabled ? double.tryParse(_slController.text) : null;
    if (tp == null && sl == null) {
      Navigator.pop(context);
      return;
    }
    final err = await widget.notifier.setPositionTpsl(
      widget.position,
      profitPrice: tp,
      lossPrice: sl,
    );
    if (!context.mounted) return;
    Navigator.pop(context);
    final l10n7 = AppLocalizations.of(context)!;
    showTopToast(context,
        message: resolveProviderError(err, l10n7) ?? l10n7.tpslSuccess,
        backgroundColor: err != null ? AppColors.fall : AppColors.rise);
  }
}

class _TpslCheckbox extends StatelessWidget {
  const _TpslCheckbox(
      {required this.label, required this.value, required this.onChanged});
  final String label;
  final bool value;
  final ValueChanged<bool> onChanged;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    return GestureDetector(
      onTap: () => onChanged(!value),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 16,
            height: 16,
            decoration: BoxDecoration(
              color: value
                  ? AppColors.brand
                  : (isDark ? Colors.white : Colors.transparent),
              borderRadius: BorderRadius.circular(3),
              border: Border.all(
                color: value ? AppColors.brand : cs.onSurface.withAlpha(220),
                width: 1.5,
              ),
            ),
            child: value
                ? const Icon(Icons.check, size: 12, color: Colors.black)
                : null,
          ),
          const SizedBox(width: 6),
          Text(label, style: TextStyle(color: cs.onSurface, fontSize: 13)),
        ],
      ),
    );
  }
}

class _TpslInput extends StatefulWidget {
  const _TpslInput({
    required this.controller,
    required this.hint,
    required this.pricePrecision,
    required this.onLatest,
    required this.onChanged,
  });
  final TextEditingController controller;
  final String hint;
  final int pricePrecision;
  final VoidCallback onLatest;
  final ValueChanged<String> onChanged;

  @override
  State<_TpslInput> createState() => _TpslInputState();
}

class _TpslInputState extends State<_TpslInput> {
  final _focusNode = FocusNode();

  @override
  void initState() {
    super.initState();
    _focusNode.addListener(() {
      if (mounted) setState(() {});
    });
  }

  @override
  void dispose() {
    _focusNode.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final isFocused = _focusNode.hasFocus;
    final bgColor = isFocused
        ? (isDark ? AppColors.darkBgSecondary : Colors.white)
        : (isDark ? AppColors.darkBgTertiary : AppColors.lightBgSecondary);
    final activeBorder = isDark
        ? AppColors.darkTextPrimary.withAlpha(200)
        : const Color(0xFF383838);
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Row(
        children: [
          Expanded(
            child: Container(
              height: 44,
              decoration: BoxDecoration(
                color: bgColor,
                borderRadius: BorderRadius.circular(8),
                border: Border.all(
                  color: isFocused ? activeBorder : cs.onSurface.withAlpha(40),
                  width: isFocused ? 1.5 : 1,
                ),
              ),
              child: Row(
                children: [
                  Padding(
                    padding: const EdgeInsets.only(left: 12),
                    child: Text(widget.hint,
                        style: TextStyle(
                            color: cs.onSurface.withAlpha(120), fontSize: 13)),
                  ),
                  Expanded(
                    child: TextField(
                      controller: widget.controller,
                      focusNode: _focusNode,
                      keyboardType:
                          const TextInputType.numberWithOptions(decimal: true),
                      inputFormatters: [
                        _PrecisionInputFormatter(widget.pricePrecision)
                      ],
                      onChanged: widget.onChanged,
                      textAlign: TextAlign.right,
                      decoration: InputDecoration(
                        border: InputBorder.none,
                        focusedBorder: InputBorder.none,
                        enabledBorder: InputBorder.none,
                        filled: false,
                        contentPadding:
                            const EdgeInsets.symmetric(horizontal: 8),
                      ),
                      style: TextStyle(
                          color: cs.onSurface,
                          fontSize: 13,
                          fontWeight: FontWeight.w600),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.only(right: 12),
                    child: Text('USDT',
                        style: TextStyle(
                            color: cs.onSurface.withAlpha(120), fontSize: 12)),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(width: 8),
          GestureDetector(
            onTap: widget.onLatest,
            child: Container(
              height: 44,
              padding: const EdgeInsets.symmetric(horizontal: 12),
              decoration: BoxDecoration(
                color: cs.inverseSurface,
                borderRadius: BorderRadius.circular(8),
              ),
              alignment: Alignment.center,
              child: Text(AppLocalizations.of(context)!.latestLabel,
                  style: TextStyle(
                      color: cs.onInverseSurface,
                      fontSize: 13,
                      fontWeight: FontWeight.w500)),
            ),
          ),
        ],
      ),
    );
  }
}

class _TpslDataRow extends StatelessWidget {
  const _TpslDataRow(
      {required this.label, required this.value, required this.valueColor});
  final String label;
  final String value;
  final Color valueColor;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(label,
            style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 12)),
        Text(value,
            style: TextStyle(
                color: valueColor, fontSize: 12, fontWeight: FontWeight.w500)),
      ],
    );
  }
}

class _ClosePositionSheet extends ConsumerStatefulWidget {
  const _ClosePositionSheet(
      {required this.position, required this.notifier, required this.symbol});
  final FuturesPosition position;
  final FuturesNotifier notifier;
  final String symbol;

  @override
  ConsumerState<_ClosePositionSheet> createState() =>
      _ClosePositionSheetState();
}

class _ClosePositionSheetState extends ConsumerState<_ClosePositionSheet> {
  final _priceController = TextEditingController();
  final _volumeController = TextEditingController();
  final _priceFocusNode = FocusNode();
  final _volumeFocusNode = FocusNode();
  bool _isMarket = false; // false=限价, true=市价
  double _percent = 0;

  String _baseCoin(String sym) {
    if (sym.contains('/')) return sym.split('/').first;
    return sym.toUpperCase().replaceFirst(RegExp(r'USDT$'), '');
  }

  @override
  void initState() {
    super.initState();
    _priceFocusNode.addListener(() {
      if (mounted) setState(() {});
    });
    _volumeFocusNode.addListener(() {
      if (mounted) setState(() {});
    });
  }

  @override
  void dispose() {
    _priceController.dispose();
    _volumeController.dispose();
    _priceFocusNode.dispose();
    _volumeFocusNode.dispose();
    super.dispose();
  }

  void _onPercentChanged(double p) {
    setState(() => _percent = p);
    final avail = widget.position.availableSize;
    final vol = avail * p;
    if (vol <= 0) {
      _volumeController.clear();
    } else {
      final precision = ref
          .read(futuresProvider(widget.symbol).select((s) => s.coinPrecision));
      _volumeController.text = vol.toStringAsFixed(precision);
    }
  }

  void _syncPercentFromVolume() {
    final avail = widget.position.availableSize;
    if (avail <= 0) return;
    final vol =
        double.tryParse(_volumeController.text.replaceAll(',', '')) ?? 0;
    setState(() => _percent = (vol / avail).clamp(0.0, 1.0));
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final inputUnfocusedBg =
        isDark ? AppColors.darkBgTertiary : AppColors.lightBgSecondary;
    final inputFocusedBg = isDark ? AppColors.darkBgSecondary : Colors.white;
    final inputActiveBorder = isDark
        ? AppColors.darkTextPrimary.withAlpha(200)
        : const Color(0xFF383838);
    final coinPrecision = ref
        .watch(futuresProvider(widget.symbol).select((s) => s.coinPrecision));
    final pricePrecision = ref
        .watch(futuresProvider(widget.symbol).select((s) => s.pricePrecision));
    final pos = widget.position;
    final l10n = AppLocalizations.of(context)!;
    final isLong = pos.side == OrderSide.long;
    final sideLabel = isLong ? l10n.longHeadLabel : l10n.shortHeadLabel;
    final leverage = pos.leverage.toInt();
    final availSize = pos.availableSize;
    final coinSymbol = _baseCoin(pos.symbol);

    // 预计盈亏：限价用输入价格，市价用标记价格估算
    double? estPnl;
    final closeVol =
        double.tryParse(_volumeController.text.replaceAll(',', '')) ?? 0;
    if (closeVol > 0) {
      if (_isMarket) {
        // 市价：以标记价格估算盈亏
        final diff = isLong
            ? (pos.markPrice - pos.entryPrice)
            : (pos.entryPrice - pos.markPrice);
        estPnl = diff * closeVol;
      } else {
        final price = double.tryParse(_priceController.text);
        if (price != null && price > 0) {
          // 多头平仓：卖出价 - 开仓价；空头平仓：开仓价 - 买入价
          final diff =
              isLong ? (price - pos.entryPrice) : (pos.entryPrice - price);
          estPnl = diff * closeVol;
        }
      }
    }

    return Padding(
      padding: EdgeInsets.only(
        left: 16,
        right: 16,
        top: 20,
        bottom: MediaQuery.of(context).viewInsets.bottom + 24,
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          // ── 标题行 ────────────────────────────────────────────
          SizedBox(
            width: double.infinity,
            child: Stack(
              alignment: Alignment.center,
              children: [
                Text(l10n.closePositionBtn,
                    style: TextStyle(
                        color: cs.onSurface,
                        fontSize: 16,
                        fontWeight: FontWeight.w600)),
                Positioned(
                  right: 0,
                  child: GestureDetector(
                    onTap: () => Navigator.pop(context),
                    child: Icon(Icons.close,
                        size: 20, color: cs.onSurface.withAlpha(153)),
                  ),
                ),
              ],
            ),
          ),
          const SizedBox(height: 4),
          Text(
            '${pos.symbol} ${l10n.perpetual} $sideLabel ${leverage}X ${_marginModeLabel(pos.marginMode, l10n)}',
            style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 12),
          ),
          const SizedBox(height: 16),

          // ── 开仓均价 / 标记价格（带边框盒子，对应原型设计）────
          Row(
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('${l10n.openAvgPrice}(USDT)',
                        style: TextStyle(
                            color: cs.onSurface.withAlpha(153), fontSize: 11)),
                    const SizedBox(height: 2),
                    Text(formatAmount(pos.entryPrice),
                        style: TextStyle(
                            color: cs.onSurface,
                            fontSize: 14,
                            fontWeight: FontWeight.w500)),
                  ],
                ),
              ),
              Column(
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text('${l10n.markLabel}(USDT)',
                      style: TextStyle(
                          color: cs.onSurface.withAlpha(153), fontSize: 11)),
                  const SizedBox(height: 2),
                  GestureDetector(
                    onTap: _isMarket
                        ? null
                        : () {
                            final text =
                                pos.markPrice.toStringAsFixed(pricePrecision);
                            _priceController.value = TextEditingValue(
                              text: text,
                              selection:
                                  TextSelection.collapsed(offset: text.length),
                            );
                            setState(() {});
                          },
                    child: Text(formatAmount(pos.markPrice),
                        style: TextStyle(
                            color: cs.onSurface,
                            fontSize: 14,
                            fontWeight: FontWeight.w500)),
                  ),
                ],
              ),
            ],
          ),
          const SizedBox(height: 12),

          // ── 价格输入 + 限价/市价切换 ─────────────────────────
          Row(
            children: [
              Expanded(
                child: Container(
                  height: 46,
                  decoration: BoxDecoration(
                    color: _isMarket
                        ? inputUnfocusedBg
                        : (_priceFocusNode.hasFocus
                            ? inputFocusedBg
                            : inputUnfocusedBg),
                    borderRadius: BorderRadius.circular(8),
                    border: !_isMarket && _priceFocusNode.hasFocus
                        ? Border.all(color: inputActiveBorder, width: 1.5)
                        : Border.all(
                            color: cs.onSurface.withAlpha(40), width: 1),
                  ),
                  child: TextField(
                    controller: _priceController,
                    focusNode: _priceFocusNode,
                    enabled: !_isMarket,
                    keyboardType:
                        const TextInputType.numberWithOptions(decimal: true),
                    inputFormatters: [_PrecisionInputFormatter(pricePrecision)],
                    style: TextStyle(color: cs.onSurface, fontSize: 14),
                    onChanged: (_) => setState(() {}),
                    decoration: InputDecoration(
                      hintText:
                          _isMarket ? l10n.marketHint : l10n.pricePlaceholder,
                      hintStyle: TextStyle(
                          color: cs.onSurface.withAlpha(100), fontSize: 14),
                      suffixText: 'USDT',
                      suffixStyle: TextStyle(
                          color: cs.onSurface.withAlpha(100), fontSize: 12),
                      filled: false,
                      contentPadding:
                          const EdgeInsets.symmetric(horizontal: 12),
                      border: InputBorder.none,
                      focusedBorder: InputBorder.none,
                      enabledBorder: InputBorder.none,
                      disabledBorder: InputBorder.none,
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 8),
              GestureDetector(
                onTap: () => setState(() {
                  _isMarket = !_isMarket;
                  _priceController.clear();
                }),
                child: Container(
                  height: 46,
                  padding: const EdgeInsets.symmetric(horizontal: 16),
                  decoration: BoxDecoration(
                    color: AppColors.brand,
                    borderRadius: BorderRadius.circular(8),
                  ),
                  alignment: Alignment.center,
                  child: Text(
                    _isMarket ? l10n.marketHint : l10n.limitLabel,
                    style: TextStyle(
                      color: Colors.black,
                      fontSize: 14,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ],
          ),
          if (!_isMarket) ...[
            const SizedBox(height: 4),
            Align(
              alignment: Alignment.centerLeft,
              child: Text(
                isLong ? l10n.closePositionMsgLong : l10n.closePositionMsgShort,
                style:
                    TextStyle(color: cs.onSurface.withAlpha(100), fontSize: 11),
              ),
            ),
          ],
          const SizedBox(height: 8),

          // ── 平仓数量输入（带币种后缀）────────────────────────
          Container(
            height: 46,
            decoration: BoxDecoration(
              color:
                  _volumeFocusNode.hasFocus ? inputFocusedBg : inputUnfocusedBg,
              borderRadius: BorderRadius.circular(8),
              border: _volumeFocusNode.hasFocus
                  ? Border.all(color: inputActiveBorder, width: 1.5)
                  : Border.all(color: cs.onSurface.withAlpha(40), width: 1),
            ),
            child: TextField(
              controller: _volumeController,
              focusNode: _volumeFocusNode,
              keyboardType:
                  const TextInputType.numberWithOptions(decimal: true),
              inputFormatters: [_PrecisionInputFormatter(coinPrecision)],
              style: TextStyle(color: cs.onSurface, fontSize: 14),
              onChanged: (_) => _syncPercentFromVolume(),
              decoration: InputDecoration(
                hintText: l10n.enterCloseVolume,
                hintStyle:
                    TextStyle(color: cs.onSurface.withAlpha(100), fontSize: 14),
                suffixText: coinSymbol,
                suffixStyle:
                    TextStyle(color: cs.onSurface.withAlpha(100), fontSize: 12),
                filled: false,
                contentPadding: const EdgeInsets.symmetric(horizontal: 12),
                border: InputBorder.none,
                focusedBorder: InputBorder.none,
                enabledBorder: InputBorder.none,
              ),
            ),
          ),
          const SizedBox(height: 12),

          // ── 滑动条 ──────────────────────────────────────────
          _PercentSlider(
            percent: _percent,
            onChanged: _onPercentChanged,
          ),
          const SizedBox(height: 12),

          // ── 可平量 / 预计盈亏 ─────────────────────────────────
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(l10n.closeableSizeCoin(coinSymbol),
                  style: TextStyle(
                      color: cs.onSurface.withAlpha(153), fontSize: 12)),
              Text(formatAmount(availSize, decimals: coinPrecision),
                  style: TextStyle(color: cs.onSurface, fontSize: 12)),
            ],
          ),
          const SizedBox(height: 6),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(l10n.estPnlLabel,
                  style: TextStyle(
                      color: cs.onSurface.withAlpha(153), fontSize: 12)),
              Text(
                estPnl == null
                    ? '-- USDT'
                    : '${estPnl >= 0 ? '+' : ''}${formatAmount(estPnl)} USDT',
                style: TextStyle(
                  color: estPnl == null
                      ? cs.onSurface.withAlpha(153)
                      : (estPnl >= 0 ? AppColors.rise : AppColors.fall),
                  fontSize: 12,
                  fontWeight: FontWeight.w500,
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),

          // ── 确定按钮 ──────────────────────────────────────────
          SizedBox(
            width: double.infinity,
            height: 52,
            child: ElevatedButton(
              onPressed: () => _close(context),
              style: ElevatedButton.styleFrom(
                backgroundColor: AppColors.brand,
                elevation: 0,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(26),
                ),
              ),
              child: Text(
                l10n.confirmLabel,
                style: TextStyle(
                    color: Colors.black,
                    fontSize: 16,
                    fontWeight: FontWeight.w600),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _close(BuildContext context) async {
    final volume = double.tryParse(_volumeController.text.replaceAll(',', ''));
    // 数量为空或0不允许提交
    if (volume == null || volume <= 0) {
      showTopToast(context,
          message: AppLocalizations.of(context)!.enterCloseVolume,
          backgroundColor: AppColors.fall);
      return;
    }
    String? err;
    if (_isMarket) {
      err = await widget.notifier.closeMarket(widget.position, volume: volume);
    } else {
      final price = double.tryParse(_priceController.text) ?? 0;
      // 限价单必须输入价格，未填则提示且不关闭弹窗
      if (price <= 0) {
        showTopToast(context,
            message: AppLocalizations.of(context)!.enterLimitPrice,
            backgroundColor: AppColors.fall);
        return;
      }
      err = await widget.notifier
          .closeLimit(widget.position, price, volume: volume);
    }
    if (!context.mounted) return;
    final l10n8 = AppLocalizations.of(context)!;
    if (err != null) {
      showTopToast(context,
          message: resolveProviderError(err, l10n8) ?? err,
          backgroundColor: AppColors.fall);
      return;
    }
    Navigator.pop(context);
    showTopToast(context,
        message: l10n8.closeOrderSubmitted, backgroundColor: AppColors.rise);
  }
}

class _AssetsPanel extends StatelessWidget {
  const _AssetsPanel({required this.info});
  final FuturesAccountInfo info;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Padding(
      padding: const EdgeInsets.fromLTRB(24, 12, 24, 12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(AppLocalizations.of(context)!.contractAccountUsdt,
              style:
                  TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 12)),
          const SizedBox(height: 4),
          Text(
            formatAmount(info.totalBalance),
            style: TextStyle(
              color: cs.onSurface,
              fontSize: 22,
              fontWeight: FontWeight.w700,
            ),
          ),
          const SizedBox(height: 10),
          Row(
            children: [
              _AssetItem(
                  label: AppLocalizations.of(context)!.availableMargin,
                  value: formatAmount(info.availableMargin),
                  align: CrossAxisAlignment.start),
              _AssetItem(
                  label: AppLocalizations.of(context)!.usedMargin,
                  value: formatAmount(info.usedMargin),
                  align: CrossAxisAlignment.center),
              _AssetItem(
                label: AppLocalizations.of(context)!.unrealizedPnl,
                value:
                    '${info.unrealizedPnl >= 0 ? '+' : ''}${formatAmount(info.unrealizedPnl)}',
                valueColor:
                    info.unrealizedPnl >= 0 ? AppColors.rise : AppColors.fall,
                align: CrossAxisAlignment.end,
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class _AssetItem extends StatelessWidget {
  const _AssetItem({
    required this.label,
    required this.value,
    this.valueColor,
    this.align = CrossAxisAlignment.start,
  });
  final String label;
  final String value;
  final Color? valueColor;
  final CrossAxisAlignment align;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final textAlign = align == CrossAxisAlignment.end
        ? TextAlign.right
        : align == CrossAxisAlignment.center
            ? TextAlign.center
            : TextAlign.left;
    return Expanded(
      child: Column(
        crossAxisAlignment: align,
        children: [
          Text(label,
              textAlign: textAlign,
              style:
                  TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 10)),
          const SizedBox(height: 2),
          Text(value,
              textAlign: textAlign,
              style: TextStyle(
                  color: valueColor ?? cs.onSurface,
                  fontSize: 13,
                  fontWeight: FontWeight.w500)),
        ],
      ),
    );
  }
}

double _toDouble(dynamic v) {
  if (v == null) return 0.0;
  if (v is num) return v.toDouble();
  return double.tryParse(v.toString()) ?? 0.0;
}

/// AppBar 顶部"现货 / 永续合约"切换 Tab
/// 与 spot_screen.dart 中的 _SegmentedTabHeader 保持一致样式
class _SpotFuturesTabHeader extends StatelessWidget {
  const _SpotFuturesTabHeader({required this.activeIndex, required this.onTap});
  final int activeIndex; // 0=现货 1=合约
  final ValueChanged<int> onTap;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    final items = [l10n.spotTab, l10n.perpetualContract];
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: List.generate(items.length, (i) {
        final isActive = i == activeIndex;
        return Padding(
          padding: EdgeInsets.only(right: i == 0 ? 16 : 0),
          child: GestureDetector(
            behavior: HitTestBehavior.opaque,
            onTap: () => onTap(i),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(
                  items[i],
                  style: TextStyle(
                    color:
                        isActive ? cs.onSurface : cs.onSurface.withAlpha(140),
                    fontSize: 16,
                    fontWeight: isActive ? FontWeight.w700 : FontWeight.w500,
                  ),
                ),
                const SizedBox(height: 4),
                Container(
                  width: 28,
                  height: 3,
                  color: isActive ? AppColors.brand : Colors.transparent,
                ),
              ],
            ),
          ),
        );
      }),
    );
  }
}

/// 精度输入格式化器
/// [decimals] = 0 时只允许正整数；> 0 时允许最多 [decimals] 位小数
class _PrecisionInputFormatter extends TextInputFormatter {
  const _PrecisionInputFormatter(this.decimals);
  final int decimals;

  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue, TextEditingValue newValue) {
    final text = newValue.text;
    if (text.isEmpty) return newValue;

    if (decimals == 0) {
      // 只允许正整数
      if (!RegExp(r'^\d+$').hasMatch(text)) return oldValue;
      return newValue;
    }

    // 允许小数：最多 [decimals] 位
    final pattern = RegExp(r'^\d+\.?\d{0,' + decimals.toString() + r'}$');
    if (!pattern.hasMatch(text)) return oldValue;
    // 不允许以多个小数点开头
    if (text.indexOf('.') != text.lastIndexOf('.')) return oldValue;
    return newValue;
  }
}

// ══════════════════════════════════════════════════════════════════════════════
// 仓位分享相关 widget
// ══════════════════════════════════════════════════════════════════════════════

/// 分享底部弹窗：预览分享卡片 + 操作按钮（供资产页复用）
class SharePositionSheet extends ConsumerStatefulWidget {
  const SharePositionSheet({super.key, required this.position});
  final FuturesPosition position;

  @override
  ConsumerState<SharePositionSheet> createState() => _SharePositionSheetState();
}

class _SharePositionSheetState extends ConsumerState<SharePositionSheet> {
  final _cardKey = GlobalKey();
  bool _sharing = false;
  bool _saving = false;
  String? _inviteCode;
  String? _inviteUrl;

  @override
  void initState() {
    super.initState();
    _loadInviteInfo();
  }

  Future<void> _loadInviteInfo() async {
    try {
      final dio = ref.read(dioClientProvider);
      final data = await AuthService(dio).getMyInfo();
      final prefix = data['promotionPrefix']?.toString() ?? '';
      final code = data['promotionCode']?.toString() ?? '';
      final url =
          (prefix.isNotEmpty || code.isNotEmpty) ? '$prefix$code' : null;
      if (mounted) {
        setState(() {
          _inviteCode = code.isNotEmpty ? code : null;
          _inviteUrl = url;
        });
      }
    } catch (e) {
      print('[ShareCard] _loadInviteInfo error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final pos = widget.position;
    final pnlPositive = pos.unrealizedPnl >= 0;
    final l10n = AppLocalizations.of(context)!;

    return Container(
      decoration: BoxDecoration(
        color: isDark ? AppColors.darkBgSecondary : AppColors.lightBgSecondary,
        borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
      ),
      padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          // 拖拽指示条
          Container(
            width: 36,
            height: 4,
            decoration: BoxDecoration(
              color: cs.onSurface.withAlpha(60),
              borderRadius: BorderRadius.circular(2),
            ),
          ),
          const SizedBox(height: 16),

          // 分享卡片预览
          RepaintBoundary(
            key: _cardKey,
            child: _PositionShareCard(
              position: pos,
              inviteCode: _inviteCode,
              inviteUrl: _inviteUrl,
            ),
          ),
          const SizedBox(height: 24),

          // 操作按钮行：取消 | 保存海报 | 分享
          Row(
            children: [
              Expanded(
                child: OutlinedButton(
                  onPressed: () => Navigator.of(context).pop(),
                  style: OutlinedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(vertical: 12),
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(8)),
                  ),
                  child: Text(l10n.cancelLabel,
                      style: TextStyle(color: cs.onSurface, fontSize: 14)),
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: OutlinedButton(
                  onPressed: _saving ? null : () => _doSave(context),
                  style: OutlinedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(vertical: 12),
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(8)),
                  ),
                  child: _saving
                      ? SizedBox(
                          width: 16,
                          height: 16,
                          child: CircularProgressIndicator(
                              strokeWidth: 2,
                              color: cs.onSurface.withAlpha(153)),
                        )
                      : Text(l10n.savePoster,
                          style: TextStyle(color: cs.onSurface, fontSize: 14)),
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: ElevatedButton(
                  onPressed: _sharing ? null : () => _doShare(context),
                  style: ElevatedButton.styleFrom(
                    backgroundColor:
                        pnlPositive ? AppColors.rise : AppColors.fall,
                    padding: const EdgeInsets.symmetric(vertical: 12),
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(8)),
                    elevation: 0,
                  ),
                  child: _sharing
                      ? const SizedBox(
                          width: 16,
                          height: 16,
                          child: CircularProgressIndicator(
                              strokeWidth: 2, color: Colors.white),
                        )
                      : Text(l10n.shareLabel,
                          style: const TextStyle(
                              color: Colors.white,
                              fontSize: 14,
                              fontWeight: FontWeight.w600)),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Future<Uint8List?> _renderCard() async {
    final boundary =
        _cardKey.currentContext?.findRenderObject() as RenderRepaintBoundary?;
    if (boundary == null) return null;
    final image = await boundary.toImage(pixelRatio: 3.0);
    final byteData = await image.toByteData(format: ui.ImageByteFormat.png);
    return byteData?.buffer.asUint8List();
  }

  Future<void> _doSave(BuildContext context) async {
    setState(() => _saving = true);
    try {
      final bytes = await _renderCard();
      if (bytes == null) return;
      // 申请权限（首次弹系统弹窗；已拒绝则 false，但仍尝试写入让 GalException 决定）
      await Gal.requestAccess();
      await Gal.putImageBytes(
        bytes,
        name: 'position_share_${DateTime.now().millisecondsSinceEpoch}',
      );
      if (!context.mounted) return;
      showTopToast(context,
          message: AppLocalizations.of(context)!.saveSuccess,
          backgroundColor: AppColors.rise);
    } on GalException catch (e) {
      if (!context.mounted) return;
      // accessDenied 时引导用户去设置开启权限
      final l10n = AppLocalizations.of(context)!;
      if (e.type == GalExceptionType.accessDenied) {
        showTopToast(context,
            message: l10n.photoPermissionDenied,
            backgroundColor: AppColors.fall);
      } else {
        showTopToast(context,
            message: l10n.saveFailed, backgroundColor: AppColors.fall);
      }
    } catch (e) {
      if (context.mounted) {
        showTopToast(context,
            message: AppLocalizations.of(context)!.saveFailed,
            backgroundColor: AppColors.fall);
      }
    } finally {
      if (mounted) setState(() => _saving = false);
    }
  }

  Future<void> _doShare(BuildContext context) async {
    setState(() => _sharing = true);
    try {
      final bytes = await _renderCard();
      if (bytes == null) return;

      final tmpDir = await getTemporaryDirectory();
      final file = File(
          '${tmpDir.path}/position_share_${DateTime.now().millisecondsSinceEpoch}.png');
      await file.writeAsBytes(bytes);

      if (!context.mounted) return;
      Navigator.of(context).pop();

      await Share.shareXFiles(
        [XFile(file.path, mimeType: 'image/png')],
        subject: AppLocalizations.of(context)!.myFuturesPosition,
      );
    } catch (e) {
      if (context.mounted) {
        showTopToast(context,
            message: AppLocalizations.of(context)!.shareFailed,
            backgroundColor: AppColors.fall);
      }
    } finally {
      if (mounted) setState(() => _sharing = false);
    }
  }
}

/// 分享卡片内容
class _PositionShareCard extends StatelessWidget {
  const _PositionShareCard({
    required this.position,
    this.inviteCode,
    this.inviteUrl,
  });
  final FuturesPosition position;
  final String? inviteCode;
  final String? inviteUrl;

  String _baseCoin(String sym) {
    if (sym.contains('/')) return sym.split('/').first;
    return sym.toUpperCase().replaceFirst(RegExp(r'USDT$'), '');
  }

  String _formatNow() {
    final t = DateTime.now();
    final mo = t.month.toString().padLeft(2, '0');
    final d = t.day.toString().padLeft(2, '0');
    final h = t.hour.toString().padLeft(2, '0');
    final mi = t.minute.toString().padLeft(2, '0');
    final s = t.second.toString().padLeft(2, '0');
    return '${t.year}-$mo-$d $h:$mi:$s';
  }

  @override
  Widget build(BuildContext context) {
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final pos = position;
    final isLong = pos.side == OrderSide.long;
    final sideColor = isLong ? AppColors.rise : AppColors.fall;
    final pnlPositive = pos.unrealizedPnl >= 0;
    final pnlColor = pnlPositive ? AppColors.rise : AppColors.fall;
    final coinSymbol = _baseCoin(pos.symbol);
    final roeStr = '${pnlPositive ? '+' : ''}${formatAmount(pos.roe)}%';
    final qrData = inviteUrl;

    // 主题色变量
    final bgColors = isDark
        ? const [Color(0xFF1A1F2E), Color(0xFF0D1117)]
        : const [Color(0xFFF8F9FB), Color(0xFFEEF0F3)];
    final textPrimary = isDark ? Colors.white : const Color(0xFF1A1F2E);
    final textSecondary = isDark
        ? Colors.white.withAlpha(120)
        : const Color(0xFF1A1F2E).withAlpha(120);
    final textMuted = isDark
        ? Colors.white.withAlpha(80)
        : const Color(0xFF1A1F2E).withAlpha(80);
    final borderColor = isDark
        ? Colors.white.withAlpha(40)
        : const Color(0xFF1A1F2E).withAlpha(30);
    final qrFgColor = isDark ? Colors.white : Colors.black;
    final qrBgColor = isDark ? const Color(0xFF1A1F2E) : Colors.white;

    return Container(
      width: double.infinity,
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: bgColors,
        ),
        borderRadius: BorderRadius.circular(16),
      ),
      clipBehavior: Clip.antiAlias,
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // LOGO + 品牌名
            Row(
              children: [
                Image.asset(
                  'assets/images/app_icon.png',
                  height: 28,
                  width: 28,
                  errorBuilder: (_, __, ___) => const SizedBox.shrink(),
                ),
                const SizedBox(width: 8),
                Text(
                  'iBit',
                  style: TextStyle(
                      color: textPrimary,
                      fontSize: 14,
                      fontWeight: FontWeight.w700,
                      letterSpacing: 0.5),
                ),
              ],
            ),
            const SizedBox(height: 14),

            // 币对 + 永续 tag
            Row(
              children: [
                Text(
                  '${coinSymbol}USDT',
                  style: TextStyle(
                      color: textPrimary,
                      fontSize: 22,
                      fontWeight: FontWeight.w800),
                ),
                const SizedBox(width: 8),
                Container(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
                  decoration: BoxDecoration(
                    color: const Color(0xFFFFAB00),
                    borderRadius: BorderRadius.circular(4),
                  ),
                  child: Text(AppLocalizations.of(context)!.perpetual,
                      style: const TextStyle(
                          color: Colors.white,
                          fontSize: 11,
                          fontWeight: FontWeight.w700)),
                ),
              ],
            ),
            const SizedBox(height: 4),

            // 方向 + 杠杆
            Text(
              '${isLong ? AppLocalizations.of(context)!.openLong : AppLocalizations.of(context)!.openShort} ${pos.leverage.toInt()}X',
              style: TextStyle(
                  color: sideColor, fontSize: 15, fontWeight: FontWeight.w700),
            ),
            const SizedBox(height: 14),

            // 收益率（大字）
            Text(AppLocalizations.of(context)!.returnRate,
                style: TextStyle(color: textSecondary, fontSize: 12)),
            const SizedBox(height: 4),
            Text(roeStr,
                style: TextStyle(
                    color: pnlColor,
                    fontSize: 36,
                    fontWeight: FontWeight.w800,
                    letterSpacing: -0.5)),
            const SizedBox(height: 16),

            // 最新价 + 开仓均价
            Row(
              children: [
                Expanded(
                  child: _ShareDataItem(
                    label: AppLocalizations.of(context)!.latestPriceFull,
                    value: formatAmount(pos.markPrice),
                    textPrimary: textPrimary,
                    textSecondary: textSecondary,
                  ),
                ),
                Expanded(
                  child: _ShareDataItem(
                    label: AppLocalizations.of(context)!.openAvgPrice,
                    value: formatAmount(pos.entryPrice),
                    align: CrossAxisAlignment.end,
                    textPrimary: textPrimary,
                    textSecondary: textSecondary,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 10),

            // 时间
            Text(_formatNow(),
                style: TextStyle(color: textMuted, fontSize: 11)),
            const SizedBox(height: 14),

            // 分隔线
            Divider(color: borderColor, height: 1),
            const SizedBox(height: 14),

            // 邀请码 + 二维码
            Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      if (inviteCode != null)
                        RichText(
                          text: TextSpan(
                            style: const TextStyle(fontSize: 15),
                            children: [
                              TextSpan(
                                text: AppLocalizations.of(context)!
                                    .inviteCodeLabel,
                                style: TextStyle(color: textSecondary),
                              ),
                              TextSpan(
                                text: inviteCode!,
                                style: const TextStyle(
                                    color: AppColors.brand,
                                    fontWeight: FontWeight.w700),
                              ),
                            ],
                          ),
                        ),
                      const SizedBox(height: 4),
                      Text(AppLocalizations.of(context)!.registerAndEarnRebate,
                          style: TextStyle(color: textMuted, fontSize: 12)),
                    ],
                  ),
                ),
                Container(
                  decoration: BoxDecoration(
                    border: Border.all(color: borderColor, width: 1),
                    borderRadius: BorderRadius.circular(6),
                  ),
                  padding: const EdgeInsets.all(4),
                  child: qrData != null
                      ? QrImageView(
                          data: qrData,
                          version: QrVersions.auto,
                          size: 80,
                          eyeStyle: QrEyeStyle(
                            eyeShape: QrEyeShape.square,
                            color: qrFgColor,
                          ),
                          dataModuleStyle: QrDataModuleStyle(
                            dataModuleShape: QrDataModuleShape.square,
                            color: qrFgColor,
                          ),
                          backgroundColor: qrBgColor,
                          errorCorrectionLevel: QrErrorCorrectLevel.M,
                        )
                      : const SizedBox(width: 80, height: 80),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class _ShareDataItem extends StatelessWidget {
  const _ShareDataItem({
    required this.label,
    required this.value,
    required this.textPrimary,
    required this.textSecondary,
    this.align = CrossAxisAlignment.start,
  });
  final String label;
  final String value;
  final Color textPrimary;
  final Color textSecondary;
  final CrossAxisAlignment align;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: align,
      children: [
        Text(label, style: TextStyle(color: textSecondary, fontSize: 11)),
        const SizedBox(height: 2),
        Text(value,
            style: TextStyle(
                color: textPrimary, fontSize: 13, fontWeight: FontWeight.w600)),
      ],
    );
  }
}

// ── 合约骨架屏 ──────────────────────────────────────────────
/// 持仓/委托 Tab 列表骨架：[rows] 行卡片占位
class _TabShimmer extends StatelessWidget {
  const _TabShimmer({required this.rows});
  final int rows;

  @override
  Widget build(BuildContext context) {
    return AppShimmer(
      child: Padding(
        padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
        child: Column(
          children: List.generate(
              rows,
              (i) => Padding(
                    padding: const EdgeInsets.only(bottom: 12),
                    child: Container(
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(8),
                      ),
                      padding: const EdgeInsets.all(12),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          // 标题行：币对 + 方向标签 + 浮盈
                          Row(children: [
                            shimmerBox(80, 14),
                            const SizedBox(width: 8),
                            shimmerBox(44, 18, radius: 4),
                            const Spacer(),
                            shimmerBox(70, 14),
                          ]),
                          const SizedBox(height: 10),
                          // 数据行
                          Row(children: [
                            Expanded(child: shimmerBox(double.infinity, 11)),
                            const SizedBox(width: 12),
                            Expanded(child: shimmerBox(double.infinity, 11)),
                            const SizedBox(width: 12),
                            Expanded(child: shimmerBox(double.infinity, 11)),
                          ]),
                          const SizedBox(height: 8),
                          Row(children: [
                            Expanded(child: shimmerBox(double.infinity, 11)),
                            const SizedBox(width: 12),
                            Expanded(child: shimmerBox(double.infinity, 11)),
                            const SizedBox(width: 12),
                            Expanded(child: shimmerBox(double.infinity, 11)),
                          ]),
                          const SizedBox(height: 10),
                          // 操作按钮行
                          Row(children: [
                            shimmerBox(56, 24, radius: 4),
                            const SizedBox(width: 8),
                            shimmerBox(56, 24, radius: 4),
                            const SizedBox(width: 8),
                            shimmerBox(56, 24, radius: 4),
                          ]),
                        ],
                      ),
                    ),
                  )),
        ),
      ),
    );
  }
}

/// 资产面板骨架
class _AssetShimmer extends StatelessWidget {
  const _AssetShimmer();

  @override
  Widget build(BuildContext context) {
    return AppShimmer(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            shimmerBox(100, 12),
            const SizedBox(height: 8),
            shimmerBox(160, 26),
            const SizedBox(height: 14),
            Row(children: [
              Expanded(
                  child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                    shimmerBox(60, 11),
                    const SizedBox(height: 6),
                    shimmerBox(80, 14),
                  ])),
              Expanded(
                  child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                    shimmerBox(60, 11),
                    const SizedBox(height: 6),
                    shimmerBox(80, 14),
                  ])),
              Expanded(
                  child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                    shimmerBox(60, 11),
                    const SizedBox(height: 6),
                    shimmerBox(80, 14),
                  ])),
            ]),
          ],
        ),
      ),
    );
  }
}

class _FuturesShimmer extends StatelessWidget {
  const _FuturesShimmer();

  @override
  Widget build(BuildContext context) {
    return AppShimmer(
      child: SingleChildScrollView(
        physics: const NeverScrollableScrollPhysics(),
        child: Column(
          children: [
            // 上半：左侧下单区 + 右侧盘口
            SizedBox(
              height: 400,
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  // 左侧下单区骨架
                  Expanded(
                    flex: 55,
                    child: Padding(
                      padding: const EdgeInsets.all(12),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          // 开多/开空 tab
                          Row(children: [
                            shimmerBox(70, 30, radius: 6),
                            const SizedBox(width: 8),
                            shimmerBox(70, 30, radius: 6),
                          ]),
                          const SizedBox(height: 12),
                          // 价格输入框
                          shimmerFill(44, radius: 8),
                          const SizedBox(height: 10),
                          // 数量输入框
                          shimmerFill(44, radius: 8),
                          const SizedBox(height: 10),
                          // 滑块
                          shimmerFill(20, radius: 10),
                          const SizedBox(height: 16),
                          // 按钮
                          Row(children: [
                            Expanded(child: shimmerFill(40, radius: 8)),
                            const SizedBox(width: 8),
                            Expanded(child: shimmerFill(40, radius: 8)),
                          ]),
                          const SizedBox(height: 16),
                          // 数据行
                          ...List.generate(
                              3,
                              (_) => Padding(
                                    padding: const EdgeInsets.only(bottom: 8),
                                    child: Row(
                                      mainAxisAlignment:
                                          MainAxisAlignment.spaceBetween,
                                      children: [
                                        shimmerBox(60, 11),
                                        shimmerBox(70, 11),
                                      ],
                                    ),
                                  )),
                        ],
                      ),
                    ),
                  ),
                  // 右侧盘口骨架
                  Expanded(
                    flex: 45,
                    child: Padding(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 8, vertical: 12),
                      child: Column(
                        children: [
                          // 盘口 header
                          Row(
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              shimmerBox(40, 11),
                              shimmerBox(60, 11),
                              shimmerBox(40, 11),
                            ],
                          ),
                          const SizedBox(height: 8),
                          // 盘口行
                          ...List.generate(
                              12,
                              (_) => Padding(
                                    padding:
                                        const EdgeInsets.symmetric(vertical: 4),
                                    child: Row(
                                      children: [
                                        Expanded(
                                            child: shimmerBox(
                                                double.infinity, 11)),
                                        const SizedBox(width: 6),
                                        Expanded(
                                            child: shimmerBox(
                                                double.infinity, 11)),
                                      ],
                                    ),
                                  )),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
            // 下半：持仓/委托区
            Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // tab 行
                  Row(children: [
                    shimmerBox(60, 28, radius: 6),
                    const SizedBox(width: 8),
                    shimmerBox(60, 28, radius: 6),
                    const SizedBox(width: 8),
                    shimmerBox(60, 28, radius: 6),
                  ]),
                  const SizedBox(height: 16),
                  // 空状态占位
                  Center(child: shimmerBox(120, 14)),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// 测量子组件渲染高度，每帧变化时回调。
class _SizeReporter extends StatefulWidget {
  const _SizeReporter({required this.child, required this.onHeight});
  final Widget child;
  final ValueChanged<double> onHeight;

  @override
  State<_SizeReporter> createState() => _SizeReporterState();
}

class _SizeReporterState extends State<_SizeReporter> {
  double? _lastHeight;

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      final rb = context.findRenderObject() as RenderBox?;
      if (rb == null || !rb.hasSize) return;
      final h = rb.size.height;
      if (h != _lastHeight) {
        _lastHeight = h;
        widget.onHeight(h);
      }
    });
    return widget.child;
  }
}
