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

import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:puzzleweave/audio/jc_audio_controller.dart';
import 'package:puzzleweave/config/device.dart';
import 'package:puzzleweave/homepage/home_board.dart';
import 'package:puzzleweave/l10n/app_localizations.dart';
import 'package:puzzleweave/models/cached_request.dart';
import 'package:puzzleweave/models/data.dart';
import 'package:puzzleweave/models/items.dart';
import 'package:puzzleweave/play/confetti_layer.dart';
import 'package:puzzleweave/skin/skin.dart';
import 'package:puzzleweave/utils/star.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';

final Logger _log = Logger('home_board_play');

// ignore: must_be_immutable
class HomeBoardPlay extends StatefulWidget {
  final double canvasWidth;
  final double canvasHeight;
  final GlobalKey collectionKey; //  接收 Collection Button Key, 方便定位
  VoidCallback? onCollectionDone; // 新增一个合集完成的回调，外部home_screen可能会关心，当合集解锁动画完成，home_screen的左上角合集icon需要放大再复原

  HomeBoardPlay({super.key, required this.canvasWidth, required this.canvasHeight, required this.collectionKey, this.onCollectionDone});

  @override
  State<HomeBoardPlay> createState() => HomeBoardPlayState();
}

class HomeBoardPlayState extends State<HomeBoardPlay> with TickerProviderStateMixin {
  late HomeBoard board;
  late JcAudioController audio;
  late Data data;
  List<ListItem>? collection;
  late CachedRequest collectionCachedRequest;
  late StreamSubscription? collectionSubscription;

  late ConfettiLayer confettiLayer;

  // 翻牌动画，完成一个关卡后翻开一个卡片
  late AnimationController _flipController;
  late Animation<double> _flipAnimation;

  // todo... 合集解锁动画，完成整个合集之后执行， 效果是整个图片缩小并位移到左上角的colleciton iconbutton, 形成合集“收纳”的效果
  late AnimationController _unlockController; //  解锁动画控制器
  late Animation<double> _unlockAnimation; // 0.0 -> 1.0

  // 当前合集是否已经完成(一个合集需要完成5x5即25个关卡才能解锁)(在切换到下一个合集之前调用判断)
  bool get _isCurCollectionDone => data.currentLevel != 0 && (data.currentCollectionIndex) * 25 == data.currentLevel;
  // bool get _isCurCollectionDone => true; // for test

  OverlayEntry? _overlayEntry; // 新增：用于管理全屏动画层

  // 发牌动画
  late AnimationController _dealingController; // 发牌动画控制器
  late Animation<double> _dealingAnimation;
  // 发牌间隔（ms）
  final int _dealingPieceInterval = 70;
  // 每个卡片移动时间（ms）
  final int _dealingPieceDuration = 300;

  // 发牌动画总时长（需要考虑到最后一个卡片的动画）
  // 共有 board.rows * board.cols 个卡片
  int get _totalDealingDuration => (board.rows * board.cols - 1) * _dealingPieceInterval + _dealingPieceDuration;
  Timer? _dealingPeriodicTimer;
  int _dealingCount = 0; // 计数：记录执行次数

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

    Device device = context.read<Device>();
    board = HomeBoard(canvasWidth: widget.canvasWidth, canvasHeight: widget.canvasHeight, device: device);
    board.isReadyNotifier.addListener(_onBoardReady);

    confettiLayer = ConfettiLayer(this);

    Future.delayed(Duration.zero, () {
      if (mounted) {
        confettiLayer.setup(context);
      }
    });

    audio = context.read<JcAudioController>();
    data = context.read<Data>();

    collectionCachedRequest = data.collection;
    // 主动获取缓存数据（关键）
    final collectionCachedData = collectionCachedRequest.cachedData;
    if (collectionCachedData != null) {
      _onCollectionDataUpdate(collectionCachedData);
    }
    collectionSubscription = collectionCachedRequest.stream.listen(_onCollectionDataUpdate, onError: _onCollectionDataError);

