import 'dart:math' as math;

import '../../data/models/market/order_book_entry.dart';

double spotDepthP(Map<String, dynamic> e) =>
    (e['price'] as num?)?.toDouble() ??
    (e['p'] as num?)?.toDouble() ??
    0;

double spotDepthQ(Map<String, dynamic> e) =>
    (e['quantity'] as num?)?.toDouble() ??
    (e['amount'] as num?)?.toDouble() ??
    (e['qty'] as num?)?.toDouble() ??
    0;

/// 与现货交易页盘口一致：按 [depthPrecision] 分桶合并同价位数量。
List<Map<String, dynamic>> aggregateSpotDepthLevels(
  List<Map<String, dynamic>> levels,
  int depthPrecision,
) {
  final merged = <double, double>{};
  final keysInOrder = <double>[];
  for (final e in levels) {
    final p = spotDepthP(e);
    final q = spotDepthQ(e);
    if (p <= 0 || q <= 0) continue;
    final key = double.parse(p.toStringAsFixed(depthPrecision));
    if (!merged.containsKey(key)) keysInOrder.add(key);
    merged[key] = (merged[key] ?? 0) + q;
  }
  return keysInOrder
      .map((k) => <String, dynamic>{'price': k, 'quantity': merged[k]!})
      .toList();
}

/// 聚合排序后，若买盘最高价 >= 卖盘最低价（精度聚合导致价格相同），
/// 丢弃这两条撮合临界档位，避免盘口出现交叉价格。
void removeCrossingDepthLevels(
  List<Map<String, dynamic>> asks, // 已按价降序排列（asks[0]=最高卖，asks.last=最低卖）
  List<Map<String, dynamic>> bids, // 已按价降序排列（bids[0]=最高买，bids.last=最低买）
) {
  if (asks.isEmpty || bids.isEmpty) return;
  final bestBid = spotDepthP(bids.first);
  final bestAsk = spotDepthP(asks.last);
  if (bestBid >= bestAsk) {
    bids.removeAt(0);
    asks.removeLast();
  }
}

List<OrderBookEntry> _mapsToEntries(List<Map<String, dynamic>> rows) {
  double cum = 0;
  return rows.map((e) {
    final amt = spotDepthQ(e);
    cum += amt;
    return OrderBookEntry(
      price: spotDepthP(e),
      amount: amt,
      total: cum,
    );
  }).toList();
}

/// 与现货交易页 [_SpotOrderBookPanel] 相同的聚合与切片，输出合并盘口/深度图用的 [OrderBook]。
/// asks：价升序（index 0 为最优卖）；bids：价降序（index 0 为最优买）。
OrderBook? spotRawDepthToOrderBook({
  required List<Map<String, dynamic>> rawAsks,
  required List<Map<String, dynamic>> rawBids,
  required int depthPrecision,
  int rowPairs = 12,
}) {
  if (rawAsks.isEmpty && rawBids.isEmpty) return null;

  final n = rowPairs;
  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);

  const bookMode = 0;
  final askTake = bookMode == 2 ? 0 : (bookMode == 1 ? n * 2 : n);
  final bidTake = bookMode == 1 ? 0 : (bookMode == 2 ? n * 2 : 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);

  final bidSlice = bidTake > 0
      ? aggregatedBids.take(bidTake).toList()
      : <Map<String, dynamic>>[];
  final bidRows = bidSlice;

  final asksAsc = askRows.reversed.toList();

  return OrderBook(
    asks: _mapsToEntries(asksAsc),
    bids: _mapsToEntries(bidRows),
  );
}
