import 'dart:math';
import 'package:flutter/material.dart';
import 'package:k_chart_plus/chart_translations.dart';
import 'package:k_chart_plus/k_chart_plus.dart';

class DepthChart extends StatefulWidget {
  final List<DepthEntity> bids, asks;
  final int baseUnit;
  final int quoteUnit;
  final Offset offset;
  final ChartColors chartColors;
  final DepthChartTranslations chartTranslations;

  /// 全量数据的最大累计量，缩放时 Y 轴范围不变
  double get fullMaxVolume {
    if (bids.isEmpty && asks.isEmpty) return 0;
    double maxVol = 0;
    if (bids.isNotEmpty) maxVol = max(maxVol, bids.first.vol);
    if (asks.isNotEmpty) maxVol = max(maxVol, asks.last.vol);
    return maxVol * 1.05;
  }

  DepthChart(
    this.bids,
    this.asks,
    this.chartColors, {
    this.baseUnit = 2,
    this.quoteUnit = 6,
    this.offset = const Offset(10, 10),
    this.chartTranslations = const DepthChartTranslations(),
  });

  @override
  _DepthChartState createState() => _DepthChartState();
}

class _DepthChartState extends State<DepthChart> {
  Offset? pressOffset;
  bool isLongPress = false;
  double _scale = 1.0;       // 缩放倍数：1.0=全量，放大时缩小价格可视范围
  double _lastScale = 1.0;

  /// 根据缩放截取买卖盘的可视价格范围。
  /// 放大时只显示中间价附近的档位，但保留累计量（不从 0 开始）。
  List<DepthEntity> _visibleBids() {
    if (_scale <= 1.0 || widget.bids.length <= 2) return widget.bids;
    final count = (widget.bids.length / _scale).ceil().clamp(2, widget.bids.length);
    // 买盘：取末尾 count 个（靠近中间价），保留原始累计量
    return widget.bids.sublist(widget.bids.length - count);
  }

  List<DepthEntity> _visibleAsks() {
    if (_scale <= 1.0 || widget.asks.length <= 2) return widget.asks;
    final count = (widget.asks.length / _scale).ceil().clamp(2, widget.asks.length);
    // 卖盘：取前 count 个（靠近中间价），保留原始累计量
    return widget.asks.sublist(0, count);
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onScaleStart: (_) => _lastScale = _scale,
      onScaleUpdate: (details) {
        if (details.scale != 1.0) {
          setState(() {
            _scale = (_lastScale * details.scale).clamp(1.0, 10.0);
          });
        }
      },
      onLongPressStart: (details) {
        pressOffset = details.localPosition;
        isLongPress = true;
        setState(() {});
      },
      onLongPressMoveUpdate: (details) {
        pressOffset = details.localPosition;
        isLongPress = true;
        setState(() {});
      },
      onLongPressEnd: (details) {
        pressOffset = null;
        isLongPress = false;
        setState(() {});
      },
      child: CustomPaint(
        size: Size(double.infinity, double.infinity),
        painter: DepthChartPainter(
          _visibleBids(),
          _visibleAsks(),
          pressOffset,
          isLongPress,
          widget.baseUnit,
          widget.quoteUnit,
          widget.chartColors,
          widget.offset,
          widget.chartTranslations,
          fixedMaxVolume: widget.fullMaxVolume,
        ),
      ),
    );
  }
}

class DepthChartPainter extends CustomPainter {
  //Buy//Sell
  List<DepthEntity>? mBuyData, mSellData;
  Offset? pressOffset;
  bool isLongPress;
  int baseUnit;
  int quoteUnit;
  ChartColors chartColors;

  double mPaddingBottom = 32.0;
  double mWidth = 0.0, mDrawHeight = 0.0, mDrawWidth = 0.0;
  double? mBuyPointWidth, mSellPointWidth;

  Offset offset;
  DepthChartTranslations chartTranslations;

  //最大的委托量（fixedMaxVolume 用于缩放时固定 Y 轴范围）
  double? mMaxVolume, mMultiple;
  double? fixedMaxVolume;

  //右侧绘制个数
  int mLineCount = 4;

  Path? mBuyPath, mSellPath;

  //买卖出区域边线绘制画笔  //买卖出取悦绘制画笔
  Paint? mBuyLinePaint,
      mSellLinePaint,
      mBuyPathPaint,
      mSellPathPaint,
      selectPaint,
      selectBorderPaint;