    // 初始化翻牌动画控制器
    _flipController = AnimationController(
      duration: const Duration(milliseconds: 1000), // 动画时长
      vsync: this, // HomeBoardState 必须实现 TickerProviderStateMixin
    );
    // 创建一个 0.0 到 1.0 的动画，用于控制翻转角度
    _flipAnimation = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(parent: _flipController, curve: Curves.easeOut))
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          // 检查整个合集是否全部完成
          _checkCollectionDone();
        }
      });

    // 初始化解锁动画控制器
    _unlockController = AnimationController(
      duration: const Duration(milliseconds: 800), // 动画时长
      vsync: this,
    );
    _unlockAnimation = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(parent: _unlockController, curve: Curves.easeInBack))
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          _log.info('合集解锁动画结束');
          _overlayEntry?.remove(); // 动画结束时移除浮层
          _overlayEntry = null;

          // 动画结束后，通知外部（HomeScreen）
          widget.onCollectionDone?.call();

          // 启动发牌动画
          _startDealingAnimation();
        }
      });

    // 初始化发牌动画
    _dealingController = AnimationController(
      duration: Duration(milliseconds: _totalDealingDuration), // 动态设置总时长
      vsync: this,
    );
    _dealingAnimation =
        CurvedAnimation(parent: _dealingController, curve: Curves.easeOut) // 使用缓动曲线
          ..addStatusListener((status) {
            if (status == AnimationStatus.completed) {
              // 发牌结束，这个时候再来切换到下一个合集
              switchToNextCollection();
              setState(() {
                board.status = HomeBoardStatus.playing; // 发牌结束进入正常的绘制状态
                board.invalidate(); // 确保最终状态绘制正确
              });
            }
          });
  }

  _onCollectionDataUpdate(colldata) async {
    _log.info('_onCollectionDataUpdate.... ');
    if (colldata != null) {
      collection = colldata as List<ListItem>;
      if (collection != null && collection!.isNotEmpty) {
        // 做一个矫正，避免没有正常退出，合集没有切换的情况
        if ((data.completedWorks.value.length / 25).floor() > data.currentCollectionIndex) {
          if (currentCollectionItem != null) {
            _log.info('合集落后于关卡，没有正常切换，矫正！');
            data.collectionDone(currentCollectionItem!);
          }
        }
        board.currentCollectionItem = currentCollectionItem;
      }
      setState(() {});

      // 远程数据没有加载到，3秒后重试
      if (colldata.length < 5) {
        Future.delayed(Duration(seconds: 3), () => refresh());
      }
    }
  }

  _onCollectionDataError(error) {
    _log.info('_onCollectionDataError.... $error');
    if (collection == null || collection!.isEmpty || collection!.length <= 2) {
      // 列表数据如果少于20，说明只是内置图，仍然刷新远程请求
      _log.warning("_onCollectionDataError, retry again");
      Future.delayed(Duration(seconds: 3), () => refresh());
    }
  }

  Future<void> refresh() async {
    _log.info('refresh...');
    await collectionCachedRequest.refresh();
  }

  // board 图片等资源加载完成的回调
  _onBoardReady() {
    if (board.status == HomeBoardStatus.loading) {
      setState(() {
        board.status = HomeBoardStatus.playing;
      });
    }
  }

  // !!! 改造点 2: 启动发牌动画
  void _startDealingAnimation() {
    setState(() {
      board.status = HomeBoardStatus.dealing;
    });
    _dealingController.duration = Duration(milliseconds: _totalDealingDuration); // 确保duration是正确的
    _dealingController.forward(from: 0.0);
    _dealingCount = 0;
    audio.playSfx(SfxType.card);
    _dealingPeriodicTimer = Timer.periodic(Duration(milliseconds: 150), (timer) {
      if (mounted) {
        _dealingCount++;
        if (_dealingCount >= (_totalDealingDuration / 150) - 2) {
          timer.cancel();
        } else {
          audio.playSfx(SfxType.card);
        }
      }
    });
  }

  ListItem? get currentCollectionItem {
    if (collection != null && collection!.isNotEmpty && data.currentCollectionIndex < collection!.length) {
      return collection![data.currentCollectionIndex];
    }
    return null;
  }

  @override
  void dispose() {
    board.isReadyNotifier.removeListener(_onBoardReady);
    confettiLayer.dispose();
    _flipController.dispose();
    _unlockController.dispose();
    _dealingController.dispose();
    collectionSubscription?.cancel();
    super.dispose();
  }

  // for test
  void testAnimation() async {
    // setState(() {
    //   board.status = HomeBoardStatus.done;
    //   board.invalidate();
    // });

    // audio.playSfx(SfxType.star);
    // confettiLayer.play();

    // // 等待confetti动画结束， 然后启动解锁动画
    // await Future.delayed(Duration(milliseconds: 500));
    // _startUnlockAnimation();
    setState(() {
      board.status = HomeBoardStatus.dealing;
      board.invalidate();
    });
    _startDealingAnimation();
    _dealingCount = 0;
    audio.playSfx(SfxType.card);
    _dealingPeriodicTimer = Timer.periodic(Duration(milliseconds: 150), (timer) {
      if (mounted) {
        _dealingCount++;
        if (_dealingCount >= (_totalDealingDuration / 150) - 2) {
          timer.cancel();
        } else {
          audio.playSfx(SfxType.card);
        }
      }
    });
  }

  // 翻牌动画结束后，检查整个合集是否全部完成，如果全部完成，需要展示一系列的动画
  void _checkCollectionDone() async {
    if (_isCurCollectionDone) {
      // 将状态置为done，canvas绘制一整张图，不再是单个卡片
      setState(() {
        board.status = HomeBoardStatus.done;
        board.invalidate();
      });

      _startUnlockAnimation();
    }
  }

  // 实现解锁动画启动方法
  void _startUnlockAnimation() {
    // 1. 获取动画参数 (与您之前的逻辑保持一致，用于计算位移和缩放目标)
    final RenderBox? targetRenderBox = widget.collectionKey.currentContext?.findRenderObject() as RenderBox?;
    if (targetRenderBox == null || !mounted) return;

    final targetPosition = targetRenderBox.localToGlobal(targetRenderBox.size.center(Offset.zero));

    final RenderBox? canvasRenderBox = context.findRenderObject() as RenderBox?;
    if (canvasRenderBox == null) return;

    // 获取 CustomPaint 顶层左上角的全局坐标
    final canvasGlobalTL = canvasRenderBox.localToGlobal(Offset.zero);

    // 计算 Canvas 中心到目标图标中心的全局位移量
    final canvasCenter = canvasRenderBox.localToGlobal(canvasRenderBox.size.center(Offset.zero));
    final Offset delta = targetPosition - canvasCenter;

    // 存储计算出的动画目标数据
    board.setUnlockAnimationTarget(targetOffset: delta, targetScale: targetRenderBox.size.width / widget.canvasWidth);

    // 存储计算出的动画目标数据，供 CustomPainter 使用
    board.setUnlockAnimationTarget(
      targetOffset: delta,
      // 目标缩放比例
      targetScale: targetRenderBox.size.width * 0.2 / widget.canvasWidth,
      // targetScale: 0,
    );

    // 2. 创建并插入全屏 Overlay Entry
    _overlayEntry = OverlayEntry(
      builder: (context) {
        // OverlayEntry 的 (0,0) 是屏幕的左上角。

        // 我们将 CustomPaint 放置在它原来的全局位置
        return Positioned(
          left: canvasGlobalTL.dx, // 初始 X 坐标
          top: canvasGlobalTL.dy, // 初始 Y 坐标
          child: SizedBox(
            // 尺寸和 HomeBoard 一致
            width: widget.canvasWidth,
            height: widget.canvasHeight,
            child: CustomPaint(
              size: Size(widget.canvasWidth, widget.canvasHeight),
              // 使用 CanvasPainter 绘制，并强制设置为 unlocking 状态
              painter: CanvasPainter(
                board: board,
                level: data.currentLevel,
                collectionIndex: data.currentCollectionIndex,
                flipAnimation: _flipAnimation,
                unlockAnimation: _unlockAnimation,
                forceStatus: HomeBoardStatus.unlocking, // 强制状态，用于 Overlay 绘制
                dealingAnimation: _dealingAnimation,
                dealingPieceInterval: _dealingPieceInterval,
                dealingPieceDuration: _dealingPieceDuration,
              ),
            ),
          ),
        );
      },
    );

    Overlay.of(context).insert(_overlayEntry!);

    // 启动动画
    setState(() {
      board.status = HomeBoardStatus.unlocking; // 切换到 unlocking 状态
    });
    _unlockController.forward(from: 0.0);
  }

  // 对外暴露的触发动画方法 (供 HomeScreen 调用)
  void startFlipAnimation() {
    _flipController.forward(from: 0.0);
    audio.playSfx(SfxType.flip);
    if (data.currentLevel != 0 && (data.currentCollectionIndex + 1) * 25 == data.currentLevel && currentCollectionItem != null) {
      data.collectionDone(currentCollectionItem!);
      // 展示撒花动画
      audio.playSfx(SfxType.star);
      confettiLayer.play();
    }
  }

  void switchToNextCollection() {
    if (currentCollectionItem == null) {
      _log.info('没有更多的合集了');
      Fluttertoast.showToast(
        msg: AppLocalizations.of(context)!.noMorePicture,
        toastLength: Toast.LENGTH_SHORT,
        gravity: ToastGravity.CENTER,
        timeInSecForIosWeb: 1,
        backgroundColor: SkinHelper.slotBorderColor,
        textColor: Colors.white,
        fontSize: 16.0,
      );
      return;
    }
    board.switchToNextCollection(currentCollectionItem!);
  }

  @override
  Widget build(BuildContext context) {
    // 检查是否正在执行全屏动画
    final isUnlocking = _unlockController.isAnimating || board.status == HomeBoardStatus.unlocking;
    return CustomPaint(
      size: Size(widget.canvasWidth, widget.canvasHeight), // 固定画布尺寸
      // 正在解锁动画时，原 CustomPaint 不绘制内容 (或者只绘制一个透明的占位图)
      painter: isUnlocking && _overlayEntry != null
          ? null // 动画运行时，原 CustomPaint 不绘制，避免冲突
          : CanvasPainter(
              board: board,
              level: data.currentLevel,
              collectionIndex: data.currentCollectionIndex,
              flipAnimation: _flipAnimation,
              unlockAnimation: _unlockAnimation,
              dealingAnimation: _dealingAnimation,
              dealingPieceDuration: _dealingPieceDuration,
              dealingPieceInterval: _dealingPieceInterval,
            ),
      child: board.status == HomeBoardStatus.loading
          ? Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(SkinHelper.slotBorderColor)))
          : Container(),
      // child: childWidget,
    );
  }

  Widget get childWidget {
    if (board.status == HomeBoardStatus.loading) {
      return Center(child: CircularProgressIndicator(valueColor: AlwaysStoppedAnimation<Color>(SkinHelper.slotBorderColor)));
    } else if (board.status == HomeBoardStatus.done) {
      return const ShiningStars(size: 80);
    } else {
      return Container();
    }
  }
}

