import 'dart:async';
import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';

import '../../../core/l10n/app_localizations.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/utils/number_format.dart';
import '../../../core/utils/spot_order_book_convert.dart';
import '../../../core/utils/symbol_display.dart';
import '../../../core/utils/top_toast.dart';
import '../../../providers/auth_provider.dart';
import '../../../providers/futures_provider.dart';
import '../../../providers/spot_coin_cache_provider.dart';
import '../../../providers/spot_provider.dart';
import '../../widgets/common/app_refresh_indicator.dart';
import '../../widgets/common/app_shimmer.dart';
import '../../widgets/common/coin_icon.dart';
import '../../widgets/common/kline_toolbar_icon.dart';
import '../../widgets/common/symbol_picker_sheet.dart';

/// 现货交易主页（与合约页风格保持一致）
class SpotScreen extends ConsumerStatefulWidget {
  const SpotScreen({super.key, required this.symbol});
  final String symbol;

  @override
  ConsumerState<SpotScreen> createState() => _SpotScreenState();
}

class _SpotScreenState extends ConsumerState<SpotScreen> {
  late final ScrollController _scroll;
  final _orderPanelKey = GlobalKey<_SpotOrderPanelState>();
  int _obRowCount = 7;
  double _obRowH = 22.0;
  double _leftPanelHeight = 520.0;

  void _onLeftPanelHeight(double h) {
    // 表头、中间价、底部分布条+占比+模式按钮的预留高度（略收紧以便多挤 1 档）
    const countFixedH = 128.0;
    // 每侧一行约 40px（含行高与视觉间距），比原 44 略紧以填满右侧与左侧对齐
    final n = ((h - countFixedH) / 40).floor().clamp(4, 14);
    final rh = ((h - countFixedH) / (n * 2)).clamp(18.0, 28.0);
    if (n != _obRowCount ||
        (rh - _obRowH).abs() > 0.1 ||
        (h - _leftPanelHeight).abs() > 1) {
      setState(() {
        _obRowCount = n;
        _obRowH = rh;
        _leftPanelHeight = h;
      });
    }
  }

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

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

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

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

    return Scaffold(
      appBar: AppBar(
        elevation: 0,
        toolbarHeight: 44,
        titleSpacing: 16,
        title: _SegmentedTabHeader(
          activeIndex: 0,
          onTap: (i) {
            if (i == 1) {
              final futuresSym = ref.read(futuresActiveSymbolProvider);
              context.go(
                  '/futures/${futuresSym.isNotEmpty ? futuresSym : 'BTCUSDT'}');
            }
          },
        ),
        centerTitle: false,
        bottom: PreferredSize(
          preferredSize: const Size.fromHeight(1),
          child: Container(height: 1, color: cs.outline.withAlpha(40)),
        ),
        actions: [
          IconButton(
            icon: KlineToolbarIcon(color: cs.onSurface.withAlpha(180)),
            onPressed: () =>
                _pushAndPausePolling(context, '/market/spot/$symbol'),
            padding: const EdgeInsets.symmetric(horizontal: 8),
            constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
          ),
        ],
      ),
      body: Listener(
        onPointerDown: (_) => FocusScope.of(context).unfocus(),
        child: Builder(builder: (ctx) {
          final isLoading =
              ref.watch(spotProvider(symbol).select((s) => s.isLoading));
          if (isLoading) return const _SpotShimmer();
          return AppRefreshIndicator(
            onRefresh: () => ref.read(spotProvider(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: _SpotOrderPanel(
                              key: _orderPanelKey,
                              symbol: symbol,
                            ),
                          ),
                        ),
                      ),
                      Expanded(
                        flex: 45,
                        child: SizedBox(
                          height: _leftPanelHeight,
                          child: RepaintBoundary(
                            child: _SpotOrderBookPanel(
                              symbol: symbol,
                              rowCount: _obRowCount,
                              rowHeight: _obRowH,
                              onPriceTap: (price) => _orderPanelKey.currentState
                                  ?.setBookPrice(price),
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  _SpotBottomSection(symbol: symbol),
                  const SizedBox(height: 16),
                ],
              ),
            ),
          );
        }),
      ),
    );
  }
}

// ══════════════════════════════════════════════════════════════════════
// 顶部 现货 / 永续合约 切换 Tab（共用：Spot/Futures 标题区均使用此组件）
// ══════════════════════════════════════════════════════════════════════

class _SegmentedTabHeader extends StatelessWidget {
  const _SegmentedTabHeader({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,
                ),
              ],
            ),
          ),
        );
      }),
    );
  }
}

// ══════════════════════════════════════════════════════════════════════
// 下单面板
// ══════════════════════════════════════════════════════════════════════

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

  @override
  ConsumerState<_SpotOrderPanel> createState() => _SpotOrderPanelState();
}

class _SpotOrderPanelState extends ConsumerState<_SpotOrderPanel> {
  final _priceCtrl = TextEditingController();
  final _amountCtrl = TextEditingController();
  final _triggerCtrl = TextEditingController();
  bool _priceFilled = false;

  @override
  void initState() {
    super.initState();
    _amountCtrl.addListener(_onAmountChanged);
  }

  @override
  void dispose() {
    _amountCtrl.removeListener(_onAmountChanged);
    _priceCtrl.dispose();
    _amountCtrl.dispose();
    _triggerCtrl.dispose();
    super.dispose();
  }

  // 输入数量时反向同步滑块（仅作展示，避免与滑块冲突）
  bool _settingFromSlider = false;
  void _onAmountChanged() {
    if (_settingFromSlider) return;
    final notifier = ref.read(spotProvider(widget.symbol).notifier);
    final s = ref.read(spotProvider(widget.symbol));
    final v = double.tryParse(_amountCtrl.text.replaceAll(',', '')) ?? 0;
    if (v <= 0) {
      notifier.setSliderPercent(0);
      return;
    }
    final max = _maxAmount(s);
    notifier.setSliderPercent(max > 0 ? (v / max).clamp(0.0, 1.0) : 0);
  }

  /// 当前用户可下单的"数量上限"（按 effectiveAmountUnit 计）
  double _maxAmount(SpotState s) {
    final price = _refPrice(s);
    if (s.side == SpotSide.buy) {
      // 买入：用 USDT 余额
      final usdt = s.availableQuote;
      if (s.effectiveAmountUnit == SpotAmountUnit.quote) return usdt;
      // base 单位：折算为基础币数量（需要参考价）
      if (price <= 0) return 0;
      return usdt / price;
    }
    // 卖出：用 base 余额
    final base = s.availableBase;
    if (s.effectiveAmountUnit == SpotAmountUnit.base) return base;
    if (price <= 0) return 0;
    return base * price;
  }

  /// 计算/换算时使用的参考价
  /// - 限价 / 计划限价：价格输入框
  /// - 计划市价：触发价
  /// - 市价：最新价
  double _refPrice(SpotState s) {
    if (s.orderType == SpotOrderType.limit) {
      return double.tryParse(_priceCtrl.text.replaceAll(',', '')) ?? 0;
    }
    if (s.orderType == SpotOrderType.conditionalMarket) {
      return double.tryParse(_triggerCtrl.text.replaceAll(',', '')) ?? 0;
    }
    return s.lastPrice;
  }