  DepthChartPainter(
    this.mBuyData,
    this.mSellData,
    this.pressOffset,
    this.isLongPress,
    this.baseUnit,
    this.quoteUnit,
    this.chartColors,
    this.offset,
    this.chartTranslations, {
    this.fixedMaxVolume,
  }) {
    mBuyLinePaint ??= Paint()
      ..isAntiAlias = true
      ..color = this.chartColors.depthBuyColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.5;
    mSellLinePaint ??= Paint()
      ..isAntiAlias = true
      ..color = this.chartColors.depthSellColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = 1.5;

    // 渐变 Paint 在 paint() 里按实际尺寸重建，这里仅占位
    mBuyPathPaint ??= Paint()..isAntiAlias = true;
    mSellPathPaint ??= Paint()..isAntiAlias = true;
    mBuyPath ??= Path();
    mSellPath ??= Path();
    init();
  }

  void init() {
    if (mBuyData == null ||
        mBuyData!.isEmpty ||
        mSellData == null ||
        mSellData!.isEmpty) return;
    if (fixedMaxVolume != null && fixedMaxVolume! > 0) {
      // 缩放时使用固定的 Y 轴范围，保持曲线形状一致
      mMaxVolume = fixedMaxVolume;
    } else {
      mMaxVolume = mBuyData![0].vol;
      mMaxVolume = max(mMaxVolume!, mSellData!.last.vol);
      mMaxVolume = mMaxVolume! * 1.05;
    }
    mMultiple = mMaxVolume! / mLineCount;

    selectPaint = Paint()
      ..isAntiAlias = true
      ..color = chartColors.selectFillColor;
    selectBorderPaint = Paint()
      ..isAntiAlias = true
      ..color = chartColors.selectBorderColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = 0.4;
  }

  @override
  void paint(Canvas canvas, Size size) {
    if (mBuyData == null ||
        mSellData == null ||
        mBuyData!.isEmpty ||
        mSellData!.isEmpty) return;
    mWidth = size.width;
    mDrawWidth = mWidth / 2;
    mDrawHeight = size.height - mPaddingBottom;

    // 用实际高度创建垂直渐变（顶部 0.5 → 底部 0.08），匹配原型设计
    final bidGradient = LinearGradient(
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
      colors: [
        chartColors.depthBuyColor.withOpacity(0.5),
        chartColors.depthBuyColor.withOpacity(0.08),
      ],
    );
    final askGradient = LinearGradient(
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
      colors: [
        chartColors.depthSellColor.withOpacity(0.5),
        chartColors.depthSellColor.withOpacity(0.08),
      ],
    );
    mBuyPathPaint = Paint()
      ..isAntiAlias = true
      ..shader = bidGradient.createShader(
          Rect.fromLTWH(0, 0, mDrawWidth, mDrawHeight));
    mSellPathPaint = Paint()
      ..isAntiAlias = true
      ..shader = askGradient.createShader(
          Rect.fromLTWH(mDrawWidth, 0, mDrawWidth, mDrawHeight));

    canvas.save();
    drawBuy(canvas);
    drawSell(canvas);
    drawText(canvas);
    canvas.restore();
  }

  void drawBuy(Canvas canvas) {
    mBuyPointWidth =
        (mDrawWidth / (mBuyData!.length - 1 == 0 ? 1 : mBuyData!.length - 1));
    mBuyPath!.reset();
    double x;
    double y;
    for (int i = 0; i < mBuyData!.length; i++) {
      if (i == 0) {
        mBuyPath!.moveTo(0, getY(mBuyData![0].vol));
      }
      x = mBuyPointWidth! * i;
      y = getY(mBuyData![i].vol);
      if (i >= 1) {
        canvas.drawLine(
            Offset(mBuyPointWidth! * (i - 1), getY(mBuyData![i - 1].vol)),
            Offset(x, y),
            mBuyLinePaint!);
      }
      if (i != mBuyData!.length - 1) {
        mBuyPath!.quadraticBezierTo(
            x, y, mBuyPointWidth! * (i + 1), getY(mBuyData![i + 1].vol));
      } else {
        if (i == 0) {
          mBuyPath!.lineTo(mDrawWidth, y);
          mBuyPath!.lineTo(mDrawWidth, mDrawHeight);
          mBuyPath!.lineTo(0, mDrawHeight);
        } else {
          mBuyPath!.quadraticBezierTo(x, y, x, mDrawHeight);
          mBuyPath!.quadraticBezierTo(x, mDrawHeight, 0, mDrawHeight);
        }
        mBuyPath!.close();
      }
    }
    canvas.drawPath(mBuyPath!, mBuyPathPaint!);
  }