// 自定义画笔实现（实际绘制逻辑在这里）
class CanvasPainter extends CustomPainter {
  final HomeBoard board;
  final int level; //当前关卡序号
  final int collectionIndex; // 当前合集序号

  final Animation<double> flipAnimation; // 0.0 -> 1.0
  final Animation<double> unlockAnimation;
  final HomeBoardStatus? forceStatus; // !!! 新增字段

  final Animation<double> dealingAnimation; // !!! 改造点 6: 发牌动画
  final int dealingPieceDuration; // !!! 改造点 7: 单个卡片动画时长
  final int dealingPieceInterval; // !!! 改造点 8: 单个卡片动画间隔

  CanvasPainter({
    required this.board,
    required this.level,
    required this.collectionIndex,
    required this.flipAnimation,
    required this.unlockAnimation,
    this.forceStatus,
    required this.dealingAnimation,
    required this.dealingPieceDuration,
    required this.dealingPieceInterval,
  }) : super(repaint: Listenable.merge([board.boardNotifier, flipAnimation, unlockAnimation, dealingAnimation])); // 触发重绘;

  @override
  void paint(Canvas canvas, Size size) {
    // !!! 改造点 1: 优先使用强制状态，否则使用 Board 状态
    final statusToPaint = forceStatus ?? board.status;

    if (statusToPaint == HomeBoardStatus.playing) {
      _paintPlaying(canvas, size);
    } else if (statusToPaint == HomeBoardStatus.done) {
      // 动画结束前，原 CustomPaint 处于 done 状态
      _paintSuccess(canvas, size);
    } else if (statusToPaint == HomeBoardStatus.unlocking) {
      // 仅在 Overlay 中调用，执行动画
      _paintUnlocking(canvas, size);
    } else if (statusToPaint == HomeBoardStatus.dealing) {
      _paintDealing(canvas, size);
    }
  }