  void setBookPrice(double price) {
    if (!mounted) return;
    final s = ref.read(spotProvider(widget.symbol));
    final formatted = price.toStringAsFixed(s.pricePrecision);
    if (s.orderType == SpotOrderType.conditionalMarket) {
      _triggerCtrl.text = formatted;
    } else if (s.orderType == SpotOrderType.limit) {
      _priceCtrl.text = formatted;
    }
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    final provider = spotProvider(widget.symbol);
    final notifier = ref.read(provider.notifier);
    final isLoggedIn = ref.watch(isLoggedInProvider);

    // 切换下单类型时清空所有输入
    ref.listen(provider.select((s) => s.orderType), (prev, next) {
      if (prev == next) return;
      _priceFilled = false;
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted) return;
        FocusManager.instance.primaryFocus?.unfocus();
        _priceCtrl.clear();
        _amountCtrl.clear();
        _triggerCtrl.clear();
        notifier.setSliderPercent(0);
      });
    });

    // 切换买/卖时清空数量与滑块（保留价格）
    ref.listen(provider.select((s) => s.side), (prev, next) {
      if (prev == next) return;
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted) return;
        _amountCtrl.clear();
        notifier.setSliderPercent(0);
      });
    });

    // 限价单首次拿到价格时回填
    ref.listen<double>(provider.select((s) => s.lastPrice), (_, next) {
      if (_priceFilled || next <= 0) return;
      _priceFilled = true;
      final precision = ref.read(provider).pricePrecision;
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted) return;
        if (_priceCtrl.text.isEmpty) {
          _priceCtrl.text = next.toStringAsFixed(precision);
        }
      });
    });

    final symbolValue = ref.watch(provider.select((s) => s.symbol));
    final change = ref.watch(provider.select((s) => s.change24h));
    final orderType = ref.watch(provider.select((s) => s.orderType));
    final side = ref.watch(provider.select((s) => s.side));
    final unit = ref.watch(provider.select((s) => s.effectiveAmountUnit));
    final showPriceInput = ref.watch(provider.select((s) => s.showPriceInput));
    final showTrigger = ref.watch(provider.select((s) => s.showTriggerPrice));
    final lastPrice = ref.watch(provider.select((s) => s.lastPrice));
    final availQuote = ref.watch(provider.select((s) => s.availableQuote));
    final availBase = ref.watch(provider.select((s) => s.availableBase));
    final base = ref.watch(provider.select((s) => s.baseCoin));
    final quote = ref.watch(provider.select((s) => s.quoteCoin));
    final pricePre = ref.watch(provider.select((s) => s.pricePrecision));
    final volPre = ref.watch(provider.select((s) => s.volumePrecision));
    final sliderPct = ref.watch(provider.select((s) => s.sliderPercent));

    final unitLabel = unit == SpotAmountUnit.quote ? quote : base;
    final priceFormatter = _PrecisionInputFormatter(pricePre);
    final amountFormatter = _PrecisionInputFormatter(
      unit == SpotAmountUnit.quote ? 2 : volPre,
    );

    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          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(symbolValue),
                      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(change),
                    style: TextStyle(
                      color: AppColors.changeColor(change),
                      fontSize: 12,
                      fontWeight: FontWeight.w500,
                      fontFeatures: const [FontFeature.tabularFigures()],
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 8),

          // 买/卖 Tab
          _BuySellTabs(
            side: side,
            onChanged: notifier.setSide,
          ),
          const SizedBox(height: 10),

          // 订单类型下拉
          _SpotOrderTypeDropdown(symbol: widget.symbol),
          const SizedBox(height: 8),

          // 触发价（仅条件委托）
          if (showTrigger) ...[
            _LargeInput(
              controller: _triggerCtrl,
              label: l10n.triggerPrice,
              unit: quote,
              inputFormatters: [priceFormatter],
            ),
            const SizedBox(height: 8),
          ],

          // 价格 输入框 / 市价占位
          if (showPriceInput) ...[
            _LargeInput(
              controller: _priceCtrl,
              label: l10n.priceLabel2,
              unit: quote,
              inputFormatters: [priceFormatter],
            ),
            const SizedBox(height: 8),
          ] else ...[
            _MarketPriceBox(label: l10n.marketBest, quote: quote),
            const SizedBox(height: 8),
          ],

          // 数量
          _LargeInput(
            controller: _amountCtrl,
            label: side == SpotSide.buy && orderType != SpotOrderType.limit
                ? l10n.amountQuoteLabel
                : l10n.quantityLabel,
            unit: unitLabel,
            // 限价单：买入时 BTC/USDT 可切换；卖出时同样；市价/计划市价单位锁定
            showUnitDropdown: orderType == SpotOrderType.limit,
            onUnitTap: orderType == SpotOrderType.limit
                ? () => _showAmountUnitSheet(context)
                : null,
            inputFormatters: [amountFormatter],
          ),

          // 总价（市价时根据滑块/数量计算）
          if (orderType != SpotOrderType.limit && lastPrice > 0)
            ListenableBuilder(
              listenable: _amountCtrl,
              builder: (_, __) {
                final v =
                    double.tryParse(_amountCtrl.text.replaceAll(',', '')) ?? 0;
                if (v <= 0) return const SizedBox.shrink();
                final estUsdt = side == SpotSide.buy ? v : v * lastPrice;
                return Padding(
                  padding: const EdgeInsets.only(top: 6),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        formatAmount(estUsdt),
                        style: TextStyle(
                          color: cs.onSurface,
                          fontSize: 15,
                          fontWeight: FontWeight.w700,
                          fontFeatures: const [FontFeature.tabularFigures()],
                        ),
                      ),
                      Text(
                        formatFiatPrice(estUsdt),
                        style: TextStyle(
                          color: cs.onSurface.withAlpha(120),
                          fontSize: 11,
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          const SizedBox(height: 10),

          // 滑块
          _PercentSlider(
            percent: sliderPct,
            onChanged: (pct) {
              final s = ref.read(provider);
              if (pct > 0 &&
                  _refPrice(s) <= 0 &&
                  orderType != SpotOrderType.market) {
                showTopToast(
                  context,
                  message: orderType == SpotOrderType.conditionalMarket
                      ? l10n.enterTriggerPrice
                      : l10n.enterPrice,
                  backgroundColor: AppColors.fall,
                );
                return;
              }
              notifier.setSliderPercent(pct);
              _settingFromSlider = true;
              if (pct == 0) {
                _amountCtrl.clear();
              } else {
                final max = _maxAmount(s);
                final v = max * pct;
                final dp =
                    s.effectiveAmountUnit == SpotAmountUnit.quote ? 2 : volPre;
                final factor = math.pow(10, dp).toDouble();
                final truncated = (v * factor).floorToDouble() / factor;
                _amountCtrl.text = truncated.toStringAsFixed(dp);
              }
              _settingFromSlider = false;
            },
          ),
          const SizedBox(height: 10),

          // 可用（买入展示计价余额，卖出展示基础币余额）
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            child: Row(
              children: [
                Text('${l10n.availableLabel} ',
                    style: TextStyle(
                        color: cs.onSurface.withAlpha(153), fontSize: 12)),
                Text(
                  side == SpotSide.buy
                      ? '${formatAmount(availQuote)} $quote'
                      : '${formatAmount(availBase)} $base',
                  style: TextStyle(
                    color: cs.onSurface,
                    fontSize: 12,
                    fontWeight: FontWeight.w600,
                  ),
                ),
                const Spacer(),
                GestureDetector(
                  onTap: () async {
                    final n = ref.read(provider.notifier);
                    n.stopPolling();
                    await context.push('/asset/transfer');
                    if (context.mounted) n.resumePolling();
                  },
                  child: Icon(Icons.swap_horiz,
                      color: cs.onSurface.withAlpha(153), size: 16),
                ),
              ],
            ),
          ),
          const SizedBox(height: 4),
          Builder(builder: (_) {
            final isBuy = side == SpotSide.buy;
            final String canText;
            if (isBuy) {
              final qty = lastPrice > 0 ? availQuote / lastPrice : 0.0;
              canText = '${formatAmount(qty, decimals: volPre)} $base';
            } else {
              canText = '${formatAmount(availBase, decimals: volPre)} $base';
            }
            return Row(
              children: [
                Text(
                  isBuy ? l10n.canBuy : l10n.canSell,
                  style: TextStyle(
                      color: cs.onSurface.withAlpha(153), fontSize: 12),
                ),
                const SizedBox(width: 4),
                Text(
                  canText,
                  style: TextStyle(
                    color: cs.onSurface,
                    fontSize: 12,
                    fontWeight: FontWeight.w600,
                  ),
                ),
              ],
            );
          }),
          const SizedBox(height: 12),

          // 主操作按钮
          SizedBox(
            width: double.infinity,
            height: 44,
            child: ElevatedButton(
              onPressed: isLoggedIn
                  ? () => _placeOrder(context)
                  : () => context.push('/login'),
              style: ElevatedButton.styleFrom(
                backgroundColor:
                    side == SpotSide.buy ? AppColors.rise : AppColors.fall,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
                elevation: 0,
              ),
              child: Text(
                side == SpotSide.buy ? l10n.buyCoin(base) : l10n.sellCoin(base),
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 15,
                  fontWeight: FontWeight.w700,
                ),
              ),
            ),
          ),
          const SizedBox(height: 12),
        ],
      ),
    );
  }

  Future<void> _placeOrder(BuildContext context) async {
    final notifier = ref.read(spotProvider(widget.symbol).notifier);
    final s = ref.read(spotProvider(widget.symbol));
    final l10n = AppLocalizations.of(context)!;

    final price = double.tryParse(_priceCtrl.text.replaceAll(',', ''));
    final inputAmount =
        double.tryParse(_amountCtrl.text.replaceAll(',', '')) ?? 0;

    if (s.orderType == SpotOrderType.limit && (price == null || price <= 0)) {
      showTopToast(context,
          message: l10n.enterPrice, backgroundColor: AppColors.fall);
      return;
    }
    if (inputAmount <= 0) {
      showTopToast(context,
          message: l10n.errEnterAmount, backgroundColor: AppColors.fall);
      return;
    }

    final unit = s.effectiveAmountUnit;
    final prep = notifier.prepareAmount(
      side: s.side,
      type: s.orderType,
      inputAmount: inputAmount,
      unit: unit,
      price: price,
    );
    if (prep == null) {
      showTopToast(context,
          message: l10n.errVolumeInsufficient, backgroundColor: AppColors.fall);
      return;
    }

    if (s.orderType == SpotOrderType.conditionalMarket) {
      showTopToast(
        context,
        message: l10n.spotConditionalNotSupported,
        backgroundColor: AppColors.fall,
      );
      return;
    }

    final err = await notifier.placeOrder(
      side: s.side,
      type: s.orderType,
      price: price,
      amount: prep.payload,
    );

    if (!context.mounted) return;
    if (err == null) {
      _amountCtrl.clear();
      notifier.setSliderPercent(0);
      showTopToast(
        context,
        message: l10n.orderSuccess,
        backgroundColor: AppColors.rise,
      );
    } else {
      showTopToast(
        context,
        message: _resolveSpotError(err, l10n),
        backgroundColor: AppColors.fall,
      );
    }
  }

  void _showSymbolPicker(BuildContext context) {
    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: (sheetCtx) => SymbolPickerSheet(
        currentSymbol: widget.symbol,
        initialTab: SymbolPickerTab.spot,
        visibleTabs: const [SymbolPickerTab.spot],
        onSelected: (newSymbol) {
          Navigator.pop(sheetCtx);
          context.go('/spot/$newSymbol');
        },
        onSpotSelected: (newSymbol) {
          Navigator.pop(sheetCtx);
          context.go('/spot/$newSymbol');
        },
      ),
    );
  }

  void _showAmountUnitSheet(BuildContext context) {
    FocusScope.of(context).unfocus();
    final cs = Theme.of(context).colorScheme;
    final s = ref.read(spotProvider(widget.symbol));
    final notifier = ref.read(spotProvider(widget.symbol).notifier);
    final units = [
      (SpotAmountUnit.base, s.baseCoin),
      (SpotAmountUnit.quote, s.quoteCoin),
    ];
    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      backgroundColor: cs.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (ctx) => SafeArea(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: units
              .map(
                (u) => GestureDetector(
                  onTap: () {
                    notifier.setAmountUnit(u.$1);
                    _amountCtrl.clear();
                    notifier.setSliderPercent(0);
                    Navigator.pop(ctx);
                  },
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        horizontal: 16, vertical: 14),
                    child: Row(
                      children: [
                        Expanded(
                          child: Text(u.$2,
                              style: TextStyle(
                                  color: s.amountUnit == u.$1
                                      ? AppColors.brand
                                      : cs.onSurface,
                                  fontSize: 14)),
                        ),
                        if (s.amountUnit == u.$1)
                          const Icon(Icons.check,
                              color: AppColors.brand, size: 18),
                      ],
                    ),
                  ),
                ),
              )
              .toList(),
        ),
      ),
    );
  }
}