  void drawSell(Canvas canvas) {
    mSellPointWidth =
        (mDrawWidth / (mSellData!.length - 1 == 0 ? 1 : mSellData!.length - 1));
    mSellPath!.reset();
    double x;
    double y;
    for (int i = 0; i < mSellData!.length; i++) {
      if (i == 0) {
        mSellPath!.moveTo(mDrawWidth, getY(mSellData![0].vol));
      }
      x = (mSellPointWidth! * i) + mDrawWidth;
      y = getY(mSellData![i].vol);
      if (i >= 1) {
        canvas.drawLine(
            Offset((mSellPointWidth! * (i - 1)) + mDrawWidth,
                getY(mSellData![i - 1].vol)),
            Offset(x, y),
            mSellLinePaint!);
      }
      if (i != mSellData!.length - 1) {
        mSellPath!.quadraticBezierTo(
            x,
            y,
            (mSellPointWidth! * (i + 1)) + mDrawWidth,
            getY(mSellData![i + 1].vol));
      } else {
        if (i == 0) {
          mSellPath!.lineTo(mWidth, y);
          mSellPath!.lineTo(mWidth, mDrawHeight);
          mSellPath!.lineTo(mDrawWidth, mDrawHeight);
        } else {
          mSellPath!.quadraticBezierTo(mWidth, y, x, mDrawHeight);
          mSellPath!.quadraticBezierTo(x, mDrawHeight, mDrawWidth, mDrawHeight);
        }
        mSellPath!.close();
      }
    }
    canvas.drawPath(mSellPath!, mSellPathPaint!);
  }

  // int? mLastPosition;

  void drawText(Canvas canvas) {
    double value;
    String str;
    for (int j = 0; j < mLineCount; j++) {
      value = mMaxVolume! - mMultiple! * j;
      str = value.toStringAsFixed(baseUnit);
      var tp = getTextPainter(str);
      tp.layout();
      tp.paint(
          canvas,
          Offset(
              mWidth - tp.width, mDrawHeight / mLineCount * j + tp.height / 2));
    }

    var startText = mBuyData!.first.price.toStringAsFixed(quoteUnit);
    TextPainter startTP = getTextPainter(startText);
    startTP.layout();
    startTP.paint(canvas, Offset(0, getBottomTextY(startTP.height)));

    double centerPrice = (mBuyData!.last.price + mSellData!.first.price) / 2;

    var center = centerPrice.toStringAsFixed(quoteUnit);
    TextPainter centerTP = getTextPainter(center);
    centerTP.layout();
    centerTP.paint(
        canvas,
        Offset(
            mDrawWidth - centerTP.width / 2, getBottomTextY(centerTP.height)));

    var endText = mSellData!.last.price.toStringAsFixed(quoteUnit);
    TextPainter endTP = getTextPainter(endText);
    endTP.layout();
    endTP.paint(
        canvas, Offset(mWidth - endTP.width, getBottomTextY(endTP.height)));

    var leftHalfText =
        ((mBuyData!.first.price + centerPrice) / 2).toStringAsFixed(quoteUnit);
    TextPainter leftHalfTP = getTextPainter(leftHalfText);
    leftHalfTP.layout();
    leftHalfTP.paint(
        canvas,
        Offset((mDrawWidth - leftHalfTP.width) / 2,
            getBottomTextY(leftHalfTP.height)));

    var rightHalfText =
        ((mSellData!.last.price + centerPrice) / 2).toStringAsFixed(quoteUnit);
    TextPainter rightHalfTP = getTextPainter(rightHalfText);
    rightHalfTP.layout();
    rightHalfTP.paint(
        canvas,
        Offset((mDrawWidth + mWidth - rightHalfTP.width) / 2,
            getBottomTextY(rightHalfTP.height)));

    if (isLongPress == true) {
      if (pressOffset!.dx <= mDrawWidth) {
        int index = _indexOfTranslateX(
            pressOffset!.dx, 0, mBuyData!.length - 1, getBuyX);
        drawSelectView(canvas, index, true);
      } else {
        int index = _indexOfTranslateX(
            pressOffset!.dx, 0, mSellData!.length - 1, getSellX);
        drawSelectView(canvas, index, false);
      }
    }
  }