  // !!! 改造点 11: 实现 _paintDealing 方法
  void _paintDealing(Canvas canvas, Size size) {
    _log.info('_paintDealing');
    final totalPieces = board.rows * board.cols;
    final totalAnimationDuration = dealingPieceInterval * (totalPieces - 1) + dealingPieceDuration;

    // 起始位置 (右下角宫格的中心)
    final startX = board.cols - 1;
    final startY = board.rows - 1;
    final startPieceCenterX = startX * board.pieceLogicalWidth + board.pieceLogicalWidth / 2;
    final startPieceCenterY = startY * board.pieceLogicalHeight + board.pieceLogicalHeight / 2;

    for (var i = 0; i < board.rows; i++) {
      for (var j = 0; j < board.cols; j++) {
        final pieceIndex = i * board.cols + j; // 卡片索引 (0 到 24)

        // 计算该卡片的动画起始时间和结束时间 (相对于 _dealingController 的总 duration)
        final animationStartTime = pieceIndex * dealingPieceInterval; // 当前卡片开始动画的毫秒数
        final animationEndTime = animationStartTime + dealingPieceDuration; // 当前卡片结束动画的毫秒数

        // 计算当前全局动画进度 (0.0 - 1.0) 对应的毫秒数
        final currentGlobalTime = dealingAnimation.value * totalAnimationDuration;

        // 判断当前卡片是否应该开始动画
        if (currentGlobalTime < animationStartTime) {
          // 还没轮到这个卡片，保持在起始位置
          _drawDealingPiece(
            canvas,
            size,
            i,
            j,
            startPieceCenterX - (j * board.pieceLogicalWidth + board.pieceLogicalWidth / 2), // 补偿目标位置的x
            startPieceCenterY - (i * board.pieceLogicalHeight + board.pieceLogicalHeight / 2), // 补偿目标位置的y
            0.0, // 进度为0，即起始位置
          );
        } else if (currentGlobalTime >= animationEndTime) {
          // 动画已结束，停留在目标位置
          _drawDealingPiece(
            canvas,
            size,
            i,
            j,
            0.0, // 目标位置的相对偏移量为0
            0.0,
            1.0, // 进度为1，即目标位置
          );
        } else {
          // 正在动画中，计算该卡片的局部动画进度
          final localProgress = (currentGlobalTime - animationStartTime) / dealingPieceDuration;
          _drawDealingPiece(
            canvas,
            size,
            i,
            j,
            startPieceCenterX - (j * board.pieceLogicalWidth + board.pieceLogicalWidth / 2), // 补偿目标位置的x
            startPieceCenterY - (i * board.pieceLogicalHeight + board.pieceLogicalHeight / 2), // 补偿目标位置的y
            localProgress,
          );
        }
      }
    }
  }