// ── 买/卖 Tabs ────────────────────────────────────────

class _BuySellTabs extends StatelessWidget {
  const _BuySellTabs({required this.side, required this.onChanged});
  final SpotSide side;
  final ValueChanged<SpotSide> onChanged;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final l10n = AppLocalizations.of(context)!;
    final bg = isDark ? AppColors.darkBgTertiary : AppColors.lightBgSecondary;

    Widget tab({
      required SpotSide value,
      required String label,
      required Color activeColor,
    }) {
      final active = side == value;
      return Expanded(
        child: GestureDetector(
          onTap: () => onChanged(value),
          behavior: HitTestBehavior.opaque,
          child: Container(
            height: 36,
            decoration: BoxDecoration(
              color: active ? activeColor : Colors.transparent,
              borderRadius: BorderRadius.circular(6),
            ),
            alignment: Alignment.center,
            child: Text(
              label,
              style: TextStyle(
                color: active ? Colors.white : cs.onSurface.withAlpha(160),
                fontSize: 14,
                fontWeight: FontWeight.w700,
              ),
            ),
          ),
        ),
      );
    }

    return Container(
      padding: const EdgeInsets.all(2),
      decoration: BoxDecoration(
        color: bg,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Row(
        children: [
          tab(
              value: SpotSide.buy,
              label: l10n.buyAction,
              activeColor: AppColors.rise),
          const SizedBox(width: 4),
          tab(
              value: SpotSide.sell,
              label: l10n.sellAction,
              activeColor: AppColors.fall),
        ],
      ),
    );
  }
}