  void drawSelectView(Canvas canvas, int index, bool isLeft) {
    DepthEntity entity = isLeft ? mBuyData![index] : mSellData![index];
    double dx = isLeft ? getBuyX(index) : getSellX(index);
    double dy = getY(entity.vol);
    double radius = 8.0;

    if (dx < mDrawWidth) {
      canvas.drawCircle(Offset(dx, dy), radius / 3,
          mBuyLinePaint!..style = PaintingStyle.fill);
      canvas.drawCircle(
          Offset(dx, dy), radius, mBuyLinePaint!..style = PaintingStyle.stroke);
    } else {
      canvas.drawCircle(Offset(dx, dy), radius / 3,
          mSellLinePaint!..style = PaintingStyle.fill);
      canvas.drawCircle(Offset(dx, dy), radius,
          mSellLinePaint!..style = PaintingStyle.stroke);
    }

    ///draw popup info
    ///
    _PopupPainter popupPainter = _PopupPainter(
      chartTranslations: this.chartTranslations,
      chartColors: this.chartColors,
      price: entity.price.toStringAsFixed(quoteUnit),
      amount: entity.vol.toStringAsFixed(baseUnit),
    );
    dx = dx < mDrawWidth ? dx + offset.dx : dx - offset.dx - popupPainter.width;
    dy = dy < mDrawHeight / 2
        ? dy + offset.dy
        : dy - offset.dy - popupPainter.height;
    Rect rect = Rect.fromLTWH(dx, dy, popupPainter.width, popupPainter.height);
    RRect boxRect = RRect.fromRectAndRadius(rect, Radius.circular(2.5));

    canvas.drawRRect(boxRect, selectPaint!);
    canvas.drawRRect(boxRect, selectBorderPaint!);
    popupPainter.paint(canvas, rect.topLeft);
  }

  ///Binary search for current value: index
  int _indexOfTranslateX(double translateX, int start, int end, Function getX) {
    if (end == start || end == -1) {
      return start;
    }
    if (end - start == 1) {
      double startValue = getX(start);
      double endValue = getX(end);
      return (translateX - startValue).abs() < (translateX - endValue).abs()
          ? start
          : end;
    }
    int mid = start + (end - start) ~/ 2;
    double midValue = getX(mid);
    if (translateX < midValue) {
      return _indexOfTranslateX(translateX, start, mid, getX);
    } else if (translateX > midValue) {
      return _indexOfTranslateX(translateX, mid, end, getX);
    } else {
      return mid;
    }
  }

  double getBuyX(int position) => position * mBuyPointWidth!;

  double getSellX(int position) => position * mSellPointWidth! + mDrawWidth;

  getTextPainter(String text) => TextPainter(
        text: TextSpan(
          text: "$text",
          style: TextStyle(color: chartColors.defaultTextColor, fontSize: 10),
        ),
        textDirection: TextDirection.ltr,
      );

  double getBottomTextY(double textHeight) =>
      (mPaddingBottom - textHeight) / 2 + mDrawHeight;

  double getY(double volume) =>
      mDrawHeight - (mDrawHeight) * volume / mMaxVolume!;

  @override
  bool shouldRepaint(DepthChartPainter oldDelegate) {
//    return oldDelegate.mBuyData != mBuyData ||
//        oldDelegate.mSellData != mSellData ||
//        oldDelegate.isLongPress != isLongPress ||
//        oldDelegate.pressOffset != pressOffset;
    return true;
  }
}

class _PopupPainter {
  ///setting
  final double space = 3.5;
  final double padding = 8.0;

  late final TextPainter pricePaint;
  late final TextPainter amountPaint;
  late final ChartColors chartColors;

  ///getter
  double get width => max(pricePaint.width, amountPaint.width) + 2 * padding;
  double get height =>
      pricePaint.height + amountPaint.height + space + 2 * padding;

  _PopupPainter({
    required DepthChartTranslations chartTranslations,
    required ChartColors chartColors,
    required String price,
    required String amount,
  }) {
    this.chartColors = chartColors;
    this.pricePaint = _getTextPainter(chartTranslations.price, price);
    this.amountPaint = _getTextPainter(chartTranslations.amount, amount);
    this.pricePaint.layout();
    this.amountPaint.layout();
  }

  void paint(Canvas canvas, Offset offset) {
    pricePaint.paint(canvas, offset + Offset(padding, padding));
    amountPaint.paint(
        canvas, offset + Offset(padding, pricePaint.height + space + padding));
  }

  TextPainter _getTextPainter(String label, String content) {
    return TextPainter(
      text: TextSpan(
        text: "$label: ",
        style: TextStyle(
            color: this.chartColors.infoWindowTitleColor, fontSize: 10),
        children: [
          TextSpan(
            text: content,
            style: TextStyle(
                color: this.chartColors.infoWindowNormalColor, fontSize: 10),
          ),
        ],
      ),
      textDirection: TextDirection.ltr,
    );
  }
}