  // 辅助方法：绘制发牌动画中的单个卡片
  void _drawDealingPiece(
    Canvas canvas,
    Size size,
    int row,
    int col,
    double startOffsetX,
    double startOffsetY, // 起始位置相对于目标位置的偏移量
    double progress, // 0.0 -> 1.0
  ) {
    final curIndex = collectionIndex * board.count + row * board.cols + col;

    final w = board.pieceLogicalWidth;
    final h = board.pieceLogicalHeight;

    final targetLeft = col * w;
    final targetTop = row * h;

    // 计算当前动画帧的偏移量
    final currentOffsetX = ui.lerpDouble(startOffsetX, 0.0, progress)!;
    final currentOffsetY = ui.lerpDouble(startOffsetY, 0.0, progress)!;

    canvas.save();
    // 先平移到卡片最终位置的左上角，再应用动画偏移
    canvas.translate(targetLeft + currentOffsetX, targetTop + currentOffsetY);

    // 绘制卡片背面
    final cardSourceRect = Rect.fromLTWH(0, 0, board.cardImage.width.toDouble(), board.cardImage.height.toDouble());
    final rect = Rect.fromLTWH(0, 0, w, h); // 绘制在当前变换后的 (0,0,w,h)

    canvas.drawImageRect(board.cardImage, cardSourceRect, rect, Paint()..isAntiAlias = true);

    // 绘制关卡数字 (与 _drawPiece 中的逻辑类似)
    final textStyle = TextStyle(
      color: Colors.white,
      fontSize: h * 0.25,
      fontWeight: FontWeight.bold,
      shadows: [Shadow(offset: const Offset(1, 1), blurRadius: 2, color: Colors.black38)],
    );
    final textSpan = TextSpan(text: (curIndex + 1).toString(), style: textStyle);
    final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr, textAlign: TextAlign.center);
    textPainter.layout(minWidth: 0, maxWidth: w);
    final textOffset = Offset((w - textPainter.width) / 2, (h - textPainter.height) / 2);
    textPainter.paint(canvas, textOffset);