// ── 订单类型下拉 ──────────────────────────────────────

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final orderType =
        ref.watch(spotProvider(symbol).select((s) => s.orderType));
    final notifier = ref.read(spotProvider(symbol).notifier);
    final l10n = AppLocalizations.of(context)!;
    const visibleTypes = [SpotOrderType.market, SpotOrderType.limit];

    final safeOrderType =
        visibleTypes.contains(orderType) ? orderType : SpotOrderType.market;

    String labelOf(SpotOrderType t) {
      switch (t) {
        case SpotOrderType.market:
          return l10n.marketOrder;
        case SpotOrderType.limit:
          return l10n.limitOrder;
        case SpotOrderType.conditionalMarket:
          return l10n.marketOrder;
      }
    }

    return GestureDetector(
      onTap: () {
        FocusScope.of(context).unfocus();
        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: visibleTypes.map((t) {
                final isActive = safeOrderType == t;
                return GestureDetector(
                  onTap: () {
                    notifier.setOrderType(t);
                    Navigator.pop(sheetCtx);
                  },
                  child: Padding(
                    padding: const EdgeInsets.symmetric(
                        horizontal: 16, vertical: 14),
                    child: Row(
                      children: [
                        Expanded(
                          child: Text(
                            labelOf(t),
                            style: TextStyle(
                                color:
                                    isActive ? AppColors.brand : cs.onSurface,
                                fontSize: 14),
                          ),
                        ),
                        if (isActive)
                          const Icon(Icons.check,
                              color: AppColors.brand, size: 18),
                      ],
                    ),
                  ),
                );
              }).toList(),
            ),
          ),
        );
      },
      child: Container(
        height: 40,
        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(labelOf(safeOrderType),
                style: TextStyle(color: cs.onSurface, fontSize: 14)),
            Icon(Icons.keyboard_arrow_down,
                color: cs.onSurface.withAlpha(153), size: 18),
          ],
        ),
      ),
    );
  }
}

// ── 市价占位 ──────────────────────────────────────────

class _MarketPriceBox extends StatelessWidget {
  const _MarketPriceBox({required this.label, required this.quote});
  final String label;
  final String quote;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final isDark = Theme.of(context).brightness == Brightness.dark;
    return Container(
      height: 44,
      decoration: BoxDecoration(
        color: isDark ? AppColors.darkBgTertiary : AppColors.lightBgSecondary,
        borderRadius: BorderRadius.circular(8),
      ),
      padding: const EdgeInsets.symmetric(horizontal: 12),
      child: Row(
        children: [
          Expanded(
            child: Text(
              label,
              style: TextStyle(
                color: cs.onSurface.withAlpha(160),
                fontSize: 15,
                fontWeight: FontWeight.w500,
              ),
            ),
          ),
          Text(quote,
              style:
                  TextStyle(color: cs.onSurface.withAlpha(150), fontSize: 13)),
        ],
      ),
    );
  }
}

// ══════════════════════════════════════════════════════════════════════
// 通用输入框（与合约页保持一致风格）
// ══════════════════════════════════════════════════════════════════════

class _LargeInput extends StatefulWidget {
  const _LargeInput({
    required this.controller,
    required this.label,
    required this.unit,
    this.showUnitDropdown = false,
    this.onUnitTap,
    this.inputFormatters,
  });
  final TextEditingController controller;
  final String label;
  final String unit;
  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) {
      oldWidget.controller.removeListener(_onChanged);
      widget.controller.addListener(_onChanged);
      _animCtrl.value = _isActive ? 1.0 : 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 activeBorder = 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: activeBorder, 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 centerTop = (44.0 - labelSize) / 2;
                  const activeTop = 5.0;
                  final labelTop = centerTop + (activeTop - centerTop) * 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: t,
                            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),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// ══════════════════════════════════════════════════════════════════════
// 百分比滑动条（与合约页相同的视觉，简化实现）
// ══════════════════════════════════════════════════════════════════════

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

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

class _PercentSliderState extends State<_PercentSlider> {
  static const _stops = [0.0, 0.25, 0.5, 0.75, 1.0];
  static const _thumbSize = 18.0;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final pct = widget.percent.clamp(0.0, 1.0);
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        SizedBox(
          height: _thumbSize,
          child: LayoutBuilder(builder: (_, constraints) {
            final w = constraints.maxWidth;
            const r = _thumbSize / 2;
            final trackW = w - _thumbSize;
            final thumbX = r + trackW * pct;
            return GestureDetector(
              behavior: HitTestBehavior.opaque,
              onHorizontalDragUpdate: (d) {
                final newPct =
                    ((thumbX + d.delta.dx - r) / trackW).clamp(0.0, 1.0);
                widget.onChanged(newPct);
              },
              onTapDown: (d) {
                final tapPct =
                    ((d.localPosition.dx - r) / trackW).clamp(0.0, 1.0);
                HapticFeedback.selectionClick();
                widget.onChanged(tapPct);
              },
              child: Stack(
                children: [
                  Positioned(
                    top: (_thumbSize - 3) / 2,
                    left: r,
                    right: r,
                    height: 3,
                    child: Container(
                      decoration: BoxDecoration(
                        color: cs.outline.withAlpha(50),
                        borderRadius: BorderRadius.circular(2),
                      ),
                    ),
                  ),
                  Positioned(
                    top: (_thumbSize - 3) / 2,
                    left: r,
                    width: thumbX - r,
                    height: 3,
                    child: Container(
                      decoration: BoxDecoration(
                        color: AppColors.brand,
                        borderRadius: BorderRadius.circular(2),
                      ),
                    ),
                  ),
                  for (final p in _stops)
                    Positioned(
                      left: r + trackW * p - 3,
                      top: (_thumbSize - 6) / 2,
                      child: Container(
                        width: 6,
                        height: 6,
                        decoration: BoxDecoration(
                          shape: BoxShape.circle,
                          color: p <= pct
                              ? AppColors.brand
                              : cs.outline.withAlpha(80),
                        ),
                      ),
                    ),
                  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),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            );
          }),
        ),
        const SizedBox(height: 4),
        LayoutBuilder(builder: (_, constraints) {
          final w = constraints.maxWidth;
          const r = _thumbSize / 2;
          final trackW = w - _thumbSize;
          final tickInterval = trackW / (_stops.length - 1);
          final btnW = (tickInterval - 6).clamp(20.0, 64.0);
          return SizedBox(
            height: 22,
            child: Stack(
              children: [
                for (final p in _stops)
                  Positioned(
                    left: (r + trackW * p - btnW / 2).clamp(0.0, w - btnW),
                    top: 0,
                    width: btnW,
                    height: 22,
                    child: Builder(builder: (_) {
                      final selected = (pct - p).abs() < 0.001;
                      return GestureDetector(
                        onTap: () {
                          HapticFeedback.selectionClick();
                          widget.onChanged(p);
                        },
                        child: Container(
                          decoration: BoxDecoration(
                            color: selected
                                ? AppColors.brand
                                : cs.outline.withAlpha(25),
                            borderRadius: BorderRadius.circular(4),
                          ),
                          alignment: Alignment.center,
                          child: Text(
                            '${(p * 100).toInt()}%',
                            style: TextStyle(
                              color: selected
                                  ? Colors.black
                                  : cs.onSurface.withAlpha(102),
                              fontSize: 10,
                              fontWeight: FontWeight.w500,
                            ),
                          ),
                        ),
                      );
                    }),
                  ),
              ],
            ),
          );
        }),
      ],
    );
  }
}

// ══════════════════════════════════════════════════════════════════════
// 盘口（布局与合约页 _OrderBookPanel 一致：列标题、深度条、买卖占比、模式切换）
// ══════════════════════════════════════════════════════════════════════

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

  @override
  ConsumerState<_SpotOrderBookPanel> createState() =>
      _SpotOrderBookPanelState();
}

class _SpotOrderBookPanelState extends ConsumerState<_SpotOrderBookPanel> {
  /// 0=双向 1=仅卖 2=仅买
  int _bookMode = 0;

  /// 0=depth0(最少小数位) 1=depth1(中间) 2=depth2(最多小数位)
  int _depthStep = 2;

  static String _precisionLabel(int precision) {
    if (precision <= 0) return '1';
    return '0.${'0' * (precision - 1)}1';
  }

  void _showDepthStepSheet(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final state = ref.read(spotProvider(widget.symbol));
    final options = [
      (0, state.depth0Pre),
      (1, state.depth1Pre),
      (2, state.depth2Pre),
    ];
    showModalBottomSheet<void>(
      context: context,
      useRootNavigator: true,
      backgroundColor: cs.surface,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
      ),
      builder: (ctx) => SafeArea(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: options.map((opt) {
            final isActive = _depthStep == opt.$1;
            return GestureDetector(
              onTap: () {
                setState(() => _depthStep = opt.$1);
                Navigator.pop(ctx);
              },
              child: Padding(
                padding:
                    const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
                child: Row(
                  children: [
                    Expanded(
                      child: Text(
                        _precisionLabel(opt.$2),
                        style: TextStyle(
                          color: isActive ? AppColors.brand : cs.onSurface,
                          fontSize: 14,
                        ),
                      ),
                    ),
                    if (isActive)
                      const Icon(Icons.check, color: AppColors.brand, size: 18),
                  ],
                ),
              ),
            );
          }).toList(),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    final state = ref.watch(spotProvider(widget.symbol));
    final rawAsks = state.orderBookAsks;
    final rawBids = state.orderBookBids;
    final n = widget.rowCount;

    final depthPrecision = _depthStep == 0
        ? state.depth0Pre
        : _depthStep == 1
            ? state.depth1Pre
            : state.depth2Pre;

    // 合并档位后按价排序：后端买卖盘均为「价高→价低」；分桶合并可能打乱顺序，需恢复单调性
    final aggregatedAsks = aggregateSpotDepthLevels(rawAsks, depthPrecision)
      ..sort((a, b) => spotDepthP(b).compareTo(spotDepthP(a)));
    final aggregatedBids = aggregateSpotDepthLevels(rawBids, depthPrecision)
      ..sort((a, b) => spotDepthP(b).compareTo(spotDepthP(a)));

    // 精度聚合后买一 >= 卖一时，丢弃两端交叉档位
    removeCrossingDepthLevels(aggregatedAsks, aggregatedBids);

    final askTake = _bookMode == 2 ? 0 : (_bookMode == 1 ? n * 2 : n);
    final bidTake = _bookMode == 1 ? 0 : (_bookMode == 2 ? n * 2 : n);

    // 卖盘：取最接近成交价的 n 档（聚合后价高→价低序列的末尾 n 条）
    final askSlice = askTake > 0 && aggregatedAsks.isNotEmpty
        ? aggregatedAsks.sublist(math.max(0, aggregatedAsks.length - askTake))
        : <Map<String, dynamic>>[];
    // 展示须自上而下「价高→价低」（远离中间价 → 靠近卖一）；若切片呈升序则反转
    final List<Map<String, dynamic>> askRows = askSlice.length >= 2 &&
            spotDepthP(askSlice.first) < spotDepthP(askSlice.last)
        ? askSlice.reversed.toList()
        : List<Map<String, dynamic>>.from(askSlice);

    // 买盘：价高在前，首条即最优买 → 取前 n 档，首行紧贴中间价
    final bidSlice = bidTake > 0
        ? aggregatedBids.take(bidTake).toList()
        : <Map<String, dynamic>>[];

    final bidRows = bidSlice;

    double maxQ = 0.001;
    double totalAsk = 0, totalBid = 0;
    for (final a in askRows) {
      final q = spotDepthQ(a);
      totalAsk += q;
      if (q > maxQ) maxQ = q;
    }
    for (final b in bidRows) {
      final q = spotDepthQ(b);
      totalBid += q;
      if (q > maxQ) maxQ = q;
    }
    final totalDepth = totalAsk + totalBid;
    final bidRatio = totalDepth > 0 ? totalBid / totalDepth : 0.5;
    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: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Flexible(
                child: Text(
                  l10n.priceUsdt,
                  overflow: TextOverflow.ellipsis,
                  style: labelStyle,
                ),
              ),
              const SizedBox(width: 4),
              Flexible(
                child: Text(
                  l10n.amountLabel2(state.baseCoin),
                  overflow: TextOverflow.ellipsis,
                  textAlign: TextAlign.end,
                  style: labelStyle,
                ),
              ),
            ],
          ),
          const SizedBox(height: 4),
          if (_bookMode != 2)
            for (var i = 0; i < askRows.length; i++)
              spotDepthP(askRows[i]) > 0
                  ? _SpotBookRow(
                      key: ValueKey('ask_$i'),
                      isSell: true,
                      price: spotDepthP(askRows[i]),
                      qty: spotDepthQ(askRows[i]),
                      maxQ: maxQ,
                      pricePrecision: depthPrecision,
                      volumePrecision: state.volumePrecision,
                      rowHeight: widget.rowHeight,
                      onTap: widget.onPriceTap != null
                          ? () => widget.onPriceTap!(spotDepthP(askRows[i]))
                          : null,
                    )
                  : _SpotBookRowPlaceholder(
                      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(
                    state.lastPriceStr != null
                        ? formatRawPrice(state.lastPriceStr!)
                        : formatPrice(state.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(
                    formatFiatPrice(state.lastPrice),
                    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++)
              spotDepthP(bidRows[i]) > 0
                  ? _SpotBookRow(
                      key: ValueKey('bid_$i'),
                      isSell: false,
                      price: spotDepthP(bidRows[i]),
                      qty: spotDepthQ(bidRows[i]),
                      maxQ: maxQ,
                      pricePrecision: depthPrecision,
                      volumePrecision: state.volumePrecision,
                      rowHeight: widget.rowHeight,
                      onTap: widget.onPriceTap != null
                          ? () => widget.onPriceTap!(spotDepthP(bidRows[i]))
                          : null,
                    )
                  : _SpotBookRowPlaceholder(
                      key: ValueKey('bid_ph_$i'),
                      rowHeight: widget.rowHeight,
                    ),
          // 不用 Spacer，留白留在底部
          const SizedBox(height: 6),
          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: [
              GestureDetector(
                onTap: () => _showDepthStepSheet(context),
                child: Container(
                  height: 20,
                  padding: const EdgeInsets.symmetric(horizontal: 4),
                  alignment: Alignment.center,
                  decoration: BoxDecoration(
                    border: Border.all(color: cs.outline.withAlpha(80)),
                    borderRadius: BorderRadius.circular(4),
                  ),
                  child: Text(
                    _precisionLabel(depthPrecision),
                    style: TextStyle(
                      color: cs.onSurface.withAlpha(180),
                      fontSize: 10,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
              const SizedBox(width: 4),
              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: _SpotBookModeIcon(mode: _bookMode),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

/// 与合约页订单簿模式图标一致
class _SpotBookModeIcon extends StatelessWidget {
  const _SpotBookModeIcon({required this.mode});
  final int mode;

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

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

    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,
    );
  }
}

class _SpotBookRowPlaceholder extends StatelessWidget {
  const _SpotBookRowPlaceholder({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),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _SpotBookRow extends StatelessWidget {
  const _SpotBookRow({
    super.key,
    required this.isSell,
    required this.price,
    required this.qty,
    required this.maxQ,
    required this.pricePrecision,
    required this.volumePrecision,
    required this.rowHeight,
    this.onTap,
  });
  final bool isSell;
  final double price;
  final double qty;
  final double maxQ;
  final int pricePrecision;
  final int volumePrecision;
  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: (_, c) {
            final ratio = (qty / (maxQ <= 0 ? 1 : maxQ)).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: c.maxWidth * ratio,
                    decoration: BoxDecoration(
                      color: color.withAlpha(38),
                      borderRadius: BorderRadius.circular(2),
                    ),
                  ),
                ),
                Padding(
                  padding:
                      const EdgeInsets.symmetric(horizontal: 2, vertical: 1),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Text(
                        price > 0
                            ? price.toStringAsFixed(pricePrecision)
                            : '--',
                        style: TextStyle(
                          color: color,
                          fontSize: 13,
                          fontWeight: FontWeight.w500,
                          fontFeatures: const [FontFeature.tabularFigures()],
                        ),
                      ),
                      Text(
                        qty > 0 ? qty.toStringAsFixed(volumePrecision) : '--',
                        style: TextStyle(
                          color: cs.onSurface,
                          fontSize: 13,
                          fontFeatures: const [FontFeature.tabularFigures()],
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

// ══════════════════════════════════════════════════════════════════════
// 底部：当前委托 / 资产
// ══════════════════════════════════════════════════════════════════════

// 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);
      }
    });
    return OverflowBox(
      alignment: Alignment.topLeft,
      minHeight: 0,
      maxHeight: double.infinity,
      child: KeyedSubtree(key: _key, child: widget.child),
    );
  }
}

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

  @override
  ConsumerState<_SpotBottomSection> createState() => _SpotBottomSectionState();
}

class _SpotBottomSectionState extends ConsumerState<_SpotBottomSection> {
  late PageController _pageController;
  bool _programmaticSwitch = false;
  final _tabHeights = [420.0, 280.0]; // orders, assets

  static int _tabToIndex(SpotTab tab) => tab == SpotTab.orders ? 0 : 1;

  static SpotTab _indexToTab(int index) =>
      index == 0 ? SpotTab.orders : SpotTab.assets;

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

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

  void _onTabTap(SpotTab tab) {
    _programmaticSwitch = true;
    ref.read(spotProvider(widget.symbol).notifier).setActiveTab(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 l10n = AppLocalizations.of(context)!;
    final symbol = widget.symbol;
    final provider = spotProvider(symbol);
    final activeTab = ref.watch(provider.select((s) => s.activeTab));
    final ordersCount =
        ref.watch(provider.select((s) => s.displayOrders.length));
    final isLoggedIn = ref.watch(isLoggedInProvider);

    return Container(
      decoration: BoxDecoration(
        border: Border(top: BorderSide(color: cs.outline)),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            child: Row(
              children: [
                _SpotBottomTab(
                  label: l10n.currentOrdersTab(ordersCount),
                  active: activeTab == SpotTab.orders,
                  onTap: () => _onTabTap(SpotTab.orders),
                ),
                const SizedBox(width: 16),
                _SpotBottomTab(
                  label: l10n.assetsTab,
                  active: activeTab == SpotTab.assets,
                  onTap: () => _onTabTap(SpotTab.assets),
                ),
                const Spacer(),
                AnimatedSwitcher(
                  duration: const Duration(milliseconds: 200),
                  child: activeTab == SpotTab.orders && isLoggedIn
                      ? _OrdersToolbar(
                          key: const ValueKey('orders_toolbar'),
                          symbol: symbol,
                        )
                      : const SizedBox.shrink(key: ValueKey('no_toolbar')),
                ),
                GestureDetector(
                  onTap: () {
                    if (!isLoggedIn) {
                      context.push('/login');
                      return;
                    }
                    final n = ref.read(spotProvider(symbol).notifier);
                    n.stopPolling();
                    context.push('/spot/$symbol/history').then((_) {
                      if (context.mounted) n.resumePolling();
                    });
                  },
                  child: Icon(Icons.access_time,
                      color: cs.onSurface.withAlpha(153), size: 18),
                ),
              ],
            ),
          ),
          // 滑动内容区
          SizedBox(
            height: _tabHeights.reduce(math.max).clamp(100.0, 2000.0),
            child: RepaintBoundary(
              child: PageView(
                controller: _pageController,
                physics: const ClampingScrollPhysics(),
                onPageChanged: (index) {
                  if (_programmaticSwitch) {
                    _programmaticSwitch = false;
                    return;
                  }
                  ref
                      .read(spotProvider(symbol).notifier)
                      .setActiveTab(_indexToTab(index));
                },
                children: [
                  _MeasureSize(
                    onSize: (h) {
                      if (_tabHeights[0] != h)
                        setState(() => _tabHeights[0] = h);
                    },
                    child: _SpotOrdersContent(symbol: symbol),
                  ),
                  _MeasureSize(
                    onSize: (h) {
                      if (_tabHeights[1] != h)
                        setState(() => _tabHeights[1] = h);
                    },
                    child: _SpotAssetsContent(symbol: symbol),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    final provider = spotProvider(symbol);
    final notifier = ref.read(provider.notifier);
    final state = ref.watch(provider);
    final orders = state.displayOrders;
    final isLoggedIn = ref.watch(isLoggedInProvider);

    if (!isLoggedIn || !orders.any((o) => o.isPending)) {
      return const SizedBox.shrink();
    }

    return GestureDetector(
      onTap: () async {
        final err = await notifier.cancelAll();
        if (!context.mounted) return;
        if (err != null) {
          showTopToast(
            context,
            message: _resolveSpotError(err, l10n),
            backgroundColor: AppColors.fall,
          );
        } else {
          showTopToast(
            context,
            message: l10n.cancelSuccess,
            backgroundColor: AppColors.rise,
          );
        }
      },
      child: Container(
        margin: const EdgeInsets.only(right: 8),
        padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
        decoration: BoxDecoration(
          color: cs.inverseSurface,
          borderRadius: BorderRadius.circular(4),
        ),
        child: Text(
          l10n.cancelAll,
          style: TextStyle(
            color: cs.onInverseSurface,
            fontSize: 11,
            fontWeight: FontWeight.w500,
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    final provider = spotProvider(symbol);
    final notifier = ref.read(provider.notifier);
    final state = ref.watch(provider);
    final orders = state.displayOrders;
    final isLoggedIn = ref.watch(isLoggedInProvider);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
          child: Row(
            children: [
              GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTap: notifier.toggleHideOtherSymbols,
                child: Row(
                  children: [
                    _SpotCheckBox(checked: state.hideOtherSymbols),
                    const SizedBox(width: 4),
                    Text(l10n.hideOtherSymbols,
                        style: TextStyle(
                            color: cs.onSurface.withAlpha(153), fontSize: 11)),
                  ],
                ),
              ),
            ],
          ),
        ),
        if (!isLoggedIn)
          _LoginPlaceholder()
        else if (orders.isEmpty)
          _EmptyHint(text: l10n.noOpenOrders)
        else
          ListView.builder(
            padding: EdgeInsets.zero,
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            itemCount: orders.length,
            itemBuilder: (_, i) =>
                _SpotOrderRow(symbol: symbol, order: orders[i]),
          ),
      ],
    );
  }
}

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final provider = spotProvider(symbol);
    final state = ref.watch(provider);
    final isLoggedIn = ref.watch(isLoggedInProvider);
    final l10n = AppLocalizations.of(context)!;

    if (!isLoggedIn) return _LoginPlaceholder();
    if (state.wallets.isEmpty) return _EmptyHint(text: l10n.noAssets);

    return ListView.builder(
      padding: EdgeInsets.zero,
      shrinkWrap: true,
      physics: const NeverScrollableScrollPhysics(),
      itemCount: state.wallets.length,
      itemBuilder: (_, i) => _SpotWalletRow(asset: state.wallets[i]),
    );
  }
}

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

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

class _SpotCheckBox extends StatelessWidget {
  const _SpotCheckBox({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 _SpotOrderRow extends ConsumerWidget {
  const _SpotOrderRow({required this.symbol, required this.order});
  final String symbol;
  final SpotOrder order;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    final notifier = ref.read(spotProvider(symbol).notifier);
    final pricePre =
        ref.watch(spotProvider(symbol).select((s) => s.pricePrecision));
    final volPre =
        ref.watch(spotProvider(symbol).select((s) => s.volumePrecision));
    final sideColor =
        order.side == SpotSide.buy ? AppColors.rise : AppColors.fall;
    final sideLabel =
        order.side == SpotSide.buy ? l10n.buyAction : l10n.sellAction;
    final typeLabel =
        order.type == SpotOrderType.limit ? l10n.limitOrder : l10n.marketOrder;
    final symDisplay = order.symbol.replaceAll('/', '');
    final priceDisplay = order.type == SpotOrderType.market && order.price <= 0
        ? l10n.marketPrice
        : formatAmount(order.price, decimals: pricePre);

    return Container(
      padding: const EdgeInsets.fromLTRB(12, 10, 12, 10),
      decoration: BoxDecoration(
        border: Border(
          bottom: BorderSide(
            color: Theme.of(context).scaffoldBackgroundColor,
            width: 6,
          ),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Text(
                symDisplay,
                style: TextStyle(
                  color: cs.onSurface,
                  fontSize: 13,
                  fontWeight: FontWeight.w700,
                ),
              ),
              const SizedBox(width: 8),
              _Tag(text: typeLabel, color: cs.onSurface.withAlpha(180)),
              const SizedBox(width: 4),
              _Tag(text: sideLabel, color: sideColor, filled: true),
              const Spacer(),
              if (order.isPending)
                GestureDetector(
                  onTap: () async {
                    final err = await notifier.cancelOrder(order);
                    if (!context.mounted) return;
                    if (err != null) {
                      showTopToast(context,
                          message: _resolveSpotError(err, l10n),
                          backgroundColor: AppColors.fall);
                    } else {
                      showTopToast(context,
                          message: l10n.cancelSuccess,
                          backgroundColor: AppColors.rise);
                    }
                  },
                  child: Container(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
                    decoration: BoxDecoration(
                      border: Border.all(color: cs.outline.withAlpha(80)),
                      borderRadius: BorderRadius.circular(4),
                    ),
                    child: Text(
                      l10n.cancelLabel,
                      style: TextStyle(color: cs.onSurface, fontSize: 11),
                    ),
                  ),
                ),
            ],
          ),
          const SizedBox(height: 8),
          _spotDataLine(
            context,
            label:
                '${l10n.orderPriceLabel}(${_baseCoin(order.symbol, quote: true)})',
            value: priceDisplay,
          ),
          const SizedBox(height: 3),
          _spotDataLine(
            context,
            label: '${l10n.tradedDealAmount}(${_baseCoin(order.symbol)})',
            value:
                '${formatAmount(order.tradedAmount, decimals: volPre)} / ${formatAmount(order.amount, decimals: volPre)}',
          ),
          if (order.createTime != null) ...[
            const SizedBox(height: 3),
            _spotDataLine(
              context,
              label: l10n.orderTime,
              value: DateFormat('yyyy-MM-dd HH:mm:ss')
                  .format(order.createTime!.toLocal()),
            ),
          ],
        ],
      ),
    );
  }
}

String _baseCoin(String symbol, {bool quote = false}) {
  final s = symbol.replaceAll('/', '').toUpperCase();
  const quotes = ['USDT', 'USDC', 'BUSD', 'TUSD'];
  for (final q in quotes) {
    if (s.endsWith(q) && s.length > q.length) {
      return quote ? q : s.substring(0, s.length - q.length);
    }
  }
  return quote ? 'USDT' : s;
}

Widget _spotDataLine(
  BuildContext context, {
  required String label,
  required String value,
}) {
  final cs = Theme.of(context).colorScheme;
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    children: [
      Text(
        label,
        style: TextStyle(color: cs.onSurface.withAlpha(153), fontSize: 11),
      ),
      Text(
        value,
        style: TextStyle(
          color: cs.onSurface,
          fontSize: 12,
          fontFeatures: const [FontFeature.tabularFigures()],
        ),
      ),
    ],
  );
}

class _SpotWalletRow extends ConsumerWidget {
  const _SpotWalletRow({required this.asset});
  final SpotWalletAsset asset;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    // 与「资产 → 现货」完全一致：lookupSpotCoinConfig / spotCoinIconUrl
    final mapState = ref.watch(spotCoinCacheProvider);
    final coinCfg = lookupSpotCoinConfig(mapState, asset.coin);
    final iconUrl = spotCoinIconUrl(mapState, asset.coin);
    final decimals = coinCfg?.assetDisplayDecimals ?? 2;

    return Container(
      padding: const EdgeInsets.fromLTRB(12, 14, 12, 14),
      decoration: BoxDecoration(
        border: Border(
          bottom: BorderSide(color: cs.outline.withAlpha(40), width: 0.6),
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 币种图标 + 名称
          Row(
            children: [
              CoinIcon(
                symbol: asset.coin,
                iconUrl: iconUrl,
                size: 40,
                shape: BoxShape.circle,
              ),
              const SizedBox(width: 10),
              Text(asset.coin,
                  style: TextStyle(
                      color: cs.onSurface,
                      fontSize: 16,
                      fontWeight: FontWeight.w700)),
            ],
          ),
          const SizedBox(height: 14),
          // 三列数据：资产余额 / 可用 / 不可用
          Row(
            children: [
              Expanded(
                child: _WalletCell(
                  label: l10n.assetBalance,
                  value: formatAmount(asset.total, decimals: decimals),
                  align: CrossAxisAlignment.start,
                ),
              ),
              Expanded(
                child: _WalletCell(
                  label: l10n.availableLabel,
                  value: formatAmount(asset.balance, decimals: decimals),
                  align: CrossAxisAlignment.center,
                ),
              ),
              Expanded(
                child: _WalletCell(
                  label: l10n.unavailableLabel,
                  value: formatAmount(asset.frozenBalance, decimals: decimals),
                  align: CrossAxisAlignment.end,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class _WalletCell extends StatelessWidget {
  const _WalletCell(
      {required this.label, required this.value, required this.align});
  final String label;
  final String value;
  final CrossAxisAlignment align;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final textAlign = align == CrossAxisAlignment.start
        ? TextAlign.left
        : align == CrossAxisAlignment.end
            ? TextAlign.right
            : TextAlign.center;
    return Column(
      crossAxisAlignment: align,
      children: [
        Text(label,
            style: TextStyle(color: cs.onSurface.withAlpha(140), fontSize: 11),
            textAlign: textAlign),
        const SizedBox(height: 4),
        Text(value,
            style: TextStyle(
              color: cs.onSurface,
              fontSize: 13,
              fontWeight: FontWeight.w600,
              fontFeatures: const [FontFeature.tabularFigures()],
            ),
            textAlign: textAlign),
      ],
    );
  }
}

class _Tag extends StatelessWidget {
  const _Tag({required this.text, required this.color, this.filled = false});
  final String text;
  final Color color;
  final bool filled;

  @override
  Widget build(BuildContext context) {
    final isDark = Theme.of(context).brightness == Brightness.dark;
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
      decoration: BoxDecoration(
        color: filled ? color.withAlpha(isDark ? 55 : 35) : Colors.transparent,
        border: Border.all(color: color.withAlpha(160), width: 0.5),
        borderRadius: BorderRadius.circular(3),
      ),
      child: Text(
        text,
        style: TextStyle(
          color: color,
          fontSize: 9,
          fontWeight: FontWeight.w500,
        ),
      ),
    );
  }
}

class _EmptyHint extends StatelessWidget {
  const _EmptyHint({required this.text});
  final String text;

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return Container(
      padding: const EdgeInsets.symmetric(vertical: 36),
      alignment: Alignment.center,
      child: Text(text,
          style: TextStyle(color: cs.onSurface.withAlpha(140), fontSize: 13)),
    );
  }
}

class _LoginPlaceholder extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    return Container(
      padding: const EdgeInsets.symmetric(vertical: 36),
      alignment: Alignment.center,
      child: Column(
        children: [
          Text(l10n.loginPrompt,
              style:
                  TextStyle(color: cs.onSurface.withAlpha(140), fontSize: 13)),
          const SizedBox(height: 10),
          OutlinedButton(
            onPressed: () => context.push('/login'),
            child: Text(l10n.loginText),
          ),
        ],
      ),
    );
  }
}

// ══════════════════════════════════════════════════════════════════════
// 输入精度限制
// ══════════════════════════════════════════════════════════════════════

class _PrecisionInputFormatter extends TextInputFormatter {
  _PrecisionInputFormatter(this.decimalRange);
  final int decimalRange;

  @override
  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue, TextEditingValue newValue) {
    final t = newValue.text;
    if (t.isEmpty) return newValue;
    if (decimalRange <= 0) {
      // 整数：保留数字
      if (RegExp(r'^[0-9]+$').hasMatch(t)) return newValue;
      return oldValue;
    }
    final pattern = RegExp(r'^\d*\.?\d{0,' + decimalRange.toString() + r'}$');
    if (pattern.hasMatch(t)) return newValue;
    return oldValue;
  }
}

// ══════════════════════════════════════════════════════════════════════
// 骨架屏
// ══════════════════════════════════════════════════════════════════════

class _SpotShimmer extends StatelessWidget {
  const _SpotShimmer();

  @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),
                          // 下单按钮
                          shimmerFill(44, 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),
                            ],
                          ),
                          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(80, 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> {
  final _key = GlobalKey();

  void _report() {
    final ctx = _key.currentContext;
    if (ctx == null) return;
    final box = ctx.findRenderObject() as RenderBox?;
    if (box == null || !box.hasSize) return;
    widget.onHeight(box.size.height);
  }

  @override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) => _report());
    return KeyedSubtree(key: _key, child: widget.child);
  }
}

// ══════════════════════════════════════════════════════════════════════
// 错误码 → 本地化
// ══════════════════════════════════════════════════════════════════════

String _resolveSpotError(String err, AppLocalizations l10n) {
  switch (err) {
    case 'errEnterPrice':
      return l10n.enterPrice;
    case 'errEnterAmount':
      return l10n.errEnterAmount;
    case 'errEnterTriggerPrice':
      return l10n.enterTriggerPrice;
    case 'errInvalidOrderId':
      return l10n.errInvalidOrderId;
    case 'errVolumeInsufficient':
      return l10n.errVolumeInsufficient;
    case 'errNoOrdersToCancel':
      return l10n.errNoOrdersToCancel;
    case 'errTimeout':
      return l10n.errTimeout;
    case 'errNetworkError':
      return l10n.errNetworkError;
    case 'errConditionalNotSupported':
      return l10n.spotConditionalNotSupported;
    default:
      return err;
  }
}