    canvas.restore();

    // 绘制边框（为了避免裁剪，在 restore 后绘制）
    final cornerRadius = 4.0;
    final outerRRect = RRect.fromRectAndRadius(
      Rect.fromLTWH(targetLeft + currentOffsetX, targetTop + currentOffsetY, w, h).deflate(0.5),
      Radius.circular(cornerRadius),
    );
    final innerRRect = RRect.fromRectAndRadius(
      Rect.fromLTWH(targetLeft + currentOffsetX, targetTop + currentOffsetY, w, h).deflate(1.5),
      Radius.circular(cornerRadius),
    );
    canvas.drawRRect(outerRRect, outerBorderPaint);
    canvas.drawRRect(innerRRect, innerBorderPaint);
  }

  void _paintUnlocking(Canvas canvas, Size size) {
    _log.info('_paintUnlocking');
    // 1. 获取动画进度 (0.0 -> 1.0)
    final progress = unlockAnimation.value;

    // 3. 计算当前的位移和缩放
    // 缩放：从 1.0 缩小到 targetScale
    final startScale = 1.0;
    final endScale = board.unlockTargetScale;
    final currentScale = ui.lerpDouble(startScale, endScale, progress)!;

    // 位移：从 (0, 0) 平移到 targetOffset
    final startOffset = Offset.zero;
    final endOffset = board.unlockTargetOffset;
    final currentOffset = Offset(ui.lerpDouble(startOffset.dx, endOffset.dx, progress)!, ui.lerpDouble(startOffset.dy, endOffset.dy, progress)!);

    // 4. 应用 Canvas 变换
    canvas.save();

    // 缩放：以 Canvas 中心为缩放原点进行缩放
    final centerX = size.width / 2;
    final centerY = size.height / 2;

    // 4.1. 移动到 Canvas 中心点 (将原点移到 CustomPaint 的中心)
    canvas.translate(centerX, centerY);

    // 4.2. 应用位移 (这是中心点相对 CustomPaint 中心点的移动)
    canvas.translate(currentOffset.dx, currentOffset.dy);

    // 4.3. 应用缩放 (以当前中心点为原点)
    canvas.scale(currentScale);

    // 4.4. 移回 Canvas 原点 (回到 CustomPaint 的左上角，已应用了 位移 + 缩放)
    canvas.translate(-centerX, -centerY);

    // 5. 绘制完整的合集图片 (与 _paintSuccess 逻辑相同)
    // 简单点， 动画就不画边框圆角这些了
    // final cornerRadius = 4.0;
    final rect = Rect.fromLTWH(0, 0, size.width, size.height);
    // final rrect = RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius));
    // final outerRRect = RRect.fromRectAndRadius(rect.deflate(0.5), Radius.circular(cornerRadius));
    // final innerRRect = RRect.fromRectAndRadius(rect.deflate(1.5), Radius.circular(cornerRadius));

    final sourceRect = Rect.fromLTWH(0, 0, board.image!.width.toDouble(), board.image!.height.toDouble());

    // 裁剪并绘制图片
    // canvas.clipRRect(rrect);  // 动画就不用clipRRect了
    canvas.drawImageRect(board.image!, sourceRect, rect, Paint()..isAntiAlias = true);

    // 绘制边框
    // canvas.drawRRect(outerRRect, outerBorderPaint);
    // canvas.drawRRect(innerRRect, innerBorderPaint);

    canvas.restore();
  }

  _paintSuccess(Canvas canvas, Size size) {
    _log.info('_paintSuccess');
    final cornerRadius = 4.0;
    final rect = Rect.fromLTWH(0, 0, size.width, size.height);
    final rrect = RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius));
    final outerRRect = RRect.fromRectAndRadius(rect.deflate(0.5), Radius.circular(cornerRadius));
    final innerRRect = RRect.fromRectAndRadius(rect.deflate(1.5), Radius.circular(cornerRadius));

    final sourceRect = Rect.fromLTWH(0, 0, board.image!.width.toDouble(), board.image!.height.toDouble());

    canvas.save();

    canvas.clipRRect(rrect);

    canvas.drawImageRect(board.image!, sourceRect, rect, Paint()..isAntiAlias = true);

    canvas.restore();

    // 绘制边框
    canvas.drawRRect(outerRRect, outerBorderPaint);
    canvas.drawRRect(innerRRect, innerBorderPaint);
  }

  _paintPlaying(Canvas canvas, Size size) {
    _log.info('_paintPlaying');

    for (var i = 0; i < board.rows; i++) {
      for (var j = 0; j < board.cols; j++) {
        // 玩过的关卡翻正面显示， 否则显示卡片背面
        final int curIndex = i * board.rows + j;
        bool flipped = level > collectionIndex * board.count + curIndex;

        _drawPiece(canvas, size, i, j, flipped);
      }
    }
  }

  final Paint outerBorderPaint = Paint()
    ..color = SkinHelper.outLineBorderColor
    ..style = PaintingStyle.stroke
    ..strokeWidth = 1.0
    ..isAntiAlias = true;

  // 边框画笔
  final Paint innerBorderPaint = Paint()
    ..color = SkinHelper.innerLineBorderColor
    ..style = PaintingStyle.stroke
    ..strokeWidth = 1.0
    ..isAntiAlias = true;

  void _drawPiece(Canvas canvas, Size size, int row, int col, bool flipped) {
    final cornerRadius = 4.0;

    final w = size.width / board.cols;
    final h = size.height / board.rows;

    final pieceWidth = board.image!.width / board.cols;
    final pieceHeight = board.image!.height / board.rows;

    final left = col * w;
    final top = row * h;
    final right = left + w;
    final bottom = top + h;

    // 为了避免描边溢出到相邻槽位，将矩形向内收缩 halfStroke
    final rect = Rect.fromLTRB(left, top, right, bottom);
    final rrect = RRect.fromRectAndRadius(rect, Radius.circular(cornerRadius));
    final outerRRect = RRect.fromRectAndRadius(rect.deflate(0.5), Radius.circular(cornerRadius));
    final innerRRect = RRect.fromRectAndRadius(rect.deflate(1.5), Radius.circular(cornerRadius));

    final cardSourceRect = Rect.fromLTWH(0, 0, board.cardImage.width.toDouble(), board.cardImage.height.toDouble());
    final imageSourceRect = Rect.fromLTWH(pieceWidth * col, pieceHeight * row, pieceWidth, pieceHeight);

    // 0-based index
    final curIndex = collectionIndex * board.count + row * board.rows + col;

    // 1. 计算当前的翻转状态
    double flipProgress = 0.0;

    if (flipAnimation.isAnimating && curIndex == level - 1) {
      // for test，只为方便查看动画效果，真正的代码是上面注释掉的
      // if (flipAnimation.isAnimating && curIndex == 24) {
      flipProgress = flipAnimation.value; // 0.0 -> 1.0
      flipped = (flipProgress > 0.5);
    }
    // _log.info('level=$level, row=$row, col=$col, flippingIndex=$flippingIndex, flipProgress=$flipProgress, currentPieceFlipped=$currentPieceFlipped');

    canvas.save();

    // 2. 居中变换原点到拼图块中心
    final centerX = left + w / 2;
    final centerY = top + h / 2;

    canvas.translate(centerX, centerY);
    // 3. 应用 3D 旋转 (围绕 Y 轴)
    if (flipProgress > 0.0) {
      // 旋转角度从 0 到 pi (180度)
      double angle = flipProgress * pi;
      // 引入透视投影（z轴缩放），让翻转效果更立体
      const double perspective = 0.0015;

      // 3D 变换矩阵
      Matrix4 transform;
      if (flipProgress <= 0.5) {
        transform = Matrix4.identity()
          ..setEntry(3, 2, perspective) // 3D 效果
          ..rotateY(angle);
      } else {
        transform = Matrix4.identity()
          ..setEntry(3, 2, perspective) // 3D 效果
          ..rotateY(angle)
          ..scale(-1.0, 1.0, 1.0); // 3. X轴缩放-1：抵消旋转带来的左右镜像
      }

      canvas.transform(transform.storage);
    }

    // 4. 移回原点
    canvas.translate(-centerX, -centerY);

    // ... 现有裁剪逻辑 ...
    canvas.clipRRect(rrect);

    if (flipped) {
      // 绘制正面
      canvas.drawImageRect(board.image!, imageSourceRect, rect, Paint()..isAntiAlias = true);
    } else {
      // 绘制背面
      // 必须反转图片源矩形，以修正翻转180度后图像的镜像问题
      final sourceRect = flipProgress > 0.5
          ? imageSourceRect // 翻转后使用正面图像
          : cardSourceRect; // 翻转前使用背面卡片

      final targetImage = flipProgress > 0.5 ? board.image! : board.cardImage;

      canvas.drawImageRect(targetImage, sourceRect, rect, Paint()..isAntiAlias = true);

      if (flipProgress <= 0.5) {
        // todo... 绘制关卡数字， 在卡片中间位置把curIndex绘制上去， 颜色白色
        // 1. 配置文字样式：白色、加粗、动态字体大小（适配卡片尺寸）
        final textStyle = TextStyle(
          color: Colors.white,
          fontSize: h * 0.25, // 字体大小为卡片高度的40%，适配不同尺寸
          fontWeight: FontWeight.bold,
          shadows: [
            // 增加黑色阴影，让白色文字在卡片背景上更清晰（可选但推荐）
            Shadow(offset: const Offset(1, 1), blurRadius: 2, color: Colors.black38),
          ],
        );

        // 2. 初始化文字绘制器
        final textSpan = TextSpan(text: (curIndex + 1).toString(), style: textStyle);
        final textPainter = TextPainter(
          text: textSpan,
          textDirection: TextDirection.ltr,
          textAlign: TextAlign.center, // 文字水平居中
        );

        // 3. 计算文字尺寸（必须调用layout()）
        textPainter.layout(
          minWidth: 0,
          maxWidth: w, // 文字最大宽度不超过卡片宽度
        );

        // 4. 计算文字居中偏移量
        final textOffset = Offset(
          left + (w - textPainter.width) / 2, // 水平居中
          top + (h - textPainter.height) / 2, // 垂直居中
        );

        // 5. 绘制文字
        textPainter.paint(canvas, textOffset);
      }
    }

    canvas.restore(); // 恢复 Canvas 状态 (移除裁剪和 transform)

    // --- 绘制边框 ---
    // 为了确保边框线宽向外延伸的部分不会被裁剪，我们需要在裁剪区域被移除后重新绘制。
    canvas.save();

    canvas.drawRRect(outerRRect, outerBorderPaint);
    canvas.drawRRect(innerRRect, innerBorderPaint);

    canvas.restore();
  }

  @override
  bool shouldRepaint(covariant CanvasPainter oldDelegate) {
    return oldDelegate.level != level ||
        oldDelegate.flipAnimation != flipAnimation ||
        oldDelegate.unlockAnimation != unlockAnimation ||
        oldDelegate.dealingAnimation != dealingAnimation ||
        oldDelegate.board != board;
  }
}
